diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 80c028040d..be72098072 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -26,6 +26,9 @@ document-features = ["dep:document-features"] ## Enables features that need rust's `std` lib to work, like print, env, ... support std = ["serde_json", "serde_json/std", "nix", "serde/std", "bincode", "wait-timeout", "uuid", "backtrace", "serial_test", "libafl_bolts/std", "typed-builder"] +## Tracks the Feedbacks and the Objectives that were interesting for a Testcase +track_hit_feedbacks = ["std"] + ## Collects performance statistics of the fuzzing pipeline and displays it on `Monitor` components introspection = [] diff --git a/libafl/src/corpus/testcase.rs b/libafl/src/corpus/testcase.rs index 6620483dc2..2808c8a8e0 100644 --- a/libafl/src/corpus/testcase.rs +++ b/libafl/src/corpus/testcase.rs @@ -2,6 +2,8 @@ //! It will contain a respective input, and metadata. use alloc::string::String; +#[cfg(feature = "track_hit_feedbacks")] +use alloc::{borrow::Cow, vec::Vec}; use core::{ cell::{Ref, RefMut}, time::Duration, @@ -67,6 +69,12 @@ where disabled: bool, /// has found crash (or timeout) or not objectives_found: usize, + /// Vector of `Feedback` names that deemed this `Testcase` as corpus worthy + #[cfg(feature = "track_hit_feedbacks")] + hit_feedbacks: Vec>, + /// Vector of `Feedback` names that deemed this `Testcase` as solution worthy + #[cfg(feature = "track_hit_feedbacks")] + hit_objectives: Vec>, } impl HasMetadata for Testcase @@ -211,6 +219,34 @@ where self.disabled = disabled; } + /// Get the hit feedbacks + #[inline] + #[cfg(feature = "track_hit_feedbacks")] + pub fn hit_feedbacks(&self) -> &Vec> { + &self.hit_feedbacks + } + + /// Get the hit feedbacks (mutable) + #[inline] + #[cfg(feature = "track_hit_feedbacks")] + pub fn hit_feedbacks_mut(&mut self) -> &mut Vec> { + &mut self.hit_feedbacks + } + + /// Get the hit objectives + #[inline] + #[cfg(feature = "track_hit_feedbacks")] + pub fn hit_objectives(&self) -> &Vec> { + &self.hit_objectives + } + + /// Get the hit objectives (mutable) + #[inline] + #[cfg(feature = "track_hit_feedbacks")] + pub fn hit_objectives_mut(&mut self) -> &mut Vec> { + &mut self.hit_objectives + } + /// Create a new Testcase instance given an input #[inline] pub fn new(mut input: I) -> Self { @@ -230,6 +266,10 @@ where parent_id: None, disabled: false, objectives_found: 0, + #[cfg(feature = "track_hit_feedbacks")] + hit_feedbacks: Vec::new(), + #[cfg(feature = "track_hit_feedbacks")] + hit_objectives: Vec::new(), } } @@ -252,6 +292,10 @@ where parent_id: Some(parent_id), disabled: false, objectives_found: 0, + #[cfg(feature = "track_hit_feedbacks")] + hit_feedbacks: Vec::new(), + #[cfg(feature = "track_hit_feedbacks")] + hit_objectives: Vec::new(), } } @@ -274,6 +318,10 @@ where parent_id: None, disabled: false, objectives_found: 0, + #[cfg(feature = "track_hit_feedbacks")] + hit_feedbacks: Vec::new(), + #[cfg(feature = "track_hit_feedbacks")] + hit_objectives: Vec::new(), } } @@ -296,6 +344,10 @@ where parent_id: None, disabled: false, objectives_found: 0, + #[cfg(feature = "track_hit_feedbacks")] + hit_feedbacks: Vec::new(), + #[cfg(feature = "track_hit_feedbacks")] + hit_objectives: Vec::new(), } } @@ -348,6 +400,10 @@ where metadata_path: None, disabled: false, objectives_found: 0, + #[cfg(feature = "track_hit_feedbacks")] + hit_feedbacks: Vec::new(), + #[cfg(feature = "track_hit_feedbacks")] + hit_objectives: Vec::new(), } } } diff --git a/libafl/src/feedbacks/concolic.rs b/libafl/src/feedbacks/concolic.rs index 33593fb730..4f613f8fd5 100644 --- a/libafl/src/feedbacks/concolic.rs +++ b/libafl/src/feedbacks/concolic.rs @@ -97,4 +97,9 @@ where ) -> Result<(), Error> { Ok(()) } + + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok(false) + } } diff --git a/libafl/src/feedbacks/differential.rs b/libafl/src/feedbacks/differential.rs index 7da601e319..420f5572bc 100644 --- a/libafl/src/feedbacks/differential.rs +++ b/libafl/src/feedbacks/differential.rs @@ -13,6 +13,8 @@ use libafl_bolts::{ }; use serde::{Deserialize, Serialize}; +#[cfg(feature = "track_hit_feedbacks")] +use crate::feedbacks::premature_last_result_err; use crate::{ events::EventFirer, executors::ExitKind, @@ -61,6 +63,9 @@ where o1_ref: Handle, /// The second observer to compare against o2_ref: Handle, + // The previous run's result of `Self::is_interesting` + #[cfg(feature = "track_hit_feedbacks")] + last_result: Option, /// The function used to compare the two observers compare_fn: F, phantomm: PhantomData<(I, S)>, @@ -86,6 +91,8 @@ where o1_ref, o2_ref, name: Cow::from(name), + #[cfg(feature = "track_hit_feedbacks")] + last_result: None, compare_fn, phantomm: PhantomData, }) @@ -108,6 +115,8 @@ where o1_ref: self.o1_ref.clone(), o2_ref: self.o2_ref.clone(), compare_fn: self.compare_fn.clone(), + #[cfg(feature = "track_hit_feedbacks")] + last_result: None, phantomm: self.phantomm, } } @@ -169,8 +178,17 @@ where let o2: &O2 = observers .get(&self.o2_ref) .ok_or_else(|| err(self.o2_ref.name()))?; + let res = (self.compare_fn)(o1, o2) == DiffResult::Diff; + #[cfg(feature = "track_hit_feedbacks")] + { + self.last_result = Some(res); + } + Ok(res) + } - Ok((self.compare_fn)(o1, o2) == DiffResult::Diff) + #[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/list.rs b/libafl/src/feedbacks/list.rs index 0f5e89d200..4c4f56a46f 100644 --- a/libafl/src/feedbacks/list.rs +++ b/libafl/src/feedbacks/list.rs @@ -143,6 +143,10 @@ where } Ok(()) } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok(!self.novelty.is_empty()) + } } impl Named for ListFeedback diff --git a/libafl/src/feedbacks/map.rs b/libafl/src/feedbacks/map.rs index e80053326d..127c0245a9 100644 --- a/libafl/src/feedbacks/map.rs +++ b/libafl/src/feedbacks/map.rs @@ -18,6 +18,8 @@ use libafl_bolts::{ use num_traits::PrimInt; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +#[cfg(feature = "track_hit_feedbacks")] +use crate::feedbacks::premature_last_result_err; use crate::{ corpus::Testcase, events::{Event, EventFirer}, @@ -391,6 +393,9 @@ pub struct MapFeedback { map_ref: Handle, /// Name of the feedback as shown in the `UserStats` stats_name: Cow<'static, str>, + // The previous run's result of [`Self::is_interesting`] + #[cfg(feature = "track_hit_feedbacks")] + last_result: Option, /// Phantom Data of Reducer phantom: PhantomData<(C, N, O, R, T)>, } @@ -424,7 +429,12 @@ where EM: EventFirer, OT: ObserversTuple, { - Ok(self.is_interesting_default(state, manager, input, observers, exit_kind)) + let res = self.is_interesting_default(state, manager, input, observers, exit_kind); + #[cfg(feature = "track_hit_feedbacks")] + { + self.last_result = Some(res); + } + Ok(res) } #[rustversion::not(nightly)] @@ -440,7 +450,13 @@ where EM: EventFirer, OT: ObserversTuple, { - Ok(self.is_interesting_default(state, manager, input, observers, exit_kind)) + let res = self.is_interesting_default(state, manager, input, observers, exit_kind); + + #[cfg(feature = "track_hit_feedbacks")] + { + self.last_result = Some(res); + } + Ok(res) } fn append_metadata( @@ -533,6 +549,11 @@ where Ok(()) } + + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + self.last_result.ok_or(premature_last_result_err()) + } } /// Specialize for the common coverage map size, maximization of u8s @@ -648,7 +669,10 @@ where } } } - + #[cfg(feature = "track_hit_feedbacks")] + { + self.last_result = Some(interesting); + } Ok(interesting) } } @@ -699,6 +723,8 @@ where 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, } } @@ -714,6 +740,8 @@ where map_ref: map_observer.handle(), stats_name: create_stats_name(&name), name, + #[cfg(feature = "track_hit_feedbacks")] + last_result: None, phantom: PhantomData, } } diff --git a/libafl/src/feedbacks/mod.rs b/libafl/src/feedbacks/mod.rs index 4ed091b2e8..4a8db96c7d 100644 --- a/libafl/src/feedbacks/mod.rs +++ b/libafl/src/feedbacks/mod.rs @@ -5,6 +5,8 @@ // TODO: make S of Feedback an associated type when specialisation + AT is stable use alloc::borrow::Cow; +#[cfg(feature = "track_hit_feedbacks")] +use alloc::vec::Vec; use core::{ fmt::{self, Debug, Formatter}, marker::PhantomData, @@ -35,12 +37,10 @@ use crate::{ state::State, Error, }; - -pub mod map; - #[cfg(feature = "std")] pub mod concolic; pub mod differential; +pub mod map; #[cfg(feature = "nautilus")] pub mod nautilus; #[cfg(feature = "std")] @@ -113,6 +113,21 @@ where ret } + /// CUT MY LIFE INTO PIECES; THIS IS MY LAST [`Feedback::is_interesting`] run + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result; + + /// Append this [`Feedback`]'s name if [`Feedback::last_result`] is true + /// If you have any nested Feedbacks, you must call this function on them if relevant. + /// See the implementations of [`CombinedFeedback`] + #[cfg(feature = "track_hit_feedbacks")] + fn append_hit_feedbacks(&self, list: &mut Vec>) -> Result<(), Error> { + if self.last_result()? { + list.push(self.name().clone()); + } + Ok(()) + } + /// Append to the testcase the generated metadata in case of a new corpus item #[inline] #[allow(unused_variables)] @@ -211,7 +226,14 @@ where self.second.init_state(state)?; Ok(()) } - + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + FL::last_result(&self.first, &self.second) + } + #[cfg(feature = "track_hit_feedbacks")] + fn append_hit_feedbacks(&self, list: &mut Vec>) -> Result<(), Error> { + FL::append_hit_feedbacks(&self.first, &self.second, list) + } #[allow(clippy::wrong_self_convention)] fn is_interesting( &mut self, @@ -326,6 +348,20 @@ where EM: EventFirer, OT: ObserversTuple; + /// Get the result of the last `Self::is_interesting` run + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(first: &A, second: &B) -> Result; + + /// Append this [`Feedback`]'s name if [`Feedback::last_result`] is true + /// If you have any nested Feedbacks, you must call this function on them if relevant. + /// See the implementations of [`CombinedFeedback`] + #[cfg(feature = "track_hit_feedbacks")] + fn append_hit_feedbacks( + first: &A, + second: &B, + list: &mut Vec>, + ) -> Result<(), Error>; + /// If this pair is interesting (with introspection features enabled) #[cfg(feature = "introspection")] #[allow(clippy::too_many_arguments)] @@ -407,6 +443,27 @@ where let b = second.is_interesting(state, manager, input, observers, exit_kind)?; Ok(a || b) } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(first: &A, second: &B) -> Result { + Ok(first.last_result()? || second.last_result()?) + } + /// Note: Eager OR's hit feedbacks will behave like Fast OR + /// because the second feedback will not have contributed to the result. + /// Set the second feedback as the first (A, B) vs (B, A) + /// to "prioritize" the result in case of Eager OR. + #[cfg(feature = "track_hit_feedbacks")] + fn append_hit_feedbacks( + first: &A, + second: &B, + list: &mut Vec>, + ) -> Result<(), Error> { + if first.last_result()? { + first.append_hit_feedbacks(list)?; + } else if second.last_result()? { + second.append_hit_feedbacks(list)?; + } + Ok(()) + } #[cfg(feature = "introspection")] fn is_pair_interesting_introspection( @@ -460,6 +517,28 @@ where second.is_interesting(state, manager, input, observers, exit_kind) } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(first: &A, second: &B) -> Result { + if first.last_result()? { + return Ok(true); + } + + // The second must have run if the first wasn't interesting + second.last_result() + } + #[cfg(feature = "track_hit_feedbacks")] + fn append_hit_feedbacks( + first: &A, + second: &B, + list: &mut Vec>, + ) -> Result<(), Error> { + if first.last_result()? { + first.append_hit_feedbacks(list)?; + } else if second.last_result()? { + second.append_hit_feedbacks(list)?; + } + Ok(()) + } #[cfg(feature = "introspection")] fn is_pair_interesting_introspection( @@ -514,6 +593,23 @@ where Ok(a && b) } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(first: &A, second: &B) -> Result { + Ok(first.last_result()? && second.last_result()?) + } + #[cfg(feature = "track_hit_feedbacks")] + fn append_hit_feedbacks( + first: &A, + second: &B, + list: &mut Vec>, + ) -> Result<(), Error> { + if first.last_result()? && second.last_result()? { + first.append_hit_feedbacks(list)?; + second.append_hit_feedbacks(list)?; + } + Ok(()) + } + #[cfg(feature = "introspection")] fn is_pair_interesting_introspection( first: &mut A, @@ -567,6 +663,30 @@ where second.is_interesting(state, manager, input, observers, exit_kind) } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(first: &A, second: &B) -> Result { + if !first.last_result()? { + return Ok(false); + } + + // The second must have run if the first wasn't interesting + second.last_result() + } + + #[cfg(feature = "track_hit_feedbacks")] + fn append_hit_feedbacks( + first: &A, + second: &B, + list: &mut Vec>, + ) -> Result<(), Error> { + if first.last_result()? { + first.append_hit_feedbacks(list)?; + } else if second.last_result()? { + second.append_hit_feedbacks(list)?; + } + Ok(()) + } + #[cfg(feature = "introspection")] fn is_pair_interesting_introspection( first: &mut A, @@ -684,6 +804,11 @@ where fn discard_metadata(&mut self, state: &mut S, input: &S::Input) -> Result<(), Error> { self.first.discard_metadata(state, input) } + + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok(!self.first.last_result()?) + } } impl Named for NotFeedback @@ -785,11 +910,19 @@ where { Ok(false) } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok(false) + } } /// A [`CrashFeedback`] reports as interesting if the target crashed. #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct CrashFeedback; +pub struct CrashFeedback { + #[cfg(feature = "track_hit_feedbacks")] + // The previous run's result of `Self::is_interesting` + last_result: Option, +} impl Feedback for CrashFeedback where @@ -808,11 +941,17 @@ where EM: EventFirer, OT: ObserversTuple, { - if let ExitKind::Crash = exit_kind { - Ok(true) - } else { - Ok(false) + let res = matches!(exit_kind, ExitKind::Crash); + #[cfg(feature = "track_hit_feedbacks")] + { + self.last_result = Some(res); } + Ok(res) + } + + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + self.last_result.ok_or(premature_last_result_err()) } } @@ -828,7 +967,10 @@ impl CrashFeedback { /// Creates a new [`CrashFeedback`] #[must_use] pub fn new() -> Self { - Self + Self { + #[cfg(feature = "track_hit_feedbacks")] + last_result: None, + } } } @@ -846,7 +988,11 @@ impl FeedbackFactory for CrashFeedback { /// A [`TimeoutFeedback`] reduces the timeout value of a run. #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct TimeoutFeedback; +pub struct TimeoutFeedback { + #[cfg(feature = "track_hit_feedbacks")] + // The previous run's result of `Self::is_interesting` + last_result: Option, +} impl Feedback for TimeoutFeedback where @@ -865,11 +1011,17 @@ where EM: EventFirer, OT: ObserversTuple, { - if let ExitKind::Timeout = exit_kind { - Ok(true) - } else { - Ok(false) + let res = matches!(exit_kind, ExitKind::Timeout); + #[cfg(feature = "track_hit_feedbacks")] + { + self.last_result = Some(res); } + Ok(res) + } + + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + self.last_result.ok_or(premature_last_result_err()) } } @@ -885,7 +1037,10 @@ impl TimeoutFeedback { /// Returns a new [`TimeoutFeedback`]. #[must_use] pub fn new() -> Self { - Self + Self { + #[cfg(feature = "track_hit_feedbacks")] + last_result: None, + } } } @@ -904,7 +1059,11 @@ impl FeedbackFactory for TimeoutFeedback { /// A [`DiffExitKindFeedback`] checks if there is a difference in the [`crate::executors::ExitKind`]s in a [`crate::executors::DiffExecutor`]. #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct DiffExitKindFeedback; +pub struct DiffExitKindFeedback { + #[cfg(feature = "track_hit_feedbacks")] + // The previous run's result of `Self::is_interesting` + last_result: Option, +} impl Feedback for DiffExitKindFeedback where @@ -923,7 +1082,16 @@ where EM: EventFirer, OT: ObserversTuple, { - Ok(matches!(exit_kind, ExitKind::Diff { .. })) + let res = matches!(exit_kind, ExitKind::Diff { .. }); + #[cfg(feature = "track_hit_feedbacks")] + { + self.last_result = Some(res); + } + Ok(res) + } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + self.last_result.ok_or(premature_last_result_err()) } } @@ -939,7 +1107,10 @@ impl DiffExitKindFeedback { /// Returns a new [`DiffExitKindFeedback`]. #[must_use] pub fn new() -> Self { - Self + Self { + #[cfg(feature = "track_hit_feedbacks")] + last_result: None, + } } } @@ -1008,6 +1179,11 @@ where fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> { Ok(()) } + + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok(false) + } } impl Named for TimeFeedback { @@ -1055,10 +1231,12 @@ where EM: EventFirer, OT: ObserversTuple, { - Ok(match self { - ConstFeedback::True => true, - ConstFeedback::False => false, - }) + Ok((*self).into()) + } + + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok((*self).into()) } } @@ -1087,3 +1265,18 @@ impl From for ConstFeedback { } } } + +impl From for bool { + fn from(value: ConstFeedback) -> Self { + match value { + ConstFeedback::True => true, + ConstFeedback::False => false, + } + } +} + +#[cfg(feature = "track_hit_feedbacks")] +/// Error if [`Feedback::last_result`] is called before the `Feedback` is actually run. +pub(crate) fn premature_last_result_err() -> Error { + Error::illegal_state("last_result called before Feedback was run") +} diff --git a/libafl/src/feedbacks/nautilus.rs b/libafl/src/feedbacks/nautilus.rs index a2407577ce..f009cfe730 100644 --- a/libafl/src/feedbacks/nautilus.rs +++ b/libafl/src/feedbacks/nautilus.rs @@ -123,4 +123,8 @@ where fn discard_metadata(&mut self, _state: &mut S, _input: &NautilusInput) -> Result<(), Error> { Ok(()) } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok(false) + } } diff --git a/libafl/src/feedbacks/new_hash_feedback.rs b/libafl/src/feedbacks/new_hash_feedback.rs index b52806801c..11427fbc45 100644 --- a/libafl/src/feedbacks/new_hash_feedback.rs +++ b/libafl/src/feedbacks/new_hash_feedback.rs @@ -10,6 +10,8 @@ use libafl_bolts::{ }; use serde::{Deserialize, Serialize}; +#[cfg(feature = "track_hit_feedbacks")] +use crate::feedbacks::premature_last_result_err; use crate::{ events::EventFirer, executors::ExitKind, @@ -85,6 +87,9 @@ pub struct NewHashFeedback { o_ref: Handle, /// Initial capacity of hash set capacity: usize, + #[cfg(feature = "track_hit_feedbacks")] + // The previous run's result of `Self::is_interesting` + last_result: Option, phantom: PhantomData, } @@ -123,18 +128,22 @@ where .get_mut::(&self.name) .unwrap(); - match observer.hash() { - Some(hash) => { - let res = backtrace_state - .update_hash_set(hash) - .expect("Failed to update the hash state"); - Ok(res) - } + let res = match observer.hash() { + Some(hash) => backtrace_state.update_hash_set(hash)?, None => { // We get here if the hash was not updated, i.e the first run or if no crash happens - Ok(false) + false } + }; + #[cfg(feature = "track_hit_feedbacks")] + { + self.last_result = Some(res); } + Ok(res) + } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + self.last_result.ok_or(premature_last_result_err()) } } @@ -178,6 +187,8 @@ where name: Cow::from(NEWHASHFEEDBACK_PREFIX.to_string() + observer.name()), o_ref: observer.handle(), capacity, + #[cfg(feature = "track_hit_feedbacks")] + last_result: None, phantom: PhantomData, } } diff --git a/libafl/src/feedbacks/stdio.rs b/libafl/src/feedbacks/stdio.rs index 6c8137a515..2007305c63 100644 --- a/libafl/src/feedbacks/stdio.rs +++ b/libafl/src/feedbacks/stdio.rs @@ -90,6 +90,11 @@ where fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> { Ok(()) } + + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok(false) + } } impl Named for StdOutToMetadataFeedback { @@ -180,6 +185,10 @@ where fn discard_metadata(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> { Ok(()) } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok(false) + } } impl Named for StdErrToMetadataFeedback { diff --git a/libafl/src/feedbacks/transferred.rs b/libafl/src/feedbacks/transferred.rs index d6d2c36b5a..fbae4950f9 100644 --- a/libafl/src/feedbacks/transferred.rs +++ b/libafl/src/feedbacks/transferred.rs @@ -6,11 +6,12 @@ use alloc::borrow::Cow; use libafl_bolts::{impl_serdeany, Error, Named}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "track_hit_feedbacks")] +use crate::feedbacks::premature_last_result_err; use crate::{ events::EventFirer, executors::ExitKind, feedbacks::Feedback, observers::ObserversTuple, state::State, HasMetadata, }; - /// Constant name of the [`TransferringMetadata`]. pub const TRANSFERRED_FEEDBACK_NAME: Cow<'static, str> = Cow::Borrowed("transferred_feedback_internal"); @@ -37,7 +38,11 @@ impl TransferringMetadata { /// Simple feedback which may be used to test whether the testcase was transferred from another node /// in a multi-node fuzzing arrangement. #[derive(Copy, Clone, Debug)] -pub struct TransferredFeedback; +pub struct TransferredFeedback { + #[cfg(feature = "track_hit_feedbacks")] + // The previous run's result of `Self::is_interesting` + last_result: Option, +} impl Named for TransferredFeedback { fn name(&self) -> &Cow<'static, str> { @@ -66,6 +71,15 @@ where EM: EventFirer, OT: ObserversTuple, { - Ok(state.metadata::()?.transferring) + let res = state.metadata::()?.transferring; + #[cfg(feature = "track_hit_feedbacks")] + { + self.last_result = Some(res); + } + Ok(res) + } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + self.last_result.ok_or(premature_last_result_err()) } } diff --git a/libafl/src/fuzzer/mod.rs b/libafl/src/fuzzer/mod.rs index e68127a61d..4d03c117cc 100644 --- a/libafl/src/fuzzer/mod.rs +++ b/libafl/src/fuzzer/mod.rs @@ -465,6 +465,9 @@ where // Add the input to the main corpus let mut testcase = Testcase::with_executions(input.clone(), *state.executions()); + #[cfg(feature = "track_hit_feedbacks")] + self.feedback_mut() + .append_hit_feedbacks(testcase.hit_feedbacks_mut())?; self.feedback_mut() .append_metadata(state, manager, observers, &mut testcase)?; let idx = state.corpus_mut().add(testcase)?; @@ -507,6 +510,9 @@ where if let Ok(mut tc) = state.current_testcase_mut() { tc.found_objective(); } + #[cfg(feature = "track_hit_feedbacks")] + self.objective_mut() + .append_hit_feedbacks(testcase.hit_objectives_mut())?; self.objective_mut() .append_metadata(state, manager, observers, &mut testcase)?; state.solutions_mut().add(testcase)?; @@ -621,6 +627,9 @@ where )?; if is_solution { + #[cfg(feature = "track_hit_feedbacks")] + self.objective_mut() + .append_hit_feedbacks(testcase.hit_objectives_mut())?; self.objective_mut() .append_metadata(state, manager, &*observers, &mut testcase)?; let idx = state.solutions_mut().add(testcase)?; @@ -656,6 +665,9 @@ where &exit_kind, )?; + #[cfg(feature = "track_hit_feedbacks")] + self.feedback_mut() + .append_hit_feedbacks(testcase.hit_feedbacks_mut())?; // Add the input to the main corpus self.feedback_mut() .append_metadata(state, manager, &*observers, &mut testcase)?; diff --git a/libafl/src/stages/tmin.rs b/libafl/src/stages/tmin.rs index cc83120ab4..e3850a7565 100644 --- a/libafl/src/stages/tmin.rs +++ b/libafl/src/stages/tmin.rs @@ -9,6 +9,8 @@ use libafl_bolts::{ HasLen, Named, }; +#[cfg(feature = "track_hit_feedbacks")] +use crate::feedbacks::premature_last_result_err; use crate::{ corpus::{Corpus, HasCurrentCorpusId, Testcase}, events::EventFirer, @@ -31,7 +33,6 @@ use crate::{ }; #[cfg(feature = "introspection")] use crate::{monitors::PerfFeature, state::HasClientPerfMonitor}; - /// Mutational stage which minimizes corpus entries. /// /// You must provide at least one mutator that actually reduces size. @@ -355,6 +356,9 @@ pub struct MapEqualityFeedback { name: Cow<'static, str>, map_ref: Handle, orig_hash: u64, + #[cfg(feature = "track_hit_feedbacks")] + // The previous run's result of `Self::is_interesting` + last_result: Option, phantom: PhantomData<(M, S)>, } @@ -393,7 +397,16 @@ where let obs = observers .get(self.observer_handle()) .expect("Should have been provided valid observer name."); - Ok(obs.as_ref().hash_simple() == self.orig_hash) + let res = obs.as_ref().hash_simple() == self.orig_hash; + #[cfg(feature = "track_hit_feedbacks")] + { + self.last_result = Some(res); + } + Ok(res) + } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + self.last_result.ok_or(premature_last_result_err()) } } @@ -442,6 +455,8 @@ where name: Cow::from("MapEq"), map_ref: obs.handle(), orig_hash: obs.as_ref().hash_simple(), + #[cfg(feature = "track_hit_feedbacks")] + last_result: None, phantom: PhantomData, } } diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index c9a27cea97..1ce841c4ef 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -24,7 +24,7 @@ all-features = true default = ["serdeany_autoreg"] cmplog = ["iced-x86"] serdeany_autoreg = ["libafl_bolts/serdeany_autoreg"] - +track_hit_feedbacks = ["libafl/track_hit_feedbacks"] [build-dependencies] cc = { version = "1.0", features = ["parallel"] } diff --git a/libafl_frida/src/asan/errors.rs b/libafl_frida/src/asan/errors.rs index 44d1d9d9ea..2e8de86622 100644 --- a/libafl_frida/src/asan/errors.rs +++ b/libafl_frida/src/asan/errors.rs @@ -701,6 +701,11 @@ where self.errors = None; Ok(()) } + + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok(self.errors.is_some()) + } } impl Named for AsanErrorsFeedback { diff --git a/libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml b/libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml index a2a51f0871..dbd7e95029 100644 --- a/libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml +++ b/libafl_libfuzzer/libafl_libfuzzer_runtime/Cargo.toml @@ -10,6 +10,7 @@ publish = false default = ["fork"] ## Enables forking mode for the LibAFL launcher (instead of starting new processes) fork = ["libafl/fork"] +track_hit_feedbacks = ["libafl/track_hit_feedbacks", "libafl_targets/track_hit_feedbacks"] [profile.release] lto = true @@ -47,6 +48,7 @@ utf8-chars = "3.0.1" env_logger = "0.10" + [build-dependencies] bindgen = "0.69.4" cc = { version = "1.0", features = ["parallel"] } diff --git a/libafl_libfuzzer/libafl_libfuzzer_runtime/src/feedbacks.rs b/libafl_libfuzzer/libafl_libfuzzer_runtime/src/feedbacks.rs index 72ad044443..a03c813459 100644 --- a/libafl_libfuzzer/libafl_libfuzzer_runtime/src/feedbacks.rs +++ b/libafl_libfuzzer/libafl_libfuzzer_runtime/src/feedbacks.rs @@ -61,6 +61,11 @@ where { Ok(*self.keep.borrow()) } + + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok(*self.keep.borrow()) + } } #[derive(Deserialize, Serialize, Debug)] @@ -133,6 +138,10 @@ where self.exit_kind = *exit_kind; Ok(false) } + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok(false) + } fn append_metadata( &mut self, diff --git a/libafl_libfuzzer/libafl_libfuzzer_runtime/src/tmin.rs b/libafl_libfuzzer/libafl_libfuzzer_runtime/src/tmin.rs index 82b8398871..dab8f27433 100644 --- a/libafl_libfuzzer/libafl_libfuzzer_runtime/src/tmin.rs +++ b/libafl_libfuzzer/libafl_libfuzzer_runtime/src/tmin.rs @@ -93,7 +93,7 @@ fn minimize_crash_with_mutator>( fuzzer.fuzz_one(&mut stages, &mut executor, &mut state, &mut mgr)?; } ExitKind::Timeout => { - let factory = TimeoutFeedback; + let factory = TimeoutFeedback::new(); let tmin = StdTMinMutationalStage::new( mutator, factory, diff --git a/libafl_nyx/src/executor.rs b/libafl_nyx/src/executor.rs index 2383e6090e..d9cc30de44 100644 --- a/libafl_nyx/src/executor.rs +++ b/libafl_nyx/src/executor.rs @@ -7,7 +7,7 @@ use std::{ use libafl::{ executors::{Executor, ExitKind, HasObservers}, inputs::HasTargetBytes, - observers::{ObserversTuple, StdErrObserver, StdOutObserver, UsesObservers}, + observers::{ObserversTuple, StdOutObserver, UsesObservers}, state::{HasExecutions, State, UsesState}, Error, }; @@ -153,6 +153,12 @@ pub struct NyxExecutorBuilder { // stderr: Option, } +impl Default for NyxExecutorBuilder { + fn default() -> Self { + Self::new() + } +} + impl NyxExecutorBuilder { pub fn new() -> Self { Self { diff --git a/libafl_qemu/src/emu/mod.rs b/libafl_qemu/src/emu/mod.rs index f152073521..6a2a06ce6b 100644 --- a/libafl_qemu/src/emu/mod.rs +++ b/libafl_qemu/src/emu/mod.rs @@ -41,8 +41,6 @@ use crate::{ #[cfg(emulation_mode = "usermode")] mod usermode; -#[cfg(emulation_mode = "usermode")] -pub use usermode::*; #[cfg(emulation_mode = "systemmode")] mod systemmode; diff --git a/libafl_targets/Cargo.toml b/libafl_targets/Cargo.toml index ccffa4eb24..3446b12bb3 100644 --- a/libafl_targets/Cargo.toml +++ b/libafl_targets/Cargo.toml @@ -59,7 +59,7 @@ windows_asan = ["common"] # Compile C code for ASAN on Windows whole_archive = [] # use +whole-archive to ensure the presence of weak symbols cmplog_extended_instrumentation = [] # support for aflpp cmplog map, we will remove this once aflpp and libafl cmplog shares the same LLVM passes. function-logging = ["common"] - +track_hit_feedbacks = ["libafl/track_hit_feedbacks"] [build-dependencies] bindgen = "0.69.4" cc = { version = "1.0", features = ["parallel"] } diff --git a/libafl_targets/src/libfuzzer/observers/oom.rs b/libafl_targets/src/libfuzzer/observers/oom.rs index 78de4c6764..8fb1dea3a6 100644 --- a/libafl_targets/src/libfuzzer/observers/oom.rs +++ b/libafl_targets/src/libfuzzer/observers/oom.rs @@ -167,4 +167,9 @@ where { Ok(Self::oomed()) } + + #[cfg(feature = "track_hit_feedbacks")] + fn last_result(&self) -> Result { + Ok(Self::oomed()) + } }