diff --git a/fuzzers/baby_fuzzer_minimizing/.gitignore b/fuzzers/baby_fuzzer_minimizing/.gitignore new file mode 100644 index 0000000000..f3e9e2b204 --- /dev/null +++ b/fuzzers/baby_fuzzer_minimizing/.gitignore @@ -0,0 +1,3 @@ +corpus +minimized +solutions diff --git a/fuzzers/baby_fuzzer_minimizing/Cargo.toml b/fuzzers/baby_fuzzer_minimizing/Cargo.toml new file mode 100644 index 0000000000..550aa468a3 --- /dev/null +++ b/fuzzers/baby_fuzzer_minimizing/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "baby_fuzzer_minimizing" +version = "0.8.1" +authors = ["Andrea Fioraldi ", "Dominik Maier ", "Addison Crump "] +edition = "2021" + +[features] +default = ["std"] +tui = [] +std = [] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +lto = true +codegen-units = 1 +opt-level = 3 +debug = true + +[dependencies] +libafl = { path = "../../libafl/" } diff --git a/fuzzers/baby_fuzzer_minimizing/README.md b/fuzzers/baby_fuzzer_minimizing/README.md new file mode 100644 index 0000000000..0b3417897f --- /dev/null +++ b/fuzzers/baby_fuzzer_minimizing/README.md @@ -0,0 +1,9 @@ +# Baby fuzzer + +This is a minimalistic example about how to create a libafl based fuzzer which leverages minimisation. + +The fuzzer steps until a crash occurs, minimising each corpus entry as it is discovered. Then, once a +solution is found, it attempts to minimise that as well. + +The tested program is a simple Rust function without any instrumentation. +For real fuzzing, you will want to add some sort to add coverage or other feedback. \ No newline at end of file diff --git a/fuzzers/baby_fuzzer_minimizing/src/main.rs b/fuzzers/baby_fuzzer_minimizing/src/main.rs new file mode 100644 index 0000000000..61430cc6c2 --- /dev/null +++ b/fuzzers/baby_fuzzer_minimizing/src/main.rs @@ -0,0 +1,142 @@ +use std::path::PathBuf; +#[cfg(windows)] +use std::ptr::write_volatile; + +use libafl::prelude::*; + +/// Coverage map with explicit assignments due to the lack of instrumentation +static mut SIGNALS: [u8; 16] = [0; 16]; + +/// Assign a signal to the signals map +fn signals_set(idx: usize) { + unsafe { SIGNALS[idx] = 1 }; +} + +#[allow(clippy::similar_names)] +pub fn main() -> Result<(), Error> { + // The closure that we want to fuzz + let mut harness = |input: &BytesInput| { + let target = input.target_bytes(); + let buf = target.as_slice(); + signals_set(0); + if !buf.is_empty() && buf[0] == b'a' { + signals_set(1); + if buf.len() > 1 && buf[1] == b'b' { + signals_set(2); + if buf.len() > 2 && buf[2] == b'c' { + return ExitKind::Crash; + } + } + } + ExitKind::Ok + }; + + // Create an observation channel using the signals map + let observer = + unsafe { StdMapObserver::new_from_ptr("signals", SIGNALS.as_mut_ptr(), SIGNALS.len()) }; + + let factory = MapEqualityFactory::new_from_observer(&observer); + + // Feedback to rate the interestingness of an input + let mut feedback = MaxMapFeedback::new(&observer); + + // A feedback to choose if an input is a solution or not + let mut objective = CrashFeedback::new(); + + // The Monitor trait define how the fuzzer stats are displayed to the user + let mon = SimpleMonitor::new(|s| println!("{}", s)); + + let mut mgr = SimpleEventManager::new(mon); + + let corpus_dir = PathBuf::from("./corpus"); + let solution_dir = PathBuf::from("./solutions"); + + // create a State from scratch + let mut state = StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + OnDiskCorpus::new(&corpus_dir).unwrap(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + OnDiskCorpus::new(&solution_dir).unwrap(), + // States of the feedbacks. + // The feedbacks can report the data that should persist in the State. + &mut feedback, + // Same for objective feedbacks + &mut objective, + ) + .unwrap(); + + // A queue policy to get testcasess from the corpus + let scheduler = QueueScheduler::new(); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + // Create the executor for an in-process function with just one observer + let mut executor = InProcessExecutor::new( + &mut harness, + tuple_list!(observer), + &mut fuzzer, + &mut state, + &mut mgr, + ) + .expect("Failed to create the Executor"); + + // Generator of printable bytearrays of max size 32 + let mut generator = RandPrintablesGenerator::new(32); + + // Generate 8 initial inputs + state + .generate_initial_inputs(&mut fuzzer, &mut executor, &mut generator, &mut mgr, 8) + .expect("Failed to generate the initial corpus"); + + // Setup a mutational stage with a basic bytes mutator + let mutator = StdScheduledMutator::new(havoc_mutations()); + let minimizer = StdScheduledMutator::new(havoc_mutations()); + let mut stages = tuple_list!( + StdMutationalStage::new(mutator), + StdTMinMutationalStage::new(minimizer, factory, 128) + ); + + while state.solutions().is_empty() { + fuzzer.fuzz_one(&mut stages, &mut executor, &mut state, &mut mgr)?; + } + + let minimized_dir = PathBuf::from("./minimized"); + + let mut state = StdState::new( + StdRand::with_seed(current_nanos()), + OnDiskCorpus::new(&minimized_dir).unwrap(), + InMemoryCorpus::new(), + &mut (), + &mut (), + ) + .unwrap(); + + // The Monitor trait define how the fuzzer stats are displayed to the user + let mon = SimpleMonitor::new(|s| println!("{}", s)); + + let mut mgr = SimpleEventManager::new(mon); + + let minimizer = StdScheduledMutator::new(havoc_mutations()); + let mut stages = tuple_list!(StdTMinMutationalStage::new( + minimizer, + CrashFeedbackFactory::default(), + 1 << 10 + )); + + let scheduler = QueueScheduler::new(); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, (), ()); + + // Create the executor for an in-process function with just one observer + let mut executor = InProcessExecutor::new(&mut harness, (), &mut fuzzer, &mut state, &mut mgr)?; + + state.load_initial_inputs_forced(&mut fuzzer, &mut executor, &mut mgr, &[solution_dir])?; + stages.perform_all(&mut fuzzer, &mut executor, &mut state, &mut mgr, 0)?; + + Ok(()) +} diff --git a/libafl/src/corpus/cached.rs b/libafl/src/corpus/cached.rs index 8ebbc5555c..328540dda3 100644 --- a/libafl/src/corpus/cached.rs +++ b/libafl/src/corpus/cached.rs @@ -46,7 +46,7 @@ where /// Replaces the testcase at the given idx #[inline] - fn replace(&mut self, idx: usize, testcase: Testcase) -> Result<(), Error> { + fn replace(&mut self, idx: usize, testcase: Testcase) -> Result, Error> { // TODO finish self.inner.replace(idx, testcase) } diff --git a/libafl/src/corpus/inmemory.rs b/libafl/src/corpus/inmemory.rs index d020b4e9fc..abb0ac8e49 100644 --- a/libafl/src/corpus/inmemory.rs +++ b/libafl/src/corpus/inmemory.rs @@ -41,12 +41,11 @@ where /// Replaces the testcase at the given idx #[inline] - fn replace(&mut self, idx: usize, testcase: Testcase) -> Result<(), Error> { + fn replace(&mut self, idx: usize, testcase: Testcase) -> Result, Error> { if idx >= self.entries.len() { return Err(Error::key_not_found(format!("Index {} out of bounds", idx))); } - self.entries[idx] = RefCell::new(testcase); - Ok(()) + Ok(self.entries[idx].replace(testcase)) } /// Removes an entry from the corpus, returning it if it was present. diff --git a/libafl/src/corpus/mod.rs b/libafl/src/corpus/mod.rs index 6d7744db4e..2fa1f1babf 100644 --- a/libafl/src/corpus/mod.rs +++ b/libafl/src/corpus/mod.rs @@ -36,8 +36,8 @@ where /// Add an entry to the corpus and return its index fn add(&mut self, testcase: Testcase) -> Result; - /// Replaces the testcase at the given idx - fn replace(&mut self, idx: usize, testcase: Testcase) -> Result<(), Error>; + /// Replaces the testcase at the given idx, returning the existing. + fn replace(&mut self, idx: usize, testcase: Testcase) -> Result, Error>; /// Removes an entry from the corpus, returning it if it was present. fn remove(&mut self, idx: usize) -> Result>, Error>; @@ -177,7 +177,11 @@ pub mod pybind { } #[inline] - fn replace(&mut self, idx: usize, testcase: Testcase) -> Result<(), Error> { + fn replace( + &mut self, + idx: usize, + testcase: Testcase, + ) -> Result, Error> { unwrap_me_mut!(self.wrapper, c, { c.replace(idx, testcase) }) } diff --git a/libafl/src/corpus/ondisk.rs b/libafl/src/corpus/ondisk.rs index 882478f462..376a9991d1 100644 --- a/libafl/src/corpus/ondisk.rs +++ b/libafl/src/corpus/ondisk.rs @@ -67,6 +67,92 @@ where /// Add an entry to the corpus and return its index #[inline] fn add(&mut self, mut testcase: Testcase) -> Result { + self.save_testcase(&mut testcase)?; + self.entries.push(RefCell::new(testcase)); + Ok(self.entries.len() - 1) + } + + /// Replaces the testcase at the given idx + #[inline] + fn replace(&mut self, idx: usize, mut testcase: Testcase) -> Result, Error> { + if idx >= self.entries.len() { + return Err(Error::key_not_found(format!("Index {} out of bounds", idx))); + } + self.save_testcase(&mut testcase)?; + let previous = self.entries[idx].replace(testcase); + self.remove_testcase(&previous)?; + Ok(previous) + } + + /// Removes an entry from the corpus, returning it if it was present. + #[inline] + fn remove(&mut self, idx: usize) -> Result>, Error> { + if idx >= self.entries.len() { + Ok(None) + } else { + let prev = self.entries.remove(idx).into_inner(); + self.remove_testcase(&prev)?; + Ok(Some(prev)) + } + } + + /// Get by id + #[inline] + fn get(&self, idx: usize) -> Result<&RefCell>, Error> { + Ok(&self.entries[idx]) + } + + /// Current testcase scheduled + #[inline] + fn current(&self) -> &Option { + &self.current + } + + /// Current testcase scheduled (mutable) + #[inline] + fn current_mut(&mut self) -> &mut Option { + &mut self.current + } +} + +impl OnDiskCorpus +where + I: Input, +{ + /// Creates the [`OnDiskCorpus`]. + /// Will error, if [`std::fs::create_dir_all()`] failed for `dir_path`. + pub fn new

