From 70eb8158e516d2446133099c0c5bf277b33b82dd Mon Sep 17 00:00:00 2001 From: "Dongjia \"toka\" Zhang" Date: Tue, 18 Feb 2025 14:25:21 +0100 Subject: [PATCH] Refactor stages (#3002) * refactor * miss * lol * revivet tests * aa * ? * a * fuck * fuck * a * m * fuck --- .github/workflows/build_and_test.yml | 1 - fuzzers/baby/tutorial/src/lib.rs | 2 +- .../binary_only/qemu_launcher/src/instance.rs | 2 +- fuzzers/forkserver/libafl-fuzz/Justfile | 2 +- fuzzers/forkserver/libafl-fuzz/src/main.rs | 5 +- .../full_system/nyx_launcher/src/instance.rs | 2 +- fuzzers/inprocess/libfuzzer_libpng/src/lib.rs | 2 +- .../libfuzzer_libpng_cmin/src/lib.rs | 2 +- .../libfuzzer_libpng_norestart/src/lib.rs | 2 +- .../libfuzzer_libpng_tcp_manager/src/lib.rs | 2 +- .../libfuzzer_windows_asan/src/lib.rs | 2 +- libafl/src/events/centralized.rs | 4 +- libafl/src/events/llmp/restarting.rs | 7 +- libafl/src/events/mod.rs | 12 +- libafl/src/events/simple.rs | 9 +- libafl/src/events/tcp.rs | 9 +- libafl/src/executors/inprocess/mod.rs | 2 +- libafl/src/executors/mod.rs | 3 +- libafl/src/fuzzer/mod.rs | 10 +- libafl/src/stages/logics.rs | 362 +++++++++++++++++- libafl/src/stages/mod.rs | 139 +------ libafl/src/stages/time_tracker.rs | 18 +- libafl/src/state/mod.rs | 32 +- libafl/src/state/stack.rs | 7 +- libafl_libfuzzer/log | 4 + libafl_libfuzzer/runtime/src/fuzz.rs | 4 +- libafl_libfuzzer/runtime/src/report.rs | 4 +- libafl_sugar/src/forkserver.rs | 4 +- libafl_sugar/src/inmemory.rs | 8 +- libafl_sugar/src/qemu.rs | 8 +- 30 files changed, 469 insertions(+), 201 deletions(-) create mode 100644 libafl_libfuzzer/log diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 1b79bb67f9..2b95630abd 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -237,7 +237,6 @@ jobs: needs: - fuzzers-preflight strategy: - fail-fast: false matrix: os: [ ubuntu-24.04 ] fuzzer: diff --git a/fuzzers/baby/tutorial/src/lib.rs b/fuzzers/baby/tutorial/src/lib.rs index 3e44abff34..b86a5677f5 100644 --- a/fuzzers/baby/tutorial/src/lib.rs +++ b/fuzzers/baby/tutorial/src/lib.rs @@ -186,7 +186,7 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re // It's important, that we store the state before restarting! // Else, the parent will not respawn a new child and quit. - restarting_mgr.on_restart(&mut state)?; + restarting_mgr.mgr_on_restart(&mut state)?; Ok(()) } diff --git a/fuzzers/binary_only/qemu_launcher/src/instance.rs b/fuzzers/binary_only/qemu_launcher/src/instance.rs index 245afc3b91..966d04d6ff 100644 --- a/fuzzers/binary_only/qemu_launcher/src/instance.rs +++ b/fuzzers/binary_only/qemu_launcher/src/instance.rs @@ -469,7 +469,7 @@ impl Instance<'_, M> { // It's important, that we store the state before restarting! // Else, the parent will not respawn a new child and quit. - self.mgr.on_restart(state)?; + self.mgr.mgr_on_restart(state)?; } } else { fuzzer.fuzz_loop(stages, executor, state, &mut self.mgr)?; diff --git a/fuzzers/forkserver/libafl-fuzz/Justfile b/fuzzers/forkserver/libafl-fuzz/Justfile index d8cce7b03f..f4bcd11e2d 100644 --- a/fuzzers/forkserver/libafl-fuzz/Justfile +++ b/fuzzers/forkserver/libafl-fuzz/Justfile @@ -53,7 +53,7 @@ build_libafl_fuzz_fuzzbench: test_instr: build_afl build_libafl_fuzz #!/bin/bash - AFL_PATH={{AFL_DIR}} {{AFL_CC_PATH}} ./test/test-instr.c -o ./test/out-instr + AFL_PATH={{AFL_DIR}} {{AFL_CC_PATH}} -O0 ./test/test-instr.c -o ./test/out-instr export LIBAFL_DEBUG_OUTPUT=1 export AFL_CORES=0 diff --git a/fuzzers/forkserver/libafl-fuzz/src/main.rs b/fuzzers/forkserver/libafl-fuzz/src/main.rs index 9e6aa8a002..715a93fb37 100644 --- a/fuzzers/forkserver/libafl-fuzz/src/main.rs +++ b/fuzzers/forkserver/libafl-fuzz/src/main.rs @@ -256,14 +256,15 @@ struct Opt { #[arg(short = 't', default_value_t = 1000)] hang_timeout: u64, + #[arg(short = 'd')] + debug_child: bool, + // Environment Variables #[clap(skip)] bench_just_one: bool, #[clap(skip)] bench_until_crash: bool, - #[clap(skip)] - debug_child: bool, #[clap(skip)] is_persistent: bool, #[clap(skip)] diff --git a/fuzzers/full_system/nyx_launcher/src/instance.rs b/fuzzers/full_system/nyx_launcher/src/instance.rs index 568297844b..b705f1790b 100644 --- a/fuzzers/full_system/nyx_launcher/src/instance.rs +++ b/fuzzers/full_system/nyx_launcher/src/instance.rs @@ -250,7 +250,7 @@ impl Instance<'_, M> { // It's important, that we store the state before restarting! // Else, the parent will not respawn a new child and quit. - self.mgr.on_restart(state)?; + self.mgr.mgr_on_restart(state)?; } else { fuzzer.fuzz_loop(stages, executor, state, &mut self.mgr)?; } diff --git a/fuzzers/inprocess/libfuzzer_libpng/src/lib.rs b/fuzzers/inprocess/libfuzzer_libpng/src/lib.rs index 09d5357bfd..fb7abc8774 100644 --- a/fuzzers/inprocess/libfuzzer_libpng/src/lib.rs +++ b/fuzzers/inprocess/libfuzzer_libpng/src/lib.rs @@ -222,7 +222,7 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re // It's important, that we store the state before restarting! // Else, the parent will not respawn a new child and quit. - restarting_mgr.on_restart(&mut state)?; + restarting_mgr.mgr_on_restart(&mut state)?; Ok(()) } diff --git a/fuzzers/inprocess/libfuzzer_libpng_cmin/src/lib.rs b/fuzzers/inprocess/libfuzzer_libpng_cmin/src/lib.rs index 850d15ed27..5f144c37c8 100644 --- a/fuzzers/inprocess/libfuzzer_libpng_cmin/src/lib.rs +++ b/fuzzers/inprocess/libfuzzer_libpng_cmin/src/lib.rs @@ -236,7 +236,7 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re // It's important, that we store the state before restarting! // Else, the parent will not respawn a new child and quit. - restarting_mgr.on_restart(&mut state)?; + restarting_mgr.mgr_on_restart(&mut state)?; Ok(()) } diff --git a/fuzzers/inprocess/libfuzzer_libpng_norestart/src/lib.rs b/fuzzers/inprocess/libfuzzer_libpng_norestart/src/lib.rs index 684d616f76..479faa3b27 100644 --- a/fuzzers/inprocess/libfuzzer_libpng_norestart/src/lib.rs +++ b/fuzzers/inprocess/libfuzzer_libpng_norestart/src/lib.rs @@ -274,7 +274,7 @@ pub extern "C" fn libafl_main() { &mut restarting_mgr, opt.loop_iters, )?; - restarting_mgr.on_restart(&mut state)?; + restarting_mgr.mgr_on_restart(&mut state)?; Ok(()) }; diff --git a/fuzzers/inprocess/libfuzzer_libpng_tcp_manager/src/lib.rs b/fuzzers/inprocess/libfuzzer_libpng_tcp_manager/src/lib.rs index 65a2044d19..ebd7bea130 100644 --- a/fuzzers/inprocess/libfuzzer_libpng_tcp_manager/src/lib.rs +++ b/fuzzers/inprocess/libfuzzer_libpng_tcp_manager/src/lib.rs @@ -219,7 +219,7 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re // It's important, that we store the state before restarting! // Else, the parent will not respawn a new child and quit. - restarting_mgr.on_restart(&mut state)?; + restarting_mgr.mgr_on_restart(&mut state)?; Ok(()) } diff --git a/fuzzers/inprocess/libfuzzer_windows_asan/src/lib.rs b/fuzzers/inprocess/libfuzzer_windows_asan/src/lib.rs index 89b50d70ab..c02ffb863e 100644 --- a/fuzzers/inprocess/libfuzzer_windows_asan/src/lib.rs +++ b/fuzzers/inprocess/libfuzzer_windows_asan/src/lib.rs @@ -184,7 +184,7 @@ fn fuzz(corpus_dirs: &[PathBuf], objective_dir: PathBuf, broker_port: u16) -> Re // It's important, that we store the state before restarting! // Else, the parent will not respawn a new child and quit. - restarting_mgr.on_restart(&mut state)?; + restarting_mgr.mgr_on_restart(&mut state)?; Ok(()) } diff --git a/libafl/src/events/centralized.rs b/libafl/src/events/centralized.rs index 781a17d3ae..64283e0070 100644 --- a/libafl/src/events/centralized.rs +++ b/libafl/src/events/centralized.rs @@ -272,9 +272,9 @@ where SP: ShMemProvider, { #[inline] - fn on_restart(&mut self, state: &mut S) -> Result<(), Error> { + fn mgr_on_restart(&mut self, state: &mut S) -> Result<(), Error> { self.client.await_safe_to_unmap_blocking(); - self.inner.on_restart(state)?; + self.inner.mgr_on_restart(state)?; Ok(()) } } diff --git a/libafl/src/events/llmp/restarting.rs b/libafl/src/events/llmp/restarting.rs index 0a05f9bbe3..00c84c6cc5 100644 --- a/libafl/src/events/llmp/restarting.rs +++ b/libafl/src/events/llmp/restarting.rs @@ -63,10 +63,9 @@ use crate::{ inputs::Input, monitors::Monitor, observers::TimeObserver, - stages::HasCurrentStageId, state::{ - HasCurrentTestcase, HasExecutions, HasImported, HasLastReportTime, HasSolutions, - MaybeHasClientPerfMonitor, Stoppable, + HasCurrentStageId, HasCurrentTestcase, HasExecutions, HasImported, HasLastReportTime, + HasSolutions, MaybeHasClientPerfMonitor, Stoppable, }, Error, }; @@ -257,7 +256,7 @@ where SP: ShMemProvider, { /// Reset the single page (we reuse it over and over from pos 0), then send the current state to the next runner. - fn on_restart(&mut self, state: &mut S) -> Result<(), Error> { + fn mgr_on_restart(&mut self, state: &mut S) -> Result<(), Error> { state.on_restart()?; if let Some(sr) = &mut self.staterestorer { diff --git a/libafl/src/events/mod.rs b/libafl/src/events/mod.rs index 02d2622199..c8ebcafedc 100644 --- a/libafl/src/events/mod.rs +++ b/libafl/src/events/mod.rs @@ -108,7 +108,7 @@ pub struct EventManagerId( use crate::events::multi_machine::NodeId; #[cfg(feature = "introspection")] use crate::monitors::stats::ClientPerfStats; -use crate::{observers::TimeObserver, stages::HasCurrentStageId}; +use crate::{observers::TimeObserver, state::HasCurrentStageId}; /// The log event severity #[derive(Serialize, Deserialize, Debug, Clone, Copy)] @@ -559,10 +559,10 @@ pub trait EventRestarter { /// You *must* ensure that [`HasCurrentStageId::on_restart`] will be invoked in this method, by you /// or an internal [`EventRestarter`], before the state is saved for recovery. /// [`std_on_restart`] is the standard implementation that you can call. - fn on_restart(&mut self, state: &mut S) -> Result<(), Error>; + fn mgr_on_restart(&mut self, state: &mut S) -> Result<(), Error>; } -/// Default implementation of [`EventRestarter::on_restart`] for implementors with the given +/// Default implementation of [`EventRestarter::mgr_on_restart`] for implementors with the given /// constraints pub fn std_on_restart(restarter: &mut EM, state: &mut S) -> Result<(), Error> where @@ -644,7 +644,7 @@ impl EventRestarter for NopEventManager where S: HasCurrentStageId, { - fn on_restart(&mut self, state: &mut S) -> Result<(), Error> { + fn mgr_on_restart(&mut self, state: &mut S) -> Result<(), Error> { std_on_restart(self, state) } } @@ -769,8 +769,8 @@ where EM: EventRestarter, { #[inline] - fn on_restart(&mut self, state: &mut S) -> Result<(), Error> { - self.inner.on_restart(state) + fn mgr_on_restart(&mut self, state: &mut S) -> Result<(), Error> { + self.inner.mgr_on_restart(state) } } diff --git a/libafl/src/events/simple.rs b/libafl/src/events/simple.rs index 77acfbb02b..bbaab9dc35 100644 --- a/libafl/src/events/simple.rs +++ b/libafl/src/events/simple.rs @@ -32,8 +32,9 @@ use crate::{ SendExiting, }, monitors::{stats::ClientStatsManager, Monitor}, - stages::HasCurrentStageId, - state::{HasExecutions, HasLastReportTime, MaybeHasClientPerfMonitor, Stoppable}, + state::{ + HasCurrentStageId, HasExecutions, HasLastReportTime, MaybeHasClientPerfMonitor, Stoppable, + }, Error, HasMetadata, }; #[cfg(feature = "std")] @@ -111,7 +112,7 @@ impl EventRestarter for SimpleEventManager where S: HasCurrentStageId, { - fn on_restart(&mut self, state: &mut S) -> Result<(), Error> { + fn mgr_on_restart(&mut self, state: &mut S) -> Result<(), Error> { std_on_restart(self, state) } } @@ -325,7 +326,7 @@ where MT: Monitor, { /// Reset the single page (we reuse it over and over from pos 0), then send the current state to the next runner. - fn on_restart(&mut self, state: &mut S) -> Result<(), Error> { + fn mgr_on_restart(&mut self, state: &mut S) -> Result<(), Error> { state.on_restart()?; // First, reset the page to 0 so the next iteration can read read from the beginning of this page diff --git a/libafl/src/events/tcp.rs b/libafl/src/events/tcp.rs index 246f1a67dc..b0d2885f95 100644 --- a/libafl/src/events/tcp.rs +++ b/libafl/src/events/tcp.rs @@ -48,10 +48,9 @@ use crate::{ }, inputs::Input, monitors::{stats::ClientStatsManager, Monitor}, - stages::HasCurrentStageId, state::{ - HasCurrentTestcase, HasExecutions, HasImported, HasLastReportTime, HasSolutions, - MaybeHasClientPerfMonitor, Stoppable, + HasCurrentStageId, HasCurrentTestcase, HasExecutions, HasImported, HasLastReportTime, + HasSolutions, MaybeHasClientPerfMonitor, Stoppable, }, Error, HasMetadata, }; @@ -627,7 +626,7 @@ impl EventRestarter for TcpEventManager where S: HasCurrentStageId, { - fn on_restart(&mut self, state: &mut S) -> Result<(), Error> { + fn mgr_on_restart(&mut self, state: &mut S) -> Result<(), Error> { std_on_restart(self, state) } } @@ -861,7 +860,7 @@ where SP: ShMemProvider, { /// Reset the single page (we reuse it over and over from pos 0), then send the current state to the next runner. - fn on_restart(&mut self, state: &mut S) -> Result<(), Error> { + fn mgr_on_restart(&mut self, state: &mut S) -> Result<(), Error> { state.on_restart()?; // First, reset the page to 0 so the next iteration can read read from the beginning of this page diff --git a/libafl/src/executors/inprocess/mod.rs b/libafl/src/executors/inprocess/mod.rs index d8450dd27e..5dd5b640d1 100644 --- a/libafl/src/executors/inprocess/mod.rs +++ b/libafl/src/executors/inprocess/mod.rs @@ -430,7 +430,7 @@ pub fn run_observers_and_save_state( } // Serialize the state and wait safely for the broker to read pending messages - event_mgr.on_restart(state).unwrap(); + event_mgr.mgr_on_restart(state).unwrap(); log::info!("Bye!"); } diff --git a/libafl/src/executors/mod.rs b/libafl/src/executors/mod.rs index 993857df9e..169de9ff3f 100644 --- a/libafl/src/executors/mod.rs +++ b/libafl/src/executors/mod.rs @@ -157,7 +157,8 @@ pub fn common_signals() -> Vec { } #[cfg(test)] -mod test { +/// Tester for executor +pub mod test { use core::marker::PhantomData; use libafl_bolts::{AsSlice, Error}; diff --git a/libafl/src/fuzzer/mod.rs b/libafl/src/fuzzer/mod.rs index e07a520ee7..c161cc152e 100644 --- a/libafl/src/fuzzer/mod.rs +++ b/libafl/src/fuzzer/mod.rs @@ -24,11 +24,11 @@ use crate::{ mark_feature_time, observers::ObserversTuple, schedulers::Scheduler, - stages::{HasCurrentStageId, StagesTuple}, + stages::StagesTuple, start_timer, state::{ - HasCorpus, HasCurrentTestcase, HasExecutions, HasImported, HasLastFoundTime, - HasLastReportTime, HasSolutions, MaybeHasClientPerfMonitor, Stoppable, + HasCorpus, HasCurrentStageId, HasCurrentTestcase, HasExecutions, HasImported, + HasLastFoundTime, HasLastReportTime, HasSolutions, MaybeHasClientPerfMonitor, Stoppable, }, Error, HasMetadata, }; @@ -207,7 +207,7 @@ pub trait Fuzzer { /// because each stage could run the harness for multiple times) /// /// If you use this fn in a restarting scenario to only run for `n` iterations, - /// before exiting, make sure you call `event_mgr.on_restart(&mut state)?;`. + /// before exiting, make sure you call `event_mgr.mgr_on_restart(&mut state)?;`. /// This way, the state will be available in the next, respawned, iteration. fn fuzz_one( &mut self, @@ -233,7 +233,7 @@ pub trait Fuzzer { /// because each stage could run the harness for multiple times) /// /// If you use this fn in a restarting scenario to only run for `n` iterations, - /// before exiting, make sure you call `event_mgr.on_restart(&mut state)?;`. + /// before exiting, make sure you call `event_mgr.mgr_on_restart(&mut state)?;`. /// This way, the state will be available in the next, respawned, iteration. fn fuzz_loop_for( &mut self, diff --git a/libafl/src/stages/logics.rs b/libafl/src/stages/logics.rs index 5a3497a14c..bad0196e0a 100644 --- a/libafl/src/stages/logics.rs +++ b/libafl/src/stages/logics.rs @@ -3,7 +3,8 @@ use core::marker::PhantomData; use crate::{ - stages::{HasNestedStageStatus, Stage, StageId, StagesTuple}, + stages::{Stage, StageId, StagesTuple}, + state::HasNestedStage, Error, }; @@ -14,7 +15,7 @@ pub struct NestedStageRetryCountRestartHelper; impl NestedStageRetryCountRestartHelper { fn should_restart(state: &mut S, _stage: &ST) -> Result where - S: HasNestedStageStatus, + S: HasNestedStage, { state.enter_inner_stage()?; Ok(true) @@ -22,7 +23,7 @@ impl NestedStageRetryCountRestartHelper { fn clear_progress(state: &mut S, _stage: &ST) -> Result<(), Error> where - S: HasNestedStageStatus, + S: HasNestedStage, { state.exit_inner_stage()?; Ok(()) @@ -41,7 +42,7 @@ impl Stage for WhileStage where CB: FnMut(&mut Z, &mut E, &mut S, &mut EM) -> Result, ST: StagesTuple, - S: HasNestedStageStatus, + S: HasNestedStage, { fn perform( &mut self, @@ -95,7 +96,7 @@ impl Stage for IfStage where CB: FnMut(&mut Z, &mut E, &mut S, &mut EM) -> Result, ST: StagesTuple, - S: HasNestedStageStatus, + S: HasNestedStage, { fn perform( &mut self, @@ -150,7 +151,7 @@ where CB: FnMut(&mut Z, &mut E, &mut S, &mut EM) -> Result, ST1: StagesTuple, ST2: StagesTuple, - S: HasNestedStageStatus, + S: HasNestedStage, { fn perform( &mut self, @@ -161,10 +162,12 @@ where ) -> Result<(), Error> { let current = state.current_stage_id()?; + // this is None if you didn't recover from restart + // because should_restart() which is called right before this will create a new stage stack let fresh = current.is_none(); - let closure_return = fresh && (self.closure)(fuzzer, executor, state, manager)?; + let closure_res = fresh && (self.closure)(fuzzer, executor, state, manager)?; - if current == Some(StageId(0)) || closure_return { + if current == Some(StageId(0)) || closure_res { if fresh { state.set_current_stage_id(StageId(0))?; } @@ -220,7 +223,7 @@ pub struct OptionalStage { impl Stage for OptionalStage where ST: StagesTuple, - S: HasNestedStageStatus, + S: HasNestedStage, { fn perform( &mut self, @@ -273,3 +276,344 @@ impl OptionalStage { } } } + +#[cfg(test)] +mod test { + use alloc::rc::Rc; + use core::{cell::RefCell, marker::PhantomData}; + + use libafl_bolts::{ + impl_serdeany, + tuples::{tuple_list, tuple_list_type}, + Error, + }; + use serde::{Deserialize, Serialize}; + + #[cfg(any(not(feature = "serdeany_autoreg"), miri))] + use crate::stages::RetryCountRestartHelper; + use crate::{ + events::NopEventManager, + executors::test::NopExecutor, + stages::{ + ClosureStage, CorpusId, HasCurrentCorpusId, IfElseStage, IfStage, Stage, StagesTuple, + WhileStage, + }, + state::{HasCurrentStageId, StdState}, + HasMetadata, NopFuzzer, + }; + + #[derive(Debug)] + pub struct ResumeSucceededStage { + phantom: PhantomData, + } + #[derive(Debug)] + pub struct ResumeFailedStage { + completed: Rc>, + phantom: PhantomData, + } + #[derive(Serialize, Deserialize, Debug)] + pub struct TestProgress { + count: usize, + } + + impl_serdeany!(TestProgress); + + impl TestProgress { + #[expect(clippy::unnecessary_wraps)] + fn should_restart(state: &mut S, _stage: &ST) -> Result + where + S: HasMetadata, + { + // check if we're resuming + let _metadata = state.metadata_or_insert_with(|| Self { count: 0 }); + Ok(true) + } + + fn clear_progress(state: &mut S, _stage: &ST) -> Result<(), Error> + where + S: HasMetadata, + { + if state.remove_metadata::().is_none() { + return Err(Error::illegal_state( + "attempted to clear status metadata when none was present", + )); + } + Ok(()) + } + } + + impl Stage for ResumeSucceededStage + where + S: HasMetadata, + { + fn perform( + &mut self, + _fuzzer: &mut Z, + _executor: &mut E, + state: &mut S, + _manager: &mut EM, + ) -> Result<(), Error> { + // metadata is attached by the status + let meta = state.metadata_mut::().unwrap(); + meta.count += 1; + assert!( + meta.count == 1, + "Test failed; we resumed a succeeded stage!" + ); + Ok(()) + } + + fn should_restart(&mut self, state: &mut S) -> Result { + TestProgress::should_restart(state, self) + } + + fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { + TestProgress::clear_progress(state, self) + } + } + + impl Stage for ResumeFailedStage + where + S: HasMetadata, + { + fn perform( + &mut self, + _fuzzer: &mut Z, + _executor: &mut E, + state: &mut S, + _manager: &mut EM, + ) -> Result<(), Error> { + // metadata is attached by the status + let meta = state.metadata_mut::().unwrap(); + meta.count += 1; + if meta.count == 1 { + return Err(Error::shutting_down()); + } else if meta.count > 2 { + panic!("Resume was somehow corrupted?") + } else { + self.completed.replace(true); + } + Ok(()) + } + + fn should_restart(&mut self, state: &mut S) -> Result { + TestProgress::should_restart(state, self) + } + + fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { + TestProgress::clear_progress(state, self) + } + } + + #[must_use] + #[allow(clippy::type_complexity)] + pub fn test_resume_stages() -> ( + Rc>, + tuple_list_type!(ResumeSucceededStage, ResumeFailedStage), + ) { + let completed = Rc::new(RefCell::new(false)); + ( + completed.clone(), + tuple_list!( + ResumeSucceededStage { + phantom: PhantomData + }, + ResumeFailedStage { + completed, + phantom: PhantomData + }, + ), + ) + } + + pub fn test_resume(completed: &Rc>, state: &mut S, mut stages: ST) + where + ST: StagesTuple, NopEventManager, S, NopFuzzer>, + S: HasCurrentStageId + HasCurrentCorpusId, + { + #[cfg(any(not(feature = "serdeany_autoreg"), miri))] + unsafe { + TestProgress::register(); + RetryCountRestartHelper::register(); + } + + let mut fuzzer = NopFuzzer::new(); + let mut executor = NopExecutor::new(); + let mut manager = NopEventManager::new(); + for _ in 0..2 { + completed.replace(false); + // fake one, just any number so retryhelper won't fail. + // in reality you always have corpus id set by stdfuzzer + state.set_corpus_id(CorpusId::from(0_usize)).unwrap(); + let Err(e) = stages.perform_all(&mut fuzzer, &mut executor, state, &mut manager) else { + panic!("Test failed; stages should fail the first time.") + }; + assert!( + matches!(e, Error::ShuttingDown), + "Unexpected error encountered." + ); + assert!(!*completed.borrow(), "Unexpectedly complete?"); + state + .on_restart() + .expect("Couldn't notify state of restart."); + assert!( + stages + .perform_all(&mut fuzzer, &mut executor, state, &mut manager) + .is_ok(), + "Test failed; stages should pass the second time." + ); + assert!( + *completed.borrow(), + "Test failed; we did not set completed." + ); + } + } + + #[test] + fn check_resumability_while() { + let once = RefCell::new(true); + + let (completed, stages) = test_resume_stages(); + let whilestage = WhileStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(once.replace(false)), + stages, + ); + let resetstage = ClosureStage::new(|_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| { + once.replace(true); + Ok(()) + }); + let mut state = StdState::nop().unwrap(); + test_resume(&completed, &mut state, tuple_list!(whilestage, resetstage)); + } + + #[test] + fn check_resumability_if() { + let once = RefCell::new(true); + let (completed, stages) = test_resume_stages(); + let ifstage = IfStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(once.replace(false)), + stages, + ); + let resetstage = ClosureStage::new(|_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| { + once.replace(true); + Ok(()) + }); + let mut state = StdState::nop().unwrap(); + test_resume(&completed, &mut state, tuple_list!(ifstage, resetstage)); + } + + #[test] + fn check_resumability_if_deep() { + let (completed, stages) = test_resume_stages(); + let ifstage = IfStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(true), + tuple_list!(IfStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(true), + tuple_list!(IfStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(true), + tuple_list!(IfStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(true), + tuple_list!(IfStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(true), + stages + ),), + ),), + )) + )), + ); + let mut state = StdState::nop().unwrap(); + test_resume(&completed, &mut state, tuple_list!(ifstage)); + } + + #[derive(Debug)] + pub struct PanicStage { + phantom: PhantomData, + } + impl PanicStage { + pub fn new() -> Self { + Self { + phantom: PhantomData, + } + } + } + impl Stage for PanicStage { + fn perform( + &mut self, + _fuzzer: &mut Z, + _executor: &mut E, + _state: &mut S, + _manager: &mut EM, + ) -> Result<(), Error> { + panic!("Test failed; panic stage should never be executed."); + } + + fn should_restart(&mut self, _state: &mut S) -> Result { + Ok(true) + } + + fn clear_progress(&mut self, _state: &mut S) -> Result<(), Error> { + Ok(()) + } + } + + #[test] + fn check_resumability_if_else_if() { + let once = RefCell::new(true); + let (completed, stages) = test_resume_stages(); + let ifstage = IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(once.replace(false)), + stages, + tuple_list!(PanicStage::new()), + ); + let resetstage = ClosureStage::new(|_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| { + once.replace(true); + Ok(()) + }); + let mut state = StdState::nop().unwrap(); + test_resume(&completed, &mut state, tuple_list!(ifstage, resetstage)); + } + + #[test] + fn check_resumability_if_else_else() { + let once = RefCell::new(false); + let (completed, stages) = test_resume_stages(); + let ifstage = IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(once.replace(true)), + tuple_list!(PanicStage::new()), + stages, + ); + let resetstage = ClosureStage::new(|_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| { + once.replace(false); + Ok(()) + }); + let mut state = StdState::nop().unwrap(); + test_resume(&completed, &mut state, tuple_list!(ifstage, resetstage)); + } + #[test] + fn check_resumability_if_else_else_deep() { + let (completed, stages) = test_resume_stages(); + let ifstage = IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(false), + tuple_list!(PanicStage::new()), + tuple_list!(IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(false), + tuple_list!(PanicStage::new()), + tuple_list!(IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(false), + tuple_list!(PanicStage::new()), + tuple_list!(IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(false), + tuple_list!(PanicStage::new()), + tuple_list!(IfElseStage::new( + |_a: &mut _, _b: &mut _, _c: &mut _, _d: &mut _| Ok(false), + tuple_list!(PanicStage::new()), + stages, + )), + )), + )), + )), + ); + let mut state = StdState::nop().unwrap(); + test_resume(&completed, &mut state, tuple_list!(ifstage)); + } +} diff --git a/libafl/src/stages/mod.rs b/libafl/src/stages/mod.rs index 9451a26d5f..135465c7cc 100644 --- a/libafl/src/stages/mod.rs +++ b/libafl/src/stages/mod.rs @@ -49,7 +49,7 @@ pub use verify_timeouts::{TimeoutsToVerify, VerifyTimeoutsStage}; use crate::{ corpus::{CorpusId, HasCurrentCorpusId}, events::SendExiting, - state::{HasExecutions, Stoppable}, + state::{HasCurrentStageId, HasExecutions, Stoppable}, Error, HasNamedMetadata, }; @@ -98,8 +98,6 @@ pub trait Stage { /// /// Before a call to perform, [`Stage::should_restart`] will be (must be!) called. /// After returning (so non-target crash or timeout in a restarting case), [`Stage::clear_progress`] gets called. - /// A call to [`Stage::perform_restartable`] will do these things implicitly. - /// DON'T call this function directly except from `preform_restartable` !! fn perform( &mut self, fuzzer: &mut Z, @@ -107,20 +105,6 @@ pub trait Stage { state: &mut S, manager: &mut EM, ) -> Result<(), Error>; - - /// Run the stage, calling [`Stage::should_restart`] and [`Stage::clear_progress`] appropriately - fn perform_restartable( - &mut self, - fuzzer: &mut Z, - executor: &mut E, - state: &mut S, - manager: &mut EM, - ) -> Result<(), Error> { - if self.should_restart(state)? { - self.perform(fuzzer, executor, state, manager)?; - } - self.clear_progress(state) - } } /// A tuple holding all `Stages` used for fuzzing. @@ -182,7 +166,10 @@ where let stage = &mut self.0; - stage.perform_restartable(fuzzer, executor, state, manager)?; + if stage.should_restart(state)? { + stage.perform(fuzzer, executor, state, manager)?; + } + stage.clear_progress(state)?; state.clear_stage_id()?; } @@ -194,7 +181,11 @@ where state.set_current_stage_id(StageId(Self::LEN))?; let stage = &mut self.0; - stage.perform_restartable(fuzzer, executor, state, manager)?; + + if stage.should_restart(state)? { + stage.perform(fuzzer, executor, state, manager)?; + } + stage.clear_progress(state)?; state.clear_stage_id()?; } @@ -261,13 +252,16 @@ where state: &mut S, manager: &mut EM, ) -> Result<(), Error> { - self.iter_mut().try_for_each(|x| { + self.iter_mut().try_for_each(|stage| { if state.stop_requested() { state.discard_stop_request(); manager.on_shutdown()?; return Err(Error::shutting_down()); } - x.perform_restartable(fuzzer, executor, state, manager) + if stage.should_restart(state)? { + stage.perform(fuzzer, executor, state, manager)?; + } + stage.clear_progress(state) }) } } @@ -418,34 +412,6 @@ impl fmt::Display for StageId { } } -/// Trait for types which track the current stage -pub trait HasCurrentStageId { - /// Set the current stage; we have started processing this stage - fn set_current_stage_id(&mut self, id: StageId) -> Result<(), Error>; - - /// Clear the current stage; we are done processing this stage - fn clear_stage_id(&mut self) -> Result<(), Error>; - - /// Fetch the current stage -- typically used after a state recovery or transfer - fn current_stage_id(&self) -> Result, Error>; - - /// Notify of a reset from which we may recover - fn on_restart(&mut self) -> Result<(), Error> { - Ok(()) - } -} - -/// Trait for types which track nested stages. Stages which themselves contain stage tuples should -/// ensure that they constrain the state with this trait accordingly. -pub trait HasNestedStageStatus: HasCurrentStageId { - /// Enter a stage scope, potentially resuming to an inner stage status. Returns Ok(true) if - /// resumed. - fn enter_inner_stage(&mut self) -> Result<(), Error>; - - /// Exit a stage scope - fn exit_inner_stage(&mut self) -> Result<(), Error>; -} - impl_serdeany!(ExecutionCountRestartHelperMetadata); /// `SerdeAny` metadata used to keep track of executions since start for a given stage. @@ -528,87 +494,16 @@ impl ExecutionCountRestartHelper { #[cfg(test)] mod test { use alloc::borrow::Cow; - use core::marker::PhantomData; - use libafl_bolts::{impl_serdeany, Error, Named}; - use serde::{Deserialize, Serialize}; + use libafl_bolts::{Error, Named}; use crate::{ corpus::{Corpus, HasCurrentCorpusId, Testcase}, inputs::NopInput, - stages::{RetryCountRestartHelper, Stage}, + stages::RetryCountRestartHelper, state::{HasCorpus, StdState}, - HasMetadata, }; - /// A stage that succeeds to resume - #[derive(Debug)] - pub struct ResumeSucceededStage { - phantom: PhantomData, - } - - /// A progress state for testing - #[derive(Serialize, Deserialize, Debug)] - pub struct TestProgress { - count: usize, - } - - impl_serdeany!(TestProgress); - - impl TestProgress { - #[expect(clippy::unnecessary_wraps)] - fn should_restart(state: &mut S, _stage: &ST) -> Result - where - S: HasMetadata, - { - // check if we're resuming - let metadata = state.metadata_or_insert_with(|| Self { count: 0 }); - - metadata.count += 1; - assert!( - metadata.count == 1, - "Test failed; we resumed a succeeded stage!" - ); - - Ok(true) - } - - fn clear_progress(state: &mut S, _stage: &ST) -> Result<(), Error> - where - S: HasMetadata, - { - if state.remove_metadata::().is_none() { - return Err(Error::illegal_state( - "attempted to clear status metadata when none was present", - )); - } - Ok(()) - } - } - - impl Stage for ResumeSucceededStage - where - S: HasMetadata, - { - fn perform( - &mut self, - _fuzzer: &mut Z, - _executor: &mut E, - _state: &mut S, - _manager: &mut EM, - ) -> Result<(), Error> { - Ok(()) - } - - fn should_restart(&mut self, state: &mut S) -> Result { - TestProgress::should_restart(state, self) - } - - fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { - TestProgress::clear_progress(state, self) - } - } - /// Test to test retries in stages #[test] fn test_tries_progress() -> Result<(), Error> { diff --git a/libafl/src/stages/time_tracker.rs b/libafl/src/stages/time_tracker.rs index b39ae2cd49..6e4f57792e 100644 --- a/libafl/src/stages/time_tracker.rs +++ b/libafl/src/stages/time_tracker.rs @@ -40,7 +40,12 @@ where self.inner.perform(fuzzer, executor, state, manager)?; let after_run = current_time(); self.count += after_run - before_run; - *state.metadata_mut::()? = T::from(self.count); + + if let Ok(meta) = state.metadata_mut::() { + *meta = T::from(self.count); + } else { + state.add_metadata::(T::from(self.count)); + } Ok(()) } @@ -51,15 +56,4 @@ where fn clear_progress(&mut self, state: &mut S) -> Result<(), Error> { self.inner.clear_progress(state) } - - fn perform_restartable( - &mut self, - fuzzer: &mut Z, - executor: &mut E, - state: &mut S, - manager: &mut M, - ) -> Result<(), Error> { - self.inner - .perform_restartable(fuzzer, executor, state, manager) - } } diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index 3b3a4327a6..1ed86ebaef 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -35,7 +35,7 @@ use crate::{ fuzzer::{Evaluator, ExecuteInputResult}, generators::Generator, inputs::{Input, NopInput}, - stages::{HasCurrentStageId, HasNestedStageStatus, StageId}, + stages::StageId, Error, HasMetadata, HasNamedMetadata, }; @@ -547,7 +547,35 @@ impl HasCurrentStageId for StdState { } } -impl HasNestedStageStatus for StdState { +/// Trait for types which track the current stage +pub trait HasCurrentStageId { + /// Set the current stage; we have started processing this stage + fn set_current_stage_id(&mut self, id: StageId) -> Result<(), Error>; + + /// Clear the current stage; we are done processing this stage + fn clear_stage_id(&mut self) -> Result<(), Error>; + + /// Fetch the current stage -- typically used after a state recovery or transfer + fn current_stage_id(&self) -> Result, Error>; + + /// Notify of a reset from which we may recover + fn on_restart(&mut self) -> Result<(), Error> { + Ok(()) + } +} + +/// Trait for types which track nested stages. Stages which themselves contain stage tuples should +/// ensure that they constrain the state with this trait accordingly. +pub trait HasNestedStage: HasCurrentStageId { + /// Enter a stage scope, potentially resuming to an inner stage status. Returns Ok(true) if + /// resumed. + fn enter_inner_stage(&mut self) -> Result<(), Error>; + + /// Exit a stage scope + fn exit_inner_stage(&mut self) -> Result<(), Error>; +} + +impl HasNestedStage for StdState { fn enter_inner_stage(&mut self) -> Result<(), Error> { self.stage_stack.enter_inner_stage() } diff --git a/libafl/src/state/stack.rs b/libafl/src/state/stack.rs index c07278cfa8..392f2ba1d4 100644 --- a/libafl/src/state/stack.rs +++ b/libafl/src/state/stack.rs @@ -3,7 +3,10 @@ use alloc::vec::Vec; use libafl_bolts::Error; use serde::{Deserialize, Serialize}; -use crate::stages::{HasCurrentStageId, HasNestedStageStatus, StageId}; +use crate::{ + stages::StageId, + state::{HasCurrentStageId, HasNestedStage}, +}; /// A stack to keep track of which stage is executing #[derive(Serialize, Deserialize, Clone, Debug, Default)] @@ -41,7 +44,7 @@ impl HasCurrentStageId for StageStack { } } -impl HasNestedStageStatus for StageStack { +impl HasNestedStage for StageStack { fn enter_inner_stage(&mut self) -> Result<(), Error> { self.stage_depth += 1; Ok(()) diff --git a/libafl_libfuzzer/log b/libafl_libfuzzer/log new file mode 100644 index 0000000000..14f7c8c6dd --- /dev/null +++ b/libafl_libfuzzer/log @@ -0,0 +1,4 @@ +warning: profiles for the non root package will be ignored, specify profiles at the workspace root: +package: /home/toka/LibAFL/bindings/pylibafl/Cargo.toml +workspace: /home/toka/LibAFL/Cargo.toml + Compiling libafl_libfuzzer v0.15.0 (/home/toka/LibAFL/libafl_libfuzzer) diff --git a/libafl_libfuzzer/runtime/src/fuzz.rs b/libafl_libfuzzer/runtime/src/fuzz.rs index 2f6c9e2457..9a352c3e01 100644 --- a/libafl_libfuzzer/runtime/src/fuzz.rs +++ b/libafl_libfuzzer/runtime/src/fuzz.rs @@ -11,8 +11,8 @@ use libafl::{ }, executors::ExitKind, monitors::{tui::TuiMonitor, Monitor, MultiMonitor}, - stages::{HasCurrentStageId, StagesTuple}, - state::{HasExecutions, HasLastReportTime, HasSolutions, Stoppable}, + stages::StagesTuple, + state::{HasCurrentStageId, HasExecutions, HasLastReportTime, HasSolutions, Stoppable}, Error, Fuzzer, HasMetadata, }; use libafl_bolts::{ diff --git a/libafl_libfuzzer/runtime/src/report.rs b/libafl_libfuzzer/runtime/src/report.rs index 88068d6f33..aeee85062f 100644 --- a/libafl_libfuzzer/runtime/src/report.rs +++ b/libafl_libfuzzer/runtime/src/report.rs @@ -5,8 +5,8 @@ use libafl::{ executors::HasObservers, feedbacks::MapFeedbackMetadata, monitors::SimpleMonitor, - stages::{HasCurrentStageId, StagesTuple}, - state::{HasExecutions, HasLastReportTime, Stoppable}, + stages::StagesTuple, + state::{HasCurrentStageId, HasExecutions, HasLastReportTime, Stoppable}, Error, Fuzzer, HasMetadata, HasNamedMetadata, }; diff --git a/libafl_sugar/src/forkserver.rs b/libafl_sugar/src/forkserver.rs index 2e285897ee..58be325d73 100644 --- a/libafl_sugar/src/forkserver.rs +++ b/libafl_sugar/src/forkserver.rs @@ -257,7 +257,7 @@ impl ForkserverBytesCoverageSugar<'_> { &mut mgr, iters, )?; - mgr.on_restart(&mut state)?; + mgr.mgr_on_restart(&mut state)?; std::process::exit(0); } else { fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; @@ -278,7 +278,7 @@ impl ForkserverBytesCoverageSugar<'_> { &mut mgr, iters, )?; - mgr.on_restart(&mut state)?; + mgr.mgr_on_restart(&mut state)?; std::process::exit(0); } else { fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; diff --git a/libafl_sugar/src/inmemory.rs b/libafl_sugar/src/inmemory.rs index e26765a26d..1a635ba10b 100644 --- a/libafl_sugar/src/inmemory.rs +++ b/libafl_sugar/src/inmemory.rs @@ -281,7 +281,7 @@ where &mut mgr, iters, )?; - mgr.on_restart(&mut state)?; + mgr.mgr_on_restart(&mut state)?; std::process::exit(0); } else { fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; @@ -296,7 +296,7 @@ where &mut mgr, iters, )?; - mgr.on_restart(&mut state)?; + mgr.mgr_on_restart(&mut state)?; std::process::exit(0); } else { fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; @@ -318,7 +318,7 @@ where &mut mgr, iters, )?; - mgr.on_restart(&mut state)?; + mgr.mgr_on_restart(&mut state)?; std::process::exit(0); } else { fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; @@ -333,7 +333,7 @@ where &mut mgr, iters, )?; - mgr.on_restart(&mut state)?; + mgr.mgr_on_restart(&mut state)?; std::process::exit(0); } else { fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; diff --git a/libafl_sugar/src/qemu.rs b/libafl_sugar/src/qemu.rs index 50c8d3a284..a2a6d849fc 100644 --- a/libafl_sugar/src/qemu.rs +++ b/libafl_sugar/src/qemu.rs @@ -319,7 +319,7 @@ where &mut mgr, iters, )?; - mgr.on_restart(&mut state)?; + mgr.mgr_on_restart(&mut state)?; std::process::exit(0); } else { fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; @@ -340,7 +340,7 @@ where &mut mgr, iters, )?; - mgr.on_restart(&mut state)?; + mgr.mgr_on_restart(&mut state)?; std::process::exit(0); } else { fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; @@ -431,7 +431,7 @@ where &mut mgr, iters, )?; - mgr.on_restart(&mut state)?; + mgr.mgr_on_restart(&mut state)?; std::process::exit(0); } else { fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; @@ -452,7 +452,7 @@ where &mut mgr, iters, )?; - mgr.on_restart(&mut state)?; + mgr.mgr_on_restart(&mut state)?; std::process::exit(0); } else { fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;