diff --git a/scripts/fuzz.sh b/scripts/fuzz.sh new file mode 100755 index 000000000000..c39ad31bedba --- /dev/null +++ b/scripts/fuzz.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -e + +# 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 commiting 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 -DOVERLAY_CONFIG=..., etc... + +export SOF_TOP=$(cd "$(dirname "$0")/.." && pwd) + +export ZEPHYR_BASE=$SOF_TOP/../zephyr +export ZEPHYR_TOOLCHAIN_VARIANT=llvm + +main() +{ + west build -p -b native_posix $SOF_TOP/app/ -- \ + -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 "$@" + + mkdir -p ./fuzz_corpus + build/zephyr/zephyr.exe ./fuzz_corpus +} + +main "$@" diff --git a/src/platform/posix/include/platform/platform.h b/src/platform/posix/include/platform/platform.h index 14a19ab2bfc1..5c9c264d54e5 100644 --- a/src/platform/posix/include/platform/platform.h +++ b/src/platform/posix/include/platform/platform.h @@ -24,6 +24,8 @@ #define PLATFORM_DEFAULT_DELAY 12 +struct sof; + void posix_dma_init(struct sof *sof); void posix_dai_init(struct sof *sof); diff --git a/src/platform/posix/ipc.c b/src/platform/posix/ipc.c index 1ccfbd2bb1ee..8a1f800cb158 100644 --- a/src/platform/posix/ipc.c +++ b/src/platform/posix/ipc.c @@ -7,6 +7,7 @@ #include #include #include +#include // 6c8f0d53-ff77-4ca1-b825-c0c4e1b0d322 DECLARE_SOF_UUID("posix-ipc-task", ipc_task_uuid, @@ -46,6 +47,8 @@ static uint8_t fuzz_in_sz; static void fuzz_isr(const void *arg) { size_t rem, i, n = MIN(posix_fuzz_sz, sizeof(fuzz_in) - fuzz_in_sz); + bool comp_new = false; + int comp_idx = 0; for (i = 0; i < n; i++) fuzz_in[fuzz_in_sz++] = posix_fuzz_buf[i]; @@ -58,20 +61,74 @@ static void fuzz_isr(const void *arg) size_t maxsz = SOF_IPC_MSG_MAX_SIZE - 4, msgsz = fuzz_in[0] * 2; - memset(global_ipc->comp_data, 0, maxsz); n = MIN(msgsz, MIN(fuzz_in_sz - 1, maxsz)); rem = fuzz_in_sz - (n + 1); + memset(global_ipc->comp_data, 0, maxsz); + memcpy(global_ipc->comp_data, &fuzz_in[1], n); + memmove(&fuzz_in[0], &fuzz_in[n + 1], rem); + fuzz_in_sz = rem; + + // One special case: a first byte of 0xff (which is in the + // otherwise-ignored size value at the front of the command -- + // we rewrite those) is interpreted as a "component new" + // command, which we format specially, with a driver index + // specified by the second byte (modulo the number of + // registered drivers). This command involves matching + // against a UUID value, which even fuzzing isn't able to + // discover at runtime. So unless we whitebox this, no + // components will be created. + if (n > 2 && ((uint8_t *)global_ipc->comp_data)[0] == 0xff) { + comp_new = true; + comp_idx = ((uint8_t *)global_ipc->comp_data)[1]; + } + // The first dword is a size value which fuzzing will stumble - // on only one time in 24M, fill it in manually. + // on only rarely, fill it in manually. *(uint32_t *)global_ipc->comp_data = msgsz; - for (i = 0; i < n; i++) { - uint8_t *cmd = global_ipc->comp_data; // why is it a void*? - cmd[i + 4] = fuzz_in[i + 1]; + struct sof_ipc_comp *cc = global_ipc->comp_data; + + // "Adjust" the command to represent a "comp new" command per + // above. Basically we want to copy in the UUID value for one + // of the runtime-enumerated drivers based on data already + // randomized in the fuzz command. + if (comp_new) { + struct { + struct sof_ipc_comp comp; + struct sof_ipc_comp_ext ext; + } *cmd = global_ipc->comp_data; + + // Set global/command type fields to TPLG_MSG/TPLG_COMP_NEW + cmd->comp.hdr.cmd &= 0x0000ffff; + cmd->comp.hdr.cmd |= SOF_IPC_GLB_TPLG_MSG; + cmd->comp.hdr.cmd |= SOF_IPC_TPLG_COMP_NEW; + + // We have only one core available in native_posix + cmd->comp.core = 0; + + // Fix up cmd size and ext_data_length to match + if (cmd->comp.hdr.size < sizeof(*cmd)) + cmd->comp.hdr.size = sizeof(*cmd); + cmd->comp.ext_data_length = cmd->comp.hdr.size - sizeof(cmd->comp); + + // Extract the list of available component drivers (do + // it every time; in practice I don't think this + // changes at runtime but in principle it might in the + // future) + int ndrvs = 0; + static struct comp_driver_info *drvs[256]; + struct list_item *iter; + struct comp_driver_list *dlist = comp_drivers_get(); + list_for_item(iter, &dlist->list) { + struct comp_driver_info *inf = + container_of(iter, struct comp_driver_info, list); + drvs[ndrvs++] = inf; + } + + struct comp_driver_info *di = drvs[comp_idx % ndrvs]; + memcpy(cmd->ext.uuid, di->drv->uid, sizeof(cmd->ext.uuid)); } - memmove(&fuzz_in[0], &fuzz_in[n + 1], rem); - fuzz_in_sz = rem; posix_ipc_isr(NULL); }