sof/scripts/fuzz.sh

164 lines
4.9 KiB
Bash
Executable File

#!/bin/bash
set -e
print_help()
{
cat <<EOFHELP
Usage:
$0 -b -- -DOVERLAY_CONFIG=stub_build_all_ipc4.conf -DEXTRA_CFLAGS=...
$0 -t 500 -- -DOVERLAY_CONFIG=stub_build_all_ipc3.conf ...
-b Do not run/fuzz: stop after the build.
-t n Fuzz for n seconds.
-o ofile Redirect the fuzzer's extremely verbose stdout. The
relatively verbose stderr is not redirected by -o.
Arguments after -- are passed as is to CMake (through west).
When passing conflicting -DVAR='VAL UE1' -DVAR='VAL UE2' to CMake,
the last 'VAL UE2' wins; previous values are silently ignored.
Fuzzing happens to require stubbing which provides a great solution to
compile-check many CONFIG_* at once. So you can stop after the build
with the -b option.
Simple wrapper around a libfuzzer test run, as much for
documentation as direct use. The idea here is really simple: build
for the Zephyr "native_posix" board (which is a just a x86
executable for the build host, not an emulated device) and run the
resulting zephyr.exe file. This specifies a "fuzz_corpus" directory
to save the seeds that produce useful coverage output for use in
repeated runs (these are not particularly large, we might consider
curating and committing such a seed directory to the tree).
The tool will run until it finds a failure condition. You will see
MANY errors on stdout from all the randomized input. Don't try to
capture this, either let it output to a terminal or arrange to keep
only the last XXX lines after the tool exits.
The only prerequisite to install is a clang compiler on the host.
Versions 12+ have all been observed to work.
You will need the kconfigs specified below for correct operation,
but can add more at the end of this script's command line to
duplicate configurations as needed. Alternatively you can pass
overlay files in kconfig syntax via:
fuzz.sh -t 300 -- -DOVERLAY_CONFIG=... -DEXTRA_CFLAGS='-Wone -Wtwo' ...
EOFHELP
}
# To test this test, make the following change locally:
: <<EOF_TEST_PATCH
--- a/src/ipc/ipc3/handler.c
+++ b/src/ipc/ipc3/handler.c
@@ -1609,6 +1609,8 @@ void ipc_boot_complete_msg(struct ipc_cmd_hdr *header, uint32_t data)
void ipc_cmd(struct ipc_cmd_hdr *_hdr)
{
+ __ASSERT(false, "test the IPC3 fuzzer test");
+
struct sof_ipc_cmd_hdr *hdr = ipc_from_hdr(_hdr);
EOF_TEST_PATCH
# When fuzzing IPC4, make the same change in src/ipc/ipc4/handler.c#ipc_cmd()
main()
{
setup
BUILD_ONLY=false
# Parse "$@". getopts stops after '--'
while getopts "ho:t:b" opt; do
case "$opt" in
h) print_help; exit 0;;
o) FUZZER_STDOUT="$OPTARG";;
t) TEST_DURATION="$OPTARG";;
b) BUILD_ONLY=true;;
*) print_help; exit 1;;
esac
done
# Pass all "$@" arguments remaining after '--' to cmake.
# This also drops _leading_ '--'.
shift $((OPTIND-1))
# Default values
: "${TEST_DURATION:=3}"
: "${FUZZER_STDOUT:=/dev/stdout}" # bashism
# Move this to a new fuzz.conf overlay file if it grows bigger
local fuzz_configs=(
-DCONFIG_ZEPHYR_POSIX=y
-DCONFIG_ASSERT=y
-DCONFIG_SYS_HEAP_BIG_ONLY=y
-DCONFIG_ZEPHYR_NATIVE_DRIVERS=y
-DCONFIG_ARCH_POSIX_LIBFUZZER=y
-DCONFIG_ARCH_POSIX_FUZZ_TICKS=100
-DCONFIG_ASAN=y
)
(set -x
# When passing conflicting -DVAR='VAL UE1' -DVAR='VAL UE2' to CMake,
# the last 'VAL UE2' wins. Previous ones are silently ignored.
west build -d build-fuzz -b native_posix "$SOF_TOP"/app/ -- \
"${fuzz_configs[@]}" "$@"
)
if $BUILD_ONLY; then
exit 0
fi
mkdir -p ./fuzz_corpus
timeoutret=0
date
timeout -k 10 "$TEST_DURATION" >"$FUZZER_STDOUT" build-fuzz/zephyr/zephyr.exe ./fuzz_corpus ||
timeoutret=$?
if [ $timeoutret -eq 124 ]; then
# The current ASAN seems overly sensitive: it frequently (but not always) complains
# loudly that it has been aborted with a so-called DEADLYSIGNAL when the timeout
# command above actually delivers just a gentle TERM by default. Counteracts the
# wrong impression this unpredictable whining can give with a proud "successfully"
# message.
printf '\n\nSuccessfully aborted fuzzer after %d seconds test duration with DEADLYSIGNAL\n' \
"$TEST_DURATION"
else
die 'zephyr.exe fuzzer stopped before timing out: %d\n' $timeoutret
fi
date
}
setup()
{
SOF_TOP=$(cd "$(dirname "$0")/.." && pwd)
export SOF_TOP
export ZEPHYR_TOOLCHAIN_VARIANT=llvm
# ZEPHYR_BASE. Does not seem required.
local WS_TOP
WS_TOP=$(cd "$SOF_TOP"; west topdir)
# The manifest itself is not a west project
ZEP_DIR=$(2>/dev/null west list -f '{path}' zephyr || echo 'zephyr')
export ZEPHYR_BASE="$WS_TOP/$ZEP_DIR"
}
die()
{
>&2 printf '%s ERROR: ' "$0"
# We want die() to be usable exactly like printf
# shellcheck disable=SC2059
>&2 printf "$@"
>&2 printf '\n'
exit 1
}
main "$@"