From 6eb2dafd34f3ade5f14b159cd7cde24cf304a6e0 Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Tue, 29 Oct 2024 18:35:17 +0100 Subject: [PATCH] Feature: Make executors and feedbacks easier to use outside of the fuzzing loop (extends #2511) (#2637) * feat(libafl_core): make executors and feedbacks more cleanly usable outside of LibAFLs Fuzzer loop * cargo +nightly fmt * updated type constraints * reformatted and final type constraint fixes * made unicode extraction stage useful separately * fix libafl_cc error message * fix state type constraint to be constrained on the method * removed unnecessary observer constraint * renamed unused variables * fix unnecessary error wrapping in helper functions * converted unicode conversion stage into associated function and fixed nautilus changes * more update * Remove extra I * more fmt * bounds? * less bounds * more less bounds * different trait bounds again * more less generics * fix unicode * fix list * remove unneeded bound --------- Co-authored-by: Lukas Dresel Co-authored-by: Toka --- fuzzers/baby/baby_fuzzer_unicode/src/main.rs | 10 +- libafl/src/executors/command.rs | 39 ++-- libafl/src/executors/forkserver.rs | 200 ++++++++++--------- libafl/src/feedbacks/concolic.rs | 22 +- libafl/src/feedbacks/list.rs | 92 ++++++--- libafl/src/feedbacks/map.rs | 149 +++++++------- libafl/src/feedbacks/nautilus.rs | 29 ++- libafl/src/feedbacks/new_hash_feedback.rs | 62 ++++-- libafl/src/feedbacks/stdio.rs | 43 ++-- libafl/src/stages/dump.rs | 115 ++++++----- libafl/src/stages/stats.rs | 39 ++-- libafl/src/stages/unicode.rs | 33 +-- 12 files changed, 498 insertions(+), 335 deletions(-) diff --git a/fuzzers/baby/baby_fuzzer_unicode/src/main.rs b/fuzzers/baby/baby_fuzzer_unicode/src/main.rs index 844ea35638..a3e2cc03d3 100644 --- a/fuzzers/baby/baby_fuzzer_unicode/src/main.rs +++ b/fuzzers/baby/baby_fuzzer_unicode/src/main.rs @@ -1,6 +1,9 @@ #[cfg(windows)] use std::ptr::write_volatile; -use std::{path::PathBuf, ptr::write}; +use std::{ + path::PathBuf, + ptr::{addr_of, addr_of_mut, write}, +}; #[cfg(feature = "tui")] use libafl::monitors::tui::TuiMonitor; @@ -24,7 +27,8 @@ use libafl_bolts::{rands::StdRand, tuples::tuple_list, AsSlice}; /// Coverage map with explicit assignments due to the lack of instrumentation static mut SIGNALS: [u8; 64] = [0; 64]; -static mut SIGNALS_PTR: *mut u8 = unsafe { SIGNALS.as_mut_ptr() }; +static mut SIGNALS_PTR: *mut u8 = addr_of_mut!(SIGNALS).cast(); +static mut SIGNALS_LEN: usize = unsafe { (*addr_of!(SIGNALS)).len() }; /// Assign a signal to the signals map fn signals_set(idx: usize) { @@ -56,7 +60,7 @@ pub fn main() { }; // Create an observation channel using the signals map - let observer = unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS.len()) }; + let observer = unsafe { StdMapObserver::from_mut_ptr("signals", SIGNALS_PTR, SIGNALS_LEN) }; // Feedback to rate the interestingness of an input let mut feedback = MaxMapFeedback::new(&observer); diff --git a/libafl/src/executors/command.rs b/libafl/src/executors/command.rs index 6ed35dc993..6ab23dc18b 100644 --- a/libafl/src/executors/command.rs +++ b/libafl/src/executors/command.rs @@ -214,21 +214,13 @@ where // this only works on unix because of the reliance on checking the process signal for detecting OOM #[cfg(all(feature = "std", unix))] -impl Executor for CommandExecutor +impl CommandExecutor where - EM: UsesState, - S: State + HasExecutions, - T: CommandConfigurator + Debug, - OT: Debug + MatchName + ObserversTuple, - Z: UsesState, + S: State + HasExecutions + UsesInput, + T: CommandConfigurator + Debug, + OT: Debug + ObserversTuple, { - fn run_target( - &mut self, - _fuzzer: &mut Z, - state: &mut Self::State, - _mgr: &mut EM, - input: &Self::Input, - ) -> Result { + fn execute_input_with_command(&mut self, state: &mut S, input: &I) -> Result { use std::os::unix::prelude::ExitStatusExt; use wait_timeout::ChildExt; @@ -288,6 +280,27 @@ where } } +#[cfg(all(feature = "std", unix))] +impl Executor for CommandExecutor +where + EM: UsesState, + S: State + HasExecutions + UsesInput, + T: CommandConfigurator + Debug, + OT: Debug + MatchName + ObserversTuple, + Z: UsesState, +{ + fn run_target( + &mut self, + _fuzzer: &mut Z, + state: &mut Self::State, + _mgr: &mut EM, + input: &Self::Input, + ) -> Result { + self.execute_input_with_command(state, input) + } +} + +// this only works on unix because of the reliance on checking the process signal for detecting OOM impl HasTimeout for CommandExecutor where S: HasCorpus, diff --git a/libafl/src/executors/forkserver.rs b/libafl/src/executors/forkserver.rs index 4d6b768d99..75cc129f41 100644 --- a/libafl/src/executors/forkserver.rs +++ b/libafl/src/executors/forkserver.rs @@ -644,6 +644,7 @@ where OT: ObserversTuple, S: UsesInput, SP: ShMemProvider, + TC: TargetBytesConverter, { /// The `target` binary that's going to run. pub fn target(&self) -> &OsString { @@ -674,6 +675,112 @@ where pub fn coverage_map_size(&self) -> Option { self.map_size } + + /// Execute input and increase the execution counter. + #[inline] + fn execute_input(&mut self, state: &mut S, input: &TC::Input) -> Result + where + S: HasExecutions, + { + *state.executions_mut() += 1; + + self.execute_input_uncounted(input) + } + + /// Execute input, but side-step the execution counter. + #[inline] + fn execute_input_uncounted(&mut self, input: &TC::Input) -> Result { + let mut exit_kind = ExitKind::Ok; + + let last_run_timed_out = self.forkserver.last_run_timed_out_raw(); + + let mut input_bytes = self.target_bytes_converter.to_target_bytes(input); + let mut input_size = input_bytes.as_slice().len(); + if input_size > self.max_input_size { + // Truncate like AFL++ does + input_size = self.max_input_size; + } else if input_size < self.min_input_size { + // Extend like AFL++ does + input_size = self.min_input_size; + let mut input_bytes_copy = Vec::with_capacity(input_size); + input_bytes_copy + .as_slice_mut() + .copy_from_slice(input_bytes.as_slice()); + input_bytes = OwnedSlice::from(input_bytes_copy); + } + let input_size_in_bytes = input_size.to_ne_bytes(); + if self.uses_shmem_testcase { + debug_assert!( + self.map.is_some(), + "The uses_shmem_testcase() bool can only exist when a map is set" + ); + // # Safety + // Struct can never be created when uses_shmem_testcase is true and map is none. + let map = unsafe { self.map.as_mut().unwrap_unchecked() }; + // The first four bytes declares the size of the shmem. + map.as_slice_mut()[..SHMEM_FUZZ_HDR_SIZE] + .copy_from_slice(&input_size_in_bytes[..SHMEM_FUZZ_HDR_SIZE]); + map.as_slice_mut()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + input_size)] + .copy_from_slice(&input_bytes.as_slice()[..input_size]); + } else { + self.input_file + .write_buf(&input_bytes.as_slice()[..input_size])?; + } + + self.forkserver.set_last_run_timed_out(false); + if let Err(err) = self.forkserver.write_ctl(last_run_timed_out) { + return Err(Error::unknown(format!( + "Unable to request new process from fork server (OOM?): {err:?}" + ))); + } + + let pid = self.forkserver.read_st().map_err(|err| { + Error::unknown(format!( + "Unable to request new process from fork server (OOM?): {err:?}" + )) + })?; + + if pid <= 0 { + return Err(Error::unknown( + "Fork server is misbehaving (OOM?)".to_string(), + )); + } + + self.forkserver.set_child_pid(Pid::from_raw(pid)); + + if let Some(status) = self.forkserver.read_st_timed(&self.timeout)? { + self.forkserver.set_status(status); + let exitcode_is_crash = if let Some(crash_exitcode) = self.crash_exitcode { + (libc::WEXITSTATUS(self.forkserver().status()) as i8) == crash_exitcode + } else { + false + }; + if libc::WIFSIGNALED(self.forkserver().status()) || exitcode_is_crash { + exit_kind = ExitKind::Crash; + #[cfg(feature = "regex")] + if let Some(asan_observer) = self.observers.get_mut(&self.asan_obs) { + asan_observer.parse_asan_output_from_asan_log_file(pid)?; + } + } + } else { + self.forkserver.set_last_run_timed_out(true); + + // We need to kill the child in case he has timed out, or we can't get the correct pid in the next call to self.executor.forkserver_mut().read_st()? + let _ = kill(self.forkserver().child_pid(), self.forkserver.kill_signal); + if let Err(err) = self.forkserver.read_st() { + return Err(Error::unknown(format!( + "Could not kill timed-out child: {err:?}" + ))); + } + exit_kind = ExitKind::Timeout; + } + + if !libc::WIFSTOPPED(self.forkserver().status()) { + self.forkserver.reset_child_pid(); + } + + Ok(exit_kind) + } } /// The builder for `ForkserverExecutor` @@ -1453,98 +1560,7 @@ where _mgr: &mut EM, input: &Self::Input, ) -> Result { - *state.executions_mut() += 1; - - let mut exit_kind = ExitKind::Ok; - - let last_run_timed_out = self.forkserver.last_run_timed_out_raw(); - - let mut input_bytes = self.target_bytes_converter.to_target_bytes(input); - let mut input_size = input_bytes.as_slice().len(); - if input_size > self.max_input_size { - // Truncate like AFL++ does - input_size = self.max_input_size; - } else if input_size < self.min_input_size { - // Extend like AFL++ does - input_size = self.min_input_size; - let mut input_bytes_copy = Vec::with_capacity(input_size); - input_bytes_copy - .as_slice_mut() - .copy_from_slice(input_bytes.as_slice()); - input_bytes = OwnedSlice::from(input_bytes_copy); - } - let input_size_in_bytes = input_size.to_ne_bytes(); - if self.uses_shmem_testcase { - debug_assert!( - self.map.is_some(), - "The uses_shmem_testcase() bool can only exist when a map is set" - ); - // # Safety - // Struct can never be created when uses_shmem_testcase is true and map is none. - let map = unsafe { self.map.as_mut().unwrap_unchecked() }; - // The first four bytes declares the size of the shmem. - map.as_slice_mut()[..SHMEM_FUZZ_HDR_SIZE] - .copy_from_slice(&input_size_in_bytes[..SHMEM_FUZZ_HDR_SIZE]); - map.as_slice_mut()[SHMEM_FUZZ_HDR_SIZE..(SHMEM_FUZZ_HDR_SIZE + input_size)] - .copy_from_slice(&input_bytes.as_slice()[..input_size]); - } else { - self.input_file - .write_buf(&input_bytes.as_slice()[..input_size])?; - } - - self.forkserver.set_last_run_timed_out(false); - if let Err(err) = self.forkserver.write_ctl(last_run_timed_out) { - return Err(Error::unknown(format!( - "Unable to request new process from fork server (OOM?): {err:?}" - ))); - } - - let pid = self.forkserver.read_st().map_err(|err| { - Error::unknown(format!( - "Unable to request new process from fork server (OOM?): {err:?}" - )) - })?; - - if pid <= 0 { - return Err(Error::unknown( - "Fork server is misbehaving (OOM?)".to_string(), - )); - } - - self.forkserver.set_child_pid(Pid::from_raw(pid)); - - if let Some(status) = self.forkserver.read_st_timed(&self.timeout)? { - self.forkserver.set_status(status); - let exitcode_is_crash = if let Some(crash_exitcode) = self.crash_exitcode { - (libc::WEXITSTATUS(self.forkserver().status()) as i8) == crash_exitcode - } else { - false - }; - if libc::WIFSIGNALED(self.forkserver().status()) || exitcode_is_crash { - exit_kind = ExitKind::Crash; - #[cfg(feature = "regex")] - if let Some(asan_observer) = self.observers.get_mut(&self.asan_obs) { - asan_observer.parse_asan_output_from_asan_log_file(pid)?; - } - } - } else { - self.forkserver.set_last_run_timed_out(true); - - // We need to kill the child in case he has timed out, or we can't get the correct pid in the next call to self.executor.forkserver_mut().read_st()? - let _ = kill(self.forkserver().child_pid(), self.forkserver.kill_signal); - if let Err(err) = self.forkserver.read_st() { - return Err(Error::unknown(format!( - "Could not kill timed-out child: {err:?}" - ))); - } - exit_kind = ExitKind::Timeout; - } - - if !libc::WIFSTOPPED(self.forkserver().status()) { - self.forkserver.reset_child_pid(); - } - - Ok(exit_kind) + self.execute_input(state, input) } } diff --git a/libafl/src/feedbacks/concolic.rs b/libafl/src/feedbacks/concolic.rs index 5d5cda21eb..93cdd658f2 100644 --- a/libafl/src/feedbacks/concolic.rs +++ b/libafl/src/feedbacks/concolic.rs @@ -38,6 +38,21 @@ impl<'map> ConcolicFeedback<'map> { observer_handle: observer.handle(), } } + + fn add_concolic_feedback_to_metadata( + &mut self, + observers: &OT, + testcase: &mut Testcase, + ) where + OT: MatchName, + { + if let Some(metadata) = observers + .get(&self.observer_handle) + .map(ConcolicObserver::create_metadata_from_current_map) + { + testcase.metadata_map_mut().insert(metadata); + } + } } impl Named for ConcolicFeedback<'_> { @@ -64,12 +79,7 @@ where observers: &OT, testcase: &mut Testcase, ) -> Result<(), Error> { - if let Some(metadata) = observers - .get(&self.observer_handle) - .map(ConcolicObserver::create_metadata_from_current_map) - { - testcase.metadata_map_mut().insert(metadata); - } + self.add_concolic_feedback_to_metadata(observers, testcase); Ok(()) } } diff --git a/libafl/src/feedbacks/list.rs b/libafl/src/feedbacks/list.rs index 1270938a48..d1590a957d 100644 --- a/libafl/src/feedbacks/list.rs +++ b/libafl/src/feedbacks/list.rs @@ -16,7 +16,7 @@ use crate::{ }; /// The metadata to remember past observed value -#[derive(Default, Serialize, Deserialize, Clone, Debug)] +#[derive(Debug, Serialize, Deserialize)] #[serde(bound = "T: Eq + Hash + for<'a> Deserialize<'a> + Serialize")] pub struct ListFeedbackMetadata { /// Contains the information of past observed set of values. @@ -42,6 +42,13 @@ impl ListFeedbackMetadata { } } +impl Default for ListFeedbackMetadata { + #[must_use] + fn default() -> Self { + Self::new() + } +} + impl HasRefCnt for ListFeedbackMetadata { fn refcnt(&self) -> isize { self.tcref @@ -60,36 +67,23 @@ pub struct ListFeedback { } libafl_bolts::impl_serdeany!( - ListFeedbackMetadata, + ListFeedbackMetadata, ,,,,,,,,,, ); -impl StateInitializer for ListFeedback +impl ListFeedback where - S: HasNamedMetadata, - T: Debug + Serialize + Hash + Eq + DeserializeOwned + Default + Copy + 'static, + T: Debug + Eq + Hash + for<'a> Deserialize<'a> + Serialize + 'static + Copy, { - fn init_state(&mut self, state: &mut S) -> Result<(), Error> { - state.add_named_metadata(self.name(), ListFeedbackMetadata::::default()); - Ok(()) - } -} - -impl Feedback for ListFeedback -where - OT: MatchName, - S: HasNamedMetadata, - T: Debug + Serialize + Hash + Eq + DeserializeOwned + Default + Copy + 'static, -{ - #[allow(clippy::wrong_self_convention)] - fn is_interesting( + fn has_interesting_list_observer_feedback( &mut self, state: &mut S, - _manager: &mut EM, - _input: &I, observers: &OT, - _exit_kind: &ExitKind, - ) -> Result { + ) -> bool + where + OT: MatchName, + S: HasNamedMetadata, + { let observer = observers.get(&self.observer_handle).unwrap(); // TODO register the list content in a testcase metadata self.novelty.clear(); @@ -103,7 +97,48 @@ where self.novelty.insert(*v); } } - Ok(!self.novelty.is_empty()) + !self.novelty.is_empty() + } + + fn append_list_observer_metadata(&mut self, state: &mut S) { + let history_set = state + .named_metadata_map_mut() + .get_mut::>(self.name()) + .unwrap(); + + for v in &self.novelty { + history_set.set.insert(*v); + } + } +} + +impl StateInitializer for ListFeedback +where + S: HasNamedMetadata, + T: Debug + Eq + Hash + for<'a> Deserialize<'a> + Serialize + Default + Copy + 'static, +{ + fn init_state(&mut self, state: &mut S) -> Result<(), Error> { + state.add_named_metadata(self.name(), ListFeedbackMetadata::::default()); + Ok(()) + } +} + +impl Feedback for ListFeedback +where + OT: MatchName, + S: HasNamedMetadata, + T: Debug + Eq + Hash + for<'a> Deserialize<'a> + Serialize + Default + Copy + 'static, +{ + #[allow(clippy::wrong_self_convention)] + fn is_interesting( + &mut self, + state: &mut S, + _manager: &mut EM, + _input: &I, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result { + Ok(self.has_interesting_list_observer_feedback(state, observers)) } #[cfg(feature = "track_hit_feedbacks")] @@ -118,14 +153,7 @@ where _observers: &OT, _testcase: &mut crate::corpus::Testcase, ) -> Result<(), Error> { - let history_set = state - .named_metadata_map_mut() - .get_mut::>(self.name()) - .unwrap(); - - for v in &self.novelty { - history_set.set.insert(*v); - } + self.append_list_observer_metadata(state); Ok(()) } } diff --git a/libafl/src/feedbacks/map.rs b/libafl/src/feedbacks/map.rs index 75a4e13bcb..e54269a7cf 100644 --- a/libafl/src/feedbacks/map.rs +++ b/libafl/src/feedbacks/map.rs @@ -407,12 +407,12 @@ where default fn is_interesting( &mut self, state: &mut S, - manager: &mut EM, - input: &I, + _manager: &mut EM, + _input: &I, observers: &OT, - exit_kind: &ExitKind, + _exit_kind: &ExitKind, ) -> Result { - let res = self.is_interesting_default(state, manager, input, observers, exit_kind); + let res = self.is_interesting_default(state, observers); #[cfg(feature = "track_hit_feedbacks")] { self.last_result = Some(res); @@ -424,12 +424,12 @@ where fn is_interesting( &mut self, state: &mut S, - manager: &mut EM, - input: &I, + _manager: &mut EM, + _input: &I, observers: &OT, - exit_kind: &ExitKind, + _exit_kind: &ExitKind, ) -> Result { - let res = self.is_interesting_default(state, manager, input, observers, exit_kind); + let res = self.is_interesting_default(state, observers); #[cfg(feature = "track_hit_feedbacks")] { @@ -553,6 +553,76 @@ where observers: &OT, _exit_kind: &ExitKind, ) -> Result { + Ok(self.is_interesting_u8_simd_optimized(state, observers)) + } +} + +impl Named for MapFeedback { + #[inline] + fn name(&self) -> &Cow<'static, str> { + &self.name + } +} + +#[allow(clippy::ptr_arg)] +fn create_stats_name(name: &Cow<'static, str>) -> Cow<'static, str> { + if name.chars().all(char::is_lowercase) { + name.clone() + } else { + name.to_lowercase().into() + } +} + +impl MapFeedback +where + C: CanTrack + AsRef + Named, +{ + /// Create new `MapFeedback` + #[must_use] + pub fn new(map_observer: &C) -> Self { + Self { + novelties: if C::NOVELTIES { Some(vec![]) } else { None }, + name: map_observer.name().clone(), + map_ref: map_observer.handle(), + stats_name: create_stats_name(map_observer.name()), + #[cfg(feature = "track_hit_feedbacks")] + last_result: None, + phantom: PhantomData, + } + } + + /// Creating a new `MapFeedback` with a specific name. This is usefully whenever the same + /// feedback is needed twice, but with a different history. Using `new()` always results in the + /// same name and therefore also the same history. + #[must_use] + pub fn with_name(name: &'static str, map_observer: &C) -> Self { + let name = Cow::from(name); + Self { + novelties: if C::NOVELTIES { Some(vec![]) } else { None }, + map_ref: map_observer.handle(), + stats_name: create_stats_name(&name), + name, + #[cfg(feature = "track_hit_feedbacks")] + last_result: None, + phantom: PhantomData, + } + } +} + +/// Specialize for the common coverage map size, maximization of u8s +#[rustversion::nightly] +impl MapFeedback +where + O: MapObserver + for<'a> AsSlice<'a, Entry = u8> + for<'a> AsIter<'a, Item = u8>, + C: CanTrack + AsRef, +{ + #[allow(clippy::wrong_self_convention)] + #[allow(clippy::needless_range_loop)] + fn is_interesting_u8_simd_optimized(&mut self, state: &mut S, observers: &OT) -> bool + where + S: HasNamedMetadata, + OT: MatchName, + { // 128 bits vectors type VectorType = core::simd::u8x16; @@ -648,14 +718,7 @@ where { self.last_result = Some(interesting); } - Ok(interesting) - } -} - -impl Named for MapFeedback { - #[inline] - fn name(&self) -> &Cow<'static, str> { - &self.name + interesting } } @@ -668,51 +731,6 @@ impl HasObserverHandle for MapFeedback { } } -#[allow(clippy::ptr_arg)] -fn create_stats_name(name: &Cow<'static, str>) -> Cow<'static, str> { - if name.chars().all(char::is_lowercase) { - name.clone() - } else { - name.to_lowercase().into() - } -} - -impl MapFeedback -where - C: CanTrack + Named + AsRef, -{ - /// Create new `MapFeedback` - #[must_use] - pub fn new(map_observer: &C) -> Self { - Self { - novelties: if C::NOVELTIES { Some(vec![]) } else { None }, - name: map_observer.name().clone(), - map_ref: map_observer.handle(), - stats_name: create_stats_name(map_observer.name()), - #[cfg(feature = "track_hit_feedbacks")] - last_result: None, - phantom: PhantomData, - } - } - - /// Creating a new `MapFeedback` with a specific name. This is usefully whenever the same - /// feedback is needed twice, but with a different history. Using `new()` always results in the - /// same name and therefore also the same history. - #[must_use] - pub fn with_name(name: &'static str, map_observer: &C) -> Self { - let name = Cow::from(name); - Self { - novelties: if C::NOVELTIES { Some(vec![]) } else { None }, - map_ref: map_observer.handle(), - stats_name: create_stats_name(&name), - name, - #[cfg(feature = "track_hit_feedbacks")] - last_result: None, - phantom: PhantomData, - } - } -} - impl MapFeedback where R: Reducer, @@ -724,14 +742,7 @@ where #[allow(clippy::wrong_self_convention)] #[allow(clippy::needless_range_loop)] #[allow(clippy::trivially_copy_pass_by_ref)] - fn is_interesting_default( - &mut self, - state: &mut S, - _manager: &mut EM, - _input: &I, - observers: &OT, - _exit_kind: &ExitKind, - ) -> bool + fn is_interesting_default(&mut self, state: &mut S, observers: &OT) -> bool where S: HasNamedMetadata, OT: MatchName, diff --git a/libafl/src/feedbacks/nautilus.rs b/libafl/src/feedbacks/nautilus.rs index eb83f17e85..5cc4986a5a 100644 --- a/libafl/src/feedbacks/nautilus.rs +++ b/libafl/src/feedbacks/nautilus.rs @@ -60,6 +60,26 @@ impl<'a> NautilusFeedback<'a> { pub fn new(context: &'a NautilusContext) -> Self { Self { ctx: &context.ctx } } + + fn append_nautilus_metadata_to_state( + &mut self, + state: &mut S, + testcase: &mut Testcase, + ) -> Result<(), Error> + where + S: HasCorpus + HasMetadata, + S::Corpus: Corpus, + { + state.corpus().load_input_into(testcase)?; + let input = testcase.input().as_ref().unwrap().clone(); + let meta = state + .metadata_map_mut() + .get_mut::() + .expect("NautilusChunksMetadata not in the state"); + meta.cks.add_tree(input.tree, self.ctx); + + Ok(()) + } } impl Named for NautilusFeedback<'_> { @@ -95,14 +115,7 @@ where _observers: &OT, testcase: &mut Testcase, ) -> Result<(), Error> { - state.corpus().load_input_into(testcase)?; - let input = testcase.input().as_ref().unwrap().clone(); - let meta = state - .metadata_map_mut() - .get_mut::() - .expect("NautilusChunksMetadata not in the state"); - meta.cks.add_tree(input.tree, self.ctx); - Ok(()) + self.append_nautilus_metadata_to_state(state, testcase) } fn discard_metadata(&mut self, _state: &mut S, _input: &NautilusInput) -> Result<(), Error> { diff --git a/libafl/src/feedbacks/new_hash_feedback.rs b/libafl/src/feedbacks/new_hash_feedback.rs index 641ce685c7..b33d14790e 100644 --- a/libafl/src/feedbacks/new_hash_feedback.rs +++ b/libafl/src/feedbacks/new_hash_feedback.rs @@ -100,34 +100,19 @@ pub struct NewHashFeedback { last_result: Option, } -impl StateInitializer for NewHashFeedback +impl NewHashFeedback where - S: HasNamedMetadata, -{ - fn init_state(&mut self, state: &mut S) -> Result<(), Error> { - state.add_named_metadata( - &self.name, - NewHashFeedbackMetadata::with_capacity(self.capacity), - ); - Ok(()) - } -} - -impl Feedback for NewHashFeedback -where - O: ObserverWithHashField, - OT: MatchName, - S: HasNamedMetadata, + O: ObserverWithHashField + Named, { #[allow(clippy::wrong_self_convention)] - fn is_interesting( + fn has_interesting_backtrace_hash_observation( &mut self, state: &mut S, - _manager: &mut EM, - _input: &I, observers: &OT, - _exit_kind: &ExitKind, - ) -> Result { + ) -> Result + where + OT: MatchName, + { let observer = observers .get(&self.o_ref) .expect("A NewHashFeedback needs a BacktraceObserver"); @@ -150,6 +135,39 @@ where } Ok(res) } +} + +impl StateInitializer for NewHashFeedback +where + S: HasNamedMetadata, +{ + fn init_state(&mut self, state: &mut S) -> Result<(), Error> { + state.add_named_metadata( + &self.name, + NewHashFeedbackMetadata::with_capacity(self.capacity), + ); + Ok(()) + } +} + +impl Feedback for NewHashFeedback +where + O: ObserverWithHashField + Named, + OT: MatchName, + S: HasNamedMetadata, +{ + #[allow(clippy::wrong_self_convention)] + fn is_interesting( + &mut self, + state: &mut S, + _manager: &mut EM, + _input: &I, + observers: &OT, + _exit_kind: &ExitKind, + ) -> Result { + self.has_interesting_backtrace_hash_observation(state, observers) + } + #[cfg(feature = "track_hit_feedbacks")] fn last_result(&self) -> Result { self.last_result.ok_or(premature_last_result_err()) diff --git a/libafl/src/feedbacks/stdio.rs b/libafl/src/feedbacks/stdio.rs index 0cc92fa139..b4a140e228 100644 --- a/libafl/src/feedbacks/stdio.rs +++ b/libafl/src/feedbacks/stdio.rs @@ -32,6 +32,34 @@ pub struct StdOutToMetadataFeedback { o_ref: Handle, } +impl StdOutToMetadataFeedback { + /// Append to the testcase the generated metadata in case of a new corpus item. + #[inline] + fn append_stdout_observation_to_testcase( + &mut self, + observers: &OT, + testcase: &mut Testcase, + ) -> Result<(), Error> + where + OT: MatchName, + { + let observer = observers + .get(&self.o_ref) + .ok_or(Error::illegal_state("StdOutObserver is missing"))?; + let buffer = observer + .stdout + .as_ref() + .ok_or(Error::illegal_state("StdOutObserver has no stdout"))?; + let stdout = String::from_utf8_lossy(buffer).into_owned(); + + testcase + .metadata_map_mut() + .insert(StdOutMetadata { stdout }); + + Ok(()) + } +} + impl StateInitializer for StdOutToMetadataFeedback {} impl Feedback for StdOutToMetadataFeedback @@ -52,20 +80,7 @@ where observers: &OT, testcase: &mut Testcase, ) -> Result<(), Error> { - let observer = observers - .get(&self.o_ref) - .ok_or(Error::illegal_state("StdOutObserver is missing"))?; - let buffer = observer - .stdout - .as_ref() - .ok_or(Error::illegal_state("StdOutObserver has no stdout"))?; - let stdout = String::from_utf8_lossy(buffer).into_owned(); - - testcase - .metadata_map_mut() - .insert(StdOutMetadata { stdout }); - - Ok(()) + self.append_stdout_observation_to_testcase(observers, testcase) } } diff --git a/libafl/src/stages/dump.rs b/libafl/src/stages/dump.rs index 90ac253890..ecfc2c2a10 100644 --- a/libafl/src/stages/dump.rs +++ b/libafl/src/stages/dump.rs @@ -61,6 +61,70 @@ where state: &mut Self::State, _manager: &mut EM, ) -> Result<(), Error> { + self.dump_state_to_disk(state) + } + + #[inline] + fn should_restart(&mut self, _state: &mut Self::State) -> Result { + // Not executing the target, so restart safety is not needed + Ok(true) + } + + #[inline] + fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> { + // Not executing the target, so restart safety is not needed + Ok(()) + } +} + +impl DumpToDiskStage +where + EM: UsesState, + Z: UsesState, + ::State: HasCorpus + HasSolutions + HasRand + HasMetadata, + <::State as HasCorpus>::Corpus: Corpus, + <::State as HasSolutions>::Solutions: Corpus, +{ + /// Create a new [`DumpToDiskStage`] + pub fn new(to_bytes: CB, corpus_dir: A, solutions_dir: B) -> Result + where + A: Into, + B: Into, + { + let corpus_dir = corpus_dir.into(); + if let Err(e) = fs::create_dir(&corpus_dir) { + if !corpus_dir.is_dir() { + return Err(Error::os_error( + e, + format!("Error creating directory {corpus_dir:?}"), + )); + } + } + let solutions_dir = solutions_dir.into(); + if let Err(e) = fs::create_dir(&solutions_dir) { + if !corpus_dir.is_dir() { + return Err(Error::os_error( + e, + format!("Error creating directory {solutions_dir:?}"), + )); + } + } + Ok(Self { + to_bytes, + solutions_dir, + corpus_dir, + phantom: PhantomData, + }) + } + + #[inline] + fn dump_state_to_disk(&mut self, state: &mut ::State) -> Result<(), Error> + where + CB: FnMut( + &<<::State as HasCorpus>::Corpus as Corpus>::Input, + &::State, + ) -> Vec, + { let (mut corpus_id, mut solutions_id) = if let Some(meta) = state.metadata_map().get::() { ( @@ -114,55 +178,4 @@ where Ok(()) } - - #[inline] - fn should_restart(&mut self, _state: &mut Self::State) -> Result { - // Not executing the target, so restart safety is not needed - Ok(true) - } - - #[inline] - fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> { - // Not executing the target, so restart safety is not needed - Ok(()) - } -} - -impl DumpToDiskStage -where - EM: UsesState, - Z: UsesState, - ::State: HasCorpus + HasSolutions + HasRand + HasMetadata, -{ - /// Create a new [`DumpToDiskStage`] - pub fn new(to_bytes: CB, corpus_dir: A, solutions_dir: B) -> Result - where - A: Into, - B: Into, - { - let corpus_dir = corpus_dir.into(); - if let Err(e) = fs::create_dir(&corpus_dir) { - if !corpus_dir.is_dir() { - return Err(Error::os_error( - e, - format!("Error creating directory {corpus_dir:?}"), - )); - } - } - let solutions_dir = solutions_dir.into(); - if let Err(e) = fs::create_dir(&solutions_dir) { - if !corpus_dir.is_dir() { - return Err(Error::os_error( - e, - format!("Error creating directory {solutions_dir:?}"), - )); - } - } - Ok(Self { - to_bytes, - solutions_dir, - corpus_dir, - phantom: PhantomData, - }) - } } diff --git a/libafl/src/stages/stats.rs b/libafl/src/stages/stats.rs index 2bcf4742a2..6be3de2fd7 100644 --- a/libafl/src/stages/stats.rs +++ b/libafl/src/stages/stats.rs @@ -62,6 +62,33 @@ where state: &mut Self::State, _manager: &mut EM, ) -> Result<(), Error> { + self.update_and_report_afl_stats(state, _manager) + } + + #[inline] + fn should_restart(&mut self, _state: &mut Self::State) -> Result { + // Not running the target so we wont't crash/timeout and, hence, don't need to restore anything + Ok(true) + } + + #[inline] + fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> { + // Not running the target so we wont't crash/timeout and, hence, don't need to restore anything + Ok(()) + } +} + +impl StatsStage { + fn update_and_report_afl_stats( + &mut self, + state: &mut ::State, + _manager: &mut EM, + ) -> Result<(), Error> + where + E: UsesState, + EM: EventFirer, + ::State: HasCorpus + HasImported, + { let Some(corpus_id) = state.current_corpus_id()? else { return Err(Error::illegal_state( "state is not currently processing a corpus index", @@ -124,18 +151,6 @@ where Ok(()) } - - #[inline] - fn should_restart(&mut self, _state: &mut Self::State) -> Result { - // Not running the target so we wont't crash/timeout and, hence, don't need to restore anything - Ok(true) - } - - #[inline] - fn clear_progress(&mut self, _state: &mut Self::State) -> Result<(), Error> { - // Not running the target so we wont't crash/timeout and, hence, don't need to restore anything - Ok(()) - } } impl StatsStage { diff --git a/libafl/src/stages/unicode.rs b/libafl/src/stages/unicode.rs index efc57b4f81..dc636bce8d 100644 --- a/libafl/src/stages/unicode.rs +++ b/libafl/src/stages/unicode.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use crate::{ corpus::Corpus, - inputs::{BytesInput, HasMutatorBytes}, + inputs::{BytesInput, HasTargetBytes}, stages::Stage, state::{HasCorpus, HasCurrentTestcase, State, UsesState}, HasMetadata, @@ -89,6 +89,24 @@ impl UnicodeIdentificationStage { phantom: PhantomData, } } + fn identify_unicode_in_current_testcase(state: &mut S) -> Result<(), Error> + where + S: HasCurrentTestcase, + ::Input: HasTargetBytes, + { + let mut tc = state.current_testcase_mut()?; + if tc.has_metadata::() { + return Ok(()); // skip recompute + } + + let input = tc.load_input(state.corpus())?; + + let bytes = input.target_bytes(); + let metadata = extract_metadata(&bytes); + tc.add_metadata(metadata); + + Ok(()) + } } impl UsesState for UnicodeIdentificationStage @@ -113,18 +131,7 @@ where state: &mut Self::State, _manager: &mut EM, ) -> Result<(), Error> { - let mut tc = state.current_testcase_mut()?; - if tc.has_metadata::() { - return Ok(()); // skip recompute - } - - let input = tc.load_input(state.corpus())?; - - let bytes = input.bytes(); - let metadata = extract_metadata(bytes); - tc.add_metadata(metadata); - - Ok(()) + UnicodeIdentificationStage::identify_unicode_in_current_testcase(state) } #[inline]