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:
parent
6e59e5bdc7
commit
1f24ad0b65
@ -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)
|
||||
);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user