diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 47d5d0127fd0..84b86bbce62e 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -198,6 +198,7 @@ int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer, int dir) { struct list_item *comp_list; + struct list_item *buf_list; PPL_LOCK_DECLARE; if (dir == PPL_CONN_DIR_COMP_TO_BUFFER) @@ -207,6 +208,29 @@ int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer, PPL_LOCK(); + /* + * Guard against double-connecting the same buffer. Calling + * list_item_prepend() on a node that is already in a list creates a + * self-loop (node->next == node) that permanently corrupts the list. + * Consequences: + * - ipc_comp_free() enters an unbounded loop inside irq_local_disable, + * stalling the simulation indefinitely. + * - pipeline_disconnect() / list_item_del() fails to unlink the buffer + * from the component, leaving a dangling pointer that causes + * use-after-free when the buffer is later freed. + * This can be triggered by a second IPC CONNECT message for the same + * buffer-component pair (within one testcase, or via state carry-over + * between fuzzer testcases when IPC topology is not torn down). + */ + buf_list = dir == PPL_DIR_DOWNSTREAM ? + &buffer->source_list : &buffer->sink_list; + if (!list_is_empty(buf_list)) { + comp_err(comp, "buffer %d already connected dir %d", + buf_get_id(buffer), dir); + PPL_UNLOCK(); + return -EINVAL; + } + comp_list = comp_buffer_list(comp, dir); buffer_attach(buffer, comp_list, dir); buffer_set_comp(buffer, comp, dir);