From 46016cb03e290bf1f482179f53de04fa9acf3cd1 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Wed, 23 Sep 2026 16:01:50 +0100 Subject: [PATCH] ipc4: handler: bound get_large_config reply size to the reply buffer The IPC4 fuzzer (simple-IPC-fuzz_sh) aborts with an AddressSanitizer stack-overflow report in the ipc_send_wq thread, inside vfprintf() under posix_print_trace(). That report is a red herring: the real failure is an assert, and the recursive assert -> k_panic -> assert loop that follows it is what eventually exhausts the 8MB pthread stack native_sim gives the thread. With -jobs>1 the harness passes -close_fd_mask=1, so printk (which goes to stdout) is discarded and only the secondary ASan report on stderr survives into the CI log. The assert is: ASSERTION FAIL [!dsp_write_err] @ sof/src/include/sof/lib/mailbox.h:52 reached from ipc_send_queued_msg() -> ipc_platform_send_msg() -> mailbox_dspbox_write(0, msg->tx_data, 918016), i.e. a reply claiming ~900KB of payload for a 4KB mailbox. ipc4_get_large_config_module_instance() seeds data_offset from config->extension.r.data_off_size, a 20-bit host-controlled field, and then passes it to drv->ops.get_large_config() as an in/out parameter. A module whose .get_configuration produces no data (template_get_config() is the one the fuzzer found, but any IPC3-oriented stub behaves the same) returns success without writing *data_offset_size, so the host-supplied value survives unchanged and is published as msg_reply->tx_size. The payload itself lives in ipc->comp_data, which is only SOF_IPC_MSG_MAX_SIZE bytes. Only the VENDOR_CONFIG_PARAM branch bounds data_off_size today, and it does so for the inbound hostbox read, not for the reply. With asserts compiled out, memcpy_s() rejects the copy but the return value is discarded and dcache_writeback_region() is still asked to write back the out-of-range length, so the caller sends a reply header advertising a size that was never copied. Bound the reply size against the space actually left in the reply buffer before it is published, and fail the command with IPC4_INVALID_CONFIG_DATA_LEN otherwise. The bound is computed from data rather than from SOF_IPC_MSG_MAX_SIZE directly because the non-vendor branch advances data by sizeof(reply) under CONFIG_LIBRARY. The same fix is applied to ipc4_process_large_config_get(), the CONFIG_SOF_USERSPACE_LL variant, which has the identical flaw. Reproduced and verified on native_sim/native/64 with the crash artifact from the failing CI run (thesofproject/sof PR 11193, job simple-IPC-fuzz_sh (4)): it aborts before the change and exits 0 after it. A subsequent 420s/8-job fuzz run over a ~10k-input corpus produced no new artifacts, and an intel_adsp/ace30/ptl cross-build is clean. Signed-off-by: Liam Girdwood Co-Authored-By: Claude Opus 5 (1M context) --- src/ipc/ipc4/handler-user.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/ipc/ipc4/handler-user.c b/src/ipc/ipc4/handler-user.c index c1cca5d7a8b6..d1197af87de8 100644 --- a/src/ipc/ipc4/handler-user.c +++ b/src/ipc/ipc4/handler-user.c @@ -1103,6 +1103,7 @@ __cold int ipc4_process_large_config_get(struct ipc4_message_request *ipc4, const struct comp_driver *drv; struct comp_dev *dev = NULL; uint32_t data_offset; + size_t data_max; int ret; assert_can_be_cold(); @@ -1167,6 +1168,21 @@ __cold int ipc4_process_large_config_get(struct ipc4_message_request *ipc4, if (ret < 0) ret = IPC4_MOD_INVALID_ID; + /* data_offset is now the reply payload size, but it was seeded from + * the host-controlled 20-bit data_off_size field and a module that + * produces no data leaves it untouched, so it can still claim up to + * ~1MB. The payload lives in ipc->comp_data (SOF_IPC_MSG_MAX_SIZE + * bytes) and the sender copies tx_size bytes of it into the DSP + * mailbox, so reject anything that does not fit rather than handing + * an out-of-range size to mailbox_dspbox_write(). + */ + data_max = SOF_IPC_MSG_MAX_SIZE - (size_t)(data - (char *)ipc_get()->comp_data); + if (!ret && data_offset > data_max) { + ipc_cmd_err(&ipc_tr, "get_large_config reply size %u exceeds %zu", + data_offset, data_max); + ret = IPC4_INVALID_CONFIG_DATA_LEN; + } + /* Copy host config and overwrite */ reply.extension.dat = config->extension.dat; reply.extension.r.data_off_size = data_offset; @@ -1199,6 +1215,7 @@ __cold static int ipc4_get_large_config_module_instance(struct ipc4_message_requ const struct comp_driver *drv; struct comp_dev *dev = NULL; uint32_t data_offset; + size_t data_max; int ret; assert_can_be_cold(); @@ -1274,6 +1291,21 @@ __cold static int ipc4_get_large_config_module_instance(struct ipc4_message_requ if (ret < 0) ret = IPC4_INVALID_RESOURCE_ID; + /* data_offset is now the reply payload size, but it was seeded from + * the host-controlled 20-bit data_off_size field and a module that + * produces no data leaves it untouched, so it can still claim up to + * ~1MB. The payload lives in ipc->comp_data (SOF_IPC_MSG_MAX_SIZE + * bytes) and ipc_platform_send_msg() copies tx_size bytes of it into + * the DSP mailbox, so reject anything that does not fit rather than + * handing an out-of-range size to mailbox_dspbox_write(). + */ + data_max = SOF_IPC_MSG_MAX_SIZE - (size_t)(data - (char *)ipc_get()->comp_data); + if (!ret && data_offset > data_max) { + ipc_cmd_err(&ipc_tr, "get_large_config reply size %u exceeds %zu", + data_offset, data_max); + ret = IPC4_INVALID_CONFIG_DATA_LEN; + } + /* Copy host config and overwrite */ reply.extension.dat = config->extension.dat; reply.extension.r.data_off_size = data_offset;