From c58099b595654cd7db90e2e5d48ddbd00f8cf59a Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Fri, 3 Jul 2026 14:31:35 +0200 Subject: [PATCH] ipc4: helper: reject module creation for non-existent pipeline comp_new_ipc4() copied the host-supplied ppl_instance_id into ipc_config.pipeline_id without verifying that the referenced pipeline exists. The module_adapter creation path then does: ipc_pipe = ipc_get_comp_by_ppl_id(ipc, COMP_TYPE_PIPELINE, config->pipeline_id, IPC_COMP_IGNORE_REMOTE); if (ipc_pipe) dev->pipeline = ipc_pipe->pipeline; /* skipped when NULL */ so when the pipeline is missing the lookup returns NULL, the assignment is skipped, and the component is created with dev->pipeline left unset. A gateway module such as the copier then dereferences that NULL pointer while wiring the graph, e.g. copier_dai_init() writing pipeline->source_comp / pipeline->sink_comp, giving a NULL-pointer SEGV reachable from a single unprivileged IPC. The case -------- The offending IPC stream carries one meaningful command: a MODULE / INIT_INSTANCE request for a copier-class module (module_id 26, instance_id 36) whose extension.ppl_instance_id names a parent pipeline (id 90) for which no GLB / CREATE_PIPELINE command was ever sent. That pipeline is therefore absent from ipc->comp_list. IPC4 mandates that the host create the pipeline before instantiating any module inside it; here that ordering is violated, so the invariant "an INIT_INSTANCE always targets an existing pipeline" does not hold. The firmware accepted the request anyway, allocated the component, ran module_init() -> copier_init() -> copier_dai_create(), and copier_dai_init() performed a WRITE to the source_comp field (offset 0x54) through the NULL dev->pipeline, faulting on the zero page: ERROR: SEGV on unknown address 0x00000054 (WRITE) copier_dai_init -> copier_dai_create -> copier_init -> module_init -> module_adapter_new_ext -> module_adapter_new -> comp_new_ipc4 -> ipc4_init_module_instance -> ipc4_process_module_message -> ipc_cmd All three copier gateway variants are affected (copier_host_create, copier_dai_create and copier_ipcgtw_create all pass dev->pipeline down), and any future module reading dev->pipeline during init would hit the same latent NULL. Fix --- Reject the request at the common creation entry point: in comp_new_ipc4(), before the component is allocated, look up the parent pipeline and fail with a NULL return (reported to the host as IPC4_MOD_NOT_INITIALIZED) when it does not exist. This fails fast with no partially-initialized component to unwind, and the later module_adapter lookup is then guaranteed to succeed. The lookup uses IPC_COMP_ALL so it stays core-agnostic: a valid parent pipeline may run on a different core than the module being instantiated (e.g. a DP module scheduled on a separate core). Not every module has a parent pipeline, so the check cannot enforce a blanket "a live component always belongs to a live pipeline" invariant: base firmware modules such as the probe are instantiated with the reserved ppl_instance_id value IPC4_INVALID_PIPELINE_ID (0xFF), which is not a pipeline id. Exempt that value from the check so those modules are still accepted; they never dereference dev->pipeline. That exemption means a gateway copier could still be created with no pipeline (0xFF). Unlike the probe, a gateway copier must be wired into the graph and therefore requires a live pipeline, so reject it in copier_init() before the gateway create path dereferences dev->pipeline. Together this keeps "no NULL dev->pipeline dereference is reachable from a single IPC" for every module driver. Signed-off-by: Tomasz Leman --- src/audio/copier/copier.c | 10 ++++++++++ src/include/ipc4/module.h | 4 ++++ src/ipc/ipc4/helper.c | 14 ++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/src/audio/copier/copier.c b/src/audio/copier/copier.c index 6e7b379a1f33..e64f98705b7f 100644 --- a/src/audio/copier/copier.c +++ b/src/audio/copier/copier.c @@ -176,6 +176,16 @@ __cold static int copier_init(struct processing_module *mod) node_id = copier->gtw_cfg.node_id; /* copier is linked to gateway */ if (node_id.dw != IPC4_INVALID_NODE_ID) { + /* A gateway copier wires itself into the pipeline graph, so it needs a + * valid parent pipeline (base FW modules using IPC4_INVALID_PIPELINE_ID + * are exempt in comp_new_ipc4()). Reject here to avoid a NULL deref. + */ + if (!dev->pipeline) { + comp_err(dev, "gateway copier requires a valid parent pipeline"); + ret = -EINVAL; + goto error; + } + cd->direction = get_gateway_direction(node_id.f.dma_type); switch (node_id.f.dma_type) { diff --git a/src/include/ipc4/module.h b/src/include/ipc4/module.h index 4e60ebc9bc13..f026f837801a 100644 --- a/src/include/ipc4/module.h +++ b/src/include/ipc4/module.h @@ -151,6 +151,10 @@ struct ipc4_module_init_data { \remark hide_methods */ + +/* Reserved ppl_instance_id: module has no parent pipeline (e.g. probe). */ +#define IPC4_INVALID_PIPELINE_ID 0xFF + struct ipc4_module_init_instance { union { diff --git a/src/ipc/ipc4/helper.c b/src/ipc/ipc4/helper.c index 5881fbbfb0b5..d5c09c3f7bf7 100644 --- a/src/ipc/ipc4/helper.c +++ b/src/ipc/ipc4/helper.c @@ -145,6 +145,20 @@ __cold struct comp_dev *comp_new_ipc4(struct ipc4_module_init_instance *module_i ipc_config.ipc_config_size); return NULL; } + + /* Reject a module naming a non-existent parent pipeline: otherwise + * dev->pipeline stays NULL and a later init path (e.g. the copier) + * dereferences it. IPC4_INVALID_PIPELINE_ID is exempt - it marks base FW + * modules with no parent pipeline. IPC_COMP_ALL: the pipeline may run on a + * different core than this module. + */ + if (ipc_config.pipeline_id != IPC4_INVALID_PIPELINE_ID && + !ipc_get_comp_by_ppl_id(ipc_get(), COMP_TYPE_PIPELINE, ipc_config.pipeline_id, + IPC_COMP_ALL)) { + tr_err(&ipc_tr, "comp 0x%x: parent pipeline %u does not exist", comp_id, + (uint32_t)ipc_config.pipeline_id); + return NULL; + } #ifdef CONFIG_DCACHE_LINE_SIZE if (!IS_ENABLED(CONFIG_LIBRARY)) sys_cache_data_invd_range((__sparse_force void __sparse_cache *)