diff --git a/libafl/src/corpus/testcase.rs b/libafl/src/corpus/testcase.rs index 58cb91e652..acab8b2318 100644 --- a/libafl/src/corpus/testcase.rs +++ b/libafl/src/corpus/testcase.rs @@ -60,7 +60,7 @@ where cached_len: Option, /// Number of executions done at discovery time executions: usize, - /// Number of fuzzing iterations of this particular input updated in perform_mutational + /// Number of fuzzing iterations of this particular input updated in `perform_mutational` scheduled_count: usize, /// Parent [`CorpusId`], if known parent_id: Option, @@ -366,15 +366,15 @@ where allow(clippy::unsafe_derive_deserialize) )] // for SerdeAny pub struct SchedulerTestcaseMetadata { - /// Number of bits set in bitmap, updated in calibrate_case + /// Number of bits set in bitmap, updated in `calibrate_case` bitmap_size: u64, /// Number of queue cycles behind handicap: u64, - /// Path depth, initialized in on_add + /// Path depth, initialized in `on_add` depth: u64, - /// Offset in n_fuzz + /// Offset in `n_fuzz` n_fuzz_entry: usize, - /// Cycles used to calibrate this (not really needed if it were not for on_replace and on_remove) + /// Cycles used to calibrate this (not really needed if it were not for `on_replace` and `on_remove`) cycle_and_time: (Duration, usize), } diff --git a/libafl/src/events/launcher.rs b/libafl/src/events/launcher.rs index 75f250e9fb..c704815a22 100644 --- a/libafl/src/events/launcher.rs +++ b/libafl/src/events/launcher.rs @@ -78,7 +78,7 @@ where SP: ShMemProvider + 'static, S: State + 'a, { - /// The ShmemProvider to use + /// The `ShmemProvider` to use shmem_provider: SP, /// The monitor instance to use monitor: MT, @@ -95,7 +95,7 @@ where /// A file name to write all client output to #[builder(default = None)] stdout_file: Option<&'a str>, - /// The actual, opened, stdout_file - so that we keep it open until the end + /// The actual, opened, `stdout_file` - so that we keep it open until the end #[cfg(all(unix, feature = "std", feature = "fork"))] #[builder(setter(skip), default = None)] opened_stdout_file: Option, @@ -103,7 +103,7 @@ where /// `stdout_file`. #[builder(default = None)] stderr_file: Option<&'a str>, - /// The actual, opened, stdout_file - so that we keep it open until the end + /// The actual, opened, `stdout_file` - so that we keep it open until the end #[cfg(all(unix, feature = "std", feature = "fork"))] #[builder(setter(skip), default = None)] opened_stderr_file: Option, @@ -409,7 +409,7 @@ where SP: ShMemProvider + 'static, S: State + 'a, { - /// The ShmemProvider to use + /// The `ShmemProvider` to use shmem_provider: SP, /// The monitor instance to use monitor: MT, @@ -429,7 +429,7 @@ where /// A file name to write all client output to #[builder(default = None)] stdout_file: Option<&'a str>, - /// The actual, opened, stdout_file - so that we keep it open until the end + /// The actual, opened, `stdout_file` - so that we keep it open until the end #[cfg(all(unix, feature = "std", feature = "fork"))] #[builder(setter(skip), default = None)] opened_stdout_file: Option, @@ -437,7 +437,7 @@ where /// `stdout_file`. #[builder(default = None)] stderr_file: Option<&'a str>, - /// The actual, opened, stdout_file - so that we keep it open until the end + /// The actual, opened, `stdout_file` - so that we keep it open until the end #[cfg(all(unix, feature = "std", feature = "fork"))] #[builder(setter(skip), default = None)] opened_stderr_file: Option, diff --git a/libafl/src/events/simple.rs b/libafl/src/events/simple.rs index c564ba19cc..a4e9c79259 100644 --- a/libafl/src/events/simple.rs +++ b/libafl/src/events/simple.rs @@ -54,7 +54,7 @@ where { /// The monitor monitor: MT, - /// The events that happened since the last handle_in_broker + /// The events that happened since the last `handle_in_broker` events: Vec>, /// The custom buf handler custom_buf_handlers: Vec>>, diff --git a/libafl/src/executors/hooks/inprocess.rs b/libafl/src/executors/hooks/inprocess.rs index 30a92c233f..7f10fd1477 100644 --- a/libafl/src/executors/hooks/inprocess.rs +++ b/libafl/src/executors/hooks/inprocess.rs @@ -42,7 +42,7 @@ pub struct InProcessHooks { /// On timeout C function pointer #[cfg(feature = "std")] pub timeout_handler: *const c_void, - /// TImer struct + /// `TImer` struct #[cfg(feature = "std")] pub timer: TimerStruct, } diff --git a/libafl/src/executors/hooks/inprocess_fork.rs b/libafl/src/executors/hooks/inprocess_fork.rs index 48e64e3db1..1b7ba6dfb4 100644 --- a/libafl/src/executors/hooks/inprocess_fork.rs +++ b/libafl/src/executors/hooks/inprocess_fork.rs @@ -101,9 +101,9 @@ pub(crate) struct InProcessForkExecutorGlobalData { pub state_ptr: *const c_void, /// Stores a pointer to the current input pub current_input_ptr: *const c_void, - /// Stores a pointer to the crash_handler function + /// Stores a pointer to the `crash_handler` function pub crash_handler: *const c_void, - /// Stores a pointer to the timeout_handler function + /// Stores a pointer to the `timeout_handler` function pub timeout_handler: *const c_void, } diff --git a/libafl/src/lib.rs b/libafl/src/lib.rs index c3660ad9bb..6d7986452d 100644 --- a/libafl/src/lib.rs +++ b/libafl/src/lib.rs @@ -27,7 +27,8 @@ Welcome to `LibAFL` clippy::missing_docs_in_private_items, clippy::module_name_repetitions, clippy::ptr_cast_constness, - clippy::unsafe_derive_deserialize + clippy::unsafe_derive_deserialize, + clippy::similar_names )] #![cfg_attr(not(test), warn( missing_debug_implementations, diff --git a/libafl/src/mutators/mopt_mutator.rs b/libafl/src/mutators/mopt_mutator.rs index 25c9647c67..274699d6c2 100644 --- a/libafl/src/mutators/mopt_mutator.rs +++ b/libafl/src/mutators/mopt_mutator.rs @@ -53,9 +53,9 @@ pub struct MOpt { pub operator_num: usize, /// The number of swarms that we want to employ during the pilot fuzzing mode pub swarm_num: usize, - /// We'll generate inputs for `period_pilot` times before we call pso_update in pilot fuzzing module + /// We'll generate inputs for `period_pilot` times before we call `pso_update` in pilot fuzzing module pub period_pilot: usize, - /// We'll generate inputs for `period_core` times before we call pso_update in core fuzzing module + /// We'll generate inputs for `period_core` times before we call `pso_update` in core fuzzing module pub period_core: usize, /// The number of testcases generated during this pilot fuzzing mode pub pilot_time: usize, @@ -77,23 +77,23 @@ pub struct MOpt { probability_now: Vec>, /// The fitness for each swarm, we'll calculate the fitness in the pilot fuzzing mode and use the best one in the core fuzzing mode pub swarm_fitness: Vec, - /// (Pilot Mode) Finds by each operators. This vector is used in pso_update + /// (Pilot Mode) Finds by each operators. This vector is used in `pso_update` pub pilot_operator_finds: Vec>, /// (Pilot Mode) Finds by each operator till now. pub pilot_operator_finds_v2: Vec>, - /// (Pilot Mode) The number of mutation operator used. This vector is used in pso_update + /// (Pilot Mode) The number of mutation operator used. This vector is used in `pso_update` pub pilot_operator_cycles: Vec>, /// (Pilot Mode) The number of mutation operator used till now pub pilot_operator_cycles_v2: Vec>, /// (Pilot Mode) The number of mutation operator used till last execution pub pilot_operator_cycles_v3: Vec>, - /// Vector used in pso_update + /// Vector used in `pso_update` pub operator_finds_puppet: Vec, - /// (Core Mode) Finds by each operators. This vector is used in pso_update + /// (Core Mode) Finds by each operators. This vector is used in `pso_update` pub core_operator_finds: Vec, /// (Core Mode) Finds by each operator till now. pub core_operator_finds_v2: Vec, - /// (Core Mode) The number of mutation operator used. This vector is used in pso_update + /// (Core Mode) The number of mutation operator used. This vector is used in `pso_update` pub core_operator_cycles: Vec, /// (Core Mode) The number of mutation operator used till now pub core_operator_cycles_v2: Vec, diff --git a/libafl/src/observers/cmp.rs b/libafl/src/observers/cmp.rs index 0c77f9aaa6..04724a08dc 100644 --- a/libafl/src/observers/cmp.rs +++ b/libafl/src/observers/cmp.rs @@ -468,10 +468,10 @@ struct cmp_map { allow(clippy::unsafe_derive_deserialize) )] // for SerdeAny pub struct AFLppCmpValuesMetadata { - /// The first map of AFLppCmpLogVals retrieved by running the un-mutated input + /// The first map of `AFLppCmpLogVals` retrieved by running the un-mutated input #[serde(skip)] pub orig_cmpvals: HashMap>, - /// The second map of AFLppCmpLogVals retrieved by runnning the mutated input + /// The second map of `AFLppCmpLogVals` retrieved by runnning the mutated input #[serde(skip)] pub new_cmpvals: HashMap>, /// The list of logged idx and headers retrieved by runnning the mutated input diff --git a/libafl/src/schedulers/powersched.rs b/libafl/src/schedulers/powersched.rs index bf3aec6e72..d6d96663f9 100644 --- a/libafl/src/schedulers/powersched.rs +++ b/libafl/src/schedulers/powersched.rs @@ -39,7 +39,7 @@ pub struct SchedulerMetadata { cycles: u64, /// Size of the observer map bitmap_size: u64, - /// Sum of log(bitmap_size) + /// Sum of `log(bitmap_size`) bitmap_size_log: f64, /// Number of filled map entries bitmap_entries: u64, diff --git a/libafl/src/stages/concolic.rs b/libafl/src/stages/concolic.rs index cabd92c83f..9c7eeb9572 100644 --- a/libafl/src/stages/concolic.rs +++ b/libafl/src/stages/concolic.rs @@ -7,18 +7,29 @@ use alloc::string::String; use alloc::{borrow::ToOwned, string::ToString, vec::Vec}; use core::marker::PhantomData; -use super::{Stage, TracingStage}; +use libafl_bolts::tuples::MatchName; + +use super::{RetryProgress, RetryingStage, Stage, TracingStage}; +#[cfg(all(feature = "concolic_mutation", feature = "introspection"))] +use crate::monitors::PerfFeature; #[cfg(all(feature = "introspection", feature = "concolic_mutation"))] use crate::state::HasClientPerfMonitor; #[cfg(feature = "concolic_mutation")] use crate::state::State; use crate::{ - corpus::Corpus, + corpus::{Corpus, HasCurrentCorpusIdx}, executors::{Executor, HasObservers}, observers::concolic::ConcolicObserver, - state::{HasCorpus, HasExecutions, HasMetadata}, + state::{HasCorpus, HasExecutions, HasMetadata, HasNamedMetadata, UsesState}, Error, }; +#[cfg(feature = "concolic_mutation")] +use crate::{ + inputs::HasBytesVec, + mark_feature_time, + observers::concolic::{ConcolicMetadata, SymExpr, SymExprRef}, + start_timer, Evaluator, +}; /// Wraps a [`TracingStage`] to add concolic observing. #[derive(Clone, Debug)] @@ -39,16 +50,16 @@ where E: UsesState, EM: UsesState, TE: Executor + HasObservers, - TE::State: HasExecutions + HasCorpus, + TE::State: HasExecutions + HasCorpus + HasNamedMetadata, Z: UsesState, { - type Progress = (); // stage cannot be resumed + type Progress = RetryProgress; #[inline] fn perform( &mut self, fuzzer: &mut Z, - executor: &mut E, + _executor: &mut E, state: &mut TE::State, manager: &mut EM, ) -> Result<(), Error> { @@ -57,8 +68,11 @@ where "state is not currently processing a corpus index", )); }; + if Self::Progress::should_skip(state, &self.inner, corpus_idx)? { + return Ok(()); + } - self.inner.perform(fuzzer, executor, state, manager)?; + self.inner.trace(fuzzer, state, manager, corpus_idx)?; if let Some(observer) = self .inner .executor() @@ -78,8 +92,16 @@ where } } +impl RetryingStage for ConcolicTracingStage { + fn max_retries(&self) -> usize { + self.inner.max_retries() + } +} + impl ConcolicTracingStage { - /// Creates a new default tracing stage using the given [`Executor`], observing traces from a [`ConcolicObserver`] with the given name. + /// Creates a new default tracing stage using the given [`Executor`], observing traces from a + /// [`ConcolicObserver`] with the given name. The [`RetryingStage::max_retries`] is + /// used from the provided inner stage. pub fn new(inner: TracingStage, observer_name: String) -> Self { Self { inner, @@ -88,19 +110,6 @@ impl ConcolicTracingStage { } } -use libafl_bolts::tuples::MatchName; - -#[cfg(all(feature = "concolic_mutation", feature = "introspection"))] -use crate::monitors::PerfFeature; -use crate::{corpus::HasCurrentCorpusIdx, state::UsesState}; -#[cfg(feature = "concolic_mutation")] -use crate::{ - inputs::HasBytesVec, - mark_feature_time, - observers::concolic::{ConcolicMetadata, SymExpr, SymExprRef}, - start_timer, Evaluator, -}; - #[cfg(feature = "concolic_mutation")] #[allow(clippy::too_many_lines)] fn generate_mutations(iter: impl Iterator) -> Vec> { diff --git a/libafl/src/stages/logics.rs b/libafl/src/stages/logics.rs index 2ae9e6b651..fff2123219 100644 --- a/libafl/src/stages/logics.rs +++ b/libafl/src/stages/logics.rs @@ -12,25 +12,25 @@ use crate::{ #[derive(Debug)] pub struct NestedStageProgress; -impl StageProgress for NestedStageProgress +impl StageProgress for NestedStageProgress where S: HasNestedStageStatus, { - fn initialize_progress(state: &mut S) -> Result<(), Error> { + fn initialize_progress(state: &mut S, _stage: &ST) -> Result<(), Error> { state.enter_inner_stage()?; Ok(()) } - fn clear_progress(state: &mut S) -> Result<(), Error> { + fn clear_progress(state: &mut S, _stage: &ST) -> Result<(), Error> { state.exit_inner_stage()?; Ok(()) } - fn progress(_state: &S) -> Result<&Self, Error> { + fn progress<'a>(_state: &'a S, _stage: &ST) -> Result<&'a Self, Error> { unimplemented!("NestedStageProgress should not be queried") } - fn progress_mut(_state: &mut S) -> Result<&mut Self, Error> { + fn progress_mut<'a>(_state: &'a mut S, _stage: &ST) -> Result<&'a mut Self, Error> { unimplemented!("NestedStageProgress should not be queried") } } diff --git a/libafl/src/stages/mod.rs b/libafl/src/stages/mod.rs index 4c50f43c9e..44e2710bc2 100644 --- a/libafl/src/stages/mod.rs +++ b/libafl/src/stages/mod.rs @@ -15,10 +15,12 @@ pub use concolic::SimpleConcolicMutationalStage; #[cfg(feature = "std")] pub use dump::*; pub use generalization::GeneralizationStage; -use libafl_bolts::tuples::HasConstLen; +use hashbrown::HashSet; +use libafl_bolts::{impl_serdeany, tuples::HasConstLen}; pub use logics::*; pub use mutational::{MutationalStage, StdMutationalStage}; pub use power::{PowerMutationalStage, StdPowerMutationalStage}; +use serde::{Deserialize, Serialize}; pub use stats::AflStatsStage; #[cfg(feature = "unicode")] pub use string::*; @@ -32,13 +34,16 @@ pub use tuneable::*; use self::push::PushStage; use crate::{ - corpus::HasCurrentCorpusIdx, + corpus::{CorpusId, HasCurrentCorpusIdx}, events::{EventFirer, EventRestarter, HasEventManagerId, ProgressReporter}, executors::{Executor, HasObservers}, inputs::UsesInput, observers::ObserversTuple, schedulers::Scheduler, - state::{HasCorpus, HasExecutions, HasLastReportTime, HasMetadata, HasRand, UsesState}, + state::{ + HasCorpus, HasExecutions, HasLastReportTime, HasMetadata, HasNamedMetadata, HasRand, + UsesState, + }, Error, EvaluatorObservers, ExecutesInput, ExecutionProcessor, HasScheduler, }; @@ -76,7 +81,7 @@ where // TODO: see RFC 2532: https://github.com/rust-lang/rust/issues/29661 // type Status: ResumableStageStatus = (); /// The resumption data for this stage. Set to () if resuming is not necessary/possible. - type Progress: StageProgress; + type Progress: StageProgress; /// Run the stage fn perform( @@ -152,9 +157,10 @@ where } Some(idx) if idx == Self::LEN => { // perform the stage, but don't set it - Head::Progress::initialize_progress(state)?; - self.0.perform(fuzzer, executor, state, manager)?; - Head::Progress::clear_progress(state)?; + let stage = &mut self.0; + Head::Progress::initialize_progress(state, stage)?; + stage.perform(fuzzer, executor, state, manager)?; + Head::Progress::clear_progress(state, stage)?; state.clear_stage()?; } Some(idx) if idx > Self::LEN => { @@ -163,9 +169,10 @@ where // this is None, but the match can't deduce that _ => { state.set_stage(Self::LEN)?; - Head::Progress::initialize_progress(state)?; - self.0.perform(fuzzer, executor, state, manager)?; - Head::Progress::clear_progress(state)?; + let stage = &mut self.0; + Head::Progress::initialize_progress(state, stage)?; + stage.perform(fuzzer, executor, state, manager)?; + Head::Progress::clear_progress(state, stage)?; state.clear_stage()?; } } @@ -541,38 +548,128 @@ pub mod pybind { } /// Trait for status tracking of stages which stash data to resume -pub trait StageProgress { +pub trait StageProgress +where + ST: ?Sized, +{ /// Initialize the current status tracking for this stage, if it is not yet initialised - fn initialize_progress(state: &mut S) -> Result<(), Error>; + fn initialize_progress(state: &mut S, stage: &ST) -> Result<(), Error>; /// Clear the current status tracking of the associated stage - fn clear_progress(state: &mut S) -> Result<(), Error>; + fn clear_progress(state: &mut S, stage: &ST) -> Result<(), Error>; /// Get the current status tracking of this stage - fn progress(state: &S) -> Result<&Self, Error>; + fn progress<'a>(state: &'a S, stage: &ST) -> Result<&'a Self, Error>; /// Get the current status tracking of this stage, mutably - fn progress_mut(state: &mut S) -> Result<&mut Self, Error>; + fn progress_mut<'a>(state: &'a mut S, stage: &ST) -> Result<&'a mut Self, Error>; } -impl StageProgress for () { - fn initialize_progress(_state: &mut S) -> Result<(), Error> { +impl StageProgress for () { + fn initialize_progress(_state: &mut S, _stage: &ST) -> Result<(), Error> { Ok(()) } - fn clear_progress(_state: &mut S) -> Result<(), Error> { + fn clear_progress(_state: &mut S, _stage: &ST) -> Result<(), Error> { Ok(()) } - fn progress(_state: &S) -> Result<&Self, Error> { + fn progress<'a>(_state: &'a S, _stage: &ST) -> Result<&'a Self, Error> { unimplemented!("The empty tuple resumable stage status should never be queried") } - fn progress_mut(_state: &mut S) -> Result<&mut Self, Error> { + fn progress_mut<'a>(_state: &'a mut S, _stage: &ST) -> Result<&'a mut Self, Error> { unimplemented!("The empty tuple resumable stage status should never be queried") } } +/// Progress which permits a fixed amount of resumes per round of fuzzing. If this amount is ever +/// exceeded, the input will no longer be executed by this stage. +#[derive(Clone, Deserialize, Serialize, Debug)] +pub struct RetryProgress { + tries_remaining: Option, + skipped: HashSet, +} + +impl_serdeany!(RetryProgress); + +/// Stage which specifies a certain amount of retries over which a scheduled input is attempted. To +/// be used in combination with [`RetryProgress`]. +pub trait RetryingStage { + /// The number of times each testcase may be retries. + fn max_retries(&self) -> usize; +} + +impl StageProgress for RetryProgress +where + S: HasNamedMetadata, + ST: RetryingStage, +{ + fn initialize_progress(state: &mut S, stage: &ST) -> Result<(), Error> { + if let Ok(metadata) = state.named_metadata_mut::(core::any::type_name_of_val(stage)) { + if let Some(ref mut remaining) = metadata.tries_remaining { + *remaining = remaining.checked_sub(1).ok_or_else(|| { + Error::illegal_state( + "Attempted further retries after we had already gotten to none remaining.", + ) + })?; + } else { + metadata.tries_remaining = Some(stage.max_retries() + 1); + } + } else { + state.add_named_metadata( + Self { + tries_remaining: Some(stage.max_retries() + 1), + skipped: HashSet::new(), + }, + core::any::type_name_of_val(stage), + ); + } + Ok(()) + } + + fn clear_progress(state: &mut S, stage: &ST) -> Result<(), Error> { + let metadata = state.named_metadata_mut::(core::any::type_name_of_val(stage))?; + metadata.tries_remaining = None; + Ok(()) + } + + fn progress<'a>(state: &'a S, stage: &ST) -> Result<&'a Self, Error> { + state.named_metadata::(core::any::type_name_of_val(stage)) + } + + fn progress_mut<'a>(state: &'a mut S, stage: &ST) -> Result<&'a mut Self, Error> { + state.named_metadata_mut::(core::any::type_name_of_val(stage)) + } +} + +impl RetryProgress { + /// Whether we should skip the provided corpus entry. + pub fn should_skip( + state: &mut S, + stage: &ST, + corpus_idx: CorpusId, + ) -> Result + where + S: HasNamedMetadata, + ST: RetryingStage, + { + let progress = Self::progress_mut(state, stage)?; + if progress.skipped.contains(&corpus_idx) { + return Ok(true); + } + let remaining = progress.tries_remaining.as_mut().ok_or_else(|| + Error::illegal_state( + "Attempted to check if we should skip a testcase without having initialised the number of tries remaining.", + ))?; + if *remaining == 0 { + progress.skipped.insert(corpus_idx); + return Ok(true); + } + Ok(false) + } +} + /// Trait for types which track the current stage pub trait HasCurrentStage { /// Set the current stage; we have started processing this stage @@ -611,11 +708,13 @@ pub mod test { use tuple_list::{tuple_list, tuple_list_type}; use crate::{ + corpus::{Corpus, Testcase}, events::NopEventManager, executors::test::NopExecutor, fuzzer::test::NopFuzzer, - stages::{Stage, StageProgress, StagesTuple}, - state::{HasMetadata, State, UsesState}, + inputs::NopInput, + stages::{RetryProgress, RetryingStage, Stage, StageProgress, StagesTuple}, + state::{test::test_std_state, HasCorpus, HasMetadata, State, UsesState}, }; #[derive(Debug)] @@ -636,11 +735,11 @@ pub mod test { impl_serdeany!(TestProgress); - impl StageProgress for TestProgress + impl StageProgress for TestProgress where S: HasMetadata, { - fn initialize_progress(state: &mut S) -> Result<(), Error> { + fn initialize_progress(state: &mut S, _stage: &ST) -> Result<(), Error> { // check if we're resuming if !state.has_metadata::() { state.add_metadata(Self { count: 0 }); @@ -648,7 +747,7 @@ pub mod test { Ok(()) } - fn clear_progress(state: &mut S) -> Result<(), Error> { + fn clear_progress(state: &mut S, _stage: &ST) -> Result<(), Error> { if state.metadata_map_mut().remove::().is_none() { return Err(Error::illegal_state( "attempted to clear status metadata when none was present", @@ -657,11 +756,11 @@ pub mod test { Ok(()) } - fn progress(state: &S) -> Result<&Self, Error> { + fn progress<'a>(state: &'a S, _stage: &ST) -> Result<&'a Self, Error> { state.metadata() } - fn progress_mut(state: &mut S) -> Result<&mut Self, Error> { + fn progress_mut<'a>(state: &'a mut S, _stage: &ST) -> Result<&'a mut Self, Error> { state.metadata_mut() } } @@ -690,7 +789,7 @@ pub mod test { _manager: &mut EM, ) -> Result<(), Error> { // metadata is attached by the status - let meta = Self::Progress::progress_mut(state)?; + let meta = Self::Progress::progress_mut(state, self)?; meta.count += 1; assert!( meta.count == 1, @@ -725,7 +824,7 @@ pub mod test { _manager: &mut EM, ) -> Result<(), Error> { // metadata is attached by the status - let meta = Self::Progress::progress_mut(state)?; + let meta = Self::Progress::progress_mut(state, self)?; meta.count += 1; if meta.count == 1 { @@ -800,4 +899,60 @@ pub mod test { ); } } + + #[test] + fn test_tries_progress() -> Result<(), Error> { + #[cfg(any(not(feature = "serdeany_autoreg"), miri))] + unsafe { + RetryProgress::register(); + } + + struct StageWithOneTry; + + impl RetryingStage for StageWithOneTry { + fn max_retries(&self) -> usize { + 1 + } + } + + let mut state = test_std_state(); + let stage = StageWithOneTry; + + let corpus_idx = state.corpus_mut().add(Testcase::new(NopInput {}))?; + + for _ in 0..10 { + // used normally, no retries means we never skip + RetryProgress::initialize_progress(&mut state, &stage)?; + assert!(!RetryProgress::should_skip(&mut state, &stage, corpus_idx)?); + RetryProgress::clear_progress(&mut state, &stage)?; + } + + for _ in 0..10 { + // used normally, only one retry means we never skip + RetryProgress::initialize_progress(&mut state, &stage)?; + assert!(!RetryProgress::should_skip(&mut state, &stage, corpus_idx)?); + RetryProgress::initialize_progress(&mut state, &stage)?; + assert!(!RetryProgress::should_skip(&mut state, &stage, corpus_idx)?); + RetryProgress::clear_progress(&mut state, &stage)?; + } + + RetryProgress::initialize_progress(&mut state, &stage)?; + assert!(!RetryProgress::should_skip(&mut state, &stage, corpus_idx)?); + // task failed, let's resume + RetryProgress::initialize_progress(&mut state, &stage)?; + // we still have one more try! + assert!(!RetryProgress::should_skip(&mut state, &stage, corpus_idx)?); + // task failed, let's resume + RetryProgress::initialize_progress(&mut state, &stage)?; + // out of retries, so now we skip + assert!(RetryProgress::should_skip(&mut state, &stage, corpus_idx)?); + RetryProgress::clear_progress(&mut state, &stage)?; + + RetryProgress::initialize_progress(&mut state, &stage)?; + // we previously exhausted this testcase's retries, so we skip + assert!(RetryProgress::should_skip(&mut state, &stage, corpus_idx)?); + RetryProgress::clear_progress(&mut state, &stage)?; + + Ok(()) + } } diff --git a/libafl/src/stages/tracing.rs b/libafl/src/stages/tracing.rs index f141447b5c..f040fe0eb6 100644 --- a/libafl/src/stages/tracing.rs +++ b/libafl/src/stages/tracing.rs @@ -3,13 +3,13 @@ use core::{fmt::Debug, marker::PhantomData}; use crate::{ - corpus::{Corpus, HasCurrentCorpusIdx}, + corpus::{Corpus, CorpusId, HasCurrentCorpusIdx}, executors::{Executor, HasObservers, ShadowExecutor}, mark_feature_time, observers::ObserversTuple, - stages::Stage, + stages::{RetryProgress, RetryingStage, Stage}, start_timer, - state::{HasCorpus, HasExecutions, State, UsesState}, + state::{HasCorpus, HasExecutions, HasNamedMetadata, State, UsesState}, Error, }; #[cfg(feature = "introspection")] @@ -19,6 +19,7 @@ use crate::{monitors::PerfFeature, state::HasClientPerfMonitor}; #[derive(Clone, Debug)] pub struct TracingStage { tracer_executor: TE, + max_retries: usize, #[allow(clippy::type_complexity)] phantom: PhantomData<(EM, TE, Z)>, } @@ -30,30 +31,23 @@ where type State = TE::State; } -impl Stage for TracingStage +impl TracingStage where - E: UsesState, TE: Executor + HasObservers, - TE::State: HasExecutions + HasCorpus, + TE::State: HasExecutions + HasCorpus + HasNamedMetadata, EM: UsesState, Z: UsesState, { - type Progress = (); // this stage cannot be resumed - - #[inline] - fn perform( + /// Perform tracing on the given [`CorpusId`]. Useful for if wrapping [`TracingStage`] with your + /// own stage and you need to manage [`super::StageProgress`] differently; see + /// [`super::ConcolicTracingStage`]'s implementation as an example of usage. + pub fn trace( &mut self, fuzzer: &mut Z, - _executor: &mut E, state: &mut TE::State, manager: &mut EM, + corpus_idx: CorpusId, ) -> Result<(), Error> { - let Some(corpus_idx) = state.current_corpus_idx()? else { - return Err(Error::illegal_state( - "state is not currently processing a corpus index", - )); - }; - start_timer!(state); let input = state.corpus().cloned_input_for_id(corpus_idx)?; @@ -83,15 +77,63 @@ where } } +impl Stage for TracingStage +where + E: UsesState, + TE: Executor + HasObservers, + TE::State: HasExecutions + HasCorpus + HasNamedMetadata, + EM: UsesState, + Z: UsesState, +{ + type Progress = RetryProgress; + + #[inline] + fn perform( + &mut self, + fuzzer: &mut Z, + _executor: &mut E, + state: &mut TE::State, + manager: &mut EM, + ) -> Result<(), Error> { + let Some(corpus_idx) = state.current_corpus_idx()? else { + return Err(Error::illegal_state( + "state is not currently processing a corpus index", + )); + }; + if Self::Progress::should_skip(state, self, corpus_idx)? { + return Ok(()); + } + + self.trace(fuzzer, state, manager, corpus_idx)?; + + Ok(()) + } +} + +impl RetryingStage for TracingStage { + fn max_retries(&self) -> usize { + self.max_retries + } +} + impl TracingStage { /// Creates a new default stage pub fn new(tracer_executor: TE) -> Self { Self { tracer_executor, + max_retries: 10, phantom: PhantomData, } } + /// Specify how many times that this stage will try again to trace the input before giving up + /// and not processing the input again. 0 retries means that the trace will be tried only once. + #[must_use] + pub fn with_retries(mut self, retries: usize) -> Self { + self.max_retries = retries; + self + } + /// Gets the underlying tracer executor pub fn executor(&self) -> &TE { &self.tracer_executor @@ -102,9 +144,11 @@ impl TracingStage { &mut self.tracer_executor } } + /// A stage that runs the shadow executor using also the shadow observers #[derive(Clone, Debug)] pub struct ShadowTracingStage { + max_retries: usize, #[allow(clippy::type_complexity)] phantom: PhantomData<(E, EM, SOT, Z)>, } @@ -122,9 +166,9 @@ where EM: UsesState, SOT: ObserversTuple, Z: UsesState, - E::State: State + HasExecutions + HasCorpus + Debug, + E::State: State + HasExecutions + HasCorpus + HasNamedMetadata + Debug, { - type Progress = (); // this stage cannot be resumed + type Progress = RetryProgress; #[inline] fn perform( @@ -139,6 +183,9 @@ where "state is not currently processing a corpus index", )); }; + if Self::Progress::should_skip(state, self, corpus_idx)? { + return Ok(()); + } start_timer!(state); let input = state.corpus().cloned_input_for_id(corpus_idx)?; @@ -171,6 +218,12 @@ where } } +impl RetryingStage for ShadowTracingStage { + fn max_retries(&self) -> usize { + self.max_retries + } +} + impl ShadowTracingStage where E: Executor + HasObservers, @@ -182,7 +235,16 @@ where /// Creates a new default stage pub fn new(_executor: &mut ShadowExecutor) -> Self { Self { + max_retries: 10, phantom: PhantomData, } } + + /// Specify how many times that this stage will try again to trace the input before giving up + /// and not processing the input again. 0 retries means that the trace will be tried only once. + #[must_use] + pub fn with_retries(mut self, retries: usize) -> Self { + self.max_retries = retries; + self + } } diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index b01488fb50..9cc387e68d 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -312,7 +312,7 @@ pub struct StdState { metadata: SerdeAnyMap, /// Metadata stored with names named_metadata: NamedSerdeAnyMap, - /// MaxSize testcase size for mutators that appreciate it + /// `MaxSize` testcase size for mutators that appreciate it max_size: usize, /// Performance statistics for this fuzzer #[cfg(feature = "introspection")] diff --git a/libafl_bolts/src/llmp.rs b/libafl_bolts/src/llmp.rs index bf0a4fd40f..a8a8cb25e9 100644 --- a/libafl_bolts/src/llmp.rs +++ b/libafl_bolts/src/llmp.rs @@ -324,7 +324,7 @@ pub enum TcpResponse { }, /// Notify the client on the other side that it has been accepted. LocalClientAccepted { - /// The ClientId this client should send messages as. + /// The `ClientId` this client should send messages as. /// Mainly used for client-side deduplication of incoming messages client_id: ClientId, }, @@ -792,7 +792,7 @@ pub struct LlmpPage { /// (The os may have tidied up the memory when the receiver starts to map) pub receivers_joined_count: AtomicU16, /// Set to != 1 by the receiver, once it left again after joining. - /// It's not safe for the sender to re-map this page before this is equal to receivers_joined_count + /// It's not safe for the sender to re-map this page before this is equal to `receivers_joined_count` pub receivers_left_count: AtomicU16, #[cfg(target_pointer_width = "64")] /// The current message ID @@ -1796,7 +1796,7 @@ where SHM: ShMem, { /// Shmem containg the actual (unsafe) page, - /// shared between one LlmpSender and one LlmpReceiver + /// shared between one `LlmpSender` and one `LlmpReceiver` shmem: SHM, } @@ -1962,9 +1962,9 @@ where /// The amount of total clients that should have connected and (and disconnected) /// after which the broker loop should quit gracefully. pub exit_cleanly_after: Option, - /// Clients that should be removed soon, (offset into llmp_clients) + /// Clients that should be removed soon, (offset into `llmp_clients`) clients_to_remove: Vec, - /// The ShMemProvider to use + /// The `ShMemProvider` to use shmem_provider: SP, #[cfg(feature = "std")] /// The timeout after which a client will be considered stale, and removed. diff --git a/libafl_bolts/src/os/unix_shmem_server.rs b/libafl_bolts/src/os/unix_shmem_server.rs index a402902a97..8c6291d259 100644 --- a/libafl_bolts/src/os/unix_shmem_server.rs +++ b/libafl_bolts/src/os/unix_shmem_server.rs @@ -282,7 +282,7 @@ pub enum ServedShMemRequest { PreFork(), /// The client's child re-registers with us after it forked. PostForkChildHello(i32), - /// The ShMem Service should exit. This is sually sent internally on `drop`, but feel free to do whatever with it? + /// The `ShMem` Service should exit. This is sually sent internally on `drop`, but feel free to do whatever with it? Exit, } diff --git a/libafl_cc/src/cfg.rs b/libafl_cc/src/cfg.rs index a7519d37fe..878c878409 100644 --- a/libafl_cc/src/cfg.rs +++ b/libafl_cc/src/cfg.rs @@ -396,6 +396,6 @@ mod tests { assert_eq!(*distances.get(&((41864 >> 1) ^ 26911)).unwrap(), 1); assert_eq!(*distances.get(&((26911 >> 1) ^ 52706)).unwrap(), 2); assert_eq!(*distances.get(&((26911 >> 1) ^ 41925)).unwrap(), 2); - assert!(distances.get(&((41864 >> 1) ^ 52706)).is_none()); + assert!(!distances.contains_key(&((41864 >> 1) ^ 52706))); } } diff --git a/libafl_cc/src/clang.rs b/libafl_cc/src/clang.rs index df742ccfc0..d8d4efba55 100644 --- a/libafl_cc/src/clang.rs +++ b/libafl_cc/src/clang.rs @@ -28,7 +28,7 @@ include!(concat!(env!("OUT_DIR"), "/clang_constants.rs")); #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LLVMPasses { //CmpLogIns, - /// The CmpLog pass + /// The `CmpLog` pass CmpLogRtn, /// The AFL coverage pass AFLCoverage, @@ -39,7 +39,7 @@ pub enum LLVMPasses { /// The dump cfg pass DumpCfg, #[cfg(unix)] - /// The CmpLog Instruction pass + /// The `CmpLog` Instruction pass CmpLogInstructions, } diff --git a/libafl_libfuzzer/src/lib.rs b/libafl_libfuzzer/src/lib.rs index b3ad2c48bd..b995ece126 100644 --- a/libafl_libfuzzer/src/lib.rs +++ b/libafl_libfuzzer/src/lib.rs @@ -97,14 +97,14 @@ extern "C" { target_family = "unix", // Disable when building with clippy, as it will complain about the missing environment // variable which is set by the build script, which is not run under clippy. - not(feature = "cargo-clippy") + not(clippy) ))] pub const LIBAFL_LIBFUZZER_RUNTIME_LIBRARY: &'static [u8] = include_bytes!(env!("LIBAFL_LIBFUZZER_RUNTIME_PATH")); #[cfg(test)] mod tests { - #[cfg(all(feature = "embed-runtime", not(feature = "cargo-clippy")))] + #[cfg(all(feature = "embed-runtime", not(clippy)))] #[test] fn test_embed_runtime_sized() { use crate::LIBAFL_LIBFUZZER_RUNTIME_LIBRARY; diff --git a/libafl_nyx/src/helper.rs b/libafl_nyx/src/helper.rs index 2184080997..03fea68516 100644 --- a/libafl_nyx/src/helper.rs +++ b/libafl_nyx/src/helper.rs @@ -11,7 +11,7 @@ use libnyx::{NyxProcess, NyxReturnValue}; const INIT_TIMEOUT: Duration = Duration::new(2, 0); pub struct NyxHelper { pub nyx_process: NyxProcess, - /// real size of trace_bits + /// real size of `trace_bits` pub real_map_size: usize, // real size of the trace_bits pub map_size: usize, diff --git a/libafl_sugar/src/inmemory.rs b/libafl_sugar/src/inmemory.rs index c799534e75..b70d241121 100644 --- a/libafl_sugar/src/inmemory.rs +++ b/libafl_sugar/src/inmemory.rs @@ -57,7 +57,7 @@ where /// Dictionary #[builder(default = None)] tokens_file: Option, - /// Flag if use CmpLog + /// Flag if use `CmpLog` #[builder(default = None)] use_cmplog: Option, /// The port used for communication between this fuzzer node and other fuzzer nodes diff --git a/libafl_sugar/src/qemu.rs b/libafl_sugar/src/qemu.rs index 092a88fb3a..be47e2d154 100644 --- a/libafl_sugar/src/qemu.rs +++ b/libafl_sugar/src/qemu.rs @@ -63,7 +63,7 @@ where /// Dictionary #[builder(default = None)] tokens_file: Option, - /// Flag if use CmpLog + /// Flag if use `CmpLog` #[builder(default = None)] use_cmplog: Option, /// The port the fuzzing nodes communicate over