From 1f24ad0b652395ea241409682845a2c3b9767d9c Mon Sep 17 00:00:00 2001 From: van Hauser Date: Thu, 16 Dec 2021 14:19:39 +0100 Subject: [PATCH] Implement AflMap (#416) * aflmap * nits * nits * switch implementation * clippy * set fuzzbench fuzzer to afl map * fix monitor display * Remove MapFindFilter and fix names * AndReducer * fixed testcase * always inline * remove inline(always) Co-authored-by: Andrea Fioraldi Co-authored-by: Dominik Maier --- fuzzers/fuzzbench/src/lib.rs | 8 +- libafl/src/feedbacks/map.rs | 152 ++++++++++++++++++++++++----------- libafl/src/monitors/mod.rs | 4 +- 3 files changed, 109 insertions(+), 55 deletions(-) diff --git a/fuzzers/fuzzbench/src/lib.rs b/fuzzers/fuzzbench/src/lib.rs index c461b721df..821b9ab387 100644 --- a/fuzzers/fuzzbench/src/lib.rs +++ b/fuzzers/fuzzbench/src/lib.rs @@ -28,7 +28,7 @@ use libafl::{ events::SimpleRestartingEventManager, executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, feedback_or, - feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback}, + feedbacks::{AflMapFeedback, CrashFeedback, MapFeedbackState, TimeFeedback}, fuzzer::{Fuzzer, StdFuzzer}, inputs::{BytesInput, HasTargetBytes}, monitors::SimpleMonitor, @@ -37,7 +37,7 @@ use libafl::{ token_mutations::I2SRandReplace, tokens_mutations, Tokens, }, - observers::{StdMapObserver, TimeObserver}, + observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, stages::{ calibrate::CalibrationStage, power::{PowerMutationalStage, PowerSchedule}, @@ -206,7 +206,7 @@ fn fuzz( // Create an observation channel using the coverage map // We don't use the hitcounts (see the Cargo.toml, we use pcguard_edges) let edges = unsafe { &mut EDGES_MAP[0..MAX_EDGES_NUM] }; - let edges_observer = StdMapObserver::new("edges", edges); + let edges_observer = HitcountsMapObserver::new(StdMapObserver::new("edges", edges)); // Create an observation channel to keep track of the execution time let time_observer = TimeObserver::new("time"); @@ -221,7 +221,7 @@ fn fuzz( // This one is composed by two Feedbacks in OR let feedback = feedback_or!( // New maximization map feedback linked to the edges observer and the feedback state - MaxMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false), + AflMapFeedback::new_tracking(&feedback_state, &edges_observer, true, false), // Time feedback, this one does not need a feedback state TimeFeedback::new_with_observer(&time_observer) ); diff --git a/libafl/src/feedbacks/map.rs b/libafl/src/feedbacks/map.rs index 77d18eda07..ca13ce5967 100644 --- a/libafl/src/feedbacks/map.rs +++ b/libafl/src/feedbacks/map.rs @@ -21,19 +21,22 @@ use crate::{ Error, }; +/// A [`MapFeedback`] that implements the AFL algorithm using an [`OrReducer`] combining the bits for the history map and the bit from ``HitcountsMapObserver``. +pub type AflMapFeedback = MapFeedback; + /// A [`MapFeedback`] that strives to maximize the map contents. -pub type MaxMapFeedback = MapFeedback; +pub type MaxMapFeedback = MapFeedback; /// A [`MapFeedback`] that strives to minimize the map contents. -pub type MinMapFeedback = MapFeedback; +pub type MinMapFeedback = MapFeedback; /// A [`MapFeedback`] that strives to maximize the map contents, /// but only, if a value is larger than `pow2` of the previous. pub type MaxMapPow2Feedback = - MapFeedback; + MapFeedback; /// A [`MapFeedback`] that strives to maximize the map contents, /// but only, if a value is larger than `pow2` of the previous. pub type MaxMapOneOrFilledFeedback = - MapFeedback; + MapFeedback; /// A `Reducer` function is used to aggregate values for the novelty search pub trait Reducer: Serialize + serde::de::DeserializeOwned + 'static @@ -44,6 +47,46 @@ where fn reduce(first: T, second: T) -> T; } +/// A [`OrReducer`] reduces the values returning the bitwise OR with the old value +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct OrReducer {} + +impl Reducer for OrReducer +where + T: PrimInt + + Default + + Copy + + 'static + + serde::Serialize + + serde::de::DeserializeOwned + + PartialOrd, +{ + #[inline] + fn reduce(history: T, new: T) -> T { + history | new + } +} + +/// A [`AndReducer`] reduces the values returning the bitwise AND with the old value +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct AndReducer {} + +impl Reducer for AndReducer +where + T: PrimInt + + Default + + Copy + + 'static + + serde::Serialize + + serde::de::DeserializeOwned + + PartialOrd, +{ + #[inline] + fn reduce(history: T, new: T) -> T { + history & new + } +} + /// A [`MaxReducer`] reduces int values and returns their maximum. #[derive(Serialize, Deserialize, Clone, Debug)] pub struct MaxReducer {} @@ -92,28 +135,26 @@ where } } -/// A `MapFindFilter` function gets called after the `MapFeedback` found a new entry. -pub trait MapFindFilter: Serialize + serde::de::DeserializeOwned + 'static +/// A `IsNovel` function is used to discriminate if a reduced value is considered novel. +pub trait IsNovel: Serialize + serde::de::DeserializeOwned + 'static where T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, { /// If a new value in the [`MapFeedback`] was found, - /// this filter can decide if the result is intersting or not. - /// This way, you can restrict the finds further. - fn is_interesting(old: T, new: T) -> bool; + /// this filter can decide if the result is considered novel or not. + fn is_novel(old: T, new: T) -> bool; } -/// A filter that never filters out any finds. -/// The default +/// [`AllIsNovel`] consider everything a novelty. Here mostly just for debugging. #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct MapNopFilter {} +pub struct AllIsNovel {} -impl MapFindFilter for MapNopFilter +impl IsNovel for AllIsNovel where T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, { #[inline] - fn is_interesting(_old: T, _new: T) -> bool { + fn is_novel(_old: T, _new: T) -> bool { true } } @@ -132,15 +173,28 @@ fn saturating_next_power_of_two(n: T) -> T { } } -/// A filter that only saves values which are at least the next pow2 class +/// Consider as novelty if the reduced value is different from the old value. #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct MaxMapPow2Filter {} -impl MapFindFilter for MaxMapPow2Filter +pub struct DifferentIsNovel {} +impl IsNovel for DifferentIsNovel where T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, { #[inline] - fn is_interesting(old: T, new: T) -> bool { + fn is_novel(old: T, new: T) -> bool { + old != new + } +} + +/// Only consider as novel the values which are at least the next pow2 class of the old value +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct NextPow2IsNovel {} +impl IsNovel for NextPow2IsNovel +where + T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, +{ + #[inline] + fn is_novel(old: T, new: T) -> bool { // We use a trait so we build our numbers from scratch here. // This way it works with Nums of any size. if new <= old { @@ -154,14 +208,14 @@ where /// A filter that only saves values which are at least the next pow2 class #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct MaxMapOneOrFilledFilter {} -impl MapFindFilter for MaxMapOneOrFilledFilter +pub struct OneOrFilledIsNovel {} +impl IsNovel for OneOrFilledIsNovel where T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, { #[inline] - fn is_interesting(old: T, new: T) -> bool { - (new == T::one() || new == T::one() || new == T::max_value()) && new > old + fn is_novel(old: T, new: T) -> bool { + (new == T::one() || new == T::max_value()) && new > old } } @@ -299,12 +353,12 @@ where /// The most common AFL-like feedback type #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(bound = "T: serde::de::DeserializeOwned")] -pub struct MapFeedback +pub struct MapFeedback where T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned + Debug, R: Reducer, O: MapObserver, - MF: MapFindFilter, + N: IsNovel, S: HasFeedbackStates, FT: FeedbackStatesTuple, { @@ -317,15 +371,15 @@ where /// Name identifier of the observer observer_name: String, /// Phantom Data of Reducer - phantom: PhantomData<(FT, I, MF, S, R, O, T)>, + phantom: PhantomData<(FT, I, N, S, R, O, T)>, } -impl Feedback for MapFeedback +impl Feedback for MapFeedback where T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned + Debug, R: Reducer, O: MapObserver, - MF: MapFindFilter, + N: IsNovel, I: Input, S: HasFeedbackStates + HasClientPerfMonitor, FT: FeedbackStatesTuple, @@ -363,7 +417,7 @@ where let item = *observer.get(i); let reduced = R::reduce(history, item); - if history != reduced && MF::is_interesting(history, reduced) { + if N::is_novel(history, reduced) { map_state.history_map[i] = reduced; interesting = true; self.novelties.as_mut().unwrap().push(i); @@ -375,7 +429,7 @@ where let item = *observer.get(i); let reduced = R::reduce(history, item); - if history != reduced && MF::is_interesting(history, reduced) { + if N::is_novel(history, reduced) { map_state.history_map[i] = reduced; interesting = true; } @@ -429,11 +483,11 @@ where } } -impl Named for MapFeedback +impl Named for MapFeedback where T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned + Debug, R: Reducer, - MF: MapFindFilter, + N: IsNovel, O: MapObserver, S: HasFeedbackStates, FT: FeedbackStatesTuple, @@ -444,7 +498,7 @@ where } } -impl MapFeedback +impl MapFeedback where T: PrimInt + Default @@ -455,7 +509,7 @@ where + PartialOrd + Debug, R: Reducer, - MF: MapFindFilter, + N: IsNovel, O: MapObserver, S: HasFeedbackStates, FT: FeedbackStatesTuple, @@ -614,25 +668,25 @@ where #[cfg(test)] mod tests { - use crate::feedbacks::{MapFindFilter, MapNopFilter, MaxMapPow2Filter}; + use crate::feedbacks::{AllIsNovel, IsNovel, NextPow2IsNovel}; #[test] - fn test_map_max_pow2_filter() { + fn test_map_is_novel() { // sanity check - assert!(MapNopFilter::is_interesting(0_u8, 0)); + assert!(AllIsNovel::is_novel(0_u8, 0)); - assert!(!MaxMapPow2Filter::is_interesting(0_u8, 0)); - assert!(MaxMapPow2Filter::is_interesting(0_u8, 1)); - assert!(!MaxMapPow2Filter::is_interesting(1_u8, 1)); - assert!(MaxMapPow2Filter::is_interesting(1_u8, 2)); - assert!(!MaxMapPow2Filter::is_interesting(2_u8, 2)); - assert!(!MaxMapPow2Filter::is_interesting(2_u8, 3)); - assert!(MaxMapPow2Filter::is_interesting(2_u8, 4)); - assert!(!MaxMapPow2Filter::is_interesting(128_u8, 128)); - assert!(!MaxMapPow2Filter::is_interesting(129_u8, 128)); - assert!(MaxMapPow2Filter::is_interesting(128_u8, 255)); - assert!(!MaxMapPow2Filter::is_interesting(255_u8, 128)); - assert!(MaxMapPow2Filter::is_interesting(254_u8, 255)); - assert!(!MaxMapPow2Filter::is_interesting(255_u8, 255)); + assert!(!NextPow2IsNovel::is_novel(0_u8, 0)); + assert!(NextPow2IsNovel::is_novel(0_u8, 1)); + assert!(!NextPow2IsNovel::is_novel(1_u8, 1)); + assert!(NextPow2IsNovel::is_novel(1_u8, 2)); + assert!(!NextPow2IsNovel::is_novel(2_u8, 2)); + assert!(!NextPow2IsNovel::is_novel(2_u8, 3)); + assert!(NextPow2IsNovel::is_novel(2_u8, 4)); + assert!(!NextPow2IsNovel::is_novel(128_u8, 128)); + assert!(!NextPow2IsNovel::is_novel(129_u8, 128)); + assert!(NextPow2IsNovel::is_novel(128_u8, 255)); + assert!(!NextPow2IsNovel::is_novel(255_u8, 128)); + assert!(NextPow2IsNovel::is_novel(254_u8, 255)); + assert!(!NextPow2IsNovel::is_novel(255_u8, 255)); } } diff --git a/libafl/src/monitors/mod.rs b/libafl/src/monitors/mod.rs index 22d7b3a4b0..f3bf219571 100644 --- a/libafl/src/monitors/mod.rs +++ b/libafl/src/monitors/mod.rs @@ -291,19 +291,19 @@ where fn display(&mut self, event_msg: String, sender_id: u32) { let fmt = format!( - "[{} #{}] run time: {}, clients: {}, corpus: {}, objectives: {}, executions: {}, {} exec/sec: {}", + "[{} #{}] run time: {}, clients: {}, corpus: {}, objectives: {}, executions: {}{}, exec/sec: {}", event_msg, sender_id, format_duration_hms(&(current_time() - self.start_time)), self.client_stats().len(), self.corpus_size(), self.objective_size(), + self.total_execs(), if let Some(stability) = self.stability() { format!(", stability: {:.2}", stability) } else { "".to_string() }, - self.total_execs(), self.execs_per_sec() ); (self.print_fn)(fmt);