Implement RetryProgress for limiting retry attempts in stages (#1890)

* do that again but smarter

* remember to register

* appease the clippy

* cleanup

* autofix clippy

* more clippy fixes

* more clippy...

* small clippy fix

* with_tries => with_retries

* most recent suggestions

* final clippy... hopefully
This commit is contained in:
Addison Crump 2024-02-28 14:12:28 +01:00 committed by GitHub
parent 7a4fb06d02
commit 8c773a6b85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 342 additions and 115 deletions

View File

@ -60,7 +60,7 @@ where
cached_len: Option<usize>,
/// 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<CorpusId>,
@ -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),
}

View File

@ -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<File>,
@ -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<File>,
@ -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<File>,
@ -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<File>,

View File

@ -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<Event<S::Input>>,
/// The custom buf handler
custom_buf_handlers: Vec<Box<CustomBufHandlerFn<S>>>,

View File

@ -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,
}

View File

@ -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,
}

View File

@ -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,

View File

@ -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<Vec<f64>>,
/// 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<f64>,
/// (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<Vec<u64>>,
/// (Pilot Mode) Finds by each operator till now.
pub pilot_operator_finds_v2: Vec<Vec<u64>>,
/// (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<Vec<u64>>,
/// (Pilot Mode) The number of mutation operator used till now
pub pilot_operator_cycles_v2: Vec<Vec<u64>>,
/// (Pilot Mode) The number of mutation operator used till last execution
pub pilot_operator_cycles_v3: Vec<Vec<u64>>,
/// Vector used in pso_update
/// Vector used in `pso_update`
pub operator_finds_puppet: Vec<u64>,
/// (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<u64>,
/// (Core Mode) Finds by each operator till now.
pub core_operator_finds_v2: Vec<u64>,
/// (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<u64>,
/// (Core Mode) The number of mutation operator used till now
pub core_operator_cycles_v2: Vec<u64>,

View File

@ -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<usize, Vec<CmpValues>>,
/// 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<usize, Vec<CmpValues>>,
/// The list of logged idx and headers retrieved by runnning the mutated input

View File

@ -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,

View File

@ -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<State = TE::State>,
EM: UsesState<State = TE::State>,
TE: Executor<EM, Z> + HasObservers,
TE::State: HasExecutions + HasCorpus,
TE::State: HasExecutions + HasCorpus + HasNamedMetadata,
Z: UsesState<State = TE::State>,
{
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<EM, TE, Z> RetryingStage for ConcolicTracingStage<EM, TE, Z> {
fn max_retries(&self) -> usize {
self.inner.max_retries()
}
}
impl<EM, TE, Z> ConcolicTracingStage<EM, TE, Z> {
/// 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<EM, TE, Z>, observer_name: String) -> Self {
Self {
inner,
@ -88,19 +110,6 @@ impl<EM, TE, Z> ConcolicTracingStage<EM, TE, Z> {
}
}
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<Item = (SymExprRef, SymExpr)>) -> Vec<Vec<(usize, u8)>> {

View File

@ -12,25 +12,25 @@ use crate::{
#[derive(Debug)]
pub struct NestedStageProgress;
impl<S> StageProgress<S> for NestedStageProgress
impl<S, ST> StageProgress<S, ST> 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")
}
}

View File

@ -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<Self::State>;
type Progress: StageProgress<Self::State, Self>;
/// 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<S> {
pub trait StageProgress<S, ST>
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<S> StageProgress<S> for () {
fn initialize_progress(_state: &mut S) -> Result<(), Error> {
impl<S, ST> StageProgress<S, ST> 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<usize>,
skipped: HashSet<CorpusId>,
}
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<S, ST> StageProgress<S, ST> 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::<Self>(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::<Self>(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::<Self>(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::<Self>(core::any::type_name_of_val(stage))
}
}
impl RetryProgress {
/// Whether we should skip the provided corpus entry.
pub fn should_skip<S, ST>(
state: &mut S,
stage: &ST,
corpus_idx: CorpusId,
) -> Result<bool, Error>
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<S> StageProgress<S> for TestProgress
impl<S, ST> StageProgress<S, ST> 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::<Self>() {
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::<Self>().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(())
}
}

View File

@ -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<EM, TE, Z> {
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<E, EM, TE, Z> Stage<E, EM, Z> for TracingStage<EM, TE, Z>
impl<EM, TE, Z> TracingStage<EM, TE, Z>
where
E: UsesState<State = TE::State>,
TE: Executor<EM, Z> + HasObservers,
TE::State: HasExecutions + HasCorpus,
TE::State: HasExecutions + HasCorpus + HasNamedMetadata,
EM: UsesState<State = TE::State>,
Z: UsesState<State = TE::State>,
{
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<E, EM, TE, Z> Stage<E, EM, Z> for TracingStage<EM, TE, Z>
where
E: UsesState<State = TE::State>,
TE: Executor<EM, Z> + HasObservers,
TE::State: HasExecutions + HasCorpus + HasNamedMetadata,
EM: UsesState<State = TE::State>,
Z: UsesState<State = TE::State>,
{
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<EM, TE, Z> RetryingStage for TracingStage<EM, TE, Z> {
fn max_retries(&self) -> usize {
self.max_retries
}
}
impl<EM, TE, Z> TracingStage<EM, TE, Z> {
/// 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<EM, TE, Z> TracingStage<EM, TE, Z> {
&mut self.tracer_executor
}
}
/// A stage that runs the shadow executor using also the shadow observers
#[derive(Clone, Debug)]
pub struct ShadowTracingStage<E, EM, SOT, Z> {
max_retries: usize,
#[allow(clippy::type_complexity)]
phantom: PhantomData<(E, EM, SOT, Z)>,
}
@ -122,9 +166,9 @@ where
EM: UsesState<State = E::State>,
SOT: ObserversTuple<E::State>,
Z: UsesState<State = E::State>,
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<E, EM, SOT, Z> RetryingStage for ShadowTracingStage<E, EM, SOT, Z> {
fn max_retries(&self) -> usize {
self.max_retries
}
}
impl<E, EM, SOT, Z> ShadowTracingStage<E, EM, SOT, Z>
where
E: Executor<EM, Z> + HasObservers,
@ -182,7 +235,16 @@ where
/// Creates a new default stage
pub fn new(_executor: &mut ShadowExecutor<E, SOT>) -> 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
}
}

View File

@ -312,7 +312,7 @@ pub struct StdState<I, C, R, SC> {
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")]

View File

@ -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<NonZeroUsize>,
/// Clients that should be removed soon, (offset into llmp_clients)
/// Clients that should be removed soon, (offset into `llmp_clients`)
clients_to_remove: Vec<usize>,
/// 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.

View File

@ -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,
}

View File

@ -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)));
}
}

View File

@ -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,
}

View File

@ -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;

View File

@ -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,

View File

@ -57,7 +57,7 @@ where
/// Dictionary
#[builder(default = None)]
tokens_file: Option<PathBuf>,
/// Flag if use CmpLog
/// Flag if use `CmpLog`
#[builder(default = None)]
use_cmplog: Option<bool>,
/// The port used for communication between this fuzzer node and other fuzzer nodes

View File

@ -63,7 +63,7 @@ where
/// Dictionary
#[builder(default = None)]
tokens_file: Option<PathBuf>,
/// Flag if use CmpLog
/// Flag if use `CmpLog`
#[builder(default = None)]
use_cmplog: Option<bool>,
/// The port the fuzzing nodes communicate over