blob: 046f90876f5dbfbd29566e3deda1d6f39d8d687b [file] [edit]
use super::domains::GenericCorpusValue;
use super::internal::{BoxedFuzzTest, FuzzTest};
use super::options::{self, CentipedeArgs, ExecutionAction};
use ::engine::engine_ffi;
use ::engine::{
BytesSink, CoverageDomainRegistry, DiagnosticSink, ExecuteContext, FeedbackSink, InputSink,
};
use spin::Mutex;
use std::ffi::CString;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::LazyLock;
/// The DiagnosticSink provided by the engine while creating the adapter.
///
/// Storing the `DiagnosticSink` in this global is necessary because C death callbacks (e.g.,
/// sanitizer traps in `crash_handler.rs`) do not have access to the adapter instance and must rely
/// on a global lookup to report crashes.
static DIAGNOSTIC_SINK: Mutex<Option<DiagnosticSink>> = Mutex::new(None);
/// A global execution context token.
///
/// This is set to `Some(ExecuteContext)` when the fuzz test is currently executing
/// (inside the `execute` method of `RustFuzzTestAdapter`) and `None` otherwise.
///
/// The inner ExecuteContext is used as a witness token to prove to the fuzzing engine that findings
/// are being emitted during the execution of a test case.
/// A global is necessary because C death callbacks (e.g. sanitizer traps in `crash_handler.rs`) do
/// not have access to the adapter instance.
static EXECUTE_CONTEXT: Mutex<Option<ExecuteContext>> = Mutex::new(None);
/// Sets the global DiagnosticSink.
pub(crate) fn set_diagnostic_sink(sink: DiagnosticSink) {
let mut guard = DIAGNOSTIC_SINK.lock();
*guard = Some(sink);
}
/// Clears the global DiagnosticSink, setting it to None.
pub(crate) fn clear_diagnostic_sink() {
let mut guard = DIAGNOSTIC_SINK.lock();
*guard = None;
}
/// Emits an error into the DiagnosticSink if the DiagnosticSink is set.
/// This function blocks till it can get the lock on the global DiagnosticSink and is not
/// signal-safe.
pub(crate) fn emit_error(message: &str) {
DIAGNOSTIC_SINK.lock().as_ref().map(|sink| sink.emit_error(message));
}
/// Emits a finding into the DiagnosticSink if the DiagnosticSink is set.
/// This function blocks till it can get the lock on the global DiagnosticSink and is not
/// signal-safe. Call `try_emit_finding` from a signal handler instead.
pub(crate) fn emit_finding(description: &str, signature: &str) {
let guard = EXECUTE_CONTEXT.lock();
let token =
guard.as_ref().expect("emit_finding should only be called when a test is executing");
DIAGNOSTIC_SINK
.lock()
.as_ref()
.expect("diagnostic sink should be present when a test is executing")
.emit_finding(token, description, signature);
}
/// Emits a finding into the DiagnosticSink if the DiagnosticSink is set, and we are in the Execute
/// callback context. It only tries once to get the lock. The return value indicates if the finding
/// was successfully emitted.
///
/// This function return false if:
/// - it could not get the lock on DiagnosticSink.
/// - it got the lock but the global DiagnosticSink was not set.
/// - diagnostic sink was set but the test was not executing.
#[allow(dead_code)]
pub(crate) fn try_emit_finding(description: &str, signature: &str) -> bool {
let Some(diagnostic_guard) = DIAGNOSTIC_SINK.try_lock() else {
return false;
};
let Some(sink) = diagnostic_guard.as_ref() else {
return false;
};
let Some(execution_guard) = EXECUTE_CONTEXT.try_lock() else {
return false;
};
let Some(token) = execution_guard.as_ref() else {
return false;
};
sink.emit_finding(token, description, signature);
return true;
}
// We double-box the input because `GenericCorpusValue` is a fat pointer (`Box<dyn Any>`),
// which cannot be directly represented as a single `usize` in `FuzzTestInputHandle` for FFI.
// By boxing it again, we get a thin pointer (`Box<Box<dyn Any>>`) that can be safely cast
// to `usize`.
// The memory is reclaimed when the engine calls `free_input`, which reconstructs the outer
// box using `Box::from_raw` and drops it, thereby dropping the inner box and the value.
fn pack_input(input: GenericCorpusValue) -> engine_ffi::FuzzTestInputHandle {
let ptr = Box::into_raw(Box::new(input));
engine_ffi::FuzzTestInputHandle(ptr as usize)
}
/// A guard that manages the global `EXECUTE_CONTEXT` state, setting it to `Some` on creation,
/// and resetting to `None` on drop.
struct ExecutionGuard;
impl ExecutionGuard {
/// Enters the execution context by setting `EXECUTE_CONTEXT`.
///
/// # Safety
/// The caller must ensure we are in a valid state to create an `ExecuteContext`.
unsafe fn enter() -> Self {
*EXECUTE_CONTEXT.lock() = Some(unsafe { ExecuteContext::new() });
Self
}
}
impl Drop for ExecutionGuard {
fn drop(&mut self) {
*EXECUTE_CONTEXT.lock() = None;
}
}
pub struct RustFuzzTestAdapter {
fuzz_test: BoxedFuzzTest,
}
impl RustFuzzTestAdapter {
pub fn set_up_coverage_domains(&self, registry: &mut CoverageDomainRegistry) {
registry.set_up_sancov_domains();
}
pub fn get_preset_seed_inputs(&self, _sink: &mut InputSink) {
// TODO(the-shank): implement this once the macro has support for specifying preset seed
// inputs
}
pub fn get_random_seed_input(&self, sink: &mut InputSink) {
match self.fuzz_test.domains().init(&mut rand::rng()) {
Ok(val) => {
sink.emit(pack_input(val));
}
Err(e) => {
emit_error(&format!("Failed to initialize random seed: {:?}", e));
}
}
}
pub fn mutate(&self, origin: &GenericCorpusValue, shrink: bool, sink: &mut InputSink) {
let mut mutant = match self.fuzz_test.domains().clone_corpus_value(origin) {
Ok(val) => val,
Err(e) => {
emit_error(&format!("Failed to clone input for mutation: {:?}", e));
return;
}
};
if let Err(e) = self.fuzz_test.domains().mutate(&mut mutant, &mut rand::rng(), shrink) {
emit_error(&format!("Failed to mutate: {:?}", e));
return;
}
sink.emit(pack_input(mutant));
}
pub fn cross_over(
&self,
origin: &GenericCorpusValue,
_other: &GenericCorpusValue,
sink: &mut InputSink,
) {
// TODO(the-shank): implement cross_over. Currently we are just calling mutate.
self.mutate(origin, false, sink);
}
pub fn execute(&self, input: &GenericCorpusValue, sink: &mut FeedbackSink) {
// TODO(the-shank): modify this in order to support persistent mode and batch execution.
static CLEAR_STARTUP_COVERAGE: AtomicBool = AtomicBool::new(true);
coverage::prepare_coverage(CLEAR_STARTUP_COVERAGE.swap(false, Ordering::AcqRel));
// SAFETY: We are currently in the `execute` callback of `RustFuzzTestAdapter`.
let _guard = unsafe { ExecutionGuard::enter() };
let result = self.fuzz_test.execute(input);
coverage::post_process_coverage(false);
sink.emit_sancov_features();
if !result {
emit_finding("Property function ran but crashed.", "Unwinding panic");
}
}
pub fn serialize_input_content(&self, input: &GenericCorpusValue, sink: &mut BytesSink) {
match self.fuzz_test.domains().serialize_corpus(input) {
Ok(serialized) => {
sink.emit(&serialized);
}
Err(e) => {
emit_error(&format!("Failed to serialize input: {:?}", e));
}
}
}
pub fn deserialize_input_content(&self, content: &[u8], sink: &mut InputSink) {
match self.fuzz_test.domains().parse_corpus(content) {
Ok(val) => {
sink.emit(pack_input(val));
}
Err(e) => {
emit_error(&format!("Failed to deserialize input: {:?}", e));
}
}
}
pub fn serialize_input_metadata(&self, _input: &GenericCorpusValue, _sink: &mut BytesSink) {
// TODO(the-shank): to be implemented
}
pub fn update_input_metadata(&self, _metadata: &[u8], _input: &mut GenericCorpusValue) {
// TODO(the-shank): to be implemented
}
pub fn free_input(&self, input: engine_ffi::FuzzTestInputHandle) {
if input.0 != 0 {
// SAFETY: The engine guarantees `input` was created by `deserialize_input_content_callback`
// (or `emit` in `InputSink`) and has not been freed yet.
unsafe {
let _ = Box::from_raw(input.0 as *mut GenericCorpusValue);
}
}
}
}
pub struct RustFuzzTestAdapterManager {
pub test_name: &'static str,
pub fuzz_test_factory: fn() -> BoxedFuzzTest,
}
impl RustFuzzTestAdapterManager {
pub fn get_binary_id(&self, sink: &mut BytesSink) {
static ARGV0: LazyLock<CString> = LazyLock::new(|| {
CString::new(
Path::new(&std::env::args().nth(0).unwrap())
.file_name()
.and_then(|f| f.to_str())
.unwrap_or(""),
)
.expect("argv[0] should not contain null bytes")
});
sink.emit(ARGV0.as_bytes());
}
// Replaces "::" with "." to avoid parsing issues in Centipede. Centipede uses ":" as a
// separator in CENTIPEDE_RUNNER_FLAGS and its parser replaces all ":" with "\0", which would
// truncate Rust paths.
pub fn get_test_name(&self, sink: &mut BytesSink) {
let name = self.test_name.replace("::", ".");
sink.emit(name.as_bytes());
}
pub fn construct_adapter(&self) -> RustFuzzTestAdapter {
let fuzz_test = (self.fuzz_test_factory)();
RustFuzzTestAdapter { fuzz_test }
}
pub fn construct_fuzz_test(&self) -> BoxedFuzzTest {
(self.fuzz_test_factory)()
}
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapterManager` passed during initialization.
/// * `sink` is a valid pointer to a `FuzzTestBytesSink` whose lifetime extends for the duration
/// of this call.
pub unsafe extern "C" fn get_binary_id_callback(
ctx: *mut engine_ffi::FuzzTestAdapterManagerCtx,
sink: *const engine_ffi::FuzzTestBytesSink,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapterManager`
// passed during initialization.
let manager = unsafe { &*(ctx as *const RustFuzzTestAdapterManager) };
// SAFETY: The engine guarantees `sink` is a valid pointer to a `FuzzTestBytesSink`
// whose lifetime extends for the duration of this call.
let mut safe_sink = unsafe { BytesSink::from_raw(sink) };
manager.get_binary_id(&mut safe_sink);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapterManager` passed during initialization.
/// * `sink` is a valid pointer to a `FuzzTestBytesSink` whose lifetime extends for the duration
/// of this call.
pub unsafe extern "C" fn get_test_name_callback(
ctx: *mut engine_ffi::FuzzTestAdapterManagerCtx,
sink: *const engine_ffi::FuzzTestBytesSink,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapterManager`
// passed during initialization.
let manager = unsafe { &*(ctx as *const RustFuzzTestAdapterManager) };
// SAFETY: The engine guarantees `sink` is a valid pointer to a `FuzzTestBytesSink`
// whose lifetime extends for the duration of this call.
let mut safe_sink = unsafe { BytesSink::from_raw(sink) };
manager.get_test_name(&mut safe_sink);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapterManager` passed during initialization.
/// * `diagnostic_sink` is a valid pointer to a `FuzzTestDiagnosticSink` whose lifetime extends
/// until `FreeCtx` is called on the adapter.
/// * `adapter_out` is a valid pointer to write the output `FuzzTestAdapter`.
pub unsafe extern "C" fn construct_adapter_callback(
ctx: *mut engine_ffi::FuzzTestAdapterManagerCtx,
diagnostic_sink: *const engine_ffi::FuzzTestDiagnosticSink,
adapter_out: *mut engine_ffi::FuzzTestAdapter,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapterManager`
// passed during initialization.
let manager = unsafe { &*(ctx as *const RustFuzzTestAdapterManager) };
// SAFETY: The engine guarantees `diagnostic_sink` is a valid pointer to a `FuzzTestDiagnosticSink`
// whose lifetime extends until `FreeCtx` is called on the adapter.
let safe_sink = unsafe { DiagnosticSink::from_raw(diagnostic_sink) };
set_diagnostic_sink(safe_sink);
// NOTE: `safe_sink` is not passed to `construct_adapter` or stored in `RustFuzzTestAdapter`.
// Instead, it is maintained in the global `DIAGNOSTIC_SINK` mutex via `set_diagnostic_sink`.
// This is necessary because C death callbacks (e.g., sanitizer traps in `crash_handler.rs`) do not
// have access to the adapter `self` pointer and must rely on a global lookup to report crashes.
let adapter = manager.construct_adapter();
let boxed_adapter = Box::new(adapter);
// SAFETY: The engine guarantees `adapter_out` is a valid pointer to write the output adapter.
// The returned raw pointer `ctx` will be managed and eventually freed by `free_ctx_callback`.
unsafe {
*adapter_out = engine_ffi::FuzzTestAdapter {
ctx: Box::into_raw(boxed_adapter) as *mut engine_ffi::FuzzTestAdapterCtx,
set_up_coverage_domains: Some(set_up_coverage_domains_callback),
get_preset_seed_inputs: Some(get_preset_seed_inputs_callback),
get_random_seed_input: Some(get_random_seed_input_callback),
mutate: Some(mutate_callback),
cross_over: Some(cross_over_callback),
execute: Some(execute_callback),
serialize_input_content: Some(serialize_input_content_callback),
deserialize_input_content: Some(deserialize_input_content_callback),
serialize_input_metadata: Some(serialize_input_metadata_callback),
update_input_metadata: Some(update_input_metadata_callback),
free_input: Some(free_input_callback),
free_ctx: Some(free_ctx_callback),
};
}
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`.
/// * `registry` is a valid pointer to `FuzzTestCoverageDomainRegistry`.
pub unsafe extern "C" fn set_up_coverage_domains_callback(
ctx: *mut engine_ffi::FuzzTestAdapterCtx,
registry: *const engine_ffi::FuzzTestCoverageDomainRegistry,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter`
// created by `construct_adapter_callback`.
let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) };
// SAFETY: The engine guarantees `registry` is a valid pointer to `FuzzTestCoverageDomainRegistry`.
let mut registry = unsafe { CoverageDomainRegistry::from_raw(registry) };
adapter.set_up_coverage_domains(&mut registry);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`.
/// * `sink` is a valid pointer to `FuzzTestInputSink`.
pub unsafe extern "C" fn get_preset_seed_inputs_callback(
ctx: *mut engine_ffi::FuzzTestAdapterCtx,
sink: *const engine_ffi::FuzzTestInputSink,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter`
// created by `construct_adapter_callback`.
let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) };
// SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestInputSink`.
let mut safe_sink = unsafe { InputSink::from_raw(sink) };
adapter.get_preset_seed_inputs(&mut safe_sink);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`.
/// * `sink` is a valid pointer to `FuzzTestInputSink`.
pub unsafe extern "C" fn get_random_seed_input_callback(
ctx: *mut engine_ffi::FuzzTestAdapterCtx,
sink: *const engine_ffi::FuzzTestInputSink,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter`
// created by `construct_adapter_callback`.
let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) };
// SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestInputSink`.
let mut safe_sink = unsafe { InputSink::from_raw(sink) };
adapter.get_random_seed_input(&mut safe_sink);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`.
/// * `origin` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue`
/// managed by the framework.
/// * `sink` is a valid pointer to `FuzzTestInputSink`.
pub unsafe extern "C" fn mutate_callback(
ctx: *mut engine_ffi::FuzzTestAdapterCtx,
origin: engine_ffi::FuzzTestInputHandle,
shrink: std::ffi::c_int,
sink: *const engine_ffi::FuzzTestInputSink,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter`
// created by `construct_adapter_callback`.
let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) };
// SAFETY: The engine guarantees `origin` is a valid `FuzzTestInputHandle` pointing to
// a heap-allocated `GenericCorpusValue` managed by the framework.
let origin_ref = unsafe { &*(origin.0 as *const GenericCorpusValue) };
// SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestInputSink`.
let mut safe_sink = unsafe { InputSink::from_raw(sink) };
adapter.mutate(origin_ref, shrink != 0, &mut safe_sink);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`.
/// * `origin` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue`
/// managed by the framework.
/// * `other` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue`
/// managed by the framework.
/// * `sink` is a valid pointer to `FuzzTestInputSink`.
pub unsafe extern "C" fn cross_over_callback(
ctx: *mut engine_ffi::FuzzTestAdapterCtx,
origin: engine_ffi::FuzzTestInputHandle,
other: engine_ffi::FuzzTestInputHandle,
sink: *const engine_ffi::FuzzTestInputSink,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter`
// created by `construct_adapter_callback`.
let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) };
// SAFETY: The engine guarantees `origin` is a valid `FuzzTestInputHandle` pointing to
// a heap-allocated `GenericCorpusValue` managed by the framework.
let origin_ref = unsafe { &*(origin.0 as *const GenericCorpusValue) };
// SAFETY: The engine guarantees `other` is a valid `FuzzTestInputHandle` pointing to
// a heap-allocated `GenericCorpusValue` managed by the framework.
let other_ref = unsafe { &*(other.0 as *const GenericCorpusValue) };
// SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestInputSink`.
let mut safe_sink = unsafe { InputSink::from_raw(sink) };
adapter.cross_over(origin_ref, other_ref, &mut safe_sink);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`.
/// * `input` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue`
/// managed by the framework.
/// * `sink` is a valid pointer to `FuzzTestFeedbackSink`.
pub unsafe extern "C" fn execute_callback(
ctx: *mut engine_ffi::FuzzTestAdapterCtx,
input: engine_ffi::FuzzTestInputHandle,
sink: *const engine_ffi::FuzzTestFeedbackSink,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter`
// created by `construct_adapter_callback`.
let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) };
// SAFETY: The engine guarantees `input` is a valid `FuzzTestInputHandle` pointing to
// a heap-allocated `GenericCorpusValue` managed by the framework.
let input_ref = unsafe { &*(input.0 as *const GenericCorpusValue) };
// SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestFeedbackSink`.
let mut safe_sink = unsafe { FeedbackSink::from_raw(sink) };
adapter.execute(input_ref, &mut safe_sink);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`.
/// * `input` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue`
/// managed by the framework.
/// * `sink` is a valid pointer to `FuzzTestBytesSink`.
pub unsafe extern "C" fn serialize_input_content_callback(
ctx: *mut engine_ffi::FuzzTestAdapterCtx,
input: engine_ffi::FuzzTestInputHandle,
sink: *const engine_ffi::FuzzTestBytesSink,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter`
// created by `construct_adapter_callback`.
let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) };
// SAFETY: The engine guarantees `input` is a valid `FuzzTestInputHandle` pointing to
// a heap-allocated `GenericCorpusValue` managed by the framework.
let input_ref = unsafe { &*(input.0 as *const GenericCorpusValue) };
// SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestBytesSink`.
let mut safe_sink = unsafe { BytesSink::from_raw(sink) };
adapter.serialize_input_content(input_ref, &mut safe_sink);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`.
/// * `content` is a valid pointer to `FuzzTestBytesView` containing serialized input content.
/// * `sink` is a valid pointer to `FuzzTestInputSink`.
pub unsafe extern "C" fn deserialize_input_content_callback(
ctx: *mut engine_ffi::FuzzTestAdapterCtx,
content: *const engine_ffi::FuzzTestBytesView,
sink: *const engine_ffi::FuzzTestInputSink,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter`
// created by `construct_adapter_callback`.
let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) };
// SAFETY: The engine guarantees `content` is a valid pointer to `FuzzTestBytesView`
// containing serialized input content.
let bytes = unsafe { &*(*content).to_bytes() };
// SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestInputSink`.
let mut safe_sink = unsafe { InputSink::from_raw(sink) };
adapter.deserialize_input_content(bytes, &mut safe_sink);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`.
/// * `input` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue`
/// managed by the framework.
/// * `sink` is a valid pointer to `FuzzTestBytesSink`.
pub unsafe extern "C" fn serialize_input_metadata_callback(
ctx: *mut engine_ffi::FuzzTestAdapterCtx,
input: engine_ffi::FuzzTestInputHandle,
sink: *const engine_ffi::FuzzTestBytesSink,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter`
// created by `construct_adapter_callback`.
let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) };
// SAFETY: The engine guarantees `input` is a valid `FuzzTestInputHandle` pointing to
// a heap-allocated `GenericCorpusValue` managed by the framework.
let input_ref = unsafe { &*(input.0 as *const GenericCorpusValue) };
// SAFETY: The engine guarantees `sink` is a valid pointer to `FuzzTestBytesSink`.
let mut safe_sink = unsafe { BytesSink::from_raw(sink) };
adapter.serialize_input_metadata(input_ref, &mut safe_sink);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`.
/// * `metadata` is a valid pointer to `FuzzTestBytesView` containing serialized input metadata.
/// * `input` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue`
/// managed by the framework, and the engine guarantees exclusive access to it for the call duration.
pub unsafe extern "C" fn update_input_metadata_callback(
ctx: *mut engine_ffi::FuzzTestAdapterCtx,
metadata: *const engine_ffi::FuzzTestBytesView,
input: engine_ffi::FuzzTestInputHandle,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter`
// created by `construct_adapter_callback`.
let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) };
// SAFETY: The engine guarantees `metadata` is a valid pointer to `FuzzTestBytesView`
// containing serialized input metadata.
let bytes = unsafe { &*(*metadata).to_bytes() };
// SAFETY: The engine guarantees `input` is a valid `FuzzTestInputHandle` pointing to
// a heap-allocated `GenericCorpusValue` managed by the framework, and provides exclusive
// access to it.
let input_ref = unsafe { &mut *(input.0 as *mut GenericCorpusValue) };
adapter.update_input_metadata(bytes, input_ref);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`.
/// * `input` is a valid `FuzzTestInputHandle` pointing to a heap-allocated `GenericCorpusValue`
/// managed by the framework that has not been freed yet.
pub unsafe extern "C" fn free_input_callback(
ctx: *mut engine_ffi::FuzzTestAdapterCtx,
input: engine_ffi::FuzzTestInputHandle,
) {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter`
// created by `construct_adapter_callback`.
let adapter = unsafe { &*(ctx as *const RustFuzzTestAdapter) };
adapter.free_input(input);
}
/// # Safety
///
/// The caller must ensure that:
/// * `ctx` is a valid pointer to the `RustFuzzTestAdapter` created by `construct_adapter_callback`.
pub unsafe extern "C" fn free_ctx_callback(ctx: *mut engine_ffi::FuzzTestAdapterCtx) {
if !ctx.is_null() {
// SAFETY: The engine guarantees `ctx` is a valid pointer to the `RustFuzzTestAdapter`
// created by `construct_adapter_callback`.
let ptr = ctx as *mut RustFuzzTestAdapter;
drop(unsafe { Box::from_raw(ptr) });
clear_diagnostic_sink();
}
}
pub fn run_smoke_test(fuzztest: &dyn FuzzTest) {
let start_time = std::time::Instant::now();
// TODO(the-shank): these should be configurable externally.
let smoke_test_duration = std::time::Duration::from_secs(1);
let only_shrink = false;
// TODO(the-shank): the rng seed should be configurable
let mut rng = rand::rng();
let mut generic_corpus_value = fuzztest
.domains()
.init(&mut rng)
.expect("domain initialization should succeed to provide an initial corpus value");
let result = fuzztest.execute(&generic_corpus_value);
// DISCUSS: probably need a more "googletest" approach to handling the result here?
assert!(result);
while start_time.elapsed() < smoke_test_duration {
fuzztest
.domains()
.mutate(&mut generic_corpus_value, &mut rng, only_shrink)
.expect("domain mutation should succeed");
let result = fuzztest.execute(&generic_corpus_value);
// DISCUSS: probably need a more "googletest" approach to handling the result here?
assert!(result);
}
}
pub enum WorkerStatus {
Success,
Failure,
}
/// Processes and executes a fuzz test using the provided [`RustFuzzTestAdapterManager`].
///
/// This function serves as the primary entry point for running a fuzz test. It manages test
/// execution across two operational modes:
///
/// 1. Worker Mode: Registers signal/sanitizer crash handlers and attempts to hand off execution to
/// the external fuzzing engine (e.g., Centipede) via [`maybe_run_as_worker`].
/// - If the binary was spawned as a worker process by the fuzzing engine, control remains in the
/// engine loop until complete.
/// - Returns cleanly on [`WorkerStatus::Success`], or panics on [`WorkerStatus::Failure`] to
/// signal test failure to the harness.
/// 2. Smoke Test Mode: If worker mode is not active (e.g., during standard `blaze test` or
/// `cargo test` unit test runs), falls back to executing a short local smoke test using sample
/// inputs and mutation iterations to verify property function sanity.
pub fn process(manager: RustFuzzTestAdapterManager) {
super::crash_handler::register_crash_handler();
// box it to get a stable heap address.
let manager = Box::new(manager);
if let Some(status) = maybe_run_as_worker(&manager) {
match status {
WorkerStatus::Success => {
// do nothing, the harness would continue to the next test
return;
}
WorkerStatus::Failure => {
std::process::exit(1);
}
}
}
// worker mode was not run -- determine what to do next.
match options::determine_execution_action(Some(manager.test_name)) {
ExecutionAction::Standalone(centipede_args) => {
let status = run_standalone_mode(&manager, centipede_args);
if status == engine_ffi::FuzzTestControllerStatus::Failure {
panic!("FuzzTest controller reported failure");
}
}
ExecutionAction::ReplayAllCrashes { args, list_file } => {
let status = run_standalone_mode(&manager, args);
if status == engine_ffi::FuzzTestControllerStatus::Failure {
panic!("FuzzTest controller reported failure");
}
// Now read the file and replay each crash
if let Ok(contents) = std::fs::read_to_string(list_file.path()) {
let options = options::get_fuzztest_options();
for crash_id in contents.lines() {
let crash_id = crash_id.trim();
if crash_id.is_empty() {
continue;
}
let replay_mode =
options::ExecutionMode::ReplayCrash(options::ReplayCrashOptions {
replay_id: crash_id.to_string(),
});
if let Ok(Some(replay_args)) = CentipedeArgs::from_execution_mode(
&replay_mode,
options,
Some(manager.test_name),
None,
) {
// We ignore the result here because replaying a crash is expected to fail
// (report failure). We want to continue replaying other crashes.
let _ = run_standalone_mode(&manager, replay_args);
}
}
}
}
ExecutionAction::SmokeTest => {
let fuzz_test = manager.construct_fuzz_test();
run_smoke_test(fuzz_test.as_ref());
}
}
}
fn make_ffi_manager(manager: &RustFuzzTestAdapterManager) -> engine_ffi::FuzzTestAdapterManager {
let ctx =
manager as *const RustFuzzTestAdapterManager as *mut engine_ffi::FuzzTestAdapterManagerCtx;
engine_ffi::FuzzTestAdapterManager {
ctx,
get_binary_id: Some(get_binary_id_callback),
get_test_name: Some(get_test_name_callback),
construct_adapter: Some(construct_adapter_callback),
}
}
/// Attempts to run the fuzz test in Centipede worker mode using the FFI engine interface.
///
/// This function constructs a C-compatible [`engine_ffi::FuzzTestAdapterManager`] wrapping the given `manager`
/// with required callback function pointers (`get_binary_id_callback`, `get_test_name_callback`, `construct_adapter_callback`),
/// and invokes [`engine_ffi::FuzzTestWorkerMaybeRun`].
/// This function constructs a C-compatible [`engine_ffi::FuzzTestAdapterManager`] wrapping
/// the given `manager` with required callback function pointers (`get_binary_id_callback`,
/// `get_test_name_callback`, `construct_adapter_callback`), and invokes
/// [`engine_ffi::FuzzTestWorkerMaybeRun`].
///
/// # Returns
/// - `Some(WorkerStatus::Success)`: The process ran in worker mode and completed successfully.
/// - `Some(WorkerStatus::Failure)`: The process ran in worker mode and encountered a failure.
/// - `None`: Worker mode was not requested (`kFuzzTestWorkerNotRequired`). The caller should fall
/// back to standalone smoke-test execution.
fn maybe_run_as_worker(manager: &RustFuzzTestAdapterManager) -> Option<WorkerStatus> {
let ffi_manager = make_ffi_manager(manager);
// SAFETY: `manager` is a valid heap-allocated `RustFuzzTestAdapterManager`,
// and `ffi_manager` contains valid C-compatible function pointer callbacks.
let status = unsafe { engine_ffi::FuzzTestWorkerMaybeRun(&ffi_manager) };
match status {
engine_ffi::FuzzTestWorkerStatus::Success => Some(WorkerStatus::Success),
engine_ffi::FuzzTestWorkerStatus::Failure => Some(WorkerStatus::Failure),
engine_ffi::FuzzTestWorkerStatus::NotRequired => None,
}
}
fn run_standalone_mode(
manager: &RustFuzzTestAdapterManager,
centipede_args: CentipedeArgs,
) -> engine_ffi::FuzzTestControllerStatus {
let ffi_manager = make_ffi_manager(manager);
// SAFETY: `ffi_manager` wraps `manager` which is guaranteed to be alive. `centipede_args` is
// alive until the end of this function, since we have the ownership, ensuring that the
// pointers passed to `FuzzTestControllerRun` remain valid.
unsafe { engine_ffi::FuzzTestControllerRun(&ffi_manager, &centipede_args.as_bytes_views()) }
}