Skip to content

audio: module_adapter: always set init_data in module_adapter_init_data#10966

Open
tmleman wants to merge 1 commit into
thesofproject:mainfrom
tmleman:topic/upstream/pr/audio/volume/fix/null-init-data
Open

audio: module_adapter: always set init_data in module_adapter_init_data#10966
tmleman wants to merge 1 commit into
thesofproject:mainfrom
tmleman:topic/upstream/pr/audio/volume/fix/null-init-data

Conversation

@tmleman

@tmleman tmleman commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

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)

@tmleman tmleman requested a review from ranj063 as a code owner June 30, 2026 14:40
Copilot AI review requested due to automatic review settings June 30, 2026 14:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes a crash in the IPC4 module adapter initialization path where module_config.init_data could remain NULL when an extended-init payload included a MODULE_DATA typed object, causing legacy modules (e.g., volume) to dereference a null config pointer during module_init().

Changes:

  • Always assign dst->init_data = cfg in module_adapter_init_data() so legacy modules always receive a valid config pointer.
  • Keep dst->avail conditional so extended-init-aware modules can continue to rely on ext_data->module_data without changing behavior.

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 <tomasz.m.leman@intel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants