Skip to content
Open
Show file tree
Hide file tree
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
43 changes: 37 additions & 6 deletions src/platform/posix/fuzz.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,68 @@
#include <zephyr/sys/time_units.h>
#include <nsi_cpu_if.h>
#include <nsi_main_semipublic.h>
#include <platform/fuzz.h>

const uint8_t *posix_fuzz_buf;
size_t posix_fuzz_sz;

/* Number of simulator quanta the budget is split into for the
* drain-or-abort loop. More quanta = earlier exit on quick testcases.
*/
#define POSIX_FUZZ_DRAIN_QUANTA 8

/**
* Entry point for fuzzing. Works by placing the data
* into two known symbols, triggering an app-visible interrupt, and
* then letting the simulator run for a fixed amount of time (intended to be
* "long enough" to handle the event and reach a quiescent state
* again)
* then letting the simulator run for up to a fixed amount of time
* split into small quanta, exiting as soon as the OS has drained the
* staged fuzz input. If the budget is exhausted before drain we drop
* pending state to keep testcases isolated.
*/
NATIVE_SIMULATOR_IF
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz)
{
static bool runner_initialized;
uint64_t total_us;
uint64_t quantum_us;
int i;

if (!runner_initialized) {
nsi_init(0, NULL);
runner_initialized = true;
}

total_us = k_ticks_to_us_ceil64(CONFIG_ZEPHYR_POSIX_FUZZ_TICKS);
quantum_us = (total_us + POSIX_FUZZ_DRAIN_QUANTA - 1) / POSIX_FUZZ_DRAIN_QUANTA;
if (quantum_us == 0)
quantum_us = 1;

/* Fresh testcase: drop any leftovers from a previous case before
* staging the new buffer, so state from the prior input cannot
* influence this one.
*/
posix_fuzz_case_begin();

/* Provide the fuzz data to the embedded OS as an interrupt, with
* "DMA-like" data placed into posix_fuzz_buf/sz.
*/
posix_fuzz_buf = data;
posix_fuzz_sz = sz;
hw_irq_ctrl_set_irq(CONFIG_ZEPHYR_POSIX_FUZZ_IRQ);

/* Give the OS time to process whatever happened in that
* interrupt and reach an idle state.
/* Bounded drain loop: run simulator in small quanta and exit
* as soon as both the raw input buffer and the staged IPC
* queue are empty.
*/
for (i = 0; i < POSIX_FUZZ_DRAIN_QUANTA; i++) {
nsi_exec_for(quantum_us);
if (!posix_fuzz_case_pending())
return 0;
}

/* Budget exhausted without full drain: hard reset so the next
* testcase starts clean.
*/
nsi_exec_for(k_ticks_to_us_ceil64(CONFIG_ZEPHYR_POSIX_FUZZ_TICKS));
posix_fuzz_case_abort();
return 0;
}
34 changes: 34 additions & 0 deletions src/platform/posix/include/platform/fuzz.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/* Copyright(c) 2026 Intel Corporation. All rights reserved. */

#ifndef PLATFORM_POSIX_FUZZ_H
#define PLATFORM_POSIX_FUZZ_H

#include <stdbool.h>

/**
* @brief Reset IPC staging state at the start of a new fuzz testcase.
*
* Must be called before staging new fuzz input so that any leftover
* state from a previous testcase that failed to drain within the tick
* budget cannot influence the new one.
*/
void posix_fuzz_case_begin(void);

/**
* @brief Query whether any staged IPC fuzz input is still pending.
*
* @return true if the raw input buffer or the IPC staging queue is
* non-empty, false when fully drained.
*/
bool posix_fuzz_case_pending(void);

/**
* @brief Hard-reset all staged IPC fuzz state.
*
* Called when the simulator tick budget is exhausted before the input
* has been fully drained, ensuring the next testcase starts clean.
*/
void posix_fuzz_case_abort(void);

#endif /* PLATFORM_POSIX_FUZZ_H */
25 changes: 25 additions & 0 deletions src/platform/posix/ipc.c
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright(c) 2022 Google LLC. All rights reserved.
// Author: Andy Ross <andyross@google.com>
#include <platform/fuzz.h>
#include <sof/lib/uuid.h>
#include <sof/ipc/msg.h>
#include <sof/lib/mailbox.h>
#include <sof/ipc/common.h>
#include <sof/ipc/schedule.h>
#include <sof/schedule/edf_schedule.h>
#include <sof/audio/component_ext.h>
#include <stdbool.h>
#include <stddef.h>

// 6c8f0d53-ff77-4ca1-b825-c0c4e1b0d322
SOF_DEFINE_REG_UUID(ipc_task_posix);
Expand All @@ -33,6 +36,28 @@ extern size_t posix_fuzz_sz;
static uint8_t fuzz_in[65536];
static size_t fuzz_in_sz;

/*
* Testcase-isolation helpers used by the libFuzzer entry point in
* fuzz.c. They keep ownership of the cross-call state in one module
* so a new testcase never observes leftovers from a previous one that
* failed to drain inside the simulator tick budget.
*/
void posix_fuzz_case_begin(void)
{
fuzz_in_sz = 0;
}

bool posix_fuzz_case_pending(void)
{
return posix_fuzz_sz != 0 || fuzz_in_sz != 0;
}

void posix_fuzz_case_abort(void)
{
posix_fuzz_sz = 0;
fuzz_in_sz = 0;
}

// The protocol here is super simple: the first byte is a message size
// in units of 16 bits (the buffer maximum defaults to 384 bytes, and
// I didn't want to waste space early in the buffer lest I confuse the
Expand Down
Loading