diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 501d90d13d..6201665e3f 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -68,7 +68,7 @@ tcp_compression = ["tcp_manager", "libafl_bolts/gzip"] multi_machine = ["tokio", "std", "enumflags2", "ahash/std"] ## Dump state of each client on exit -dump_state = ["std", "libafl_bolts/unsafe_stable_anymap"] +dump_state = ["std"] ## Enables the `NaiveTokenizer` and `StacktraceObserver` regex = ["std", "dep:regex"] diff --git a/libafl/src/events/mod.rs b/libafl/src/events/mod.rs index 4dbce74f73..cbb9d6e539 100644 --- a/libafl/src/events/mod.rs +++ b/libafl/src/events/mod.rs @@ -98,22 +98,23 @@ impl Handler for ShutdownSignalData { unsafe { // println!("Exiting from the handler...."); - #[cfg(unix)] + #[cfg(feature = "dump_state")] { - #[cfg(not(feature = "dump_state"))] + // fuzzer will exit at the end of fuzzing run. + INTERRUPT_FUZZER = true; + } + + #[cfg(not(feature = "dump_state"))] + { + #[cfg(unix)] { libc::_exit(CTRL_C_EXIT); } - - #[cfg(feature = "dump_state")] { - // fuzzer will exit at the end of fuzzing run. - INTERRUPT_FUZZER = true; + #[cfg(windows)] + windows::Win32::System::Threading::ExitProcess(100); } } - - #[cfg(windows)] - windows::Win32::System::Threading::ExitProcess(100); } } diff --git a/libafl/src/fuzzer/mod.rs b/libafl/src/fuzzer/mod.rs index e4bd9bf9b2..7ced24f5d0 100644 --- a/libafl/src/fuzzer/mod.rs +++ b/libafl/src/fuzzer/mod.rs @@ -2,6 +2,7 @@ use alloc::string::ToString; use core::{fmt::Debug, marker::PhantomData, time::Duration}; +use std::path::PathBuf; #[cfg(all(feature = "std", feature = "dump_state"))] use std::{ fs::{self, File}, @@ -17,7 +18,7 @@ use libafl_bolts::os::CTRL_C_EXIT; use serde::{de::DeserializeOwned, Serialize}; #[cfg(all(feature = "std", feature = "dump_state"))] -use crate::state::MaybeHasDumpStateDir; +use crate::state::MaybeCanDumpState; use crate::{ corpus::{Corpus, CorpusId, HasCurrentCorpusId, HasTestcase, Testcase}, events::{Event, EventConfig, EventFirer, EventProcessor, ProgressReporter}, @@ -238,25 +239,33 @@ where if INTERRUPT_FUZZER { log::info!("Interrupting fuzzer..."); + let dump_path: Option = state.dump_state_dir().cloned(); + // Dump state if needed. - if let Some(dump_path) = state.dump_state_dir() { + if let Some(dump_path) = dump_path { log::info!("Dumping state to disk..."); - let serialized = postcard::to_allocvec(&state)?; + let state_dump = state.gen_dump_state()?; + let dump_serialized = postcard::to_allocvec(&state_dump)?; // Taken from staterestorer // generate a filename let mut hasher = RandomState::with_seeds(0, 0, 0, 0).build_hasher(); // Using the last few k as randomness for a filename, hoping it's unique. - hasher.write(&serialized[serialized.len().saturating_sub(4096)..]); + hasher + .write(&dump_serialized[dump_serialized.len().saturating_sub(4096)..]); let filename = format!("{:016x}.libafl_state", hasher.finish()); - let dump_file = dump_path.join(&filename); + let dump_file = dump_path.join(filename); fs::create_dir_all(dump_path.as_path())?; - File::create(dump_file)?.write_all(&serialized)?; + File::create(dump_file)?.write_all(&dump_serialized)?; } + #[cfg(unix)] libc::exit(CTRL_C_EXIT); + + #[cfg(windows)] + windows::Win32::System::Threading::ExitProcess(100); } } diff --git a/libafl/src/state/mod.rs b/libafl/src/state/mod.rs index b58278ffaa..671715771c 100644 --- a/libafl/src/state/mod.rs +++ b/libafl/src/state/mod.rs @@ -70,7 +70,7 @@ pub trait State: + DeserializeOwned + MaybeHasClientPerfMonitor + MaybeHasScalabilityMonitor - + MaybeHasDumpStateDir + + MaybeCanDumpState + HasCurrentCorpusId + HasCurrentStage { @@ -169,9 +169,15 @@ impl MaybeHasScalabilityMonitor for T where T: HasScalabilityMonitor {} /// Trait for getting the optional dump directory for the state #[cfg(all(feature = "std", feature = "dump_state"))] -pub trait MaybeHasDumpStateDir { +pub trait MaybeCanDumpState { + /// The dump state type + type StateDump: Serialize + for<'de> Deserialize<'de>; + /// Get the dump dir, if there is one. fn dump_state_dir(&self) -> Option<&PathBuf>; + + /// Generate the state dump + fn gen_dump_state(&mut self) -> Result; } /// Trait for getting the optional dump directory for the state @@ -179,7 +185,7 @@ pub trait MaybeHasDumpStateDir { pub trait MaybeHasDumpStateDir {} #[cfg(all(feature = "std", not(feature = "dump_state")))] -impl MaybeHasDumpStateDir for T {} +impl MaybeCanDumpState for T {} /// Trait for offering a [`ScalabilityMonitor`] #[cfg(feature = "scalability_introspection")] @@ -299,6 +305,20 @@ pub struct StdState { phantom: PhantomData, } +/// The standard state dump +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(bound = "I: serde::de::DeserializeOwned")] +pub struct StdStateDump +where + I: Input, +{ + /// Fuzzer start time. + start_time: Duration, + + /// Loaded inputs + testcases: Vec>, +} + impl UsesInput for StdState where I: Input, @@ -307,18 +327,40 @@ where } #[cfg(all(feature = "std", feature = "dump_state"))] -impl MaybeHasDumpStateDir for StdState { +impl MaybeCanDumpState for StdState +where + C: Corpus, + I: Input, +{ + type StateDump = StdStateDump; + fn dump_state_dir(&self) -> Option<&PathBuf> { self.dump_state_dir.as_ref() } + + fn gen_dump_state(&mut self) -> Result { + let mut tcs: Vec> = Vec::new(); + for corpus_id in self.corpus.ids() { + let mut tc = self.corpus.get(corpus_id)?.clone(); + let tc_ref = tc.get_mut(); + tc_ref.load_input(&self.corpus)?; + tcs.push(tc_ref.clone()); + } + + Ok(StdStateDump { + start_time: self.start_time, + testcases: tcs, + }) + } } impl State for StdState where C: Corpus, + I: Input, R: Rand, SC: Corpus, - Self: UsesInput, + Self: UsesInput, { } @@ -1247,10 +1289,16 @@ impl HasMaxSize for NopState { } #[cfg(all(feature = "std", feature = "dump_state"))] -impl MaybeHasDumpStateDir for NopState { +impl MaybeCanDumpState for NopState { + type StateDump = (); + fn dump_state_dir(&self) -> Option<&PathBuf> { None } + + fn gen_dump_state(&mut self) -> Result { + Ok(()) + } } impl UsesInput for NopState