Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions src/platform/posix/ipc.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <sof/lib/mailbox.h>
#include <sof/ipc/common.h>
#include <sof/ipc/schedule.h>
#include <sof/ipc/topology.h>
#include <sof/schedule/edf_schedule.h>
#include <sof/audio/component_ext.h>
#include <stdbool.h>
Expand Down Expand Up @@ -37,6 +38,131 @@ extern size_t posix_fuzz_sz;
static uint8_t fuzz_in[65536];
static size_t fuzz_in_sz;

/*
* posix_ipc_teardown - drop all IPC-tracked objects left over from the
* previous fuzz testcase so the next one starts from a clean topology.
*
* Walking and freeing comp_list in the same pass is unsafe because the
* SOF free helpers unlink the entry from the list, so we snapshot the
* IDs of one type into a local array first and then call the typed
* free function for each one. Passes run in dependency order
* (COMPONENT -> BUFFER -> PIPELINE) to keep the SOF topology layer
* from dereferencing already-freed parents.
*
* A final force-drain pass walks anything still on the list and removes
* it with list_item_del() + rfree() directly. That keeps the harness
* future-proof against new COMP_TYPE_* values: even if upstream adds a
* type we forget to register here, comp_list will still be empty on
* the next entry. The inner union object (cd/cb/pipeline) is leaked in
* that edge case, which is acceptable for a fuzzing harness.
*/
#define POSIX_TEARDOWN_MAX_DEVS 256

static void posix_ipc_teardown(void)
{
static const uint16_t free_order[] = {
COMP_TYPE_COMPONENT,
#if CONFIG_IPC_MAJOR_3
/*
* IPC4 never stores COMP_TYPE_BUFFER in comp_list
* (see ipc4/helper.c) and ipc_buffer_free() is not
* compiled for IPC4, so skip the buffer pass there.
*/
COMP_TYPE_BUFFER,
#endif
COMP_TYPE_PIPELINE,
};
uint32_t ids[POSIX_TEARDOWN_MAX_DEVS];
struct ipc_comp_dev *icd;
struct list_item *pos;
int n;

if (!global_ipc)
return;

/*
* Pre-pass: prepare components and pipelines for the ordered free
* passes below.
*
* ipc_comp_free() refuses to free a component unless its state is
* COMP_STATE_READY. A fuzz testcase that ran INIT_INSTANCE followed
* by SET_PIPELINE_STATE:RUNNING will leave components in PREPARE,
* PAUSED, or ACTIVE state. Force every component back to READY so
* the free pass can proceed without silently skipping entries.
*
* For pipelines with an active scheduler task, cancel the task before
* ipc_pipeline_free() calls schedule_task_free() on it. Without this,
* schedule_task_free() blocks waiting for the task to complete, which
* on native_sim can stall for up to 100 LL periods.
*/
list_for_item(pos, &global_ipc->comp_list) {
icd = container_of(pos, struct ipc_comp_dev, list);
if (icd->type == COMP_TYPE_COMPONENT && icd->cd) {
icd->cd->state = COMP_STATE_READY;
/* Ensure buffer lists are valid so ipc_comp_free()
* does not bail at the uninitialized-list check.
* A component whose init failed partway may have
* NULL list pointers.
*/
if (!icd->cd->bsource_list.next)
list_init(&icd->cd->bsource_list);
if (!icd->cd->bsink_list.next)
list_init(&icd->cd->bsink_list);
}
if (icd->type == COMP_TYPE_PIPELINE &&
icd->pipeline && icd->pipeline->pipe_task)
schedule_task_cancel(icd->pipeline->pipe_task);
}

for (int pass = 0; pass < (int)ARRAY_SIZE(free_order); pass++) {
uint16_t type = free_order[pass];

n = 0;
list_for_item(pos, &global_ipc->comp_list) {
Comment on lines +117 to +121
icd = container_of(pos, struct ipc_comp_dev, list);
if (icd->type == type && n < POSIX_TEARDOWN_MAX_DEVS)
ids[n++] = icd->id;
}

for (int i = 0; i < n; i++) {
switch (type) {
case COMP_TYPE_COMPONENT:
ipc_comp_free(global_ipc, ids[i]);
break;
#if CONFIG_IPC_MAJOR_3
case COMP_TYPE_BUFFER:
ipc_buffer_free(global_ipc, ids[i]);
break;
#endif
case COMP_TYPE_PIPELINE:
ipc_pipeline_free(global_ipc, ids[i]);
break;
}
}
}

/*
* Force-drain anything remaining (unknown/future COMP_TYPE_*).
* Snapshot the residual list then remove each entry directly.
* The inner union pointer leaks, but comp_list will be empty
* and the next testcase will not observe stale entries.
*/
n = 0;
list_for_item(pos, &global_ipc->comp_list) {
if (n < POSIX_TEARDOWN_MAX_DEVS) {
icd = container_of(pos, struct ipc_comp_dev, list);
ids[n++] = icd->id;
}
}
for (int i = 0; i < n; i++) {
icd = ipc_get_comp_dev(global_ipc, COMP_TYPE_ANY, ids[i]);
if (icd) {
list_item_del(&icd->list);
rfree(icd);
}
}
}

/*
* Testcase-isolation helpers used by the libFuzzer entry point in
* fuzz.c. They keep ownership of the cross-call state in one module
Expand All @@ -45,6 +171,7 @@ static size_t fuzz_in_sz;
*/
void posix_fuzz_case_begin(void)
{
posix_ipc_teardown();
fuzz_in_sz = 0;
}

Expand Down
Loading