From 695f100499612e73a485ba3a0433d470df09f890 Mon Sep 17 00:00:00 2001 From: Langston Barrett Date: Tue, 11 Jun 2024 16:04:23 -0400 Subject: [PATCH] Separate state trait/impl for generative fuzzers Generative fuzzers do not need a corpus. To this end, this commit splits out `GenState` (trait) and `StdGenState` (struct) from `State` (trait) and `StdState` (struct). The idea is that we will support some notion of a generative fuzzer (perhaps `GenFuzzer`) that will not rely on having a "current corpus ID". Down the line, we may also want to move input loading/generation/scheduling into a method on `GenState` that returns a `State`, see #2200. --- libafl/src/feedbacks/mod.rs | 46 ++--- libafl/src/state/mod.rs | 374 ++++++++++++++++++++++++++++++------ 2 files changed, 340 insertions(+), 80 deletions(-) diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 29bff15fbc..3f203c768a 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -34,7 +34,7 @@ use crate::{ events::EventFirer, executors::ExitKind, observers::{ObserversTuple, TimeObserver}, - state::State, + state::GenState, Error, }; #[cfg(feature = "std")] @@ -59,7 +59,7 @@ pub mod transferred; /// indicating the "interestingness" of the last run. pub trait Feedback: Named where - S: State, + S: GenState, { /// Initializes the feedback state. /// This method is called after that the `State` is created. @@ -170,7 +170,7 @@ where A: Feedback, B: Feedback, FL: FeedbackLogic, - S: State, + S: GenState, { /// First [`Feedback`] pub first: A, @@ -185,7 +185,7 @@ where A: Feedback, B: Feedback, FL: FeedbackLogic, - S: State, + S: GenState, { fn name(&self) -> &Cow<'static, str> { &self.name @@ -197,7 +197,7 @@ where A: Feedback, B: Feedback, FL: FeedbackLogic, - S: State, + S: GenState, { /// Create a new combined feedback pub fn new(first: A, second: B) -> Self { @@ -221,7 +221,7 @@ where A: Feedback, B: Feedback, FL: FeedbackLogic, - S: State, + S: GenState, { fn init_state(&mut self, state: &mut S) -> Result<(), Error> { self.first.init_state(state)?; @@ -316,7 +316,7 @@ where A: Feedback + FeedbackFactory, B: Feedback + FeedbackFactory, FL: FeedbackLogic, - S: State, + S: GenState, { fn create_feedback(&self, ctx: &T) -> CombinedFeedback { CombinedFeedback::new( @@ -331,7 +331,7 @@ pub trait FeedbackLogic: 'static where A: Feedback, B: Feedback, - S: State, + S: GenState, { /// The name of this combination fn name() -> &'static str; @@ -416,7 +416,7 @@ impl FeedbackLogic for LogicEagerOr where A: Feedback, B: Feedback, - S: State, + S: GenState, { fn name() -> &'static str { "Eager OR" @@ -487,7 +487,7 @@ impl FeedbackLogic for LogicFastOr where A: Feedback, B: Feedback, - S: State, + S: GenState, { fn name() -> &'static str { "Fast OR" @@ -565,7 +565,7 @@ impl FeedbackLogic for LogicEagerAnd where A: Feedback, B: Feedback, - S: State, + S: GenState, { fn name() -> &'static str { "Eager AND" @@ -632,7 +632,7 @@ impl FeedbackLogic for LogicFastAnd where A: Feedback, B: Feedback, - S: State, + S: GenState, { fn name() -> &'static str { "Fast AND" @@ -731,7 +731,7 @@ pub type FastOrFeedback = CombinedFeedback; pub struct NotFeedback where A: Feedback, - S: State, + S: GenState, { /// The feedback to invert pub first: A, @@ -743,7 +743,7 @@ where impl Debug for NotFeedback where A: Feedback + Debug, - S: State, + S: GenState, { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("NotFeedback") @@ -756,7 +756,7 @@ where impl Feedback for NotFeedback where A: Feedback, - S: State, + S: GenState, { fn init_state(&mut self, state: &mut S) -> Result<(), Error> { self.first.init_state(state) @@ -810,7 +810,7 @@ where impl Named for NotFeedback where A: Feedback, - S: State, + S: GenState, { #[inline] fn name(&self) -> &Cow<'static, str> { @@ -821,7 +821,7 @@ where impl NotFeedback where A: Feedback, - S: State, + S: GenState, { /// Creates a new [`NotFeedback`]. pub fn new(first: A) -> Self { @@ -889,7 +889,7 @@ macro_rules! feedback_not { /// Hack to use () as empty Feedback impl Feedback for () where - S: State, + S: GenState, { #[allow(clippy::wrong_self_convention)] fn is_interesting( @@ -922,7 +922,7 @@ pub struct CrashFeedback { impl Feedback for CrashFeedback where - S: State, + S: GenState, { #[allow(clippy::wrong_self_convention)] fn is_interesting( @@ -992,7 +992,7 @@ pub struct TimeoutFeedback { impl Feedback for TimeoutFeedback where - S: State, + S: GenState, { #[allow(clippy::wrong_self_convention)] fn is_interesting( @@ -1063,7 +1063,7 @@ pub struct DiffExitKindFeedback { impl Feedback for DiffExitKindFeedback where - S: State, + S: GenState, { #[allow(clippy::wrong_self_convention)] fn is_interesting( @@ -1133,7 +1133,7 @@ pub struct TimeFeedback { impl Feedback for TimeFeedback where - S: State, + S: GenState, { #[allow(clippy::wrong_self_convention)] fn is_interesting( @@ -1211,7 +1211,7 @@ pub enum ConstFeedback { impl Feedback for ConstFeedback where - S: State, + S: GenState, { #[inline] #[allow(clippy::wrong_self_convention)] diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index 1697b08ed7..a62c498c7e 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -1,4 +1,8 @@ -//! The fuzzer, and state are the core pieces of every good fuzzer +//! This module defines two traits for the state of fuzzers ([`GenState`] and +//! [`State`]), together with their canonical `impl`s ([`StdGenState`] and +//! [`StdState`]). [`GenState`] is intended for use with *generative* fuzzers, +//! i.e., fuzzers that do not store a corpus or mutate testcases from +//! one. [`State`] is intended for mutational fuzzers. #[cfg(feature = "std")] use alloc::vec::Vec; @@ -44,20 +48,30 @@ use crate::{ /// The maximum size of a testcase pub const DEFAULT_MAX_SIZE: usize = 1_048_576; -/// The [`State`] of the fuzzer. -/// Contains all important information about the current run. +/// The state of a generative fuzzer. +/// +/// Contains all the important information about the current fuzzing campaign. /// Will be used to restart the fuzzing process at any time. -pub trait State: +/// +/// See also [`State`] for the corresponding trait for mutational fuzzers. +pub trait GenState: UsesInput + Serialize + DeserializeOwned + MaybeHasClientPerfMonitor + MaybeHasScalabilityMonitor - + HasCurrentCorpusId + HasCurrentStage { } +/// The of a mutational fuzzer. +/// +/// Contains all the important information about the current fuzzing campaign. +/// Will be used to restart the fuzzing process at any time. +/// +/// See also [`GenState`] for the corresponding trait for generative fuzzers. +pub trait State: GenState + HasCurrentCorpusId {} + /// Structs which implement this trait are aware of the state. This is used for type enforcement. pub trait UsesState: UsesInput::Input> { /// The state known by this type. @@ -215,14 +229,15 @@ impl<'a, I, S, Z> Debug for LoadConfig<'a, I, S, Z> { } } -/// The state a fuzz run. +/// The state a generative fuzzing campaign. Implements [`GenState`]. +/// +/// See [`StdState`] for the corresponding `impl` of [`State`]. #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(bound = " - C: serde::Serialize + for<'a> serde::Deserialize<'a>, SC: serde::Serialize + for<'a> serde::Deserialize<'a>, R: serde::Serialize + for<'a> serde::Deserialize<'a> ")] -pub struct StdState { +pub struct StdGenState { /// RNG instance rand: R, /// How many times the executor ran the harness/target @@ -231,8 +246,6 @@ pub struct StdState { start_time: Duration, /// the number of new paths that imported from other fuzzers imported: usize, - /// The corpus - corpus: C, // Solutions corpus solutions: SC, /// Metadata stored for this state by one of the components @@ -246,6 +259,235 @@ pub struct StdState { introspection_monitor: ClientPerfMonitor, #[cfg(feature = "scalability_introspection")] scalability_monitor: ScalabilityMonitor, + /// The last time we reported progress (if available/used). + /// This information is used by fuzzer `maybe_report_progress`. + last_report_time: Option, + stage_stack: StageStack, + phantom: PhantomData, +} + +impl StdGenState +where + I: Input, + R: Rand, + SC: Corpus::Input>, +{ + fn new_no_init_objetive(rand: R, solutions: SC) -> Self { + Self { + rand, + executions: 0, + imported: 0, + start_time: Duration::from_millis(0), + metadata: SerdeAnyMap::default(), + named_metadata: NamedSerdeAnyMap::default(), + solutions, + max_size: DEFAULT_MAX_SIZE, + #[cfg(feature = "introspection")] + introspection_monitor: ClientPerfMonitor::new(), + #[cfg(feature = "scalability_introspection")] + scalability_monitor: ScalabilityMonitor::new(), + last_report_time: None, + stage_stack: StageStack::default(), + phantom: PhantomData, + } + } + + /// Creates a new state, taking ownership of all of the individual components during fuzzing. + pub fn new(rand: R, solutions: SC, objective: &mut O) -> Result + where + O: Feedback, + { + let mut state = Self::new_no_init_objetive(rand, solutions); + objective.init_state(&mut state)?; + Ok(state) + } +} + +impl UsesInput for StdGenState +where + I: Input, +{ + type Input = I; +} + +impl GenState for StdGenState +where + R: Rand, + SC: Corpus, + Self: UsesInput, +{ +} + +impl HasRand for StdGenState +where + R: Rand, +{ + type Rand = R; + + /// The rand instance + #[inline] + fn rand(&self) -> &Self::Rand { + &self.rand + } + + /// The rand instance (mutable) + #[inline] + fn rand_mut(&mut self) -> &mut Self::Rand { + &mut self.rand + } +} + +impl HasSolutions for StdGenState +where + I: Input, + SC: Corpus::Input>, +{ + type Solutions = SC; + + /// Returns the solutions corpus + #[inline] + fn solutions(&self) -> &SC { + &self.solutions + } + + /// Returns the solutions corpus (mutable) + #[inline] + fn solutions_mut(&mut self) -> &mut SC { + &mut self.solutions + } +} + +impl HasMetadata for StdGenState { + /// Get all the metadata into an [`hashbrown::HashMap`] + #[inline] + fn metadata_map(&self) -> &SerdeAnyMap { + &self.metadata + } + + /// Get all the metadata into an [`hashbrown::HashMap`] (mutable) + #[inline] + fn metadata_map_mut(&mut self) -> &mut SerdeAnyMap { + &mut self.metadata + } +} + +impl HasNamedMetadata for StdGenState { + /// Get all the metadata into an [`hashbrown::HashMap`] + #[inline] + fn named_metadata_map(&self) -> &NamedSerdeAnyMap { + &self.named_metadata + } + + /// Get all the metadata into an [`hashbrown::HashMap`] (mutable) + #[inline] + fn named_metadata_map_mut(&mut self) -> &mut NamedSerdeAnyMap { + &mut self.named_metadata + } +} + +impl HasExecutions for StdGenState { + /// The executions counter + #[inline] + fn executions(&self) -> &u64 { + &self.executions + } + + /// The executions counter (mutable) + #[inline] + fn executions_mut(&mut self) -> &mut u64 { + &mut self.executions + } +} + +impl HasImported for StdGenState { + /// Return the number of new paths that imported from other fuzzers + #[inline] + fn imported(&self) -> &usize { + &self.imported + } + + /// Return the number of new paths that imported from other fuzzers + #[inline] + fn imported_mut(&mut self) -> &mut usize { + &mut self.imported + } +} + +impl HasLastReportTime for StdGenState { + /// The last time we reported progress,if available/used. + /// This information is used by fuzzer `maybe_report_progress`. + fn last_report_time(&self) -> &Option { + &self.last_report_time + } + + /// The last time we reported progress,if available/used (mutable). + /// This information is used by fuzzer `maybe_report_progress`. + fn last_report_time_mut(&mut self) -> &mut Option { + &mut self.last_report_time + } +} + +impl HasMaxSize for StdGenState { + fn max_size(&self) -> usize { + self.max_size + } + + fn set_max_size(&mut self, max_size: usize) { + self.max_size = max_size; + } +} + +impl HasStartTime for StdGenState { + /// The starting time + #[inline] + fn start_time(&self) -> &Duration { + &self.start_time + } + + /// The starting time (mutable) + #[inline] + fn start_time_mut(&mut self) -> &mut Duration { + &mut self.start_time + } +} + +#[cfg(feature = "introspection")] +impl HasClientPerfMonitor for StdGenState { + fn introspection_monitor(&self) -> &ClientPerfMonitor { + &self.introspection_monitor + } + + fn introspection_monitor_mut(&mut self) -> &mut ClientPerfMonitor { + &mut self.introspection_monitor + } +} + +#[cfg(feature = "scalability_introspection")] +impl HasScalabilityMonitor for StdGenState { + fn scalability_monitor(&self) -> &ScalabilityMonitor { + &self.scalability_monitor + } + + fn scalability_monitor_mut(&mut self) -> &mut ScalabilityMonitor { + &mut self.scalability_monitor + } +} + +/// The state a mutational fuzzing campaign. Implements [`State`]. +/// +/// See [`StdGenState`] for the corresponding `impl` of [`GenState`]. +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(bound = " + C: serde::Serialize + for<'a> serde::Deserialize<'a>, + SC: serde::Serialize + for<'a> serde::Deserialize<'a>, + R: serde::Serialize + for<'a> serde::Deserialize<'a> + ")] +pub struct StdState { + inner: StdGenState, + /// The corpus + corpus: C, + /// The current index of the corpus; used to record for resumable fuzzing. + corpus_idx: Option, #[cfg(feature = "std")] /// Remaining initial inputs to load, if any remaining_initial_files: Option>, @@ -256,13 +498,6 @@ pub struct StdState { /// If inputs have been processed for multicore loading /// relevant only for `load_initial_inputs_multicore` multicore_inputs_processed: Option, - /// The last time we reported progress (if available/used). - /// This information is used by fuzzer `maybe_report_progress`. - last_report_time: Option, - /// The current index of the corpus; used to record for resumable fuzzing. - corpus_idx: Option, - stage_stack: StageStack, - phantom: PhantomData, } impl UsesInput for StdState @@ -272,6 +507,15 @@ where type Input = I; } +impl GenState for StdState +where + C: Corpus, + R: Rand, + SC: Corpus, + Self: UsesInput, +{ +} + impl State for StdState where C: Corpus, @@ -290,13 +534,13 @@ where /// The rand instance #[inline] fn rand(&self) -> &Self::Rand { - &self.rand + self.inner.rand() } /// The rand instance (mutable) #[inline] fn rand_mut(&mut self) -> &mut Self::Rand { - &mut self.rand + self.inner.rand_mut() } } @@ -354,13 +598,13 @@ where /// Returns the solutions corpus #[inline] fn solutions(&self) -> &SC { - &self.solutions + self.inner.solutions() } /// Returns the solutions corpus (mutable) #[inline] fn solutions_mut(&mut self) -> &mut SC { - &mut self.solutions + self.inner.solutions_mut() } } @@ -368,13 +612,13 @@ impl HasMetadata for StdState { /// Get all the metadata into an [`hashbrown::HashMap`] #[inline] fn metadata_map(&self) -> &SerdeAnyMap { - &self.metadata + self.inner.metadata_map() } /// Get all the metadata into an [`hashbrown::HashMap`] (mutable) #[inline] fn metadata_map_mut(&mut self) -> &mut SerdeAnyMap { - &mut self.metadata + self.inner.metadata_map_mut() } } @@ -382,13 +626,13 @@ impl HasNamedMetadata for StdState { /// Get all the metadata into an [`hashbrown::HashMap`] #[inline] fn named_metadata_map(&self) -> &NamedSerdeAnyMap { - &self.named_metadata + self.inner.named_metadata_map() } /// Get all the metadata into an [`hashbrown::HashMap`] (mutable) #[inline] fn named_metadata_map_mut(&mut self) -> &mut NamedSerdeAnyMap { - &mut self.named_metadata + self.inner.named_metadata_map_mut() } } @@ -396,13 +640,13 @@ impl HasExecutions for StdState { /// The executions counter #[inline] fn executions(&self) -> &u64 { - &self.executions + self.inner.executions() } /// The executions counter (mutable) #[inline] fn executions_mut(&mut self) -> &mut u64 { - &mut self.executions + self.inner.executions_mut() } } @@ -410,13 +654,13 @@ impl HasImported for StdState { /// Return the number of new paths that imported from other fuzzers #[inline] fn imported(&self) -> &usize { - &self.imported + self.inner.imported() } /// Return the number of new paths that imported from other fuzzers #[inline] fn imported_mut(&mut self) -> &mut usize { - &mut self.imported + self.inner.imported_mut() } } @@ -424,23 +668,23 @@ impl HasLastReportTime for StdState { /// The last time we reported progress,if available/used. /// This information is used by fuzzer `maybe_report_progress`. fn last_report_time(&self) -> &Option { - &self.last_report_time + self.inner.last_report_time() } /// The last time we reported progress,if available/used (mutable). /// This information is used by fuzzer `maybe_report_progress`. fn last_report_time_mut(&mut self) -> &mut Option { - &mut self.last_report_time + self.inner.last_report_time_mut() } } impl HasMaxSize for StdState { fn max_size(&self) -> usize { - self.max_size + self.inner.max_size() } fn set_max_size(&mut self, max_size: usize) { - self.max_size = max_size; + self.inner.set_max_size(max_size); } } @@ -448,13 +692,13 @@ impl HasStartTime for StdState { /// The starting time #[inline] fn start_time(&self) -> &Duration { - &self.start_time + self.inner.start_time() } /// The starting time (mutable) #[inline] fn start_time_mut(&mut self) -> &mut Duration { - &mut self.start_time + self.inner.start_time_mut() } } @@ -532,7 +776,7 @@ where } } -impl HasCurrentStage for StdState { +impl HasCurrentStage for StdGenState { fn set_current_stage_idx(&mut self, idx: StageId) -> Result<(), Error> { self.stage_stack.set_current_stage_idx(idx) } @@ -550,7 +794,25 @@ impl HasCurrentStage for StdState { } } -impl HasNestedStageStatus for StdState { +impl HasCurrentStage for StdState { + fn set_current_stage_idx(&mut self, idx: StageId) -> Result<(), Error> { + self.inner.set_current_stage_idx(idx) + } + + fn clear_stage(&mut self) -> Result<(), Error> { + self.inner.clear_stage() + } + + fn current_stage_idx(&self) -> Result, Error> { + self.inner.current_stage_idx() + } + + fn on_restart(&mut self) -> Result<(), Error> { + self.inner.on_restart() + } +} + +impl HasNestedStageStatus for StdGenState { fn enter_inner_stage(&mut self) -> Result<(), Error> { self.stage_stack.enter_inner_stage() } @@ -560,6 +822,16 @@ impl HasNestedStageStatus for StdState { } } +impl HasNestedStageStatus for StdState { + fn enter_inner_stage(&mut self) -> Result<(), Error> { + self.inner.enter_inner_stage() + } + + fn exit_inner_stage(&mut self) -> Result<(), Error> { + self.inner.exit_inner_stage() + } +} + #[cfg(feature = "std")] impl StdState where @@ -1077,28 +1349,15 @@ where F: Feedback, O: Feedback, { + let inner = StdGenState::new_no_init_objetive(rand, solutions); let mut state = Self { - rand, - executions: 0, - imported: 0, - start_time: Duration::from_millis(0), - metadata: SerdeAnyMap::default(), - named_metadata: NamedSerdeAnyMap::default(), + inner, corpus, - solutions, - max_size: DEFAULT_MAX_SIZE, - #[cfg(feature = "introspection")] - introspection_monitor: ClientPerfMonitor::new(), - #[cfg(feature = "scalability_introspection")] - scalability_monitor: ScalabilityMonitor::new(), + corpus_idx: None, #[cfg(feature = "std")] remaining_initial_files: None, #[cfg(feature = "std")] dont_reenter: None, - last_report_time: None, - corpus_idx: None, - stage_stack: StageStack::default(), - phantom: PhantomData, #[cfg(feature = "std")] multicore_inputs_processed: None, }; @@ -1111,22 +1370,22 @@ where #[cfg(feature = "introspection")] impl HasClientPerfMonitor for StdState { fn introspection_monitor(&self) -> &ClientPerfMonitor { - &self.introspection_monitor + self.inner.introspection_monitor() } fn introspection_monitor_mut(&mut self) -> &mut ClientPerfMonitor { - &mut self.introspection_monitor + self.inner.introspection_monitor_mut() } } #[cfg(feature = "scalability_introspection")] impl HasScalabilityMonitor for StdState { fn scalability_monitor(&self) -> &ScalabilityMonitor { - &self.scalability_monitor + self.inner.scalability_monitor() } fn scalability_monitor_mut(&mut self) -> &mut ScalabilityMonitor { - &mut self.scalability_monitor + self.inner.scalability_monitor_mut() } } @@ -1211,6 +1470,7 @@ impl HasRand for NopState { } } +impl GenState for NopState where I: Input {} impl State for NopState where I: Input {} impl HasCurrentCorpusId for NopState {