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,
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)
);

View File

@ -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<FT, I, O, S, T> = MapFeedback<FT, I, DifferentIsNovel, O, OrReducer, S, T>;
/// 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.
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,
/// but only, if a value is larger than `pow2` of the previous.
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,
/// but only, if a value is larger than `pow2` of the previous.
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
pub trait Reducer<T>: 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<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.
#[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<T>: Serialize + serde::de::DeserializeOwned + 'static
/// A `IsNovel` function is used to discriminate if a reduced value is considered novel.
pub trait IsNovel<T>: 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<T> MapFindFilter<T> for MapNopFilter
impl<T> IsNovel<T> 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<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)]
pub struct MaxMapPow2Filter {}
impl<T> MapFindFilter<T> for MaxMapPow2Filter
pub struct DifferentIsNovel {}
impl<T> IsNovel<T> 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<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.
// 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<T> MapFindFilter<T> for MaxMapOneOrFilledFilter
pub struct OneOrFilledIsNovel {}
impl<T> IsNovel<T> 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<FT, I, MF, O, R, S, T>
pub struct MapFeedback<FT, I, N, O, R, S, T>
where
T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned + Debug,
R: Reducer<T>,
O: MapObserver<T>,
MF: MapFindFilter<T>,
N: IsNovel<T>,
S: HasFeedbackStates<FT>,
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<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
T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned + Debug,
R: Reducer<T>,
O: MapObserver<T>,
MF: MapFindFilter<T>,
N: IsNovel<T>,
I: Input,
S: HasFeedbackStates<FT> + 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<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
T: PrimInt + Default + Copy + 'static + serde::Serialize + serde::de::DeserializeOwned + Debug,
R: Reducer<T>,
MF: MapFindFilter<T>,
N: IsNovel<T>,
O: MapObserver<T>,
S: HasFeedbackStates<FT>,
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
T: PrimInt
+ Default
@ -455,7 +509,7 @@ where
+ PartialOrd
+ Debug,
R: Reducer<T>,
MF: MapFindFilter<T>,
N: IsNovel<T>,
O: MapObserver<T>,
S: HasFeedbackStates<FT>,
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));
}
}

View File

@ -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);