pipeline: reject double-connect of already-attached buffer#10812
Open
tmleman wants to merge 1 commit into
Open
pipeline: reject double-connect of already-attached buffer#10812tmleman wants to merge 1 commit into
tmleman wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens the audio pipeline graph connection logic by preventing double-connects of the same buffer in a given direction, avoiding doubly-linked list corruption that can lead to hangs in ipc_comp_free() and potential use-after-free crashes during disconnect/free paths (notably observed via IPC3 persistent-mode fuzzing).
Changes:
- Add an early-exit guard in
pipeline_connect()to reject connections when the buffer’s relevant list node is already attached. - Emit an error and return
-EINVALon duplicate connect attempts.
| * 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 ? |
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.