(dir_path: P) -> Result + where + P: AsRef, + { + fn new(dir_path: PathBuf) -> Result, Error> { + fs::create_dir_all(&dir_path)?; + Ok(OnDiskCorpus { + entries: vec![], + current: None, + dir_path, + meta_format: None, + }) + } + new(dir_path.as_ref().to_path_buf()) + } + + /// Creates the [`OnDiskCorpus`] specifying the type of `Metadata` to be saved to disk. + /// Will error, if [`std::fs::create_dir_all()`] failed for `dir_path`. + pub fn new_save_meta( + dir_path: PathBuf, + meta_format: Option, + ) -> Result { + fs::create_dir_all(&dir_path)?; + Ok(Self { + entries: vec![], + current: None, + dir_path, + meta_format, + }) + } + + fn save_testcase(&mut self, testcase: &mut Testcase) -> Result<(), Error> { if testcase.filename().is_none() { // TODO walk entry metadata to ask for pieces of filename (e.g. :havoc in AFL) let file_orig = testcase @@ -128,84 +214,22 @@ where testcase .store_input() .expect("Could not save testcase to disk"); - self.entries.push(RefCell::new(testcase)); - Ok(self.entries.len() - 1) - } - - /// Replaces the testcase at the given idx - #[inline] - fn replace(&mut self, idx: usize, testcase: Testcase) -> Result<(), Error> { - if idx >= self.entries.len() { - return Err(Error::key_not_found(format!("Index {} out of bounds", idx))); - } - self.entries[idx] = RefCell::new(testcase); Ok(()) } - /// Removes an entry from the corpus, returning it if it was present. - #[inline] - fn remove(&mut self, idx: usize) -> Result>, Error> { - if idx >= self.entries.len() { - Ok(None) - } else { - Ok(Some(self.entries.remove(idx).into_inner())) + fn remove_testcase(&mut self, testcase: &Testcase) -> Result<(), Error> { + if let Some(filename) = testcase.filename() { + fs::remove_file(filename)?; } - } - - /// Get by id - #[inline] - fn get(&self, idx: usize) -> Result<&RefCell>, Error> { - Ok(&self.entries[idx]) - } - - /// Current testcase scheduled - #[inline] - fn current(&self) -> &Option { - &self.current - } - - /// Current testcase scheduled (mutable) - #[inline] - fn current_mut(&mut self) -> &mut Option { - &mut self.current - } -} - -impl OnDiskCorpus -where - I: Input, -{ - /// Creates the [`OnDiskCorpus`]. - /// Will error, if [`std::fs::create_dir_all()`] failed for `dir_path`. - pub fn new

