From 8d59dd2d516b1151aa056621f59c68154bcbdb0c Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Tue, 21 Jul 2026 20:00:50 +0200 Subject: [PATCH 1/2] audio: selector: guard against missing source component in trigger The IPC fuzzer hit a NULL pointer dereference in the IPC3 path while triggering a pipeline that contains a selector component. Both AddressSanitizer and UBSan report a READ SEGV at address 0x30 in dev_comp_type() (src/include/sof/audio/component.h). 0x30 is the offset of comp_dev::ipc_config.type, i.e. the accessor was called on a NULL comp_dev. Root cause: a comp_buffer links a producer on its ->source side to a consumer on its ->sink side, and buffers are zero-initialised, so an unconnected side stays NULL. selector_trigger() checks only that its source buffer exists and then passes that buffer's producer component straight to dev_comp_type() to look for an upstream KPB: type = dev_comp_type(comp_buffer_get_source_component(sourceb)); When the source buffer is attached to the selector but never attached to an upstream producer, comp_buffer::source is NULL and dev_comp_type() dereferences it. The mirror-image lookup in kpb.c already guards against a missing peer component; selector_trigger() was the only site doing this without the check. Look up the producer component first and treat a missing producer as "not a KPB" instead of dereferencing it. Also honour the comp_set_state() return value: the crashing trigger was a PAUSE issued from COMP_STATE_READY, which comp_set_state() already rejects with -EINVAL, but selector_trigger() ignored the error and fell through to the dereference. Bail out on error like every other component trigger handler. Signed-off-by: Tomasz Leman --- src/audio/selector/selector.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/audio/selector/selector.c b/src/audio/selector/selector.c index faf21da0e228..df814b6c24f6 100644 --- a/src/audio/selector/selector.c +++ b/src/audio/selector/selector.c @@ -411,7 +411,7 @@ static int selector_cmd(struct comp_dev *dev, int cmd, void *data, static int selector_trigger(struct comp_dev *dev, int cmd) { struct comp_buffer *sourceb; - enum sof_comp_type type; + struct comp_dev *source_comp; int ret; comp_dbg(dev, "entry"); @@ -423,6 +423,8 @@ static int selector_trigger(struct comp_dev *dev, int cmd) } ret = comp_set_state(dev, cmd); + if (ret < 0) + return ret; if (ret == COMP_STATUS_STATE_ALREADY_SET) ret = 0; @@ -430,9 +432,14 @@ static int selector_trigger(struct comp_dev *dev, int cmd) * kpb_init_draining() and kpb_draining_task() are interrupted by * new pipeline_task() */ - type = dev_comp_type(comp_buffer_get_source_component(sourceb)); + /* The source buffer may be present without an attached producer + * component (e.g. a partially connected pipeline), so guard against a + * NULL source component before dereferencing it. + */ + source_comp = comp_buffer_get_source_component(sourceb); - return type == SOF_COMP_KPB ? PPL_STATUS_PATH_TERMINATE : ret; + return source_comp && dev_comp_type(source_comp) == SOF_COMP_KPB ? + PPL_STATUS_PATH_TERMINATE : ret; } /** From 94776bfbdfe592c8c8446a3edc7c60a89bee5b41 Mon Sep 17 00:00:00 2001 From: Tomasz Leman Date: Tue, 4 Aug 2026 19:39:47 +0200 Subject: [PATCH 2/2] audio: pipeline: guard NULL buffer peer in pipeline_get_dai_comp The IPC fuzzer built an IPC3 pipeline whose component owned a half-connected buffer - present on the component's buffer list but with no component attached to its opposite end - and then issued a SOF_IPC_STREAM_POSITION request. ipc_stream_position() calls pipeline_get_timestamp(), which walks the graph with pipeline_get_dai_comp() to locate the DAI endpoint. For each hop the walk takes the buffer's peer component via buffer_get_comp() and immediately dereferences comp->pipeline. For a dangling buffer buffer_get_comp() returns NULL, so the read faults at NULL + 8 (the ->pipeline field). A comp_buffer only carries a peer on a side once both ends are attached, but the IPC3 graph is assembled incrementally (each COMP_CONNECT attaches a single end) so a buffer can be left half-connected. The STREAM_POSITION path reaches this walk directly, independent of the trigger path, and does not go through the pipeline-run connectivity checks: a pipeline can be completed and then have a half-connected buffer attached by a later COMP_CONNECT, and a subsequent POSITION request walks the dangling edge. Guard the peer before dereferencing it, mirroring the existing checks in the tree: the IPC4 sibling pipeline_get_dai_comp_latency() already checks "if (!source || !source->pipeline)", and the XRUN rewind walk in pipeline_trigger_xrun() checks "if (!buffer_comp || !buffer_comp->pipeline)". A fully-connected buffer always has a non-NULL peer and a cross-pipeline peer keeps a non-NULL pointer, so only a genuinely dangling buffer is rejected and valid topologies are unaffected. pipeline_get_timestamp() already tolerates a NULL DAI result. Signed-off-by: Tomasz Leman --- src/audio/pipeline/pipeline-graph.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index adcb00a80719..6e154dfbfba6 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -506,8 +506,10 @@ struct comp_dev *pipeline_get_dai_comp(uint32_t pipeline_id, int dir) buffer = buffer_from_list(blist->next, dir); comp = buffer_get_comp(buffer, dir); - /* buffer_comp is in another pipeline and it is not complete */ - if (!comp->pipeline) + /* A half-connected buffer has no peer component on this side, or + * buffer_comp is in another pipeline and it is not complete. + */ + if (!comp || !comp->pipeline) return NULL; crt = ipc_get_ppl_comp(ipc, comp->pipeline->pipeline_id, dir);