diff --git a/src/audio/buffers/comp_buffer.c b/src/audio/buffers/comp_buffer.c index cc38a7dfead7..0902af84d986 100644 --- a/src/audio/buffers/comp_buffer.c +++ b/src/audio/buffers/comp_buffer.c @@ -622,13 +622,6 @@ void comp_update_buffer_consume(struct comp_buffer *buffer, uint32_t bytes) #endif } -static inline struct list_item *buffer_comp_list(struct comp_buffer *buffer, - int dir) -{ - return dir == PPL_DIR_DOWNSTREAM ? - &buffer->source_list : &buffer->sink_list; -} - /* * Locking: must be called with interrupts disabled (or sys_mutex held for * userspace LL builds)! Serialized IPCs protect us diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 47d5d0127fd0..adac7d3f477d 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,35 @@ 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). + * + * list_is_empty() on an embedded list node (not a list head) returns + * true when the node is unlinked (node->next == node, set by + * list_init() at creation or after list_item_del()). Once linked into + * a component's buffer list, node->next points elsewhere and + * list_is_empty() returns false — meaning the buffer is already + * connected. + */ + buf_list = buffer_comp_list(buffer, dir); + 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); diff --git a/src/include/sof/audio/buffer.h b/src/include/sof/audio/buffer.h index 6e6b8a9caef8..6f591dd528b7 100644 --- a/src/include/sof/audio/buffer.h +++ b/src/include/sof/audio/buffer.h @@ -300,6 +300,17 @@ void buffer_attach(struct comp_buffer *buffer, struct list_item *head, int dir); */ void buffer_detach(struct comp_buffer *buffer, struct list_item *head, int dir); +/** + * Get the buffer's list node used to link it into a component's buffer list. + * For PPL_DIR_DOWNSTREAM the buffer is a source to the component (source_list), + * for PPL_DIR_UPSTREAM the buffer is a sink (sink_list). + */ +static inline struct list_item *buffer_comp_list(struct comp_buffer *buffer, int dir) +{ + return dir == PPL_DIR_DOWNSTREAM ? + &buffer->source_list : &buffer->sink_list; +} + static inline struct comp_dev *buffer_get_comp(struct comp_buffer *buffer, int dir) { struct comp_dev *comp = (dir == PPL_DIR_DOWNSTREAM) ?