From e4aa1fad0fab24d3c585e7dcca4875cd34bf7281 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 28 May 2026 13:10:31 +0200 Subject: [PATCH 1/6] platform: posix: fix extern type mismatch for posix_fuzz_buf/sz The fuzzer entry point in src/platform/posix/fuzz.c defines the shared staging symbols as: const uint8_t *posix_fuzz_buf; size_t posix_fuzz_sz; but src/platform/posix/ipc.c forward-declared them as extern uint8_t *posix_fuzz_buf, posix_fuzz_sz; Match the definitions in ipc.c and drop the now-unnecessary cast in fuzz.c. Also fix the stale 'native_fuzz_buf' reference in the doc comment - the symbol was renamed long ago. No functional change is intended on testcases whose size happens to fit in 8 bits; longer inputs (the common case for IPC4 large_config payloads) were previously being silently truncated. Signed-off-by: Tomasz Leman --- src/platform/posix/fuzz.c | 4 ++-- src/platform/posix/ipc.c | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/platform/posix/fuzz.c b/src/platform/posix/fuzz.c index 555a22d40578..e5c515c15a45 100644 --- a/src/platform/posix/fuzz.c +++ b/src/platform/posix/fuzz.c @@ -32,9 +32,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz) } /* Provide the fuzz data to the embedded OS as an interrupt, with - * "DMA-like" data placed into native_fuzz_buf/sz + * "DMA-like" data placed into posix_fuzz_buf/sz. */ - posix_fuzz_buf = (void *)data; + posix_fuzz_buf = data; posix_fuzz_sz = sz; hw_irq_ctrl_set_irq(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ); diff --git a/src/platform/posix/ipc.c b/src/platform/posix/ipc.c index 701686e6ff97..1b86d103323e 100644 --- a/src/platform/posix/ipc.c +++ b/src/platform/posix/ipc.c @@ -22,8 +22,9 @@ static void posix_ipc_isr(void *arg) ipc_schedule_process(global_ipc); } -// External symbols set up by the fuzzing layer -extern uint8_t *posix_fuzz_buf, posix_fuzz_sz; +// External symbols set up by the fuzzing layer in fuzz.c. +extern const uint8_t *posix_fuzz_buf; +extern size_t posix_fuzz_sz; // Lots of space. Should really synchronize with the -max_len // parameter to libFuzzer (defaults to 4096), but that requires From de4aa32da8e278e350c728bf580b69cfb67dc372 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Wed, 27 May 2026 17:17:34 +0200 Subject: [PATCH 2/6] fuzz: posix: size MAILBOX_HOSTBOX from SOF_IPC_MSG_MAX_SIZE The POSIX fuzzing platform hard-codes MAILBOX_HOSTBOX_SIZE to 1024 bytes, which is smaller than the IPC4 maximum message size (SOF_IPC_MSG_MAX_SIZE = 0x1000 = 4096 bytes). This is too small to hold the full IPC4 message envelope and blocks the harness from populating the hostbox with realistic fuzz payload for IPC4 paths that read from MAILBOX_HOSTBOX_BASE. It is also a latent overflow: ipc_platform_do_cmd() in posix/ipc.c calls memcpy(posix_hostbox, comp_data, SOF_IPC_MSG_MAX_SIZE) on the IPC3 path. Today this is safe only because IPC3 SOF_IPC_MSG_MAX_SIZE is 384, but any Kconfig change that grows that value would write past the end of the 1024-byte posix_hostbox array. Derive MAILBOX_HOSTBOX_SIZE from SOF_IPC_MSG_MAX_SIZE so the storage always matches the maximum framed message. The posix_hostbox array declaration in posix.c is sized via the same macro and tracks automatically. No behaviour change for existing IPC3 flows. Prepares the harness for a follow-up commit that copies IPC4 fuzz payload into the hostbox so large_config / set_dx / pipeline-state-data handlers see meaningful input. Signed-off-by: Tomasz Leman --- src/platform/posix/include/platform/lib/memory.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/posix/include/platform/lib/memory.h b/src/platform/posix/include/platform/lib/memory.h index e15e5f370aec..0c115e01687e 100644 --- a/src/platform/posix/include/platform/lib/memory.h +++ b/src/platform/posix/include/platform/lib/memory.h @@ -6,13 +6,14 @@ #define PLATFORM_HOST_PLATFORM_MEMORY_H #include +#include #define PLATFORM_DCACHE_ALIGN 64 #define uncache_to_cache(addr) (addr) #define cache_to_uncache(addr) (addr) extern uint32_t posix_hostbox[]; -#define MAILBOX_HOSTBOX_SIZE 1024 +#define MAILBOX_HOSTBOX_SIZE SOF_IPC_MSG_MAX_SIZE #define MAILBOX_HOSTBOX_BASE (&posix_hostbox[0]) extern uint32_t posix_dspbox[]; From 3ed1b2462944dee6db737e5fa2dc386aa598d510 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Wed, 27 May 2026 23:07:57 +0200 Subject: [PATCH 3/6] fuzz: posix: mirror IPC4 payload into MAILBOX_HOSTBOX On real hardware (and in the IPC3 harness path) the host kernel deposits the IPC payload into the hostbox shared-memory region before the DSP firmware is signalled. Many IPC4 handlers consume their payload by reading from MAILBOX_HOSTBOX_BASE directly -- for example LARGE_CONFIG_SET / LARGE_CONFIG_GET, vendor config, SET_DX and SET_PIPELINE_STATE in sof/src/ipc/ipc4/handler-user.c and ipc/ipc4/handler-kernel.c. Until now the posix fuzz harness only populated the IPC3 hostbox; in IPC4 builds the region stayed zero-filled, so those handlers either rejected the message early or operated on uninitialised data instead of on the fuzzer-controlled bytes. Lift the existing mailbox mirror copy out of the IPC3-only branch so it runs for both major versions. posix_hostbox is sized to SOF_IPC_MSG_MAX_SIZE (see Commit "fuzz: posix: size MAILBOX_HOSTBOX from SOF_IPC_MSG_MAX_SIZE"), so the copy length is correct for both targets. Update the function comment to record the new contract. This immediately exposes additional reachable code in the IPC4 build (payload decoders that previously saw only zeros) without affecting IPC3 behaviour. Signed-off-by: Tomasz Leman --- src/platform/posix/ipc.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/platform/posix/ipc.c b/src/platform/posix/ipc.c index 1b86d103323e..35e203c2d468 100644 --- a/src/platform/posix/ipc.c +++ b/src/platform/posix/ipc.c @@ -146,11 +146,38 @@ static void fuzz_isr(const void *arg) // ipc_platform_compact_read_msg(), writing 8 bytes unconditionally on // the header object it receives, which is then returned here, and // then passed to ipc_cmd(). +// +// The harness also mirrors the framed message into MAILBOX_HOSTBOX so +// that handlers reading payload directly from the hostbox region +// (large_config_set/get, set_dx, set_pipeline_state, vendor_config and +// friends in ipc4/handler-user.c and ipc4/handler-kernel.c) observe +// the fuzz bytes rather than stale or zero-filled memory. +// +// The two IPC majors split header and payload differently: +// +// * IPC3 carries the header in-band at the start of the message, and +// mailbox_validate() walks the full message starting from offset 0 +// of the hostbox. The full message is mirrored as-is. +// +// * IPC4 splits the 8-byte compact header (consumed via +// ipc_compact_read_msg()) from the payload, which on real hardware +// lives in HOSTBOX. The harness therefore mirrors only the +// post-header bytes, so the first dword of MAILBOX_HOSTBOX matches +// the first dword of the IPC4 payload (e.g. pipelines_count for +// SET_PIPELINE_STATE) instead of header bits. +// +// posix_hostbox is sized to SOF_IPC_MSG_MAX_SIZE (see +// platform/lib/memory.h), so the copy is always in bounds for both +// IPC3 and IPC4 message envelopes. enum task_state ipc_platform_do_cmd(struct ipc *ipc) { struct ipc_cmd_hdr *hdr; #ifdef CONFIG_IPC_MAJOR_4 + memset(posix_hostbox, 0, SOF_IPC_MSG_MAX_SIZE); + memcpy(posix_hostbox, + (const uint8_t *)global_ipc->comp_data + sizeof(struct ipc_cmd_hdr), + SOF_IPC_MSG_MAX_SIZE - sizeof(struct ipc_cmd_hdr)); hdr = ipc_compact_read_msg(); #else memcpy(posix_hostbox, global_ipc->comp_data, SOF_IPC_MSG_MAX_SIZE); From 435c0d4409060d4f70d8c841a5f0b321251624ed Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 28 May 2026 09:03:13 +0200 Subject: [PATCH 4/6] platform: posix: fix MAILBOX_*_BASE byte-pointer units On every other SOF platform, MAILBOX_HOSTBOX_BASE, MAILBOX_DSPBOX_BASE, MAILBOX_STREAM_BASE and MAILBOX_TRACE_BASE expand to a byte address (an integer literal or SRAM_INBOX_BASE), so the generic mailbox API in sof/src/include/sof/lib/mailbox.h can do plain byte arithmetic -- `MAILBOX_HOSTBOX_BASE + offset` and `memcpy(_s)(..., bytes)` -- and land on the intended byte. On POSIX, the bases were defined as `(&posix_hostbox[0])` etc., i.e. plain `uint32_t *` expressions. Pointer arithmetic on a `uint32_t *` scales the addend by `sizeof(uint32_t) == 4`, so `MAILBOX_HOSTBOX_BASE + offset` silently addressed byte `offset * 4`, four times further into the buffer than the API contract. This was latent for years because MAILBOX_HOSTBOX_SIZE was hard-coded to 1024 on POSIX while the largest byte offset used through mailbox_hostbox_read() in IPC3 mailbox_validate() (offset = 8, bytes = SOF_IPC_MSG_MAX_SIZE - 8 = 376) stays within 32 + 376 = 408 bytes, comfortably under 1024. After commit "fuzz: posix: size MAILBOX_HOSTBOX from SOF_IPC_MSG_MAX_SIZE" (384 in IPC3 builds, 4096 in IPC4 builds), the scaled IPC3 read overruns the now exactly right-sized backing buffer by 24 bytes, which AddressSanitizer catches as a global-buffer-overflow inside libc memcpy called from mailbox_hostbox_read() -> memcpy_s(). Reproducer (with the new 2-byte framing): two-byte fuzz input "\x80\x01" (msgsz=384, header-only message). Cast each base to `(uint8_t *)` so byte-offset arithmetic is honoured and the macro semantics match every other platform. The `uint32_t[]` backing storage is kept for natural alignment; only how the macro exposes that storage changes. Signed-off-by: Tomasz Leman --- .../posix/include/platform/lib/memory.h | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/platform/posix/include/platform/lib/memory.h b/src/platform/posix/include/platform/lib/memory.h index 0c115e01687e..7782ab55048b 100644 --- a/src/platform/posix/include/platform/lib/memory.h +++ b/src/platform/posix/include/platform/lib/memory.h @@ -12,20 +12,32 @@ #define uncache_to_cache(addr) (addr) #define cache_to_uncache(addr) (addr) +/* + * Mailbox base macros must yield byte-pointer arithmetic so that the + * generic mailbox API in `sof/src/include/sof/lib/mailbox.h` + * (`MAILBOX_HOSTBOX_BASE + offset`, etc., where `offset` is a byte + * offset) addresses the intended byte. The backing storage is + * declared as `uint32_t[]` only for natural-alignment; addressing is + * done through a `uint8_t *` cast so byte offsets do not get scaled + * by `sizeof(uint32_t)`. Every other SOF platform defines these + * bases as plain byte addresses (integer literals or + * `SRAM_INBOX_BASE`), so the cast keeps POSIX consistent with that + * ABI. + */ extern uint32_t posix_hostbox[]; #define MAILBOX_HOSTBOX_SIZE SOF_IPC_MSG_MAX_SIZE -#define MAILBOX_HOSTBOX_BASE (&posix_hostbox[0]) +#define MAILBOX_HOSTBOX_BASE ((uint8_t *)&posix_hostbox[0]) extern uint32_t posix_dspbox[]; #define MAILBOX_DSPBOX_SIZE 4096 -#define MAILBOX_DSPBOX_BASE (&posix_dspbox[0]) +#define MAILBOX_DSPBOX_BASE ((uint8_t *)&posix_dspbox[0]) extern uint32_t posix_stream[]; #define MAILBOX_STREAM_SIZE 4096 -#define MAILBOX_STREAM_BASE (&posix_stream[0]) +#define MAILBOX_STREAM_BASE ((uint8_t *)&posix_stream[0]) extern uint32_t posix_trace[]; -#define MAILBOX_TRACE_BASE (&posix_trace[0]) +#define MAILBOX_TRACE_BASE ((uint8_t *)&posix_trace[0]) #define MAILBOX_TRACE_SIZE 4096 #define PLATFORM_HEAP_SYSTEM 1 From 6efed3dfc600df78aac65d0529cbc8a62a8fdecd Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 28 May 2026 09:03:31 +0200 Subject: [PATCH 5/6] ipc4: helper: reject pipeline ext payload larger than hostbox ipc4_create_pipeline_payload_decode() reads pipeline extension objects out of the hostbox region. The function already validates that `size = hdr->payload_words * 4` is at least `sizeof(*hdr)`, but the symmetric upper bound check against MAILBOX_HOSTBOX_SIZE was emitting tr_err() and continuing. Once the loop is entered, every per-object bounds check at lines 290 and 299 is expressed relative to the attacker-controlled `size`, so a payload header that claims `payload_words` significantly larger than the mailbox lets the decoder walk `obj` arbitrarily far past the end of MAILBOX_HOSTBOX_BASE while dereferencing `obj->object_words` and `obj->object_id`. This was unreachable on real hardware because the host kernel is trusted to bound the payload, and unreachable in the SOF fuzz harness until commit "fuzz: posix: mirror IPC4 payload into MAILBOX_HOSTBOX" wired the fuzz-controlled bytes into the hostbox. A 60 s libFuzzer/ASan campaign with the IPC4 dictionary then surfaced a reproducible global-buffer-overflow at helper.c:298 (READ of size 4 at `posix_hostbox + 4096`). Convert the existing warning into a hard rejection so the decoder returns -EINVAL before entering the object-walk loop. Signed-off-by: Tomasz Leman --- src/ipc/ipc4/helper.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 892e704eb2e4..8e3073ab7797 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -271,6 +271,7 @@ __cold static int ipc4_create_pipeline_payload_decode(char *data, if (size > MAILBOX_HOSTBOX_SIZE) { tr_err(&ipc_tr, "Payload size too large: %u : %#x", hdr->payload_words, *((uint32_t *)hdr)); + return -EINVAL; } tr_info(&ipc_tr, "payload size %u array %u: %#x", hdr->payload_words, hdr->data_obj_array, *((uint32_t *)hdr)); From 0998622235499ec259187020a13b51dccac0c794 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Thu, 28 May 2026 16:44:42 +0200 Subject: [PATCH 6/6] ipc4: handler: bounds-check multi-pipeline SET_PIPELINE_STATE count For multi_ppl=1, ipc4_set_pipeline_state() takes pipelines_count straight from the host mailbox and uses it as the ppl_id[] loop bound, with no validation against the mailbox size. The IPC4 fuzzer reached this path with a 6-byte input that decodes to type=SOF_IPC4_GLB_SET_PIPELINE_STATE, multi_ppl=1 and a count field that easily exceeds MAILBOX_HOSTBOX. Once an earlier testcase had created a matching pipeline, the ppl_id[i] read walked past the end of the hostbox and AddressSanitizer reported a heap-buffer overflow. Cap pipelines_count at what the hostbox can actually hold and reject oversized requests with IPC4_ERROR_INVALID_PARAM, logging the offending count via ipc_cmd_err() for parity with the surrounding handlers. Signed-off-by: Tomasz Leman --- src/ipc/ipc4/handler-user.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index e55af1c66ec5..df243fa6ff58 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -441,6 +441,21 @@ static int ipc4_set_pipeline_state(struct ipc4_message_request *ipc4) if (state.extension.r.multi_ppl) { ppl_count = ppl_data->pipelines_count; + /* + * pipelines_count is read straight from the host-provided + * mailbox payload, so cap it at what the mailbox can + * physically hold. Anything larger means the host promised + * more ppl_id[] entries than fit in MAILBOX_HOSTBOX, and + * dereferencing the flex array would read out of bounds. + */ + if (ppl_count > (MAILBOX_HOSTBOX_SIZE - + sizeof(struct ipc4_pipeline_set_state_data)) / + sizeof(uint32_t)) { + ipc_cmd_err(&ipc_tr, + "ipc: pipelines_count %u exceeds mailbox bound", + ppl_count); + return IPC4_ERROR_INVALID_PARAM; + } ppl_id = ppl_data->ppl_id; dcache_invalidate_region((__sparse_force void __sparse_cache *)ppl_id, sizeof(int) * ppl_count);