Skip to content

Commit 76ce222

Browse files
committed
pipeline: reject double-connect of already-attached buffer
pipeline_connect() had no guard against being called twice for the same buffer-component pair. Calling list_item_prepend() on a node that is already in a doubly-linked list corrupts the list by creating a self-loop where node->next points back to itself instead of to the list head. The corruption was discovered through IPC3 fuzzing in persistent mode. Without per-testcase topology teardown, components and buffers created by testcase N survive into testcase N+1. When N+1 sends a TPLG_COMP_CONNECT for IDs that N already connected, ipc_comp_connect() finds the surviving objects and calls pipeline_connect() a second time. The same sequence can also be triggered within a single testcase by two CONNECT messages for the same pair. The self-loop causes ipc_comp_free() to hang indefinitely. The function walks bsource_list / bsink_list with comp_dev_for_each_producer_safe() whose termination condition checks node->next == &comp->bsource_list. With the self-loop that check is always false. The walk runs inside irq_local_disable(), so the native_sim timer cannot preempt the thread and nsi_exec_for() never returns, making libFuzzer's max_total_time limit unreachable. A second failure mode arises when ipc_buffer_free() calls pipeline_disconnect() on the corrupted buffer. list_item_del() updates comp->bsource_list.next to node->next, which due to the self-loop is the node itself — leaving the component's list head pointing into the freed buffer memory. When that memory is reused by a later allocation and overwritten, the next walk of bsource_list dereferences an invalid pointer, crashing with a null dereference or a corrupt-pointer access. Add an early-exit guard in pipeline_connect() before buffer_attach(): if the buffer's relevant list node (source_list for COMP_TO_BUFFER, sink_list for BUFFER_TO_COMP) is not a self-loop singleton, the buffer is already attached and the connect is rejected with -EINVAL. list_is_empty() returns true only for a freshly created or correctly disconnected buffer, so no valid use case is rejected. Verified with -s address on the full IPC3 corpus (~66K inputs, 75 s): zero crashes, zero hangs, ~2800 exec/s. The unfixed build stalled indefinitely on the same run. Signed-off-by: Tomasz Leman <tomasz.m.leman@intel.com>
1 parent 4241c75 commit 76ce222

1 file changed

Lines changed: 24 additions & 0 deletions

File tree

src/audio/pipeline/pipeline-graph.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer,
198198
int dir)
199199
{
200200
struct list_item *comp_list;
201+
struct list_item *buf_list;
201202
PPL_LOCK_DECLARE;
202203

203204
if (dir == PPL_CONN_DIR_COMP_TO_BUFFER)
@@ -207,6 +208,29 @@ int pipeline_connect(struct comp_dev *comp, struct comp_buffer *buffer,
207208

208209
PPL_LOCK();
209210

211+
/*
212+
* Guard against double-connecting the same buffer. Calling
213+
* list_item_prepend() on a node that is already in a list creates a
214+
* self-loop (node->next == node) that permanently corrupts the list.
215+
* Consequences:
216+
* - ipc_comp_free() enters an unbounded loop inside irq_local_disable,
217+
* stalling the simulation indefinitely.
218+
* - pipeline_disconnect() / list_item_del() fails to unlink the buffer
219+
* from the component, leaving a dangling pointer that causes
220+
* use-after-free when the buffer is later freed.
221+
* This can be triggered by a second IPC CONNECT message for the same
222+
* buffer-component pair (within one testcase, or via state carry-over
223+
* between fuzzer testcases when IPC topology is not torn down).
224+
*/
225+
buf_list = dir == PPL_DIR_DOWNSTREAM ?
226+
&buffer->source_list : &buffer->sink_list;
227+
if (!list_is_empty(buf_list)) {
228+
comp_err(comp, "buffer %d already connected dir %d",
229+
buf_get_id(buffer), dir);
230+
PPL_UNLOCK();
231+
return -EINVAL;
232+
}
233+
210234
comp_list = comp_buffer_list(comp, dir);
211235
buffer_attach(buffer, comp_list, dir);
212236
buffer_set_comp(buffer, comp, dir);

0 commit comments

Comments
 (0)