Commit b665fcb
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.
Move buffer_comp_list() from a file-static helper in comp_buffer.c to a
public static inline in buffer.h alongside similar accessors like
buffer_get_comp(). This enables reuse in pipeline_connect() where the
same direction-to-list-node mapping is needed for the guard check.
Add an early-exit guard in pipeline_connect() before buffer_attach():
use buffer_comp_list() to obtain the relevant list node, then check
list_is_empty(). On a list node (not a list head), list_is_empty()
returns true only when node->next == node, meaning the node is unlinked.
If it returns false the buffer is already connected and the call is
rejected with -EINVAL.
Verified with -s address on the full IPC3 corpus (~95K runs, 41 s):
zero crashes, zero hangs, ~2300 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 b665fcb
3 files changed
Lines changed: 41 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
622 | 622 | | |
623 | 623 | | |
624 | 624 | | |
625 | | - | |
626 | | - | |
627 | | - | |
628 | | - | |
629 | | - | |
630 | | - | |
631 | | - | |
632 | 625 | | |
633 | 626 | | |
634 | 627 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
198 | 198 | | |
199 | 199 | | |
200 | 200 | | |
| 201 | + | |
201 | 202 | | |
202 | 203 | | |
203 | 204 | | |
| |||
207 | 208 | | |
208 | 209 | | |
209 | 210 | | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
210 | 240 | | |
211 | 241 | | |
212 | 242 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
300 | 300 | | |
301 | 301 | | |
302 | 302 | | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
303 | 314 | | |
304 | 315 | | |
305 | 316 | | |
| |||
0 commit comments