diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index ee96174f00..2cee053087 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -276,6 +276,7 @@ jobs: needs: - ubuntu - fuzzers-preflight + - common strategy: fail-fast: true matrix: @@ -366,7 +367,8 @@ jobs: - 'fuzzers/*qemu*/**' fuzzers-qemu: - needs: changes + needs: + - common if: ${{ needs.changes.outputs.qemu == 'true' }} strategy: matrix: diff --git a/libafl/src/fuzzer/mod.rs b/libafl/src/fuzzer/mod.rs index b38127445b..5610f8bc14 100644 --- a/libafl/src/fuzzer/mod.rs +++ b/libafl/src/fuzzer/mod.rs @@ -18,8 +18,8 @@ use crate::{ stages::{HasCurrentStage, StagesTuple}, start_timer, state::{ - HasCorpus, HasCurrentTestcase, HasExecutions, HasImported, HasLastReportTime, HasSolutions, - Stoppable, UsesState, + HasCorpus, HasCurrentTestcase, HasExecutions, HasLastFoundTime, HasLastReportTime, + HasSolutions, Stoppable, UsesState, }, Error, HasMetadata, }; @@ -378,7 +378,6 @@ where + HasSolutions + HasExecutions + HasCorpus - + HasImported + HasCurrentTestcase<::Input> + HasCurrentCorpusId, { @@ -597,7 +596,7 @@ where OT: ObserversTuple + Serialize + DeserializeOwned, F: Feedback, OF: Feedback, - CS::State: HasCorpus + HasSolutions + HasExecutions + HasImported, + CS::State: HasCorpus + HasSolutions + HasExecutions, { /// Process one input, adding to the respective corpora if needed and firing the right events #[inline] @@ -630,7 +629,7 @@ where F: Feedback, OF: Feedback, OT: ObserversTuple + Serialize + DeserializeOwned, - CS::State: HasCorpus + HasSolutions + HasExecutions + HasImported, + CS::State: HasCorpus + HasSolutions + HasExecutions + HasLastFoundTime, { /// Process one input, adding to the respective corpora if needed and firing the right events #[inline] @@ -663,6 +662,8 @@ where manager: &mut EM, input: ::Input, ) -> Result { + *state.last_found_time_mut() = current_time(); + let exit_kind = self.execute_input(state, executor, manager, &input)?; let observers = executor.observers(); // Always consider this to be "interesting" @@ -670,7 +671,7 @@ where // Maybe a solution #[cfg(not(feature = "introspection"))] - let is_solution = + let is_solution: bool = self.objective_mut() .is_interesting(state, manager, &input, &*observers, &exit_kind)?; @@ -766,7 +767,6 @@ where + HasMetadata + HasCorpus + HasTestcase - + HasImported + HasLastReportTime + HasCurrentCorpusId + HasCurrentStage, diff --git a/libafl/src/stages/mod.rs b/libafl/src/stages/mod.rs index 3ca6462865..645ebb6627 100644 --- a/libafl/src/stages/mod.rs +++ b/libafl/src/stages/mod.rs @@ -80,6 +80,8 @@ pub mod tuneable; #[cfg(feature = "unicode")] pub mod unicode; +pub mod pruning; + /// A stage is one step in the fuzzing process. /// Multiple stages will be scheduled one by one for each input. pub trait Stage: UsesState diff --git a/libafl/src/stages/pruning.rs b/libafl/src/stages/pruning.rs new file mode 100644 index 0000000000..c67a0a8b09 --- /dev/null +++ b/libafl/src/stages/pruning.rs @@ -0,0 +1,162 @@ +//! Corpus pruning stage + +use core::marker::PhantomData; + +use libafl_bolts::{rands::Rand, Error}; + +use crate::{ + corpus::Corpus, + stages::Stage, + state::{HasCorpus, HasRand, UsesState}, +}; +#[cfg(feature = "std")] +use crate::{events::EventRestarter, state::Stoppable}; + +#[derive(Debug)] +/// The stage to probablistically disable a corpus entry. +/// This stage should be wrapped in a if stage and run only when the fuzzer perform restarting +/// The idea comes from `https://mschloegel.me/paper/schiller2023fuzzerrestarts.pdf` +pub struct CorpusPruning { + /// The chance of retaining this corpus + prob: f64, + phantom: PhantomData, +} + +impl CorpusPruning { + fn new(prob: f64) -> Self { + Self { + prob, + phantom: PhantomData, + } + } +} + +impl Default for CorpusPruning { + fn default() -> Self { + Self::new(0.05) + } +} + +impl UsesState for CorpusPruning +where + EM: UsesState, +{ + type State = EM::State; +} + +impl Stage for CorpusPruning +where + EM: UsesState, + E: UsesState, + Z: UsesState, + Self::State: HasCorpus + HasRand, +{ + #[allow(clippy::cast_precision_loss)] + fn perform( + &mut self, + _fuzzer: &mut Z, + _executor: &mut E, + state: &mut Self::State, + _manager: &mut EM, + ) -> Result<(), Error> { + // Iterate over every corpus entry + let n_corpus = state.corpus().count_all(); + let mut do_retain = vec![]; + let mut retain_any = false; + for _ in 0..n_corpus { + let r = state.rand_mut().below(100) as f64; + let retain = self.prob * 100_f64 < r; + if retain { + retain_any = true; + } + do_retain.push(retain); + } + + // Make sure that at least somthing is in the + if !retain_any { + let r = state.rand_mut().below(n_corpus); + do_retain[r] = true; + } + + for (i_th, retain) in do_retain.iter().enumerate().take(n_corpus) { + if !retain { + let corpus_id = state.corpus().nth_from_all(i_th); + + let corpus = state.corpus_mut(); + let removed = corpus.remove(corpus_id)?; + corpus.add_disabled(removed)?; + } + } + + // println!("There was {}, and we retained {} corpura", n_corpus, state.corpus().count()); + Ok(()) + } + + fn should_restart(&mut self, _state: &mut Self::State) -> Result { + // Not executing the target, so restart safety is not needed + Ok(true) + } + fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> { + // Not executing the target, so restart safety is not needed + Ok(()) + } +} + +/// A stage for conditional restart +#[derive(Debug, Default)] +#[cfg(feature = "std")] +pub struct RestartStage { + phantom: PhantomData<(E, EM, Z)>, +} + +#[cfg(feature = "std")] +impl UsesState for RestartStage +where + E: UsesState, +{ + type State = E::State; +} + +#[cfg(feature = "std")] +impl Stage for RestartStage +where + E: UsesState, + EM: UsesState + EventRestarter, + Z: UsesState, +{ + #[allow(unreachable_code)] + fn perform( + &mut self, + _fuzzer: &mut Z, + _executor: &mut E, + state: &mut Self::State, + manager: &mut EM, + ) -> Result<(), Error> { + manager.on_restart(state).unwrap(); + state.request_stop(); + Ok(()) + } + + fn should_restart(&mut self, _state: &mut Self::State) -> Result { + Ok(true) + } + + fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> { + Ok(()) + } +} + +#[cfg(feature = "std")] +impl RestartStage +where + E: UsesState, +{ + /// Constructor for this conditionally enabled stage. + /// If the closure returns true, the wrapped stage will be executed, else it will be skipped. + #[must_use] + pub fn new() -> Self { + Self { + phantom: PhantomData, + } + } +} diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index e31efb3efc..85e20dbe83 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -187,6 +187,15 @@ pub trait HasStartTime { fn start_time_mut(&mut self) -> &mut Duration; } +/// Trait for the last report time, the last time this node reported progress +pub trait HasLastFoundTime { + /// The last time we found something by ourselves + fn last_found_time(&self) -> &Duration; + + /// The last time we found something by ourselves (mutable) + fn last_found_time_mut(&mut self) -> &mut Duration; +} + /// Trait for the last report time, the last time this node reported progress pub trait HasLastReportTime { /// The last time we reported progress,if available/used. @@ -260,6 +269,8 @@ pub struct StdState { /// The last time we reported progress (if available/used). /// This information is used by fuzzer `maybe_report_progress`. last_report_time: Option, + /// The last time something was added to the corpus + last_found_time: Duration, /// The current index of the corpus; used to record for resumable fuzzing. corpus_id: Option, /// Request the fuzzer to stop at the start of the next stage @@ -424,6 +435,20 @@ impl HasImported for StdState { } } +impl HasLastFoundTime for StdState { + /// Return the number of new paths that imported from other fuzzers + #[inline] + fn last_found_time(&self) -> &Duration { + &self.last_found_time + } + + /// Return the number of new paths that imported from other fuzzers + #[inline] + fn last_found_time_mut(&mut self) -> &mut Duration { + &mut self.last_found_time + } +} + impl HasLastReportTime for StdState { /// The last time we reported progress,if available/used. /// This information is used by fuzzer `maybe_report_progress`. @@ -1127,6 +1152,7 @@ where #[cfg(feature = "std")] dont_reenter: None, last_report_time: None, + last_found_time: libafl_bolts::current_time(), corpus_id: None, stage_stack: StageStack::default(), phantom: PhantomData,