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 <andreafioraldi@gmail.com>
Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
van Hauser 2021-12-16 14:19:39 +01:00 committed by GitHub
parent 6e59e5bdc7
commit 1f24ad0b65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 109 additions and 55 deletions

View File

@ -28,7 +28,7 @@ use libafl::{
events::SimpleRestartingEventManager, events::SimpleRestartingEventManager,
executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor},
feedback_or, feedback_or,
feedbacks::{CrashFeedback, MapFeedbackState, MaxMapFeedback, TimeFeedback}, feedbacks::{AflMapFeedback, CrashFeedback, MapFeedbackState, TimeFeedback},
fuzzer::{Fuzzer, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
inputs::{BytesInput, HasTargetBytes}, inputs::{BytesInput, HasTargetBytes},
monitors::SimpleMonitor, monitors::SimpleMonitor,
@ -37,7 +37,7 @@ use libafl::{
token_mutations::I2SRandReplace, token_mutations::I2SRandReplace,
tokens_mutations, Tokens, tokens_mutations, Tokens,
}, },
observers::{StdMapObserver, TimeObserver}, observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
stages::{ stages::{
calibrate::CalibrationStage, calibrate::CalibrationStage,
power::{PowerMutationalStage, PowerSchedule}, power::{PowerMutationalStage, PowerSchedule},
@ -206,7 +206,7 @@ fn fuzz(
// Create an observation channel using the coverage map // Create an observation channel using the coverage map
// We don't use the hitcounts (see the Cargo.toml, we use pcguard_edges) // 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 = 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 // Create an observation channel to keep track of the execution time
let time_observer = TimeObserver::new("time"); let time_observer = TimeObserver::new("time");
@ -221,7 +221,7 @@ fn fuzz(
// This one is composed by two Feedbacks in OR // This one is composed by two Feedbacks in OR
let feedback = feedback_or!( let feedback = feedback_or!(
// New maximization map feedback linked to the edges observer and the feedback state // 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 // Time feedback, this one does not need a feedback state
TimeFeedback::new_with_observer(&time_observer) TimeFeedback::new_with_observer(&time_observer)
); );

View File

@ -21,19 +21,22 @@ use crate::{
Error, 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<FT, I, O, S, T> = MapFeedback<FT, I, DifferentIsNovel, O, OrReducer, S, T>;
/// A [`MapFeedback`] that strives to maximize the map contents. /// A [`MapFeedback`] that strives to maximize the map contents.
pub type MaxMapFeedback<FT, I, O, S, T> = MapFeedback<FT, I, MapNopFilter, O, MaxReducer, S, T>; pub type MaxMapFeedback<FT, I, O, S, T> = MapFeedback<FT, I, DifferentIsNovel, O, MaxReducer, S, T>;
/// A [`MapFeedback`] that strives to minimize the map contents. /// A [`MapFeedback`] that strives to minimize the map contents.
pub type MinMapFeedback<FT, I, O, S, T> = MapFeedback<FT, I, MapNopFilter, O, MinReducer, S, T>; pub type MinMapFeedback<FT, I, O, S, T> = MapFeedback<FT, I, DifferentIsNovel, O, MinReducer, S, T>;
/// A [`MapFeedback`] that strives to maximize the map contents, /// A [`MapFeedback`] that strives to maximize the map contents,
/// but only, if a value is larger than `pow2` of the previous. /// but only, if a value is larger than `pow2` of the previous.
pub type MaxMapPow2Feedback<FT, I, O, S, T> = pub type MaxMapPow2Feedback<FT, I, O, S, T> =
MapFeedback<FT, I, MaxMapPow2Filter, O, MaxReducer, S, T>; MapFeedback<FT, I, NextPow2IsNovel, O, MaxReducer, S, T>;
/// A [`MapFeedback`] that strives to maximize the map contents, /// A [`MapFeedback`] that strives to maximize the map contents,
/// but only, if a value is larger than `pow2` of the previous. /// but only, if a value is larger than `pow2` of the previous.
pub type MaxMapOneOrFilledFeedback<FT, I, O, S, T> = pub type MaxMapOneOrFilledFeedback<FT, I, O, S, T> =
MapFeedback<FT, I, MaxMapOneOrFilledFilter, O, MaxReducer, S, T>; MapFeedback<FT, I, OneOrFilledIsNovel, O, MaxReducer, S, T>;
/// A `Reducer` function is used to aggregate values for the novelty search /// A `Reducer` function is used to aggregate values for the novelty search
pub trait Reducer<T>: Serialize + serde::de::DeserializeOwned + 'static pub trait Reducer<T>: Serialize + serde::de::DeserializeOwned + 'static
@ -44,6 +47,46 @@ where
fn reduce(first: T, second: T) -> T; 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<T> Reducer<T> 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<T> Reducer<T> 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. /// A [`MaxReducer`] reduces int values and returns their maximum.
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MaxReducer {} pub struct MaxReducer {}
@ -92,28 +135,26 @@ where
} }
} }
/// A `MapFindFilter` function gets called after the `MapFeedback` found a new entry. /// A `IsNovel` function is used to discriminate if a reduced value is considered novel.
pub trait MapFindFilter<T>: Serialize + serde::de::DeserializeOwned + 'static pub trait IsNovel<T>: Serialize + serde::de::DeserializeOwned + 'static
where where
T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{ {
/// If a new value in the [`MapFeedback`] was found, /// If a new value in the [`MapFeedback`] was found,
/// this filter can decide if the result is intersting or not. /// this filter can decide if the result is considered novel or not.
/// This way, you can restrict the finds further. fn is_novel(old: T, new: T) -> bool;
fn is_interesting(old: T, new: T) -> bool;
} }
/// A filter that never filters out any finds. /// [`AllIsNovel`] consider everything a novelty. Here mostly just for debugging.
/// The default
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MapNopFilter {} pub struct AllIsNovel {}
impl<T> MapFindFilter<T> for MapNopFilter impl<T> IsNovel<T> for AllIsNovel
where where
T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{ {
#[inline] #[inline]
fn is_interesting(_old: T, _new: T) -> bool { fn is_novel(_old: T, _new: T) -> bool {
true true
} }
} }
@ -132,15 +173,28 @@ fn saturating_next_power_of_two<T: PrimInt>(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)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MaxMapPow2Filter {} pub struct DifferentIsNovel {}
impl<T> MapFindFilter<T> for MaxMapPow2Filter impl<T> IsNovel<T> for DifferentIsNovel
where where
T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{ {
#[inline] #[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<T> IsNovel<T> 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. // We use a trait so we build our numbers from scratch here.
// This way it works with Nums of any size. // This way it works with Nums of any size.
if new <= old { if new <= old {
@ -154,14 +208,14 @@ where
/// A filter that only saves values which are at least the next pow2 class /// A filter that only saves values which are at least the next pow2 class
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MaxMapOneOrFilledFilter {} pub struct OneOrFilledIsNovel {}
impl<T> MapFindFilter<T> for MaxMapOneOrFilledFilter impl<T> IsNovel<T> for OneOrFilledIsNovel
where where
T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned, T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned,
{ {
#[inline] #[inline]
fn is_interesting(old: T, new: T) -> bool { fn is_novel(old: T, new: T) -> bool {
(new == T::one() || new == T::one() || new == T::max_value()) && new > old (new == T::one() || new == T::max_value()) && new > old
} }
} }
@ -299,12 +353,12 @@ where
/// The most common AFL-like feedback type /// The most common AFL-like feedback type
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(bound = "T: serde::de::DeserializeOwned")] #[serde(bound = "T: serde::de::DeserializeOwned")]
pub struct MapFeedback<FT, I, MF, O, R, S, T> pub struct MapFeedback<FT, I, N, O, R, S, T>
where where
T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned + Debug, T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned + Debug,
R: Reducer<T>, R: Reducer<T>,
O: MapObserver<T>, O: MapObserver<T>,
MF: MapFindFilter<T>, N: IsNovel<T>,
S: HasFeedbackStates<FT>, S: HasFeedbackStates<FT>,
FT: FeedbackStatesTuple, FT: FeedbackStatesTuple,
{ {
@ -317,15 +371,15 @@ where
/// Name identifier of the observer /// Name identifier of the observer
observer_name: String, observer_name: String,
/// Phantom Data of Reducer /// Phantom Data of Reducer
phantom: PhantomData<(FT, I, MF, S, R, O, T)>, phantom: PhantomData<(FT, I, N, S, R, O, T)>,
} }
impl<FT, I, MF, O, R, S, T> Feedback<I, S> for MapFeedback<FT, I, MF, O, R, S, T> impl<FT, I, N, O, R, S, T> Feedback<I, S> for MapFeedback<FT, I, N, O, R, S, T>
where where
T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned + Debug, T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned + Debug,
R: Reducer<T>, R: Reducer<T>,
O: MapObserver<T>, O: MapObserver<T>,
MF: MapFindFilter<T>, N: IsNovel<T>,
I: Input, I: Input,
S: HasFeedbackStates<FT> + HasClientPerfMonitor, S: HasFeedbackStates<FT> + HasClientPerfMonitor,
FT: FeedbackStatesTuple, FT: FeedbackStatesTuple,
@ -363,7 +417,7 @@ where
let item = *observer.get(i); let item = *observer.get(i);
let reduced = R::reduce(history, item); 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; map_state.history_map[i] = reduced;
interesting = true; interesting = true;
self.novelties.as_mut().unwrap().push(i); self.novelties.as_mut().unwrap().push(i);
@ -375,7 +429,7 @@ where
let item = *observer.get(i); let item = *observer.get(i);
let reduced = R::reduce(history, item); 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; map_state.history_map[i] = reduced;
interesting = true; interesting = true;
} }
@ -429,11 +483,11 @@ where
} }
} }
impl<FT, I, MF, O, R, S, T> Named for MapFeedback<FT, I, MF, O, R, S, T> impl<FT, I, N, O, R, S, T> Named for MapFeedback<FT, I, N, O, R, S, T>
where where
T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned + Debug, T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned + Debug,
R: Reducer<T>, R: Reducer<T>,
MF: MapFindFilter<T>, N: IsNovel<T>,
O: MapObserver<T>, O: MapObserver<T>,
S: HasFeedbackStates<FT>, S: HasFeedbackStates<FT>,
FT: FeedbackStatesTuple, FT: FeedbackStatesTuple,
@ -444,7 +498,7 @@ where
} }
} }
impl<FT, I, MF, O, R, S, T> MapFeedback<FT, I, MF, O, R, S, T> impl<FT, I, N, O, R, S, T> MapFeedback<FT, I, N, O, R, S, T>
where where
T: PrimInt T: PrimInt
+ Default + Default
@ -455,7 +509,7 @@ where
+ PartialOrd + PartialOrd
+ Debug, + Debug,
R: Reducer<T>, R: Reducer<T>,
MF: MapFindFilter<T>, N: IsNovel<T>,
O: MapObserver<T>, O: MapObserver<T>,
S: HasFeedbackStates<FT>, S: HasFeedbackStates<FT>,
FT: FeedbackStatesTuple, FT: FeedbackStatesTuple,
@ -614,25 +668,25 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::feedbacks::{MapFindFilter, MapNopFilter, MaxMapPow2Filter}; use crate::feedbacks::{AllIsNovel, IsNovel, NextPow2IsNovel};
#[test] #[test]
fn test_map_max_pow2_filter() { fn test_map_is_novel() {
// sanity check // sanity check
assert!(MapNopFilter::is_interesting(0_u8, 0)); assert!(AllIsNovel::is_novel(0_u8, 0));
assert!(!MaxMapPow2Filter::is_interesting(0_u8, 0)); assert!(!NextPow2IsNovel::is_novel(0_u8, 0));
assert!(MaxMapPow2Filter::is_interesting(0_u8, 1)); assert!(NextPow2IsNovel::is_novel(0_u8, 1));
assert!(!MaxMapPow2Filter::is_interesting(1_u8, 1)); assert!(!NextPow2IsNovel::is_novel(1_u8, 1));
assert!(MaxMapPow2Filter::is_interesting(1_u8, 2)); assert!(NextPow2IsNovel::is_novel(1_u8, 2));
assert!(!MaxMapPow2Filter::is_interesting(2_u8, 2)); assert!(!NextPow2IsNovel::is_novel(2_u8, 2));
assert!(!MaxMapPow2Filter::is_interesting(2_u8, 3)); assert!(!NextPow2IsNovel::is_novel(2_u8, 3));
assert!(MaxMapPow2Filter::is_interesting(2_u8, 4)); assert!(NextPow2IsNovel::is_novel(2_u8, 4));
assert!(!MaxMapPow2Filter::is_interesting(128_u8, 128)); assert!(!NextPow2IsNovel::is_novel(128_u8, 128));
assert!(!MaxMapPow2Filter::is_interesting(129_u8, 128)); assert!(!NextPow2IsNovel::is_novel(129_u8, 128));
assert!(MaxMapPow2Filter::is_interesting(128_u8, 255)); assert!(NextPow2IsNovel::is_novel(128_u8, 255));
assert!(!MaxMapPow2Filter::is_interesting(255_u8, 128)); assert!(!NextPow2IsNovel::is_novel(255_u8, 128));
assert!(MaxMapPow2Filter::is_interesting(254_u8, 255)); assert!(NextPow2IsNovel::is_novel(254_u8, 255));
assert!(!MaxMapPow2Filter::is_interesting(255_u8, 255)); assert!(!NextPow2IsNovel::is_novel(255_u8, 255));
} }
} }

View File

@ -291,19 +291,19 @@ where
fn display(&mut self, event_msg: String, sender_id: u32) { fn display(&mut self, event_msg: String, sender_id: u32) {
let fmt = format!( let fmt = format!(
"[{} #{}] run time: {}, clients: {}, corpus: {}, objectives: {}, executions: {}, {} exec/sec: {}", "[{} #{}] run time: {}, clients: {}, corpus: {}, objectives: {}, executions: {}{}, exec/sec: {}",
event_msg, event_msg,
sender_id, sender_id,
format_duration_hms(&(current_time() - self.start_time)), format_duration_hms(&(current_time() - self.start_time)),
self.client_stats().len(), self.client_stats().len(),
self.corpus_size(), self.corpus_size(),
self.objective_size(), self.objective_size(),
self.total_execs(),
if let Some(stability) = self.stability() { if let Some(stability) = self.stability() {
format!(", stability: {:.2}", stability) format!(", stability: {:.2}", stability)
} else { } else {
"".to_string() "".to_string()
}, },
self.total_execs(),
self.execs_per_sec() self.execs_per_sec()
); );
(self.print_fn)(fmt); (self.print_fn)(fmt);