From aa39bfb846eb4a60ee9c4da4b0572707742780d4 Mon Sep 17 00:00:00 2001 From: Andy Ross Date: Wed, 1 Feb 2023 14:01:18 -0800 Subject: [PATCH 1/3] fuzz: Whitebox component creation The TPLG_COMP_NEW message type works by matching a 128 bit UUID to a runtime list of drivers. That's too much depth for even directed fuzzing to find randomly. So whitebox that command: leverage the fact that the first four bytes of the message are being ignored anyway (that field is the message length, which we get externally from the fuzzing rig and overwrite). Treat the first byte as a magic number indicating the command type (0xff in this case being "comp_new") and the second a parameter interpreted as a driver index. Then fix up the fields manually so it gets through the logic in helper.c:get_drv() successfully and tries to create a random component. Signed-off-by: Andy Ross --- src/platform/posix/ipc.c | 71 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 7 deletions(-) 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); } From 38602e84d52eb989958c4a9b9499920261df4807 Mon Sep 17 00:00:00 2001 From: Andy Ross Date: Wed, 1 Feb 2023 14:05:41 -0800 Subject: [PATCH 2/3] platform/posix: Fix header warning Transitive header order no longer declares struct sof for us here. No need to pull in a full header as we only use pointers to it. Signed-off-by: Andy Ross --- src/platform/posix/include/platform/platform.h | 2 ++ 1 file changed, 2 insertions(+) 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); From e6d6c1b49783804a5eddc5c335628cd3b83a848f Mon Sep 17 00:00:00 2001 From: Andy Ross Date: Wed, 1 Feb 2023 14:27:40 -0800 Subject: [PATCH 3/3] scripts: Add fuzz test wrapper Simple script to explain how fuzz testing works Signed-off-by: Andy Ross --- scripts/fuzz.sh | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 scripts/fuzz.sh 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 "$@"