From cf484d7fcd385706aa62ebf9ef3065f4ec32c24d Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Tue, 30 Jun 2026 18:03:44 +0200 Subject: [PATCH] audio: module_adapter: always set init_data in module_adapter_init_data Fix a NULL pointer dereference found by the IPC4 libFuzzer harness (native_sim/x86, ASAN-enabled build). The fuzzer discovered two crash inputs (87 and 241 bytes) that trigger SEGV at address 0x28 in volume_init(), crashing on: vol->config[0].channel_id == IPC4_ALL_CHANNELS_MASK where vol (cfg->init_data) is NULL and offset 0x28 (40) matches offsetof(ipc4_peak_volume_module_cfg, config[0].channel_id). Root cause ---------- module_adapter_init_data() only assigned dst->init_data when the legacy IPC path was active: if (!config->ipc_extended_init || !dst->ext_data->module_data) { dst->init_data = cfg; dst->avail = true; } The fuzzer crafted a payload with the extended_init bit set in the IPC4 extension word and a well-formed MODULE_DATA typed object in the payload. This satisfied both conditions to skip the assignment, leaving init_data as NULL (from struct zero-initialization). Any subsequent module_init() that dereferences cfg->init_data without a NULL check would crash. Affected modules include volume (confirmed crash), selector, and mux (latent NULL derefs); only src_ipc4 and smart_amp had pre-existing guards. Fix --- Move the init_data assignment outside the conditional so it is always set: dst->init_data = cfg; if (!config->ipc_extended_init || !dst->ext_data->module_data) dst->avail = true; This is safe because module_ext_init_decode() strips the extended init header and typed objects from the spec before module_adapter_init_data() is called (lines 118-122 of the same file advance spec->data past the consumed objects). Therefore cfg always points to the same [base_module_cfg + module-specific data] layout regardless of whether extended_init was set. The avail flag remains conditional: extended-init-aware modules (cadence_ipc4) set cfg.avail = false during their init and read ext_data->module_data directly, never dereferencing init_data. Alternatives considered ----------------------- 1. Adding a NULL check (if (!vol) return -EINVAL) in each affected module (volume_init, selector_init, mux_init). Rejected because it treats the symptom per-module rather than fixing the framework invariant, and would need to be replicated in every future module that reads init_data. 2. Rejecting extended_init messages for modules that do not support it (capability flag per module). This would be architecturally cleaner but requires a larger refactor and a new per-module capability declaration. The current fix is pragmatic and safe without that infrastructure. Verification ------------ - Both crash artifacts execute cleanly (no SEGV, graceful -EINVAL). - Smoke test (1500 fuzzer runs, 12 seconds) passes with normal coverage growth and no new crashes. - cadence_ipc4 (the only extended-init-aware module) is unaffected: it reads ext_data->module_data directly and never uses init_data. Found-by: IPC4 libFuzzer (ASAN, native_sim) Signed-off-by: Tomasz Leman --- src/audio/module_adapter/module_adapter_ipc4.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/audio/module_adapter/module_adapter_ipc4.c b/src/audio/module_adapter/module_adapter_ipc4.c index 57b918b20f86..d22ac68b4c3b 100644 --- a/src/audio/module_adapter/module_adapter_ipc4.c +++ b/src/audio/module_adapter/module_adapter_ipc4.c @@ -187,11 +187,13 @@ int module_adapter_init_data(struct comp_dev *dev, } } - /* Assume legacy API if module data was not found in ext_init payload */ - if (!config->ipc_extended_init || !dst->ext_data->module_data) { - dst->init_data = cfg; /* legacy API */ + /* Always provide init_data so legacy modules have a valid pointer. + * Extended-init-aware modules (e.g. cadence) read + * ext_data->module_data directly and never dereference init_data. + */ + dst->init_data = cfg; + if (!config->ipc_extended_init || !dst->ext_data->module_data) dst->avail = true; - } return 0; }