Resumable stages redux (#1780)

* initial stage refactor

* repair test

* redqueen

* fixup python bindings

* docs and clippy fixes

* misc hidden changes

* Status => Progress

* fix remaining fuzzers

* fix libafl_libfuzzer

* implement resume for nested stages

* deep testing for stage resumeability

* refactor: use let-else, docfix

* refactor in fuzzbench_forkserver_cmplog

* fmt

* actually use progress

* HasStageStatus => HasCurrentStage
This commit is contained in:
Addison Crump 2024-01-09 15:18:14 +01:00 committed by GitHub
parent 3d126f21cc
commit ba8ca6723b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1287 additions and 527 deletions

View File

@ -137,13 +137,9 @@ pub fn main() -> Result<(), Error> {
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,
CorpusId::from(0_usize),
)?;
state.set_corpus_idx(CorpusId::from(0usize))?;
stages.perform_all(&mut fuzzer, &mut executor, &mut state, &mut mgr)?;
Ok(())
}

View File

@ -9,7 +9,7 @@ use std::{
use clap::{Arg, ArgAction, Command};
use libafl::{
corpus::{Corpus, CorpusId, InMemoryOnDiskCorpus, OnDiskCorpus},
corpus::{Corpus, HasCurrentCorpusIdx, InMemoryOnDiskCorpus, OnDiskCorpus},
events::SimpleEventManager,
executors::forkserver::{ForkserverExecutor, TimeoutForkserverExecutor},
feedback_or,
@ -374,9 +374,14 @@ fn fuzz(
let cb = |_fuzzer: &mut _,
_executor: &mut _,
state: &mut StdState<_, InMemoryOnDiskCorpus<_>, _, _>,
_event_manager: &mut _,
corpus_id: CorpusId|
-> Result<bool, libafl::Error> {
_event_manager: &mut _|
-> Result<bool, Error> {
let Some(corpus_id) = state.current_corpus_idx()? else {
return Err(Error::illegal_state(
"state is not currently processing a corpus index",
));
};
let corpus = state.corpus().get(corpus_id)?.borrow();
let res = corpus.scheduled_count() == 1; // let's try on the 2nd trial

View File

@ -148,6 +148,18 @@ pub trait Corpus: UsesInput + Serialize + for<'de> Deserialize<'de> {
}
}
/// Trait for types which track the current corpus index
pub trait HasCurrentCorpusIdx {
/// Set the current corpus index; we have started processing this corpus entry
fn set_corpus_idx(&mut self, idx: CorpusId) -> Result<(), Error>;
/// Clear the current corpus index; we are done with this entry
fn clear_corpus_idx(&mut self) -> Result<(), Error>;
/// Fetch the current corpus index -- typically used after a state recovery or transfer
fn current_corpus_idx(&self) -> Result<Option<CorpusId>, Error>;
}
/// [`Iterator`] over the ids of a [`Corpus`]
#[derive(Debug)]
pub struct CorpusIdIterator<'a, C>

View File

@ -962,6 +962,8 @@ where
/// Reset the single page (we reuse it over and over from pos 0), then send the current state to the next runner.
fn on_restart(&mut self, state: &mut S) -> Result<(), Error> {
state.on_restart()?;
// First, reset the page to 0 so the next iteration can read read from the beginning of this page
self.staterestorer.reset();
self.staterestorer.save(&(

View File

@ -128,7 +128,7 @@ pub struct EventManagerId(
#[cfg(feature = "introspection")]
use crate::monitors::ClientPerfMonitor;
use crate::{inputs::UsesInput, state::UsesState};
use crate::{inputs::UsesInput, stages::HasCurrentStage, state::UsesState};
/// The log event severity
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
@ -548,8 +548,11 @@ where
/// Restartable trait
pub trait EventRestarter: UsesState {
/// For restarting event managers, implement a way to forward state to their next peers.
/// You *must* ensure that [`HasCurrentStage::on_restart`] will be invoked in this method, by you
/// or an internal [`EventRestarter`], before the state is saved for recovery.
#[inline]
fn on_restart(&mut self, _state: &mut Self::State) -> Result<(), Error> {
fn on_restart(&mut self, state: &mut Self::State) -> Result<(), Error> {
state.on_restart()?;
self.await_restart_safe();
Ok(())
}
@ -605,7 +608,7 @@ pub trait HasCustomBufHandlers: UsesState {
}
/// An eventmgr for tests, and as placeholder if you really don't need an event manager.
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug)]
pub struct NopEventManager<S> {
phantom: PhantomData<S>,
}
@ -620,6 +623,12 @@ impl<S> NopEventManager<S> {
}
}
impl<S> Default for NopEventManager<S> {
fn default() -> Self {
Self::new()
}
}
impl<S> UsesState for NopEventManager<S>
where
S: State,

View File

@ -353,6 +353,8 @@ where
{
/// Reset the single page (we reuse it over and over from pos 0), then send the current state to the next runner.
fn on_restart(&mut self, state: &mut S) -> Result<(), Error> {
state.on_restart()?;
// First, reset the page to 0 so the next iteration can read read from the beginning of this page
self.staterestorer.reset();
self.staterestorer.save(&(

View File

@ -849,6 +849,8 @@ where
/// Reset the single page (we reuse it over and over from pos 0), then send the current state to the next runner.
fn on_restart(&mut self, state: &mut S) -> Result<(), Error> {
state.on_restart()?;
// First, reset the page to 0 so the next iteration can read read from the beginning of this page
self.staterestorer.reset();
self.staterestorer.save(&if self.save_state {
@ -856,6 +858,7 @@ where
} else {
None
})?;
self.await_restart_safe();
Ok(())
}

View File

@ -690,10 +690,10 @@ mod tests {
command::{CommandExecutor, InputLocation},
Executor,
},
fuzzer::test::NopFuzzer,
inputs::BytesInput,
monitors::SimpleMonitor,
state::NopState,
NopFuzzer,
state::test::NopState,
};
#[test]

View File

@ -2186,9 +2186,9 @@ mod tests {
use crate::{
events::NopEventManager,
executors::{inprocess::InProcessHandlers, Executor, ExitKind, InProcessExecutor},
fuzzer::test::NopFuzzer,
inputs::{NopInput, UsesInput},
state::NopState,
NopFuzzer,
state::test::NopState,
};
impl UsesInput for () {
@ -2226,8 +2226,8 @@ mod tests {
use crate::{
events::SimpleEventManager,
executors::{inprocess::InChildProcessHandlers, InProcessForkExecutor},
state::NopState,
NopFuzzer,
fuzzer::test::NopFuzzer,
state::test::NopState,
};
let provider = StdShMemProvider::new().unwrap();

View File

@ -1,49 +1,41 @@
//! Executors take input, and run it in the target.
pub mod inprocess;
use core::fmt::Debug;
pub use combined::CombinedExecutor;
#[cfg(all(feature = "std", any(unix, doc)))]
pub use command::CommandExecutor;
pub use differential::DiffExecutor;
#[cfg(all(feature = "std", feature = "fork", unix))]
pub use forkserver::{Forkserver, ForkserverExecutor, TimeoutForkserverExecutor};
pub use inprocess::InProcessExecutor;
#[cfg(all(feature = "std", feature = "fork", unix))]
pub use inprocess::InProcessForkExecutor;
use serde::{Deserialize, Serialize};
pub use shadow::ShadowExecutor;
#[cfg(any(unix, feature = "std"))]
pub use timeout::TimeoutExecutor;
pub use with_observers::WithObservers;
use crate::{
observers::{ObserversTuple, UsesObservers},
state::UsesState,
Error,
};
pub mod combined;
#[cfg(all(feature = "std", any(unix, doc)))]
pub mod command;
pub mod differential;
pub use differential::DiffExecutor;
#[cfg(all(feature = "std", feature = "fork", unix))]
pub mod forkserver;
pub mod inprocess;
pub mod shadow;
/// Timeout executor.
/// Not possible on `no-std` Windows or `no-std`, but works for unix
#[cfg(any(unix, feature = "std"))]
pub mod timeout;
#[cfg(any(unix, feature = "std"))]
pub use timeout::TimeoutExecutor;
#[cfg(all(feature = "std", feature = "fork", unix))]
pub mod forkserver;
#[cfg(all(feature = "std", feature = "fork", unix))]
pub use forkserver::{Forkserver, ForkserverExecutor, TimeoutForkserverExecutor};
pub mod combined;
pub use combined::CombinedExecutor;
pub mod shadow;
pub use shadow::ShadowExecutor;
pub mod with_observers;
pub use with_observers::WithObservers;
#[cfg(all(feature = "std", any(unix, doc)))]
pub mod command;
use core::{fmt::Debug, marker::PhantomData};
#[cfg(all(feature = "std", any(unix, doc)))]
pub use command::CommandExecutor;
use libafl_bolts::AsSlice;
use serde::{Deserialize, Serialize};
use crate::{
inputs::HasTargetBytes,
observers::{ObserversTuple, UsesObservers},
state::{HasExecutions, State, UsesState},
Error,
};
/// How an execution finished.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
@ -149,13 +141,42 @@ where
fn post_run_reset(&mut self) {}
}
#[cfg(test)]
pub mod test {
use core::marker::PhantomData;
use libafl_bolts::{AsSlice, Error};
use crate::{
events::NopEventManager,
executors::{Executor, ExitKind},
fuzzer::test::NopFuzzer,
inputs::{BytesInput, HasTargetBytes},
state::{test::NopState, HasExecutions, State, UsesState},
};
/// A simple executor that does nothing.
/// If intput len is 0, `run_target` will return Err
#[derive(Debug)]
struct NopExecutor<S> {
pub struct NopExecutor<S> {
phantom: PhantomData<S>,
}
impl<S> NopExecutor<S> {
#[must_use]
pub fn new() -> Self {
Self {
phantom: PhantomData,
}
}
}
impl<S> Default for NopExecutor<S> {
fn default() -> Self {
Self::new()
}
}
impl<S> UsesState for NopExecutor<S>
where
S: State,
@ -187,20 +208,11 @@ where
}
}
#[cfg(test)]
mod test {
use core::marker::PhantomData;
use super::{Executor, NopExecutor};
use crate::{events::NopEventManager, inputs::BytesInput, state::NopState, NopFuzzer};
#[test]
fn nop_executor() {
let empty_input = BytesInput::new(vec![]);
let nonempty_input = BytesInput::new(vec![1u8]);
let mut executor = NopExecutor {
phantom: PhantomData,
};
let mut executor = NopExecutor::new();
let mut fuzzer = NopFuzzer::new();
let mut state = NopState::new();

View File

@ -163,7 +163,7 @@ mod tests {
feedbacks::{differential::DiffResult, DiffFeedback, Feedback},
inputs::{BytesInput, UsesInput},
observers::Observer,
state::{NopState, State, UsesState},
state::{test::NopState, State, UsesState},
};
#[derive(Debug)]

View File

@ -6,12 +6,8 @@ use core::{fmt::Debug, marker::PhantomData, time::Duration};
use libafl_bolts::current_time;
use serde::{de::DeserializeOwned, Serialize};
#[cfg(test)]
use crate::inputs::Input;
#[cfg(test)]
use crate::state::NopState;
use crate::{
corpus::{Corpus, CorpusId, HasTestcase, Testcase},
corpus::{Corpus, CorpusId, HasCurrentCorpusIdx, HasTestcase, Testcase},
events::{Event, EventConfig, EventFirer, EventProcessor, ProgressReporter},
executors::{Executor, ExitKind, HasObservers},
feedbacks::Feedback,
@ -19,7 +15,7 @@ use crate::{
mark_feature_time,
observers::ObserversTuple,
schedulers::Scheduler,
stages::StagesTuple,
stages::{HasCurrentStage, StagesTuple},
start_timer,
state::{
HasCorpus, HasExecutions, HasImported, HasLastReportTime, HasMetadata, HasSolutions,
@ -583,8 +579,14 @@ where
EM: ProgressReporter + EventProcessor<E, Self, State = CS::State>,
F: Feedback<CS::State>,
OF: Feedback<CS::State>,
CS::State:
HasExecutions + HasMetadata + HasCorpus + HasTestcase + HasImported + HasLastReportTime,
CS::State: HasExecutions
+ HasMetadata
+ HasCorpus
+ HasTestcase
+ HasImported
+ HasLastReportTime
+ HasCurrentCorpusIdx
+ HasCurrentStage,
ST: StagesTuple<E, EM, CS::State, Self>,
{
fn fuzz_one(
@ -599,7 +601,13 @@ where
state.introspection_monitor_mut().start_timer();
// Get the next index from the scheduler
let idx = if let Some(idx) = state.current_corpus_idx()? {
idx // we are resuming
} else {
let idx = self.scheduler.next(state)?;
state.set_corpus_idx(idx)?; // set up for resume
idx
};
// Mark the elapsed time for the scheduler
#[cfg(feature = "introspection")]
@ -610,7 +618,7 @@ where
state.introspection_monitor_mut().reset_stage_index();
// Execute all stages
stages.perform_all(self, executor, state, manager, idx)?;
stages.perform_all(self, executor, state, manager)?;
// Init timer for manager
#[cfg(feature = "introspection")]
@ -629,6 +637,9 @@ where
// increase scheduled count, this was fuzz_level in afl
testcase.set_scheduled_count(scheduled_count + 1);
}
state.clear_corpus_idx()?;
Ok(idx)
}
}
@ -733,13 +744,26 @@ where
}
#[cfg(test)]
#[derive(Clone, Debug, Default)]
pub(crate) struct NopFuzzer<I> {
phantom: PhantomData<I>,
pub mod test {
use core::marker::PhantomData;
use libafl_bolts::Error;
use crate::{
corpus::CorpusId,
events::ProgressReporter,
stages::{HasCurrentStage, StagesTuple},
state::{HasExecutions, HasLastReportTime, HasMetadata, State, UsesState},
Fuzzer,
};
#[derive(Clone, Debug)]
pub struct NopFuzzer<S> {
phantom: PhantomData<S>,
}
#[cfg(test)]
impl<I> NopFuzzer<I> {
impl<S> NopFuzzer<S> {
#[must_use]
pub fn new() -> Self {
Self {
phantom: PhantomData,
@ -747,21 +771,25 @@ impl<I> NopFuzzer<I> {
}
}
#[cfg(test)]
impl<I> UsesState for NopFuzzer<I>
where
I: Input,
{
type State = NopState<I>;
impl<S> Default for NopFuzzer<S> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
impl<ST, E, I, EM> Fuzzer<E, EM, ST> for NopFuzzer<I>
impl<S> UsesState for NopFuzzer<S>
where
E: UsesState<State = NopState<I>>,
EM: ProgressReporter<State = NopState<I>>,
I: Input,
ST: StagesTuple<E, EM, NopState<I>, Self>,
S: State,
{
type State = S;
}
impl<ST, E, EM> Fuzzer<E, EM, ST> for NopFuzzer<E::State>
where
E: UsesState,
EM: ProgressReporter<State = E::State>,
ST: StagesTuple<E, EM, E::State, Self>,
E::State: HasMetadata + HasExecutions + HasLastReportTime + HasCurrentStage,
{
fn fuzz_one(
&mut self,
@ -773,6 +801,7 @@ where
unimplemented!()
}
}
}
#[cfg(feature = "python")]
#[allow(missing_docs)]

View File

@ -392,7 +392,7 @@ where
write!(
f,
"StdMOptMutator with {} mutations for Input type {}",
self.mutations.len(),
MT::LEN,
core::any::type_name::<I>()
)
}
@ -547,7 +547,7 @@ where
) -> Result<Self, Error> {
if !state.has_metadata::<MOpt>() {
let rand_seed = state.rand_mut().next();
state.add_metadata::<MOpt>(MOpt::new(mutations.len(), swarm_num, rand_seed)?);
state.add_metadata::<MOpt>(MOpt::new(MT::LEN, swarm_num, rand_seed)?);
}
Ok(Self {
name: format!("StdMOptMutator[{}]", mutations.names().join(",")),

View File

@ -1396,7 +1396,7 @@ pub fn str_decode(item: &str) -> Result<Vec<u8>, Error> {
mod tests {
use libafl_bolts::{
rands::StdRand,
tuples::{tuple_list, HasConstLen},
tuples::{tuple_list, tuple_list_type, HasConstLen},
};
use super::*;
@ -1408,11 +1408,34 @@ mod tests {
state::{HasMetadata, StdState},
};
fn test_mutations<I, S>() -> impl MutatorsTuple<I, S>
where
S: HasRand + HasMetadata + HasMaxSize,
I: HasBytesVec,
{
type TestMutatorsTupleType = tuple_list_type!(
BitFlipMutator,
ByteFlipMutator,
ByteIncMutator,
ByteDecMutator,
ByteNegMutator,
ByteRandMutator,
ByteAddMutator,
WordAddMutator,
DwordAddMutator,
QwordAddMutator,
ByteInterestingMutator,
WordInterestingMutator,
DwordInterestingMutator,
BytesDeleteMutator,
BytesDeleteMutator,
BytesDeleteMutator,
BytesDeleteMutator,
BytesExpandMutator,
BytesInsertMutator,
BytesRandInsertMutator,
BytesSetMutator,
BytesRandSetMutator,
BytesCopyMutator,
BytesSwapMutator,
);
fn test_mutations() -> TestMutatorsTupleType {
tuple_list!(
BitFlipMutator::new(),
ByteFlipMutator::new(),
@ -1481,7 +1504,7 @@ mod tests {
for _ in 0..2 {
let mut new_testcases = vec![];
for idx in 0..(mutations.len()) {
for idx in 0..TestMutatorsTupleType::LEN {
for input in &inputs {
let mut mutant = input.clone();
match mutations

View File

@ -135,7 +135,7 @@ where
write!(
f,
"StdScheduledMutator with {} mutations for Input type {}",
self.mutations.len(),
MT::LEN,
core::any::type_name::<I>()
)
}
@ -197,8 +197,8 @@ where
/// Get the next mutation to apply
fn schedule(&self, state: &mut S, _: &I) -> MutationId {
debug_assert!(!self.mutations().is_empty());
state.rand_mut().below(self.mutations().len() as u64).into()
debug_assert!(MT::LEN != 0);
state.rand_mut().below(MT::LEN as u64).into()
}
}
@ -368,7 +368,7 @@ where
write!(
f,
"LoggerScheduledMutator with {} mutations for Input type {}",
self.scheduled.mutations().len(),
MT::LEN,
core::any::type_name::<I>()
)
}
@ -452,11 +452,8 @@ where
/// Get the next mutation to apply
fn schedule(&self, state: &mut S, _: &I) -> MutationId {
debug_assert!(!self.scheduled.mutations().is_empty());
state
.rand_mut()
.below(self.scheduled.mutations().len() as u64)
.into()
debug_assert!(MT::LEN != 0);
state.rand_mut().below(MT::LEN as u64).into()
}
fn scheduled_mutate(

View File

@ -100,7 +100,7 @@ where
write!(
f,
"TuneableScheduledMutator with {} mutations for Input type {}",
self.mutations.len(),
MT::LEN,
core::any::type_name::<I>()
)
}
@ -186,7 +186,7 @@ where
/// Get the next mutation to apply
fn schedule(&self, state: &mut S, _: &I) -> MutationId {
debug_assert!(!self.mutations().is_empty());
debug_assert!(MT::LEN != 0);
// Assumption: we can not reach this code path without previously adding this metadatum.
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
@ -199,7 +199,7 @@ where
metadata.next_id = 0.into();
}
debug_assert!(
self.mutations().len() > ret.0,
MT::LEN > ret.0,
"TuneableScheduler: next vec may not contain id larger than number of mutations!"
);
return ret;
@ -214,7 +214,7 @@ where
let metadata = TuneableScheduledMutatorMetadata::get_mut(state).unwrap();
debug_assert_eq!(
self.mutations.len(),
MT::LEN,
metadata.mutation_probabilities_cumulative.len(),
"TuneableScheduler: mutation probabilities do not match with number of mutations"
);
@ -230,7 +230,7 @@ where
}
// fall back to random if no entries in either vec, the scheduling is not tuned.
state.rand_mut().below(self.mutations().len() as u64).into()
state.rand_mut().below(MT::LEN as u64).into()
}
}
@ -376,7 +376,7 @@ mod test {
use crate::{
inputs::BytesInput,
mutators::{ByteRandMutator, ScheduledMutator},
state::NopState,
state::test::NopState,
};
#[test]

View File

@ -12,7 +12,7 @@ use num_traits::Bounded;
use serde::{Deserialize, Serialize};
use crate::{
corpus::{Corpus, CorpusId, SchedulerTestcaseMetadata},
corpus::{Corpus, HasCurrentCorpusIdx, SchedulerTestcaseMetadata},
events::{Event, EventFirer, LogSeverity},
executors::{Executor, ExitKind, HasObservers},
feedbacks::{map::MapFeedbackMetadata, HasObserverName},
@ -92,6 +92,8 @@ where
E::State: HasCorpus + HasMetadata + HasNamedMetadata + HasExecutions,
Z: Evaluator<E, EM, State = E::State>,
{
type Progress = (); // TODO stage may be resumed, but how?
#[inline]
#[allow(
clippy::let_and_return,
@ -104,8 +106,13 @@ where
executor: &mut E,
state: &mut E::State,
mgr: &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",
));
};
// Run this stage only once for each corpus entry and only if we haven't already inspected it
{
let corpus = state.corpus().get(corpus_idx)?.borrow();

View File

@ -10,7 +10,7 @@ use libafl_bolts::{rands::Rand, tuples::MatchName};
use serde::{Deserialize, Serialize};
use crate::{
corpus::{Corpus, CorpusId},
corpus::{Corpus, HasCurrentCorpusIdx},
events::EventFirer,
executors::{Executor, HasObservers},
inputs::HasBytesVec,
@ -77,6 +77,8 @@ where
O: MapObserver,
Z: UsesState<State = E::State>,
{
type Progress = (); // TODO this stage needs resume
#[inline]
#[allow(clippy::let_and_return)]
fn perform(
@ -85,17 +87,9 @@ where
executor: &mut E, // don't need the *main* executor for tracing
state: &mut E::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
// Run with the mutated input
Self::colorize(
fuzzer,
executor,
state,
manager,
corpus_idx,
&self.map_observer_name,
)?;
Self::colorize(fuzzer, executor, state, manager, &self.map_observer_name)?;
Ok(())
}
@ -156,9 +150,14 @@ where
executor: &mut E,
state: &mut E::State,
manager: &mut EM,
corpus_idx: CorpusId,
name: &str,
) -> Result<E::Input, Error> {
let Some(corpus_idx) = state.current_corpus_idx()? else {
return Err(Error::illegal_state(
"state is not currently processing a corpus index",
));
};
let mut input = state.corpus().cloned_input_for_id(corpus_idx)?;
// The backup of the input
let backup = input.clone();

View File

@ -13,7 +13,7 @@ use crate::state::HasClientPerfMonitor;
#[cfg(feature = "concolic_mutation")]
use crate::state::State;
use crate::{
corpus::{Corpus, CorpusId},
corpus::Corpus,
executors::{Executor, HasObservers},
observers::concolic::ConcolicObserver,
state::{HasCorpus, HasExecutions, HasMetadata},
@ -42,6 +42,8 @@ where
TE::State: HasExecutions + HasCorpus,
Z: UsesState<State = TE::State>,
{
type Progress = (); // stage cannot be resumed
#[inline]
fn perform(
&mut self,
@ -49,10 +51,14 @@ where
executor: &mut E,
state: &mut TE::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
self.inner
.perform(fuzzer, executor, state, manager, corpus_idx)?;
let Some(corpus_idx) = state.current_corpus_idx()? else {
return Err(Error::illegal_state(
"state is not currently processing a corpus index",
));
};
self.inner.perform(fuzzer, executor, state, manager)?;
if let Some(observer) = self
.inner
.executor()
@ -86,7 +92,7 @@ use libafl_bolts::tuples::MatchName;
#[cfg(all(feature = "concolic_mutation", feature = "introspection"))]
use crate::monitors::PerfFeature;
use crate::state::UsesState;
use crate::{corpus::HasCurrentCorpusIdx, state::UsesState};
#[cfg(feature = "concolic_mutation")]
use crate::{
inputs::HasBytesVec,
@ -360,6 +366,8 @@ where
Z::Input: HasBytesVec,
Z::State: State + HasExecutions + HasCorpus,
{
type Progress = (); // TODO we need a resume for this type
#[inline]
fn perform(
&mut self,
@ -367,8 +375,13 @@ where
executor: &mut E,
state: &mut Z::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 testcase = state.corpus().get(corpus_idx)?.clone();
mark_feature_time!(state, PerfFeature::GetInputFromCorpus);
@ -391,7 +404,6 @@ where
input_copy.bytes_mut()[index] = new_byte;
}
// Time is measured directly the `evaluate_input` function
let _: (crate::ExecuteInputResult, Option<CorpusId>) =
fuzzer.evaluate_input(state, executor, manager, input_copy)?;
}
}

View File

@ -52,6 +52,8 @@ where
Z: UsesState,
Z::State: HasCorpus + HasSolutions + HasRand + HasMetadata,
{
type Progress = (); // if this fails, we have bigger problems
#[inline]
fn perform(
&mut self,
@ -59,7 +61,6 @@ where
_executor: &mut E,
state: &mut Z::State,
_manager: &mut EM,
_corpus_idx: CorpusId,
) -> Result<(), Error> {
let (mut corpus_idx, mut solutions_idx) =
if let Some(meta) = state.metadata_map().get::<DumpToDiskMetadata>() {

View File

@ -9,7 +9,7 @@ use core::{fmt::Debug, marker::PhantomData};
use libafl_bolts::AsSlice;
use crate::{
corpus::{Corpus, CorpusId},
corpus::{Corpus, HasCurrentCorpusIdx},
executors::{Executor, HasObservers},
feedbacks::map::MapNoveltiesMetadata,
inputs::{BytesInput, GeneralizedInputMetadata, GeneralizedItem, HasBytesVec, UsesInput},
@ -64,6 +64,8 @@ where
EM: UsesState<State = E::State>,
Z: UsesState<State = E::State>,
{
type Progress = (); // TODO this stage needs a resume
#[inline]
#[allow(clippy::too_many_lines)]
fn perform(
@ -72,8 +74,13 @@ where
executor: &mut E,
state: &mut E::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",
));
};
let (mut payload, original, novelties) = {
start_timer!(state);
{

View File

@ -3,17 +3,43 @@
use core::marker::PhantomData;
use crate::{
corpus::CorpusId,
stages::{Stage, StagesTuple},
stages::{HasCurrentStage, HasNestedStageStatus, Stage, StageProgress, StagesTuple},
state::UsesState,
Error,
};
/// Progress for nested stages. This merely enters/exits the inner stage's scope.
#[derive(Debug)]
pub struct NestedStageProgress;
impl<S> StageProgress<S> for NestedStageProgress
where
S: HasNestedStageStatus,
{
fn initialize_progress(state: &mut S) -> Result<(), Error> {
state.enter_inner_stage()?;
Ok(())
}
fn clear_progress(state: &mut S) -> Result<(), Error> {
state.exit_inner_stage()?;
Ok(())
}
fn progress(_state: &S) -> Result<&Self, Error> {
unimplemented!("NestedStageProgress should not be queried")
}
fn progress_mut(_state: &mut S) -> Result<&mut Self, Error> {
unimplemented!("NestedStageProgress should not be queried")
}
}
#[derive(Debug)]
/// Perform the stage while the closure evaluates to true
pub struct WhileStage<CB, E, EM, ST, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<bool, Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<bool, Error>,
E: UsesState,
EM: UsesState<State = E::State>,
ST: StagesTuple<E, EM, E::State, Z>,
@ -26,7 +52,7 @@ where
impl<CB, E, EM, ST, Z> UsesState for WhileStage<CB, E, EM, ST, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<bool, Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<bool, Error>,
E: UsesState,
EM: UsesState<State = E::State>,
ST: StagesTuple<E, EM, E::State, Z>,
@ -37,31 +63,34 @@ where
impl<CB, E, EM, ST, Z> Stage<E, EM, Z> for WhileStage<CB, E, EM, ST, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<bool, Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<bool, Error>,
E: UsesState,
EM: UsesState<State = E::State>,
ST: StagesTuple<E, EM, E::State, Z>,
Z: UsesState<State = E::State>,
E::State: HasNestedStageStatus,
{
type Progress = NestedStageProgress;
fn perform(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut E::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
while (self.closure)(fuzzer, executor, state, manager, corpus_idx)? {
self.stages
.perform_all(fuzzer, executor, state, manager, corpus_idx)?;
while state.current_stage()?.is_some() || (self.closure)(fuzzer, executor, state, manager)?
{
self.stages.perform_all(fuzzer, executor, state, manager)?;
}
Ok(())
}
}
impl<CB, E, EM, ST, Z> WhileStage<CB, E, EM, ST, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<bool, Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<bool, Error>,
E: UsesState,
EM: UsesState<State = E::State>,
ST: StagesTuple<E, EM, E::State, Z>,
@ -82,7 +111,7 @@ where
#[derive(Debug)]
pub struct IfStage<CB, E, EM, ST, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<bool, Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<bool, Error>,
E: UsesState,
EM: UsesState<State = E::State>,
ST: StagesTuple<E, EM, E::State, Z>,
@ -95,7 +124,7 @@ where
impl<CB, E, EM, ST, Z> UsesState for IfStage<CB, E, EM, ST, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<bool, Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<bool, Error>,
E: UsesState,
EM: UsesState<State = E::State>,
ST: StagesTuple<E, EM, E::State, Z>,
@ -106,23 +135,25 @@ where
impl<CB, E, EM, ST, Z> Stage<E, EM, Z> for IfStage<CB, E, EM, ST, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<bool, Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<bool, Error>,
E: UsesState,
EM: UsesState<State = E::State>,
ST: StagesTuple<E, EM, E::State, Z>,
Z: UsesState<State = E::State>,
E::State: HasNestedStageStatus,
{
type Progress = NestedStageProgress;
fn perform(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut E::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
if (self.closure)(fuzzer, executor, state, manager, corpus_idx)? {
if state.current_stage()?.is_some() || (self.closure)(fuzzer, executor, state, manager)? {
self.if_stages
.perform_all(fuzzer, executor, state, manager, corpus_idx)?;
.perform_all(fuzzer, executor, state, manager)?;
}
Ok(())
}
@ -130,7 +161,7 @@ where
impl<CB, E, EM, ST, Z> IfStage<CB, E, EM, ST, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<bool, Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<bool, Error>,
E: UsesState,
EM: UsesState<State = E::State>,
ST: StagesTuple<E, EM, E::State, Z>,
@ -151,7 +182,7 @@ where
#[derive(Debug)]
pub struct IfElseStage<CB, E, EM, ST1, ST2, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<bool, Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<bool, Error>,
E: UsesState,
EM: UsesState<State = E::State>,
ST1: StagesTuple<E, EM, E::State, Z>,
@ -166,7 +197,7 @@ where
impl<CB, E, EM, ST1, ST2, Z> UsesState for IfElseStage<CB, E, EM, ST1, ST2, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<bool, Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<bool, Error>,
E: UsesState,
EM: UsesState<State = E::State>,
ST1: StagesTuple<E, EM, E::State, Z>,
@ -178,35 +209,54 @@ where
impl<CB, E, EM, ST1, ST2, Z> Stage<E, EM, Z> for IfElseStage<CB, E, EM, ST1, ST2, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<bool, Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<bool, Error>,
E: UsesState,
EM: UsesState<State = E::State>,
ST1: StagesTuple<E, EM, E::State, Z>,
ST2: StagesTuple<E, EM, E::State, Z>,
Z: UsesState<State = E::State>,
E::State: HasNestedStageStatus,
{
type Progress = NestedStageProgress;
fn perform(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut E::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
if (self.closure)(fuzzer, executor, state, manager, corpus_idx)? {
self.if_stages
.perform_all(fuzzer, executor, state, manager, corpus_idx)?;
} else {
self.else_stages
.perform_all(fuzzer, executor, state, manager, corpus_idx)?;
let current = state.current_stage()?;
let fresh = current.is_none();
let closure_return = fresh && (self.closure)(fuzzer, executor, state, manager)?;
if current == Some(0) || closure_return {
if fresh {
state.set_stage(0)?;
}
state.enter_inner_stage()?;
self.if_stages
.perform_all(fuzzer, executor, state, manager)?;
} else {
if fresh {
state.set_stage(1)?;
}
state.enter_inner_stage()?;
self.else_stages
.perform_all(fuzzer, executor, state, manager)?;
}
state.exit_inner_stage()?;
state.clear_stage()?;
Ok(())
}
}
impl<CB, E, EM, ST1, ST2, Z> IfElseStage<CB, E, EM, ST1, ST2, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<bool, Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<bool, Error>,
E: UsesState,
EM: UsesState<State = E::State>,
ST1: StagesTuple<E, EM, E::State, Z>,
@ -253,17 +303,19 @@ where
EM: UsesState<State = E::State>,
ST: StagesTuple<E, EM, E::State, Z>,
Z: UsesState<State = E::State>,
E::State: HasNestedStageStatus,
{
type Progress = ();
fn perform(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut E::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
if let Some(stages) = &mut self.stages {
stages.perform_all(fuzzer, executor, state, manager, corpus_idx)
stages.perform_all(fuzzer, executor, state, manager)
} else {
Ok(())
}
@ -304,3 +356,178 @@ where
}
}
}
#[cfg(test)]
mod test {
use core::{cell::RefCell, marker::PhantomData};
use libafl_bolts::{tuples::tuple_list, Error};
use crate::{
inputs::NopInput,
stages::{
test::{test_resume, test_resume_stages},
ClosureStage, IfElseStage, IfStage, Stage, WhileStage,
},
state::{test::test_std_state, State, UsesState},
};
#[test]
fn check_resumability_while() {
let once = RefCell::new(true);
let (completed, stages) = test_resume_stages();
let whilestage = WhileStage::new(|_, _, _, _| Ok(once.replace(false)), stages);
let resetstage = ClosureStage::new(|_, _, _, _| {
once.replace(true);
Ok(())
});
let mut state = test_std_state::<NopInput>();
test_resume(&completed, &mut state, tuple_list!(whilestage, resetstage));
}
#[test]
fn check_resumability_if() {
let once = RefCell::new(true);
let (completed, stages) = test_resume_stages();
let ifstage = IfStage::new(|_, _, _, _| Ok(once.replace(false)), stages);
let resetstage = ClosureStage::new(|_, _, _, _| {
once.replace(true);
Ok(())
});
let mut state = test_std_state::<NopInput>();
test_resume(&completed, &mut state, tuple_list!(ifstage, resetstage));
}
#[test]
fn check_resumability_if_deep() {
let (completed, stages) = test_resume_stages();
let ifstage = IfStage::new(
|_, _, _, _| Ok(true),
tuple_list!(IfStage::new(
|_, _, _, _| Ok(true),
tuple_list!(IfStage::new(
|_, _, _, _| Ok(true),
tuple_list!(IfStage::new(
|_, _, _, _| Ok(true),
tuple_list!(IfStage::new(|_, _, _, _| Ok(true), stages),),
),),
))
)),
);
let mut state = test_std_state::<NopInput>();
test_resume(&completed, &mut state, tuple_list!(ifstage));
}
#[derive(Debug)]
pub struct PanicStage<S> {
phantom: PhantomData<S>,
}
impl<S> PanicStage<S> {
pub fn new() -> Self {
Self {
phantom: PhantomData,
}
}
}
impl<S> UsesState for PanicStage<S>
where
S: State,
{
type State = S;
}
impl<E, EM, Z> Stage<E, EM, Z> for PanicStage<E::State>
where
E: UsesState,
EM: UsesState<State = E::State>,
Z: UsesState<State = E::State>,
{
type Progress = ();
fn perform(
&mut self,
_fuzzer: &mut Z,
_executor: &mut E,
_state: &mut Self::State,
_manager: &mut EM,
) -> Result<(), Error> {
panic!("Test failed; panic stage should never be executed.");
}
}
#[test]
fn check_resumability_if_else_if() {
let once = RefCell::new(true);
let (completed, stages) = test_resume_stages();
let ifstage = IfElseStage::new(
|_, _, _, _| Ok(once.replace(false)),
stages,
tuple_list!(PanicStage::new()),
);
let resetstage = ClosureStage::new(|_, _, _, _| {
once.replace(true);
Ok(())
});
let mut state = test_std_state::<NopInput>();
test_resume(&completed, &mut state, tuple_list!(ifstage, resetstage));
}
#[test]
fn check_resumability_if_else_else() {
let once = RefCell::new(false);
let (completed, stages) = test_resume_stages();
let ifstage = IfElseStage::new(
|_, _, _, _| Ok(once.replace(true)),
tuple_list!(PanicStage::new()),
stages,
);
let resetstage = ClosureStage::new(|_, _, _, _| {
once.replace(false);
Ok(())
});
let mut state = test_std_state::<NopInput>();
test_resume(&completed, &mut state, tuple_list!(ifstage, resetstage));
}
#[test]
fn check_resumability_if_else_else_deep() {
let (completed, stages) = test_resume_stages();
let ifstage = IfElseStage::new(
|_, _, _, _| Ok(false),
tuple_list!(PanicStage::new()),
tuple_list!(IfElseStage::new(
|_, _, _, _| Ok(false),
tuple_list!(PanicStage::new()),
tuple_list!(IfElseStage::new(
|_, _, _, _| Ok(false),
tuple_list!(PanicStage::new()),
tuple_list!(IfElseStage::new(
|_, _, _, _| Ok(false),
tuple_list!(PanicStage::new()),
tuple_list!(IfElseStage::new(
|_, _, _, _| Ok(false),
tuple_list!(PanicStage::new()),
stages,
)),
)),
)),
)),
);
let mut state = test_std_state::<NopInput>();
test_resume(&completed, &mut state, tuple_list!(ifstage));
}
}

View File

@ -4,72 +4,35 @@ A well-known [`Stage`], for example, is the mutational stage, running multiple [
Other stages may enrich [`crate::corpus::Testcase`]s with metadata.
*/
/// Mutational stage is the normal fuzzing stage.
pub mod mutational;
pub use mutational::{MutationalStage, StdMutationalStage};
use core::{convert::From, marker::PhantomData};
pub mod tmin;
pub use tmin::{
MapEqualityFactory, MapEqualityFeedback, StdTMinMutationalStage, TMinMutationalStage,
};
pub mod push;
pub mod tracing;
pub use tracing::{ShadowTracingStage, TracingStage};
pub mod calibrate;
pub use calibrate::CalibrationStage;
pub mod power;
pub use power::{PowerMutationalStage, StdPowerMutationalStage};
pub mod generalization;
pub use generalization::GeneralizationStage;
pub mod stats;
pub use stats::AflStatsStage;
pub mod owned;
pub use owned::StagesOwnedList;
pub mod logics;
pub use logics::*;
pub mod tuneable;
pub use tuneable::*;
pub mod colorization;
pub use colorization::*;
#[cfg(feature = "std")]
pub mod concolic;
#[cfg(feature = "std")]
pub use concolic::ConcolicTracingStage;
#[cfg(feature = "std")]
pub use concolic::SimpleConcolicMutationalStage;
#[cfg(feature = "unicode")]
pub mod string;
#[cfg(feature = "unicode")]
pub use string::*;
#[cfg(feature = "std")]
pub mod sync;
#[cfg(feature = "std")]
pub use sync::*;
#[cfg(feature = "std")]
pub mod dump;
use core::{convert::From, marker::PhantomData};
#[cfg(feature = "std")]
pub use dump::*;
pub use generalization::GeneralizationStage;
use libafl_bolts::tuples::HasConstLen;
pub use logics::*;
pub use mutational::{MutationalStage, StdMutationalStage};
pub use power::{PowerMutationalStage, StdPowerMutationalStage};
pub use stats::AflStatsStage;
#[cfg(feature = "unicode")]
pub use string::*;
#[cfg(feature = "std")]
pub use sync::*;
pub use tmin::{
MapEqualityFactory, MapEqualityFeedback, StdTMinMutationalStage, TMinMutationalStage,
};
pub use tracing::{ShadowTracingStage, TracingStage};
pub use tuneable::*;
use self::push::PushStage;
use crate::{
corpus::CorpusId,
corpus::HasCurrentCorpusIdx,
events::{EventFirer, EventRestarter, HasEventManagerId, ProgressReporter},
executors::{Executor, HasObservers},
inputs::UsesInput,
@ -79,6 +42,28 @@ use crate::{
Error, EvaluatorObservers, ExecutesInput, ExecutionProcessor, HasScheduler,
};
/// Mutational stage is the normal fuzzing stage.
pub mod mutational;
pub mod push;
pub mod tmin;
pub mod calibrate;
pub mod colorization;
#[cfg(feature = "std")]
pub mod concolic;
#[cfg(feature = "std")]
pub mod dump;
pub mod generalization;
pub mod logics;
pub mod power;
pub mod stats;
#[cfg(feature = "unicode")]
pub mod string;
#[cfg(feature = "std")]
pub mod sync;
pub mod tracing;
pub mod tuneable;
/// A stage is one step in the fuzzing process.
/// Multiple stages will be scheduled one by one for each input.
pub trait Stage<E, EM, Z>: UsesState
@ -87,6 +72,12 @@ where
EM: UsesState<State = Self::State>,
Z: UsesState<State = Self::State>,
{
// TODO: default this to () when associated_type_defaults is stable
// 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>;
/// Run the stage
fn perform(
&mut self,
@ -94,7 +85,6 @@ where
executor: &mut E,
state: &mut Self::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error>;
}
@ -104,7 +94,7 @@ where
E: UsesState<State = S>,
EM: UsesState<State = S>,
Z: UsesState<State = S>,
S: UsesInput,
S: UsesInput + HasCurrentStage,
{
/// Performs all `Stages` in this tuple
fn perform_all(
@ -113,7 +103,6 @@ where
executor: &mut E,
state: &mut S,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error>;
}
@ -122,27 +111,33 @@ where
E: UsesState<State = S>,
EM: UsesState<State = S>,
Z: UsesState<State = S>,
S: UsesInput,
S: UsesInput + HasCurrentStage,
{
fn perform_all(
&mut self,
_: &mut Z,
_: &mut E,
_: &mut S,
stage: &mut S,
_: &mut EM,
_: CorpusId,
) -> Result<(), Error> {
if stage.current_stage()?.is_some() {
Err(Error::illegal_state(
"Got to the end of the tuple without completing resume.",
))
} else {
Ok(())
}
}
}
impl<Head, Tail, E, EM, Z> StagesTuple<E, EM, Head::State, Z> for (Head, Tail)
where
Head: Stage<E, EM, Z>,
Tail: StagesTuple<E, EM, Head::State, Z>,
Tail: StagesTuple<E, EM, Head::State, Z> + HasConstLen,
E: UsesState<State = Head::State>,
EM: UsesState<State = Head::State>,
Z: UsesState<State = Head::State>,
Head::State: HasCurrentStage,
{
fn perform_all(
&mut self,
@ -150,15 +145,33 @@ where
executor: &mut E,
state: &mut Head::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
// Perform the current stage
self.0
.perform(fuzzer, executor, state, manager, corpus_idx)?;
match state.current_stage()? {
Some(idx) if idx < Self::LEN => {
// do nothing; we are resuming
}
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)?;
state.clear_stage()?;
}
Some(idx) if idx > Self::LEN => {
unreachable!("We should clear the stage index before we get here...");
}
// 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)?;
state.clear_stage()?;
}
}
// Execute the remaining stages
self.1
.perform_all(fuzzer, executor, state, manager, corpus_idx)
self.1.perform_all(fuzzer, executor, state, manager)
}
}
@ -166,7 +179,7 @@ where
#[derive(Debug)]
pub struct ClosureStage<CB, E, EM, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<(), Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<(), Error>,
E: UsesState,
{
closure: CB,
@ -175,7 +188,7 @@ where
impl<CB, E, EM, Z> UsesState for ClosureStage<CB, E, EM, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<(), Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<(), Error>,
E: UsesState,
{
type State = E::State;
@ -183,27 +196,28 @@ where
impl<CB, E, EM, Z> Stage<E, EM, Z> for ClosureStage<CB, E, EM, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<(), Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<(), Error>,
E: UsesState,
EM: UsesState<State = E::State>,
Z: UsesState<State = E::State>,
{
type Progress = ();
fn perform(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut E::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
(self.closure)(fuzzer, executor, state, manager, corpus_idx)
(self.closure)(fuzzer, executor, state, manager)
}
}
/// A stage that takes a closure
impl<CB, E, EM, Z> ClosureStage<CB, E, EM, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<(), Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<(), Error>,
E: UsesState,
{
/// Create a new [`ClosureStage`]
@ -218,7 +232,7 @@ where
impl<CB, E, EM, Z> From<CB> for ClosureStage<CB, E, EM, Z>
where
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM, CorpusId) -> Result<(), Error>,
CB: FnMut(&mut Z, &mut E, &mut E::State, &mut EM) -> Result<(), Error>,
E: UsesState,
{
#[must_use]
@ -257,7 +271,8 @@ where
impl<CS, E, EM, OT, PS, Z> Stage<E, EM, Z> for PushStageAdapter<CS, EM, OT, PS, Z>
where
CS: Scheduler,
CS::State: HasExecutions + HasMetadata + HasRand + HasCorpus + HasLastReportTime,
CS::State:
HasExecutions + HasMetadata + HasRand + HasCorpus + HasLastReportTime + HasCurrentCorpusIdx,
E: Executor<EM, Z> + HasObservers<Observers = OT, State = CS::State>,
EM: EventFirer<State = CS::State>
+ EventRestarter
@ -270,16 +285,23 @@ where
+ EvaluatorObservers<OT>
+ HasScheduler<Scheduler = CS>,
{
type Progress = (); // TODO implement resume
fn perform(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut CS::State,
event_mgr: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
let push_stage = &mut self.push_stage;
let Some(corpus_idx) = state.current_corpus_idx()? else {
return Err(Error::illegal_state(
"state is not currently processing a corpus index",
));
};
push_stage.set_current_corpus_idx(corpus_idx);
push_stage.init(fuzzer, state, event_mgr, executor.observers_mut())?;
@ -318,11 +340,13 @@ pub mod pybind {
use pyo3::prelude::*;
use crate::{
corpus::CorpusId,
corpus::HasCurrentCorpusIdx,
events::pybind::PythonEventManager,
executors::pybind::PythonExecutor,
fuzzer::pybind::{PythonStdFuzzer, PythonStdFuzzerWrapper},
stages::{mutational::pybind::PythonStdMutationalStage, Stage, StagesTuple},
stages::{
mutational::pybind::PythonStdMutationalStage, HasCurrentStage, Stage, StagesTuple,
},
state::{
pybind::{PythonStdState, PythonStdStateWrapper},
UsesState,
@ -347,6 +371,8 @@ pub mod pybind {
}
impl Stage<PythonExecutor, PythonEventManager, PythonStdFuzzer> for PyObjectStage {
type Progress = (); // we don't support resumption in python, and maybe can't?
#[inline]
fn perform(
&mut self,
@ -354,8 +380,13 @@ pub mod pybind {
executor: &mut PythonExecutor,
state: &mut PythonStdState,
manager: &mut PythonEventManager,
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",
));
};
Python::with_gil(|py| -> PyResult<()> {
self.inner.call_method1(
py,
@ -435,6 +466,9 @@ pub mod pybind {
}
impl Stage<PythonExecutor, PythonEventManager, PythonStdFuzzer> for PythonStage {
// TODO if we implement resumption for StdMutational, we need to apply it here
type Progress = ();
#[inline]
#[allow(clippy::let_and_return)]
fn perform(
@ -443,10 +477,9 @@ pub mod pybind {
executor: &mut PythonExecutor,
state: &mut PythonStdState,
manager: &mut PythonEventManager,
corpus_idx: CorpusId,
) -> Result<(), Error> {
unwrap_me_mut!(self.wrapper, s, {
s.perform(fuzzer, executor, state, manager, corpus_idx)
s.perform(fuzzer, executor, state, manager)
})
}
}
@ -482,10 +515,18 @@ pub mod pybind {
executor: &mut PythonExecutor,
state: &mut PythonStdState,
manager: &mut PythonEventManager,
corpus_idx: CorpusId,
) -> Result<(), Error> {
for s in &mut self.list {
s.perform(fuzzer, executor, state, manager, corpus_idx)?;
for (i, s) in self.list.iter_mut().enumerate() {
if let Some(continued) = state.current_stage()? {
assert!(continued >= i);
if continued > i {
continue;
}
} else {
state.set_stage(i)?;
}
s.perform(fuzzer, executor, state, manager)?;
state.clear_stage()?;
}
Ok(())
}
@ -498,3 +539,265 @@ pub mod pybind {
Ok(())
}
}
/// Trait for status tracking of stages which stash data to resume
pub trait StageProgress<S> {
/// Initialize the current status tracking for this stage, if it is not yet initialised
fn initialize_progress(state: &mut S) -> Result<(), Error>;
/// Clear the current status tracking of the associated stage
fn clear_progress(state: &mut S) -> Result<(), Error>;
/// Get the current status tracking of this stage
fn progress(state: &S) -> Result<&Self, Error>;
/// Get the current status tracking of this stage, mutably
fn progress_mut(state: &mut S) -> Result<&mut Self, Error>;
}
impl<S> StageProgress<S> for () {
fn initialize_progress(_state: &mut S) -> Result<(), Error> {
Ok(())
}
fn clear_progress(_state: &mut S) -> Result<(), Error> {
Ok(())
}
fn progress(_state: &S) -> Result<&Self, Error> {
unimplemented!("The empty tuple resumable stage status should never be queried")
}
fn progress_mut(_state: &mut S) -> Result<&mut Self, Error> {
unimplemented!("The empty tuple resumable stage status should never be queried")
}
}
/// Trait for types which track the current stage
pub trait HasCurrentStage {
/// Set the current stage; we have started processing this stage
fn set_stage(&mut self, idx: usize) -> Result<(), Error>;
/// Clear the current stage; we are done processing this stage
fn clear_stage(&mut self) -> Result<(), Error>;
/// Fetch the current stage -- typically used after a state recovery or transfer
fn current_stage(&self) -> Result<Option<usize>, Error>;
/// Notify of a reset from which we may recover
fn on_restart(&mut self) -> Result<(), Error> {
Ok(())
}
}
/// Trait for types which track nested stages. Stages which themselves contain stage tuples should
/// ensure that they constrain the state with this trait accordingly.
pub trait HasNestedStageStatus: HasCurrentStage {
/// Enter a stage scope, potentially resuming to an inner stage status. Returns Ok(true) if
/// resumed.
fn enter_inner_stage(&mut self) -> Result<(), Error>;
/// Exit a stage scope
fn exit_inner_stage(&mut self) -> Result<(), Error>;
}
#[cfg(test)]
pub mod test {
use alloc::rc::Rc;
use core::{cell::RefCell, marker::PhantomData};
use libafl_bolts::{impl_serdeany, Error};
use serde::{Deserialize, Serialize};
use tuple_list::{tuple_list, tuple_list_type};
use crate::{
events::NopEventManager,
executors::test::NopExecutor,
fuzzer::test::NopFuzzer,
stages::{Stage, StageProgress, StagesTuple},
state::{HasMetadata, State, UsesState},
};
#[derive(Debug)]
pub struct ResumeSucceededStage<S> {
phantom: PhantomData<S>,
}
#[derive(Debug)]
pub struct ResumeFailedStage<S> {
completed: Rc<RefCell<bool>>,
phantom: PhantomData<S>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct TestProgress {
count: usize,
}
impl_serdeany!(TestProgress);
impl<S> StageProgress<S> for TestProgress
where
S: HasMetadata,
{
fn initialize_progress(state: &mut S) -> Result<(), Error> {
// check if we're resuming
if !state.has_metadata::<Self>() {
state.add_metadata(Self { count: 0 });
}
Ok(())
}
fn clear_progress(state: &mut S) -> 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",
));
}
Ok(())
}
fn progress(state: &S) -> Result<&Self, Error> {
state.metadata()
}
fn progress_mut(state: &mut S) -> Result<&mut Self, Error> {
state.metadata_mut()
}
}
impl<S> UsesState for ResumeSucceededStage<S>
where
S: State,
{
type State = S;
}
impl<E, EM, Z> Stage<E, EM, Z> for ResumeSucceededStage<Z::State>
where
E: UsesState<State = Z::State>,
EM: UsesState<State = Z::State>,
Z: UsesState,
Z::State: HasMetadata,
{
type Progress = TestProgress;
fn perform(
&mut self,
_fuzzer: &mut Z,
_executor: &mut E,
state: &mut Self::State,
_manager: &mut EM,
) -> Result<(), Error> {
// metadata is attached by the status
let meta = Self::Progress::progress_mut(state)?;
meta.count += 1;
assert!(
meta.count == 1,
"Test failed; we resumed a succeeded stage!"
);
Ok(())
}
}
impl<S> UsesState for ResumeFailedStage<S>
where
S: State,
{
type State = S;
}
impl<E, EM, Z> Stage<E, EM, Z> for ResumeFailedStage<Z::State>
where
E: UsesState<State = Z::State>,
EM: UsesState<State = Z::State>,
Z: UsesState,
Z::State: HasMetadata,
{
type Progress = TestProgress;
fn perform(
&mut self,
_fuzzer: &mut Z,
_executor: &mut E,
state: &mut Self::State,
_manager: &mut EM,
) -> Result<(), Error> {
// metadata is attached by the status
let meta = Self::Progress::progress_mut(state)?;
meta.count += 1;
if meta.count == 1 {
return Err(Error::shutting_down());
} else if meta.count > 2 {
panic!("Resume was somehow corrupted?")
} else {
self.completed.replace(true);
}
Ok(())
}
}
#[must_use]
#[allow(clippy::type_complexity)]
pub fn test_resume_stages<S>() -> (
Rc<RefCell<bool>>,
tuple_list_type!(ResumeSucceededStage<S>, ResumeFailedStage<S>),
) {
let completed = Rc::new(RefCell::new(false));
(
completed.clone(),
tuple_list!(
ResumeSucceededStage {
phantom: PhantomData
},
ResumeFailedStage {
completed,
phantom: PhantomData
},
),
)
}
pub fn test_resume<ST, S>(completed: &Rc<RefCell<bool>>, state: &mut S, mut stages: ST)
where
ST: StagesTuple<NopExecutor<S>, NopEventManager<S>, S, NopFuzzer<S>>,
S: State,
{
#[cfg(any(not(feature = "serdeany_autoreg"), miri))]
unsafe {
TestProgress::register();
}
let mut fuzzer = NopFuzzer::new();
let mut executor = NopExecutor::new();
let mut manager = NopEventManager::new();
for _ in 0..2 {
completed.replace(false);
let Err(e) = stages.perform_all(&mut fuzzer, &mut executor, state, &mut manager) else {
panic!("Test failed; stages should fail the first time.")
};
assert!(
matches!(e, Error::ShuttingDown),
"Unexpected error encountered."
);
assert!(!*completed.borrow(), "Unexpectedly complete?");
state
.on_restart()
.expect("Couldn't notify state of restart.");
assert!(
stages
.perform_all(&mut fuzzer, &mut executor, state, &mut manager)
.is_ok(),
"Test failed; stages should pass the second time."
);
assert!(
*completed.borrow(),
"Test failed; we did not set completed."
);
}
}
}

View File

@ -6,7 +6,7 @@ use core::marker::PhantomData;
use libafl_bolts::rands::Rand;
use crate::{
corpus::{Corpus, CorpusId, Testcase},
corpus::{Corpus, CorpusId, HasCurrentCorpusIdx, Testcase},
fuzzer::Evaluator,
inputs::Input,
mark_feature_time,
@ -114,8 +114,13 @@ where
executor: &mut E,
state: &mut Z::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",
));
};
let num = self.iterations(state, corpus_idx)?;
start_timer!(state);
@ -211,6 +216,8 @@ where
Z::State: HasCorpus + HasRand,
I: MutatedTransform<Self::Input, Self::State> + Clone,
{
type Progress = (); // TODO should this stage be resumed?
#[inline]
#[allow(clippy::let_and_return)]
fn perform(
@ -219,9 +226,8 @@ where
executor: &mut E,
state: &mut Z::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
let ret = self.perform_mutational(fuzzer, executor, state, manager, corpus_idx);
let ret = self.perform_mutational(fuzzer, executor, state, manager);
#[cfg(feature = "introspection")]
state.introspection_monitor_mut().finish_stage();
@ -300,6 +306,8 @@ where
Z::State: HasCorpus + HasRand,
I: MutatedTransform<Self::Input, Self::State> + Clone,
{
type Progress = (); // TODO implement resume
#[inline]
#[allow(clippy::let_and_return)]
#[allow(clippy::cast_possible_wrap)]
@ -309,8 +317,13 @@ where
executor: &mut E,
state: &mut Z::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",
));
};
let mut testcase = state.corpus().get(corpus_idx)?.borrow_mut();
let Ok(input) = I::try_transform_from(&mut testcase, state, corpus_idx) else {
return Ok(());

View File

@ -1,66 +0,0 @@
//! A dynamic collection of owned Stages
use alloc::{boxed::Box, vec::Vec};
use libafl_bolts::anymap::AsAny;
use crate::{
corpus::CorpusId,
stages::{Stage, StagesTuple},
state::UsesState,
Error,
};
/// Combine `Stage` and `AsAny`
pub trait AnyStage<E, EM, Z>: Stage<E, EM, Z> + AsAny
where
E: UsesState<State = Self::State>,
EM: UsesState<State = Self::State>,
Z: UsesState<State = Self::State>,
{
}
/// An owned list of `Observer` trait objects
#[derive(Default)]
#[allow(missing_debug_implementations)]
pub struct StagesOwnedList<E, EM, Z>
where
E: UsesState,
{
/// The named trait objects map
#[allow(clippy::type_complexity)]
pub list: Vec<Box<dyn AnyStage<E, EM, Z, State = E::State, Input = E::Input>>>,
}
impl<E, EM, Z> StagesTuple<E, EM, E::State, Z> for StagesOwnedList<E, EM, Z>
where
E: UsesState,
EM: UsesState<State = E::State>,
Z: UsesState<State = E::State>,
{
fn perform_all(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut E::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
for s in &mut self.list {
s.perform(fuzzer, executor, state, manager, corpus_idx)?;
}
Ok(())
}
}
impl<E, EM, Z> StagesOwnedList<E, EM, Z>
where
E: UsesState,
{
/// Create a new instance
#[must_use]
#[allow(clippy::type_complexity)]
pub fn new(list: Vec<Box<dyn AnyStage<E, EM, Z, Input = E::Input, State = E::State>>>) -> Self {
Self { list }
}
}

View File

@ -71,6 +71,8 @@ where
Z: Evaluator<E, EM, State = E::State>,
I: MutatedTransform<E::Input, E::State> + Clone,
{
type Progress = (); // TODO should we resume this stage?
#[inline]
#[allow(clippy::let_and_return)]
fn perform(
@ -79,9 +81,8 @@ where
executor: &mut E,
state: &mut E::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
let ret = self.perform_mutational(fuzzer, executor, state, manager, corpus_idx);
let ret = self.perform_mutational(fuzzer, executor, state, manager);
ret
}
}

View File

@ -9,7 +9,7 @@ use libafl_bolts::current_time;
use serde_json::json;
use crate::{
corpus::{Corpus, CorpusId},
corpus::{Corpus, HasCurrentCorpusIdx},
events::EventFirer,
schedulers::minimizer::IsFavoredMetadata,
stages::Stage,
@ -62,14 +62,21 @@ where
Z: UsesState<State = E::State>,
E::State: HasImported + HasCorpus + HasMetadata,
{
type Progress = (); // this stage does not require resume
fn perform(
&mut self,
_fuzzer: &mut Z,
_executor: &mut E,
state: &mut E::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",
));
};
// Report your stats every `STATS_REPORT_INTERVAL`
// compute pending, pending_favored, imported, own_finds
{

View File

@ -8,7 +8,7 @@ use libafl_bolts::{impl_serdeany, Error};
use serde::{Deserialize, Serialize};
use crate::{
corpus::{CorpusId, HasTestcase},
corpus::HasTestcase,
inputs::{BytesInput, HasBytesVec},
stages::Stage,
state::{HasCorpus, HasMetadata, State, UsesState},
@ -104,14 +104,21 @@ where
EM: UsesState<State = S>,
Z: UsesState<State = S>,
{
type Progress = (); // this stage does not need to be resumed
fn perform(
&mut self,
_fuzzer: &mut Z,
_executor: &mut E,
state: &mut Self::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",
));
};
let mut tc = state.testcase_mut(corpus_idx)?;
if tc.has_metadata::<StringIdentificationMetadata>() {
return Ok(()); // skip recompute

View File

@ -67,6 +67,8 @@ where
Z: Evaluator<E, EM>,
Z::State: HasCorpus + HasRand + HasMetadata,
{
type Progress = (); // TODO load from directory should be resumed
#[inline]
fn perform(
&mut self,
@ -74,7 +76,6 @@ where
executor: &mut E,
state: &mut Z::State,
manager: &mut EM,
_corpus_idx: CorpusId,
) -> Result<(), Error> {
let last = state
.metadata_map()
@ -253,6 +254,8 @@ where
ICB: InputConverter<From = DI, To = S::Input>,
DI: Input,
{
type Progress = (); // TODO this should be resumed in the case that a testcase causes a crash
#[inline]
fn perform(
&mut self,
@ -260,7 +263,6 @@ where
executor: &mut E,
state: &mut Z::State,
manager: &mut EM,
_corpus_idx: CorpusId,
) -> Result<(), Error> {
if self.client.can_convert() {
let last_id = state

View File

@ -7,7 +7,7 @@ use ahash::RandomState;
use libafl_bolts::{HasLen, Named};
use crate::{
corpus::{Corpus, CorpusId, Testcase},
corpus::{Corpus, CorpusId, HasCurrentCorpusIdx, Testcase},
events::EventFirer,
executors::{Executor, ExitKind, HasObservers},
feedbacks::{Feedback, FeedbackFactory, HasObserverName},
@ -61,8 +61,13 @@ where
executor: &mut E,
state: &mut CS::State,
manager: &mut EM,
base_corpus_idx: CorpusId,
) -> Result<(), Error> {
let Some(base_corpus_idx) = state.current_corpus_idx()? else {
return Err(Error::illegal_state(
"state is not currently processing a corpus index",
));
};
let orig_max_size = state.max_size();
// basically copy-pasted from mutational.rs
let num = self.iterations(state, base_corpus_idx)?;
@ -209,15 +214,16 @@ where
+ HasFeedback<Feedback = F1>
+ HasScheduler<Scheduler = CS>,
{
type Progress = (); // TODO this stage desperately needs a resume
fn perform(
&mut self,
fuzzer: &mut Z,
executor: &mut E,
state: &mut CS::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
self.perform_minification(fuzzer, executor, state, manager, corpus_idx)?;
self.perform_minification(fuzzer, executor, state, manager)?;
#[cfg(feature = "introspection")]
state.introspection_monitor_mut().finish_stage();

View File

@ -3,7 +3,7 @@
use core::{fmt::Debug, marker::PhantomData};
use crate::{
corpus::{Corpus, CorpusId},
corpus::{Corpus, HasCurrentCorpusIdx},
executors::{Executor, HasObservers, ShadowExecutor},
mark_feature_time,
observers::ObserversTuple,
@ -38,6 +38,8 @@ where
EM: UsesState<State = TE::State>,
Z: UsesState<State = TE::State>,
{
type Progress = (); // this stage cannot be resumed
#[inline]
fn perform(
&mut self,
@ -45,8 +47,13 @@ where
_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)?;
@ -117,6 +124,8 @@ where
Z: UsesState<State = E::State>,
E::State: State + HasExecutions + HasCorpus + Debug,
{
type Progress = (); // this stage cannot be resumed
#[inline]
fn perform(
&mut self,
@ -124,8 +133,13 @@ where
executor: &mut ShadowExecutor<E, SOT>,
state: &mut E::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)?;

View File

@ -7,7 +7,7 @@ use libafl_bolts::{current_time, impl_serdeany, rands::Rand};
use serde::{Deserialize, Serialize};
use crate::{
corpus::{Corpus, CorpusId},
corpus::{Corpus, CorpusId, HasCurrentCorpusIdx},
mark_feature_time,
mutators::{MutationResult, Mutator},
stages::{
@ -173,8 +173,13 @@ where
executor: &mut E,
state: &mut Z::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",
));
};
let fuzz_time = self.seed_fuzz_time(state)?;
let iters = self.fixed_iters(state)?;
@ -269,6 +274,8 @@ where
Z::State: HasCorpus + HasRand + HasNamedMetadata + HasMetadata,
I: MutatedTransform<Z::Input, Z::State> + Clone,
{
type Progress = (); // TODO should this stage be resumed?
#[inline]
#[allow(clippy::let_and_return)]
fn perform(
@ -277,9 +284,8 @@ where
executor: &mut E,
state: &mut Z::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
let ret = self.perform_mutational(fuzzer, executor, state, manager, corpus_idx);
let ret = self.perform_mutational(fuzzer, executor, state, manager);
#[cfg(feature = "introspection")]
state.introspection_monitor_mut().finish_stage();

View File

@ -1,5 +1,6 @@
//! The fuzzer, and state are the core pieces of every good fuzzer
use alloc::vec::Vec;
use core::{
cell::{Ref, RefMut},
fmt::Debug,
@ -10,11 +11,10 @@ use core::{
use std::{
fs,
path::{Path, PathBuf},
vec::Vec,
};
use libafl_bolts::{
rands::{Rand, StdRand},
rands::Rand,
serdeany::{NamedSerdeAnyMap, SerdeAny, SerdeAnyMap},
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
@ -24,12 +24,13 @@ use crate::monitors::ClientPerfMonitor;
#[cfg(feature = "scalability_introspection")]
use crate::monitors::ScalabilityMonitor;
use crate::{
corpus::{Corpus, CorpusId, HasTestcase, Testcase},
corpus::{Corpus, CorpusId, HasCurrentCorpusIdx, HasTestcase, Testcase},
events::{Event, EventFirer, LogSeverity},
feedbacks::Feedback,
fuzzer::{Evaluator, ExecuteInputResult},
generators::Generator,
inputs::{Input, UsesInput},
stages::{HasCurrentStage, HasNestedStageStatus},
Error,
};
@ -40,7 +41,13 @@ pub const DEFAULT_MAX_SIZE: usize = 1_048_576;
/// Contains all important information about the current run.
/// Will be used to restart the fuzzing process at any time.
pub trait State:
UsesInput + Serialize + DeserializeOwned + MaybeHasClientPerfMonitor + MaybeHasScalabilityMonitor
UsesInput
+ Serialize
+ DeserializeOwned
+ MaybeHasClientPerfMonitor
+ MaybeHasScalabilityMonitor
+ HasCurrentCorpusIdx
+ HasCurrentStage
{
}
@ -321,6 +328,12 @@ pub struct StdState<I, C, R, SC> {
/// The last time we reported progress (if available/used).
/// This information is used by fuzzer `maybe_report_progress`.
last_report_time: Option<Duration>,
/// The current index of the corpus; used to record for resumable fuzzing.
corpus_idx: Option<CorpusId>,
/// The stage indexes for each nesting of stages
stage_idx_stack: Vec<usize>,
/// The current stage depth
stage_depth: usize,
phantom: PhantomData<I>,
}
@ -514,6 +527,67 @@ impl<I, C, R, SC> HasStartTime for StdState<I, C, R, SC> {
}
}
impl<I, C, R, SC> HasCurrentCorpusIdx for StdState<I, C, R, SC> {
fn set_corpus_idx(&mut self, idx: CorpusId) -> Result<(), Error> {
self.corpus_idx = Some(idx);
Ok(())
}
fn clear_corpus_idx(&mut self) -> Result<(), Error> {
self.corpus_idx = None;
Ok(())
}
fn current_corpus_idx(&self) -> Result<Option<CorpusId>, Error> {
Ok(self.corpus_idx)
}
}
impl<I, C, R, SC> HasCurrentStage for StdState<I, C, R, SC> {
fn set_stage(&mut self, idx: usize) -> Result<(), Error> {
// ensure we are in the right frame
if self.stage_depth != self.stage_idx_stack.len() {
return Err(Error::illegal_state(
"stage not resumed before setting stage",
));
}
self.stage_idx_stack.push(idx);
Ok(())
}
fn clear_stage(&mut self) -> Result<(), Error> {
self.stage_idx_stack.pop();
// ensure we are in the right frame
if self.stage_depth != self.stage_idx_stack.len() {
return Err(Error::illegal_state(
"we somehow cleared too many or too few states!",
));
}
Ok(())
}
fn current_stage(&self) -> Result<Option<usize>, Error> {
Ok(self.stage_idx_stack.get(self.stage_depth).copied())
}
fn on_restart(&mut self) -> Result<(), Error> {
self.stage_depth = 0; // reset the stage depth so that we may resume inward
Ok(())
}
}
impl<I, C, R, SC> HasNestedStageStatus for StdState<I, C, R, SC> {
fn enter_inner_stage(&mut self) -> Result<(), Error> {
self.stage_depth += 1;
Ok(())
}
fn exit_inner_stage(&mut self) -> Result<(), Error> {
self.stage_depth -= 1;
Ok(())
}
}
#[cfg(feature = "std")]
impl<C, I, R, SC> StdState<I, C, R, SC>
where
@ -897,6 +971,9 @@ where
#[cfg(feature = "std")]
dont_reenter: None,
last_report_time: None,
corpus_idx: None,
stage_depth: 0,
stage_idx_stack: Vec::new(),
phantom: PhantomData,
};
feedback.init_state(&mut state)?;
@ -927,6 +1004,27 @@ impl<I, C, R, SC> HasScalabilityMonitor for StdState<I, C, R, SC> {
}
}
#[cfg(test)]
pub mod test {
use core::{marker::PhantomData, time::Duration};
use libafl_bolts::{rands::StdRand, serdeany::SerdeAnyMap, Error};
use serde::{Deserialize, Serialize};
use crate::{
corpus::{CorpusId, HasCurrentCorpusIdx, InMemoryCorpus},
inputs::{Input, NopInput, UsesInput},
stages::test::{test_resume, test_resume_stages},
state::{
HasCurrentStage, HasExecutions, HasLastReportTime, HasMetadata, HasRand, State,
StdState,
},
};
#[cfg(feature = "introspection")]
use crate::{monitors::ClientPerfMonitor, state::HasClientPerfMonitor};
#[cfg(feature = "scalability_introspection")]
use crate::{monitors::ScalabilityMonitor, state::HasScalabilityMonitor};
/// A very simple state without any bells or whistles, for testing.
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct NopState<I> {
@ -1000,6 +1098,34 @@ impl<I> HasRand for NopState<I> {
impl<I> State for NopState<I> where I: Input {}
impl<I> HasCurrentCorpusIdx for NopState<I> {
fn set_corpus_idx(&mut self, _idx: CorpusId) -> Result<(), Error> {
Ok(())
}
fn clear_corpus_idx(&mut self) -> Result<(), Error> {
Ok(())
}
fn current_corpus_idx(&self) -> Result<Option<CorpusId>, Error> {
Ok(None)
}
}
impl<I> HasCurrentStage for NopState<I> {
fn set_stage(&mut self, _idx: usize) -> Result<(), Error> {
Ok(())
}
fn clear_stage(&mut self) -> Result<(), Error> {
Ok(())
}
fn current_stage(&self) -> Result<Option<usize>, Error> {
Ok(None)
}
}
#[cfg(feature = "introspection")]
impl<I> HasClientPerfMonitor for NopState<I> {
fn introspection_monitor(&self) -> &ClientPerfMonitor {
@ -1022,6 +1148,28 @@ impl<I> HasScalabilityMonitor for NopState<I> {
}
}
#[must_use]
pub fn test_std_state<I: Input>() -> StdState<I, InMemoryCorpus<I>, StdRand, InMemoryCorpus<I>>
{
StdState::new(
StdRand::with_seed(0),
InMemoryCorpus::<I>::new(),
InMemoryCorpus::new(),
&mut (),
&mut (),
)
.expect("couldn't instantiate the test state")
}
#[test]
fn resume_simple() {
let mut state = test_std_state::<NopInput>();
let (completed, stages) = test_resume_stages();
test_resume(&completed, &mut state, stages);
}
}
#[cfg(feature = "python")]
#[allow(missing_docs)]
/// `State` Python bindings

View File

@ -11,7 +11,7 @@ pub use tuple_list::{tuple_list, tuple_list_type, TupleList};
#[cfg(any(feature = "xxh3", feature = "alloc"))]
use crate::hash_std;
use crate::Named;
use crate::{HasLen, Named};
/// Returns if the type `T` is equal to `U`
/// From <https://stackoverflow.com/a/60138532/7658998>
@ -92,21 +92,10 @@ where
pub trait HasConstLen {
/// The length as constant `usize`
const LEN: usize;
/// The length
fn len(&self) -> usize;
/// Returns true, if empty
fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl HasConstLen for () {
const LEN: usize = 0;
fn len(&self) -> usize {
0
}
}
impl<Head, Tail> HasConstLen for (Head, Tail)
@ -114,9 +103,18 @@ where
Tail: HasConstLen,
{
const LEN: usize = 1 + Tail::LEN;
}
impl<C> HasLen for C
where
C: HasConstLen,
{
fn len(&self) -> usize {
1 + self.1.len()
Self::LEN
}
fn is_empty(&self) -> bool {
Self::LEN != 0
}
}

View File

@ -22,7 +22,7 @@ use libafl::{
tui::{ui::TuiUI, TuiMonitor},
Monitor, MultiMonitor, SimpleMonitor,
},
stages::StagesTuple,
stages::{HasCurrentStage, StagesTuple},
state::{HasExecutions, HasLastReportTime, HasMetadata, HasSolutions, UsesState},
Error, Fuzzer,
};
@ -66,7 +66,7 @@ fn do_fuzz<F, ST, E, S, EM>(
) -> Result<(), Error>
where
F: Fuzzer<E, EM, ST, State = S>,
S: HasMetadata + HasExecutions + UsesInput + HasSolutions + HasLastReportTime,
S: HasMetadata + HasExecutions + UsesInput + HasSolutions + HasLastReportTime + HasCurrentStage,
E: UsesState<State = S>,
EM: ProgressReporter<State = S>,
ST: StagesTuple<E, EM, S, F>,

View File

@ -224,7 +224,7 @@ macro_rules! fuzz_with {
// Set up a generalization stage for grimoire
let generalization = GeneralizationStage::new(&edges_observer);
let generalization = IfStage::new(|_, _, _, _, _| Ok(grimoire.into()), tuple_list!(generalization));
let generalization = IfStage::new(|_, _, _, _| Ok(grimoire.into()), tuple_list!(generalization));
let calibration = CalibrationStage::new(&map_feedback);
@ -320,7 +320,7 @@ macro_rules! fuzz_with {
let string_replace_power = StdMutationalStage::transforming(string_replace_mutator);
let string_analysis = StringIdentificationStage::new();
let string_analysis = IfStage::new(|_, _, _, _, _| Ok((unicode_used && mutator_status.std_mutational).into()), tuple_list!(string_analysis, string_power, string_replace_power));
let string_analysis = IfStage::new(|_, _, _, _| Ok((unicode_used && mutator_status.std_mutational).into()), tuple_list!(string_analysis, string_power, string_replace_power));
// Attempt to use tokens from libfuzzer dicts
if !state.has_metadata::<Tokens>() {
@ -342,19 +342,19 @@ macro_rules! fuzz_with {
// Setup a randomic Input2State stage, conditionally within a custom mutator
let i2s =
StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(I2SRandReplace::new())));
let i2s = IfStage::new(|_, _, _, _, _| Ok((!mutator_status.custom_mutation).into()), (i2s, ()));
let i2s = IfStage::new(|_, _, _, _| Ok((!mutator_status.custom_mutation).into()), (i2s, ()));
let cm_i2s = StdMutationalStage::new(unsafe {
LLVMCustomMutator::mutate_unchecked(StdScheduledMutator::new(tuple_list!(
I2SRandReplace::new()
)))
});
let cm_i2s = IfStage::new(|_, _, _, _, _| Ok(mutator_status.custom_mutation.into()), (cm_i2s, ()));
let cm_i2s = IfStage::new(|_, _, _, _| Ok(mutator_status.custom_mutation.into()), (cm_i2s, ()));
// TODO configure with mutation stacking options from libfuzzer
let std_mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
let std_power = StdPowerMutationalStage::new(std_mutator);
let std_power = IfStage::new(|_, _, _, _, _| Ok(mutator_status.std_mutational.into()), (std_power, ()));
let std_power = IfStage::new(|_, _, _, _| Ok(mutator_status.std_mutational.into()), (std_power, ()));
// for custom mutator and crossover, each have access to the LLVMFuzzerMutate -- but it appears
// that this method doesn't normally offer stacked mutations where one may expect them
@ -375,10 +375,10 @@ macro_rules! fuzz_with {
let std_mutator_no_mutate = StdScheduledMutator::with_max_stack_pow(havoc_crossover(), 3);
let cm_power = StdPowerMutationalStage::new(custom_mutator);
let cm_power = IfStage::new(|_, _, _, _, _| Ok(mutator_status.custom_mutation.into()), (cm_power, ()));
let cm_power = IfStage::new(|_, _, _, _| Ok(mutator_status.custom_mutation.into()), (cm_power, ()));
let cm_std_power = StdMutationalStage::new(std_mutator_no_mutate);
let cm_std_power =
IfStage::new(|_, _, _, _, _| Ok(mutator_status.std_no_mutate.into()), (cm_std_power, ()));
IfStage::new(|_, _, _, _| Ok(mutator_status.std_no_mutate.into()), (cm_std_power, ()));
// a custom crossover is defined
// while the scenario that a custom crossover is defined without a custom mutator is unlikely
@ -392,10 +392,10 @@ macro_rules! fuzz_with {
let std_mutator_no_crossover = StdScheduledMutator::new(havoc_mutations_no_crossover().merge(tokens_mutations()));
let cc_power = StdMutationalStage::new(custom_crossover);
let cc_power = IfStage::new(|_, _, _, _, _| Ok(mutator_status.custom_crossover.into()), (cc_power, ()));
let cc_power = IfStage::new(|_, _, _, _| Ok(mutator_status.custom_crossover.into()), (cc_power, ()));
let cc_std_power = StdPowerMutationalStage::new(std_mutator_no_crossover);
let cc_std_power =
IfStage::new(|_, _, _, _, _| Ok(mutator_status.std_no_crossover.into()), (cc_std_power, ()));
IfStage::new(|_, _, _, _| Ok(mutator_status.std_no_crossover.into()), (cc_std_power, ()));
let grimoire_mutator = StdScheduledMutator::with_max_stack_pow(
tuple_list!(
@ -408,7 +408,7 @@ macro_rules! fuzz_with {
),
3,
);
let grimoire = IfStage::new(|_, _, _, _, _| Ok(grimoire.into()), (StdMutationalStage::transforming(grimoire_mutator), ()));
let grimoire = IfStage::new(|_, _, _, _| Ok(grimoire.into()), (StdMutationalStage::transforming(grimoire_mutator), ()));
// A minimization+queue policy to get testcasess from the corpus
let scheduler = IndexesLenTimeMinimizerScheduler::new(PowerQueueScheduler::new(&mut state, &edges_observer, PowerSchedule::FAST));
@ -479,7 +479,7 @@ macro_rules! fuzz_with {
// Setup a tracing stage in which we log comparisons
let tracing = IfStage::new(|_, _, _, _, _| Ok(!$options.skip_tracing()), (TracingStage::new(InProcessExecutor::new(
let tracing = IfStage::new(|_, _, _, _| Ok(!$options.skip_tracing()), (TracingStage::new(InProcessExecutor::new(
&mut tracing_harness,
tuple_list!(cmplog_observer),
&mut fuzzer,

View File

@ -6,7 +6,7 @@ use libafl::{
feedbacks::{MapFeedbackMetadata, MAPFEEDBACK_PREFIX},
inputs::UsesInput,
monitors::SimpleMonitor,
stages::StagesTuple,
stages::{HasCurrentStage, StagesTuple},
state::{HasExecutions, HasLastReportTime, HasMetadata, HasNamedMetadata},
Error, Fuzzer,
};
@ -24,7 +24,12 @@ fn do_report<F, ST, E, S, EM>(
) -> Result<(), Error>
where
F: Fuzzer<E, EM, ST, State = S>,
S: HasMetadata + HasNamedMetadata + HasExecutions + UsesInput + HasLastReportTime,
S: HasMetadata
+ HasNamedMetadata
+ HasExecutions
+ UsesInput
+ HasLastReportTime
+ HasCurrentStage,
E: HasObservers<State = S>,
EM: ProgressReporter<State = S>,
ST: StagesTuple<E, EM, S, F>,

View File

@ -12,7 +12,6 @@ use core::{
use libafl::{
executors::{inprocess::inprocess_get_state, ExitKind},
inputs::UsesInput,
state::NopState,
};
pub use crate::emu::SyscallHookResult;
@ -363,36 +362,6 @@ where
}
}
impl<I, QT> QemuHooks<QT, NopState<I>>
where
QT: QemuHelperTuple<NopState<I>>,
NopState<I>: UsesInput<Input = I>,
{
pub fn reproducer(emulator: Emulator, helpers: QT) -> Box<Self> {
Self::new(emulator, helpers)
}
pub fn repro_run<H>(&mut self, harness: &mut H, input: &I) -> ExitKind
where
H: FnMut(&I) -> ExitKind,
{
unsafe {
if FIRST_EXEC {
self.helpers.first_exec_all(self);
FIRST_EXEC = false;
}
}
self.helpers.pre_exec_all(&self.emulator, input);
let mut exit_kind = harness(input);
self.helpers
.post_exec_all(&self.emulator, input, &mut (), &mut exit_kind);
exit_kind
}
}
impl<QT, S> QemuHooks<QT, S>
where
QT: QemuHelperTuple<S>,

View File

@ -4,7 +4,7 @@ use core::marker::PhantomData;
#[cfg(feature = "introspection")]
use libafl::state::HasClientPerfMonitor;
use libafl::{
corpus::{Corpus, CorpusId},
corpus::{Corpus, HasCurrentCorpusIdx},
executors::{Executor, HasObservers},
inputs::{BytesInput, UsesInput},
observers::ObserversTuple,
@ -40,6 +40,8 @@ where
EM: UsesState<State = TE::State>,
Z: UsesState<State = TE::State>,
{
type Progress = (); // TODO this needs resumption
#[inline]
fn perform(
&mut self,
@ -47,9 +49,11 @@ where
_executor: &mut E,
state: &mut TE::State,
manager: &mut EM,
corpus_idx: CorpusId,
) -> Result<(), Error> {
// First run with the un-mutated input
let corpus_idx = state.current_corpus_idx()?.ok_or_else(|| {
Error::illegal_state("state is not currently processing a corpus index")
})?;
let unmutated_input = state.corpus().cloned_input_for_id(corpus_idx)?;