| 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, ¢ipede_args.as_bytes_views()) } |
| } |