diff --git a/src/platform/posix/ipc.c b/src/platform/posix/ipc.c index 8ef28ec654e4..6a7a5d66b4f6 100644 --- a/src/platform/posix/ipc.c +++ b/src/platform/posix/ipc.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -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) { + 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 @@ -45,6 +171,7 @@ static size_t fuzz_in_sz; */ void posix_fuzz_case_begin(void) { + posix_ipc_teardown(); fuzz_in_sz = 0; }