(dir_path: P) -> Result - where - P: AsRef, - { - fn new(dir_path: PathBuf) -> Result, Error> { - fs::create_dir_all(&dir_path)?; - Ok(OnDiskCorpus { - entries: vec![], - current: None, - dir_path, - meta_format: None, - }) + if self.meta_format.is_some() { + let mut filename = PathBuf::from(testcase.filename().as_ref().unwrap()); + filename.set_file_name(format!( + ".{}.metadata", + filename.file_name().unwrap().to_string_lossy() + )); + fs::remove_file(filename)?; } - new(dir_path.as_ref().to_path_buf()) - } - - /// Creates the [`OnDiskCorpus`] specifying the type of `Metadata` to be saved to disk. - /// Will error, if [`std::fs::create_dir_all()`] failed for `dir_path`. - pub fn new_save_meta( - dir_path: PathBuf, - meta_format: Option, - ) -> Result { - fs::create_dir_all(&dir_path)?; - Ok(Self { - entries: vec![], - current: None, - dir_path, - meta_format, - }) + Ok(()) } } #[cfg(feature = "python")] diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 00e081e59d..66b4dfc6e8 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -296,6 +296,61 @@ where OT: ObserversTuple; } +/// Factory for feedbacks which should be sensitive to an existing context, e.g. observer(s) from a +/// specific execution +pub trait FeedbackFactory +where + F: Feedback, + I: Input, + S: HasClientPerfMonitor, +{ + /// Create the feedback from the provided context + fn create_feedback(&self, ctx: &T) -> F; +} + +impl FeedbackFactory for FU +where + FU: Fn(&T) -> FE, + FE: Feedback, + I: Input, + S: HasClientPerfMonitor, +{ + fn create_feedback(&self, ctx: &T) -> FE { + self(ctx) + } +} + +/// A feedback factory which merely invokes `::default()` for the feedback type provided +#[derive(Default, Debug, Copy, Clone)] +pub struct DefaultFeedbackFactory +where + F: Default, +{ + phantom: PhantomData, +} + +impl DefaultFeedbackFactory +where + F: Default, +{ + /// Create the feedback factory + #[must_use] + pub fn new() -> Self { + Self::default() + } +} + +impl FeedbackFactory for DefaultFeedbackFactory +where + F: Feedback + Default, + I: Input, + S: HasClientPerfMonitor, +{ + fn create_feedback(&self, _ctx: &T) -> F { + F::default() + } +} + /// Eager `OR` combination of two feedbacks #[derive(Debug, Clone)] pub struct LogicEagerOr {} @@ -776,6 +831,9 @@ impl Default for CrashFeedback { } } +/// A feedback factory for crash feedbacks +pub type CrashFeedbackFactory = DefaultFeedbackFactory; + /// A [`TimeoutFeedback`] reduces the timeout value of a run. #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TimeoutFeedback {} @@ -827,6 +885,9 @@ impl Default for TimeoutFeedback { } } +/// A feedback factory for timeout feedbacks +pub type TimeoutFeedbackFactory = DefaultFeedbackFactory; + /// Nop feedback that annotates execution time in the new testcase, if any /// for this Feedback, the testcase is never interesting (use with an OR). /// It decides, if the given [`TimeObserver`] value of a run is interesting. diff --git a/libafl/src/inputs/mod.rs b/libafl/src/inputs/mod.rs index 8ff5fc2963..f7c4811039 100644 --- a/libafl/src/inputs/mod.rs +++ b/libafl/src/inputs/mod.rs @@ -62,7 +62,6 @@ pub trait Input: Clone + Serialize + serde::de::DeserializeOwned + Debug { } /// Load the content of this input from a file - #[cfg(feature = "std")] fn from_file

(path: P) -> Result where P: AsRef, diff --git a/libafl/src/schedulers/mod.rs b/libafl/src/schedulers/mod.rs index 6a9949d575..0f072f7fd0 100644 --- a/libafl/src/schedulers/mod.rs +++ b/libafl/src/schedulers/mod.rs @@ -39,22 +39,17 @@ pub trait Scheduler where I: Input, { - /// Add an entry to the corpus and return its index + /// Added an entry to the corpus at the given index fn on_add(&self, _state: &mut S, _idx: usize) -> Result<(), Error> { Ok(()) } - /// Replaces the testcase at the given idx - fn on_replace( - &self, - _state: &mut S, - _idx: usize, - _testcase: &Testcase, - ) -> Result<(), Error> { + /// Replaced the given testcase at the given idx + fn on_replace(&self, _state: &mut S, _idx: usize, _prev: &Testcase) -> Result<(), Error> { Ok(()) } - /// Removes an entry from the corpus, returning it if it was present. + /// Removed the given entry from the corpus at the given index fn on_remove( &self, _state: &mut S, diff --git a/libafl/src/stages/mod.rs b/libafl/src/stages/mod.rs index 74d0559a08..844aec861b 100644 --- a/libafl/src/stages/mod.rs +++ b/libafl/src/stages/mod.rs @@ -8,6 +8,11 @@ Other stages may enrich [`crate::corpus::Testcase`]s with metadata. pub mod mutational; pub use mutational::{MutationalStage, StdMutationalStage}; +pub mod tmin; +pub use tmin::{ + MapEqualityFactory, MapEqualityFeedback, StdTMinMutationalStage, TMinMutationalStage, +}; + pub mod push; pub mod tracing; diff --git a/libafl/src/stages/tmin.rs b/libafl/src/stages/tmin.rs new file mode 100644 index 0000000000..ead937aed6 --- /dev/null +++ b/libafl/src/stages/tmin.rs @@ -0,0 +1,386 @@ +//! The [`TMinMutationalStage`] is a stage which will attempt to minimize corpus entries. + +use alloc::string::{String, ToString}; +use core::{ + fmt::Debug, + hash::{Hash, Hasher}, + marker::PhantomData, +}; + +use ahash::AHasher; + +#[cfg(feature = "introspection")] +use crate::monitors::PerfFeature; +use crate::{ + bolts::{tuples::Named, HasLen}, + corpus::{Corpus, Testcase}, + events::EventFirer, + executors::{Executor, ExitKind, HasObservers}, + feedbacks::{Feedback, FeedbackFactory, HasObserverName}, + inputs::Input, + mark_feature_time, + mutators::Mutator, + observers::{MapObserver, ObserversTuple}, + schedulers::Scheduler, + stages::Stage, + start_timer, + state::{HasClientPerfMonitor, HasCorpus, HasExecutions, HasMaxSize}, + Error, ExecutesInput, ExecutionProcessor, HasFeedback, HasScheduler, +}; + +/// Mutational stage which minimizes corpus entries. +/// +/// You must provide at least one mutator that actually reduces size. +pub trait TMinMutationalStage: + Stage + FeedbackFactory +where + CS: Scheduler, + E: Executor + HasObservers, + EM: EventFirer, + F1: Feedback, + F2: Feedback, + I: Input + Hash + HasLen, + M: Mutator, + OT: ObserversTuple, + S: HasClientPerfMonitor + HasCorpus + HasExecutions + HasMaxSize, + Z: ExecutionProcessor + + ExecutesInput + + HasFeedback + + HasScheduler, +{ + /// The mutator registered for this stage + fn mutator(&self) -> &M; + + /// The mutator registered for this stage (mutable) + fn mutator_mut(&mut self) -> &mut M; + + /// Gets the number of iterations this mutator should run for. + fn iterations(&self, state: &mut S, corpus_idx: usize) -> Result; + + /// Runs this (mutational) stage for new objectives + #[allow(clippy::cast_possible_wrap)] // more than i32 stages on 32 bit system - highly unlikely... + fn perform_minification( + &mut self, + fuzzer: &mut Z, + executor: &mut E, + state: &mut S, + manager: &mut EM, + base_corpus_idx: usize, + ) -> Result<(), Error> { + let orig_max_size = state.max_size(); + // basically copy-pasted from mutational.rs + let num = self.iterations(state, base_corpus_idx)?; + + start_timer!(state); + let mut base = state + .corpus() + .get(base_corpus_idx)? + .borrow_mut() + .load_input()? + .clone(); + let mut hasher = AHasher::new_with_keys(0, 0); + base.hash(&mut hasher); + let base_hash = hasher.finish(); + mark_feature_time!(state, PerfFeature::GetInputFromCorpus); + + fuzzer.execute_input(state, executor, manager, &base)?; + let observers = executor.observers(); + + let mut feedback = self.create_feedback(observers); + + let mut i = 0; + loop { + if i >= num { + break; + } + + let mut next_i = i + 1; + let mut input = base.clone(); + + let before_len = input.len(); + + state.set_max_size(before_len); + + start_timer!(state); + self.mutator_mut().mutate(state, &mut input, i as i32)?; + mark_feature_time!(state, PerfFeature::Mutate); + + let corpus_idx = if input.len() < before_len { + // run the input + let exit_kind = fuzzer.execute_input(state, executor, manager, &input)?; + let observers = executor.observers(); + + // let the fuzzer process this execution -- it's possible that we find something + // interesting, or even a solution + let (_, corpus_idx) = fuzzer.process_execution( + state, + manager, + input.clone(), + observers, + &exit_kind, + false, + )?; + + if feedback.is_interesting(state, manager, &input, observers, &exit_kind)? { + // we found a reduced corpus entry! use the smaller base + base = input; + + // do more runs! maybe we can minify further + next_i = 0; + } + + corpus_idx + } else { + // we can't guarantee that the mutators provided will necessarily reduce size, so + // skip any mutations that actually increase size so we don't waste eval time + None + }; + + start_timer!(state); + self.mutator_mut().post_exec(state, i as i32, corpus_idx)?; + mark_feature_time!(state, PerfFeature::MutatePostExec); + + i = next_i; + } + + let mut hasher = AHasher::new_with_keys(0, 0); + base.hash(&mut hasher); + let new_hash = hasher.finish(); + if base_hash != new_hash { + let mut testcase = Testcase::with_executions(base, *state.executions()); + fuzzer + .feedback_mut() + .append_metadata(state, &mut testcase)?; + let prev = state.corpus_mut().replace(base_corpus_idx, testcase)?; + fuzzer + .scheduler_mut() + .on_replace(state, base_corpus_idx, &prev)?; + } + + state.set_max_size(orig_max_size); + + Ok(()) + } +} + +/// The default corpus entry minimising mutational stage +#[derive(Clone, Debug)] +pub struct StdTMinMutationalStage +where + I: Input + HasLen, + M: Mutator, +{ + mutator: M, + factory: FF, + runs: usize, + #[allow(clippy::type_complexity)] + phantom: PhantomData<(CS, E, EM, F1, F2, I, S, T, Z)>, +} + +impl Stage + for StdTMinMutationalStage +where + CS: Scheduler, + E: Executor + HasObservers, + EM: EventFirer, + F1: Feedback, + F2: Feedback, + FF: FeedbackFactory, + I: Input + Hash + HasLen, + M: Mutator, + OT: ObserversTuple, + S: HasClientPerfMonitor + HasCorpus + HasExecutions + HasMaxSize, + Z: ExecutionProcessor + + ExecutesInput + + HasFeedback + + HasScheduler, +{ + fn perform( + &mut self, + fuzzer: &mut Z, + executor: &mut E, + state: &mut S, + manager: &mut EM, + corpus_idx: usize, + ) -> Result<(), Error> { + self.perform_minification(fuzzer, executor, state, manager, corpus_idx)?; + + #[cfg(feature = "introspection")] + state.introspection_monitor_mut().finish_stage(); + + Ok(()) + } +} + +impl FeedbackFactory + for StdTMinMutationalStage +where + F2: Feedback, + FF: FeedbackFactory, + I: Input + HasLen, + M: Mutator, + S: HasClientPerfMonitor, +{ + fn create_feedback(&self, ctx: &T) -> F2 { + self.factory.create_feedback(ctx) + } +} + +impl TMinMutationalStage + for StdTMinMutationalStage +where + CS: Scheduler, + E: HasObservers + Executor, + EM: EventFirer, + F1: Feedback, + F2: Feedback, + FF: FeedbackFactory, + I: Input + HasLen + Hash, + M: Mutator, + OT: ObserversTuple, + S: HasClientPerfMonitor + HasCorpus + HasExecutions + HasMaxSize, + Z: ExecutionProcessor + + ExecutesInput + + HasFeedback + + HasScheduler, +{ + /// The mutator, added to this stage + #[inline] + fn mutator(&self) -> &M { + &self.mutator + } + + /// The list of mutators, added to this stage (as mutable ref) + #[inline] + fn mutator_mut(&mut self) -> &mut M { + &mut self.mutator + } + + /// Gets the number of iterations from a fixed number of runs + fn iterations(&self, _state: &mut S, _corpus_idx: usize) -> Result { + Ok(self.runs) + } +} + +impl + StdTMinMutationalStage +where + I: Input + HasLen, + M: Mutator, +{ + /// Creates a new minimising mutational stage that will minimize provided corpus entries + pub fn new(mutator: M, factory: FF, runs: usize) -> Self { + Self { + mutator, + factory, + runs, + phantom: PhantomData, + } + } +} + +/// A feedback which checks if the hash of the currently observed map is equal to the original hash +/// provided +#[derive(Clone, Debug)] +pub struct MapEqualityFeedback { + name: String, + obs_name: String, + orig_hash: u64, + phantom: PhantomData, +} + +impl MapEqualityFeedback { + /// Create a new map equality feedback -- can be used with feedback logic + #[must_use] + pub fn new(name: &str, obs_name: &str, orig_hash: u64) -> Self { + MapEqualityFeedback { + name: name.to_string(), + obs_name: obs_name.to_string(), + orig_hash, + phantom: PhantomData, + } + } +} + +impl Named for MapEqualityFeedback { + fn name(&self) -> &str { + &self.name + } +} + +impl HasObserverName for MapEqualityFeedback { + fn observer_name(&self) -> &str { + &self.obs_name + } +} + +impl Feedback for MapEqualityFeedback +where + I: Input, + M: MapObserver, + S: HasClientPerfMonitor, +{ + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &I, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + let obs = observers + .match_name::(self.observer_name()) + .expect("Should have been provided valid observer name."); + Ok(obs.hash() == self.orig_hash) + } +} + +/// A feedback factory for ensuring that the maps for minimized inputs are the same +#[derive(Debug, Clone)] +pub struct MapEqualityFactory { + obs_name: String, + phantom: PhantomData, +} + +impl MapEqualityFactory +where + M: MapObserver, +{ + /// Creates a new map equality feedback for the given observer + pub fn new_from_observer(obs: &M) -> Self { + Self { + obs_name: obs.name().to_string(), + phantom: PhantomData, + } + } +} + +impl HasObserverName for MapEqualityFactory { + fn observer_name(&self) -> &str { + &self.obs_name + } +} + +impl FeedbackFactory, I, S, OT> for MapEqualityFactory +where + I: Input, + M: MapObserver, + OT: ObserversTuple, + S: HasClientPerfMonitor, +{ + fn create_feedback(&self, observers: &OT) -> MapEqualityFeedback { + let obs = observers + .match_name::(self.observer_name()) + .expect("Should have been provided valid observer name."); + MapEqualityFeedback { + name: "MapEq".to_string(), + obs_name: self.obs_name.clone(), + orig_hash: obs.hash(), + phantom: PhantomData, + } + } +}