Add mutator stats method (#40)

* add LogMutation trait

* change &self to &mut self

* move self.scheduler out of StdFuzzer

* reorder generics?, implement post_exec

* append metadata to the corresponding testcase in the corpus

* turn mutations into Mutators

* impl Named for mutations

* add LoggerScheduledMutator, add fn get_name() to MutatorTuple

* Fix BytesDeleteMutator, and format

* remove TupleList bound on Tail

* turn TokenInsert, TokenReplace into Mutator, fill havoc_mutations

* libfuzzer_libpng

* libfuzzer_libpng_cmpalloc

* libfuzzer_libmozjpeg

* fix tests

* fix libfuzzer_libmozjpeg

* fix tests

* fix LoggerScheduledMutator::mutate

* use vec<u8> instead of String

* fix post_exec and get_name

* fmt

* NamedTuple and HasNameIdTuple

* always clear mutations log

* fix tests

* format

* remove libafl_targets default features

* use vec<string> instead of vec<vec<u8>>

* add alloc::string::String

* format

Co-authored-by: Andrea Fioraldi <andreafioraldi@gmail.com>
This commit is contained in:
Toka 2021-03-25 21:04:18 +09:00 committed by GitHub
parent 21b790060d
commit 82f5dad784
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 2293 additions and 893 deletions

View File

@ -13,9 +13,10 @@ use libafl::{
events::{setup_restarting_mgr, EventManager}, events::{setup_restarting_mgr, EventManager},
executors::{inprocess::InProcessExecutor, Executor, ExitKind, HasObservers}, executors::{inprocess::InProcessExecutor, Executor, ExitKind, HasObservers},
feedbacks::{CrashFeedback, MaxMapFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
inputs::{HasTargetBytes, Input}, inputs::{HasTargetBytes, Input},
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens}, mutators::scheduled::{havoc_mutations, StdScheduledMutator},
mutators::token_mutations::Tokens,
observers::{HitcountsMapObserver, ObserversTuple, StdMapObserver}, observers::{HitcountsMapObserver, ObserversTuple, StdMapObserver},
stages::mutational::StdMutationalStage, stages::mutational::StdMutationalStage,
state::{HasCorpus, HasMetadata, State}, state::{HasCorpus, HasMetadata, State},
@ -470,12 +471,12 @@ unsafe fn fuzz(
} }
// Setup a basic mutator with a mutational stage // Setup a basic mutator with a mutational stage
let mutator = HavocBytesMutator::default(); let mutator = StdScheduledMutator::new(havoc_mutations());
let stage = StdMutationalStage::new(mutator); let stage = StdMutationalStage::new(mutator);
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus // A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage)); let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
// Create the executor for an in-process function with just one observer for edge coverage // Create the executor for an in-process function with just one observer for edge coverage
let mut executor = FridaInProcessExecutor::new( let mut executor = FridaInProcessExecutor::new(
@ -502,12 +503,7 @@ unsafe fn fuzz(
// In case the corpus is empty (on first run), reset // In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 { if state.corpus().count() < 1 {
state state
.load_initial_inputs( .load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
&mut executor,
&mut restarting_mgr,
fuzzer.scheduler(),
&corpus_dirs,
)
.expect(&format!( .expect(&format!(
"Failed to load initial corpus at {:?}", "Failed to load initial corpus at {:?}",
&corpus_dirs &corpus_dirs
@ -515,7 +511,7 @@ unsafe fn fuzz(
println!("We imported {} inputs from disk.", state.corpus().count()); println!("We imported {} inputs from disk.", state.corpus().count());
} }
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr)?; fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?;
// Never reached // Never reached
Ok(()) Ok(())

View File

@ -10,8 +10,8 @@ use libafl::{
events::setup_restarting_mgr, events::setup_restarting_mgr,
executors::{inprocess::InProcessExecutor, ExitKind}, executors::{inprocess::InProcessExecutor, ExitKind},
feedbacks::{CrashFeedback, MaxMapFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
mutators::scheduled::HavocBytesMutator, mutators::scheduled::{havoc_mutations, StdScheduledMutator},
mutators::token_mutations::Tokens, mutators::token_mutations::Tokens,
observers::StdMapObserver, observers::StdMapObserver,
stages::mutational::StdMutationalStage, stages::mutational::StdMutationalStage,
@ -100,11 +100,12 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
} }
// Setup a basic mutator with a mutational stage // Setup a basic mutator with a mutational stage
let mutator = HavocBytesMutator::default(); let mutator = StdScheduledMutator::new(havoc_mutations());
let stage = StdMutationalStage::new(mutator); let stage = StdMutationalStage::new(mutator);
let scheduler = RandCorpusScheduler::new();
// A fuzzer with just one stage and a random policy to get testcasess from the corpus // A fuzzer with just one stage and a random policy to get testcasess from the corpus
let fuzzer = StdFuzzer::new(RandCorpusScheduler::new(), tuple_list!(stage)); let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
// The wrapped harness function, calling out to the LLVM-style harness // The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |buf: &[u8]| { let mut harness = |buf: &[u8]| {
@ -132,12 +133,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
// In case the corpus is empty (on first run), reset // In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 { if state.corpus().count() < 1 {
state state
.load_initial_inputs( .load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
&mut executor,
&mut restarting_mgr,
fuzzer.scheduler(),
&corpus_dirs,
)
.expect(&format!( .expect(&format!(
"Failed to load initial corpus at {:?}", "Failed to load initial corpus at {:?}",
&corpus_dirs &corpus_dirs
@ -145,7 +141,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
println!("We imported {} inputs from disk.", state.corpus().count()); println!("We imported {} inputs from disk.", state.corpus().count());
} }
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr)?; fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?;
// Never reached // Never reached
Ok(()) Ok(())

View File

@ -18,7 +18,7 @@ debug = true
[dependencies] [dependencies]
libafl = { path = "../../libafl/" } libafl = { path = "../../libafl/" }
libafl_targets = { path = "../../libafl_targets/" } libafl_targets = { path = "../../libafl_targets/", features = ["sancov", "libfuzzer_compatibility"] }
# TODO Include it only when building cc # TODO Include it only when building cc
libafl_cc = { path = "../../libafl_cc/" } libafl_cc = { path = "../../libafl_cc/" }

View File

@ -14,8 +14,9 @@ use libafl::{
events::setup_restarting_mgr, events::setup_restarting_mgr,
executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor},
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens}, mutators::scheduled::{havoc_mutations, StdScheduledMutator},
mutators::token_mutations::Tokens,
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
stages::mutational::StdMutationalStage, stages::mutational::StdMutationalStage,
state::{HasCorpus, HasMetadata, State}, state::{HasCorpus, HasMetadata, State},
@ -110,12 +111,12 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
} }
// Setup a basic mutator with a mutational stage // Setup a basic mutator with a mutational stage
let mutator = HavocBytesMutator::default(); let mutator = StdScheduledMutator::new(havoc_mutations());
let stage = StdMutationalStage::new(mutator); let stage = StdMutationalStage::new(mutator);
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus // A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage)); let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
// The wrapped harness function, calling out to the LLVM-style harness // The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |buf: &[u8]| { let mut harness = |buf: &[u8]| {
@ -145,12 +146,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
// In case the corpus is empty (on first run), reset // In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 { if state.corpus().count() < 1 {
state state
.load_initial_inputs( .load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
&mut executor,
&mut restarting_mgr,
fuzzer.scheduler(),
&corpus_dirs,
)
.expect(&format!( .expect(&format!(
"Failed to load initial corpus at {:?}", "Failed to load initial corpus at {:?}",
&corpus_dirs &corpus_dirs
@ -158,7 +154,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
println!("We imported {} inputs from disk.", state.corpus().count()); println!("We imported {} inputs from disk.", state.corpus().count());
} }
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr)?; fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?;
// Never reached // Never reached
Ok(()) Ok(())

View File

@ -6,16 +6,14 @@ use std::{env, path::PathBuf};
#[cfg(unix)] #[cfg(unix)]
use libafl::{ use libafl::{
bolts::{shmem::UnixShMem, tuples::tuple_list}, bolts::{shmem::UnixShMem, tuples::tuple_list},
corpus::{ corpus::{Corpus, InMemoryCorpus, OnDiskCorpus, RandCorpusScheduler},
Corpus, InMemoryCorpus, IndexesLenTimeMinimizerCorpusScheduler, OnDiskCorpus,
QueueCorpusScheduler,
},
events::setup_restarting_mgr, events::setup_restarting_mgr,
executors::{inprocess::InProcessExecutor, ExitKind}, executors::{inprocess::InProcessExecutor, ExitKind},
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens}, mutators::scheduled::{havoc_mutations, StdScheduledMutator},
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, mutators::token_mutations::Tokens,
observers::{HitcountsMapObserver, StdMapObserver},
stages::mutational::StdMutationalStage, stages::mutational::StdMutationalStage,
state::{HasCorpus, HasMetadata, State}, state::{HasCorpus, HasMetadata, State},
stats::SimpleStats, stats::SimpleStats,
@ -109,7 +107,6 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
MaxMapFeedback::new_with_observer_track(&edges_observer, true, false), MaxMapFeedback::new_with_observer_track(&edges_observer, true, false),
MaxMapFeedback::new_with_observer(&cmps_observer), MaxMapFeedback::new_with_observer(&cmps_observer),
MaxMapFeedback::new_with_observer(&allocs_observer), MaxMapFeedback::new_with_observer(&allocs_observer),
TimeFeedback::new()
), ),
// Corpus in which we store solutions (crashes in this example), // Corpus in which we store solutions (crashes in this example),
// on disk so the user can get them after stopping the fuzzer // on disk so the user can get them after stopping the fuzzer
@ -133,12 +130,12 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
} }
// Setup a basic mutator with a mutational stage // Setup a basic mutator with a mutational stage
let mutator = HavocBytesMutator::default(); let mutator = StdScheduledMutator::new(havoc_mutations());
let stage = StdMutationalStage::new(mutator); let stage = StdMutationalStage::new(mutator);
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus let scheduler = RandCorpusScheduler::new();
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); // A fuzzer with just one stage and a random policy to get testcasess from the corpus
let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage)); let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
// The wrapped harness function, calling out to the LLVM-style harness // The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |buf: &[u8]| { let mut harness = |buf: &[u8]| {
@ -148,14 +145,9 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
// Create the executor for an in-process function with just one observer for edge coverage // Create the executor for an in-process function with just one observer for edge coverage
let mut executor = InProcessExecutor::new( let mut executor = InProcessExecutor::new(
"in-process(edges,cmps,allocs)", "in-process(edges)",
&mut harness, &mut harness,
tuple_list!( tuple_list!(edges_observer, cmps_observer, allocs_observer),
edges_observer,
cmps_observer,
allocs_observer,
TimeObserver::new("time")
),
&mut state, &mut state,
&mut restarting_mgr, &mut restarting_mgr,
)?; )?;
@ -171,12 +163,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
// In case the corpus is empty (on first run), reset // In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 { if state.corpus().count() < 1 {
state state
.load_initial_inputs( .load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
&mut executor,
&mut restarting_mgr,
fuzzer.scheduler(),
&corpus_dirs,
)
.expect(&format!( .expect(&format!(
"Failed to load initial corpus at {:?}", "Failed to load initial corpus at {:?}",
&corpus_dirs &corpus_dirs
@ -184,7 +171,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
println!("We imported {} inputs from disk.", state.corpus().count()); println!("We imported {} inputs from disk.", state.corpus().count());
} }
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr)?; fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?;
// Never reached // Never reached
Ok(()) Ok(())

View File

@ -2,16 +2,16 @@
mkdir -p ./crashes mkdir -p ./crashes
cargo build --example libfuzzer_libpng --release || exit 1 cargo build --example libfuzzer_libpng_cmpalloc --release || exit 1
cp ../../target/release/examples/libfuzzer_libpng ./.libfuzzer_test.elf cp ../../target/release/examples/libfuzzer_libpng ./.libfuzzer_test.elf
# The broker # The broker
RUST_BACKTRACE=full taskset 0 ./.libfuzzer_test.elf & RUST_BACKTRACE=full taskset -c 0 ./.libfuzzer_test.elf &
# Give the broker time to spawn # Give the broker time to spawn
sleep 2 sleep 2
echo "Spawning client" echo "Spawning client"
# The 1st fuzzer client, pin to cpu 0x1 # The 1st fuzzer client, pin to cpu 0x1
RUST_BACKTRACE=full taskset 1 ./.libfuzzer_test.elf 2>/dev/null RUST_BACKTRACE=full taskset -c 1 ./.libfuzzer_test.elf 2>/dev/null
killall .libfuzzer_test.elf killall .libfuzzer_test.elf
rm -rf ./.libfuzzer_test.elf rm -rf ./.libfuzzer_test.elf

View File

@ -15,7 +15,7 @@ use libafl::{
events::setup_restarting_mgr, events::setup_restarting_mgr,
executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor}, executors::{inprocess::InProcessExecutor, ExitKind, TimeoutExecutor},
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, HasCorpusScheduler, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens}, mutators::{scheduled::HavocBytesMutator, token_mutations::Tokens},
observers::{HitcountsMapObserver, StdMapObserver, TimeObserver}, observers::{HitcountsMapObserver, StdMapObserver, TimeObserver},
stages::mutational::StdMutationalStage, stages::mutational::StdMutationalStage,
@ -122,12 +122,12 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
} }
// Setup a basic mutator with a mutational stage // Setup a basic mutator with a mutational stage
let mutator = HavocBytesMutator::default(); let mutator = StdScheduledMutator::new(havoc_mutations());
let stage = StdMutationalStage::new(mutator); let stage = StdMutationalStage::new(mutator);
// A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus // A fuzzer with just one stage and a minimization+queue policy to get testcasess from the corpus
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
let fuzzer = StdFuzzer::new(scheduler, tuple_list!(stage)); let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
// The wrapped harness function, calling out to the LLVM-style harness // The wrapped harness function, calling out to the LLVM-style harness
let mut harness = |buf: &[u8]| { let mut harness = |buf: &[u8]| {
@ -159,12 +159,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
// In case the corpus is empty (on first run), reset // In case the corpus is empty (on first run), reset
if state.corpus().count() < 1 { if state.corpus().count() < 1 {
state state
.load_initial_inputs( .load_initial_inputs(&mut executor, &mut restarting_mgr, &scheduler, &corpus_dirs)
&mut executor,
&mut restarting_mgr,
fuzzer.scheduler(),
&corpus_dirs,
)
.expect(&format!( .expect(&format!(
"Failed to load initial corpus at {:?}", "Failed to load initial corpus at {:?}",
&corpus_dirs &corpus_dirs
@ -172,7 +167,7 @@ fn fuzz(corpus_dirs: Vec<PathBuf>, objective_dir: PathBuf, broker_port: u16) ->
println!("We imported {} inputs from disk.", state.corpus().count()); println!("We imported {} inputs from disk.", state.corpus().count());
} }
fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr)?; fuzzer.fuzz_loop(&mut state, &mut executor, &mut restarting_mgr, &scheduler)?;
// Never reached // Never reached
Ok(()) Ok(())

View File

@ -46,7 +46,7 @@ required-features = ["std"]
tuple_list = "0.1.2" tuple_list = "0.1.2"
hashbrown = { version = "0.9", features = ["serde", "ahash-compile-time-rng"] } # A faster hashmap, nostd compatible hashbrown = { version = "0.9", features = ["serde", "ahash-compile-time-rng"] } # A faster hashmap, nostd compatible
num = "*" num = "*"
xxhash-rust = { version = "0.8.0", features = ["xxh3"] } # xxh3 hashing for rust xxhash-rust = { version = "0.8.0", features = ["xxh3", "const_xxh3"] } # xxh3 hashing for rust
serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib
erased-serde = "0.3.12" erased-serde = "0.3.12"
postcard = { version = "0.5.1", features = ["alloc"] } # no_std compatible serde serialization fromat postcard = { version = "0.5.1", features = ["alloc"] } # no_std compatible serde serialization fromat

View File

@ -4,6 +4,8 @@ pub use tuple_list::{tuple_list, tuple_list_type, TupleList};
use core::any::TypeId; use core::any::TypeId;
use xxhash_rust::const_xxh3::xxh3_64;
pub trait HasLen { pub trait HasLen {
fn len(&self) -> usize; fn len(&self) -> usize;
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool {
@ -19,13 +21,59 @@ impl HasLen for () {
impl<Head, Tail> HasLen for (Head, Tail) impl<Head, Tail> HasLen for (Head, Tail)
where where
Tail: TupleList + HasLen, Tail: HasLen,
{ {
fn len(&self) -> usize { fn len(&self) -> usize {
1 + self.1.len() 1 + self.1.len()
} }
} }
pub trait HasNameId {
fn const_name(&self) -> &'static str;
fn name_id(&self) -> u64 {
xxh3_64(self.const_name().as_bytes())
}
}
pub trait HasNameIdTuple: HasLen {
fn get_const_name(&self, index: usize) -> Option<&'static str>;
fn get_name_id(&self, index: usize) -> Option<u64>;
}
impl HasNameIdTuple for () {
fn get_const_name(&self, _index: usize) -> Option<&'static str> {
None
}
fn get_name_id(&self, _index: usize) -> Option<u64> {
None
}
}
impl<Head, Tail> HasNameIdTuple for (Head, Tail)
where
Head: 'static + HasNameId,
Tail: HasNameIdTuple,
{
fn get_const_name(&self, index: usize) -> Option<&'static str> {
if index == 0 {
Some(self.0.const_name())
} else {
self.1.get_const_name(index - 1)
}
}
fn get_name_id(&self, index: usize) -> Option<u64> {
if index == 0 {
Some(self.0.name_id())
} else {
self.1.get_name_id(index - 1)
}
}
}
pub trait MatchFirstType { pub trait MatchFirstType {
fn match_first_type<T: 'static>(&self) -> Option<&T>; fn match_first_type<T: 'static>(&self) -> Option<&T>;
fn match_first_type_mut<T: 'static>(&mut self) -> Option<&mut T>; fn match_first_type_mut<T: 'static>(&mut self) -> Option<&mut T>;
@ -43,7 +91,7 @@ impl MatchFirstType for () {
impl<Head, Tail> MatchFirstType for (Head, Tail) impl<Head, Tail> MatchFirstType for (Head, Tail)
where where
Head: 'static, Head: 'static,
Tail: TupleList + MatchFirstType, Tail: MatchFirstType,
{ {
fn match_first_type<T: 'static>(&self) -> Option<&T> { fn match_first_type<T: 'static>(&self) -> Option<&T> {
if TypeId::of::<T>() == TypeId::of::<Head>() { if TypeId::of::<T>() == TypeId::of::<Head>() {
@ -75,7 +123,7 @@ impl MatchType for () {
impl<Head, Tail> MatchType for (Head, Tail) impl<Head, Tail> MatchType for (Head, Tail)
where where
Head: 'static, Head: 'static,
Tail: TupleList + MatchType, Tail: MatchType,
{ {
fn match_type<T: 'static>(&self, f: fn(t: &T)) { fn match_type<T: 'static>(&self, f: fn(t: &T)) {
if TypeId::of::<T>() == TypeId::of::<Head>() { if TypeId::of::<T>() == TypeId::of::<Head>() {
@ -98,6 +146,30 @@ pub trait Named {
fn name(&self) -> &str; fn name(&self) -> &str;
} }
pub trait NamedTuple: HasLen {
fn get_name(&self, index: usize) -> Option<&str>;
}
impl NamedTuple for () {
fn get_name(&self, _index: usize) -> Option<&str> {
None
}
}
impl<Head, Tail> NamedTuple for (Head, Tail)
where
Head: 'static + Named,
Tail: NamedTuple,
{
fn get_name(&self, index: usize) -> Option<&str> {
if index == 0 {
Some(self.0.name())
} else {
self.1.get_name(index - 1)
}
}
}
pub trait MatchNameAndType { pub trait MatchNameAndType {
fn match_name_type<T: 'static>(&self, name: &str) -> Option<&T>; fn match_name_type<T: 'static>(&self, name: &str) -> Option<&T>;
fn match_name_type_mut<T: 'static>(&mut self, name: &str) -> Option<&mut T>; fn match_name_type_mut<T: 'static>(&mut self, name: &str) -> Option<&mut T>;
@ -115,7 +187,7 @@ impl MatchNameAndType for () {
impl<Head, Tail> MatchNameAndType for (Head, Tail) impl<Head, Tail> MatchNameAndType for (Head, Tail)
where where
Head: 'static + Named, Head: 'static + Named,
Tail: TupleList + MatchNameAndType, Tail: MatchNameAndType,
{ {
fn match_name_type<T: 'static>(&self, name: &str) -> Option<&T> { fn match_name_type<T: 'static>(&self, name: &str) -> Option<&T> {
if TypeId::of::<T>() == TypeId::of::<Head>() && name == self.0.name() { if TypeId::of::<T>() == TypeId::of::<Head>() && name == self.0.name() {

View File

@ -246,7 +246,7 @@ mod tests {
#[test] #[test]
fn test_event_serde() { fn test_event_serde() {
let obv = StdMapObserver::new("test", unsafe { &mut MAP }); let obv = StdMapObserver::new("test", unsafe { &mut MAP }, unsafe { MAP.len() });
let map = tuple_list!(obv); let map = tuple_list!(obv);
let observers_buf = postcard::to_allocvec(&map).unwrap(); let observers_buf = postcard::to_allocvec(&map).unwrap();

View File

@ -7,7 +7,7 @@ pub use map::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
bolts::tuples::{Named, TupleList}, bolts::tuples::Named,
corpus::Testcase, corpus::Testcase,
executors::ExitKind, executors::ExitKind,
inputs::Input, inputs::Input,
@ -92,7 +92,7 @@ where
impl<Head, Tail, I> FeedbacksTuple<I> for (Head, Tail) impl<Head, Tail, I> FeedbacksTuple<I> for (Head, Tail)
where where
Head: Feedback<I>, Head: Feedback<I>,
Tail: FeedbacksTuple<I> + TupleList, Tail: FeedbacksTuple<I>,
I: Input, I: Input,
{ {
fn is_interesting_all<OT: ObserversTuple>( fn is_interesting_all<OT: ObserversTuple>(

View File

@ -42,17 +42,29 @@ where
} }
/// The main fuzzer trait. /// The main fuzzer trait.
pub trait Fuzzer<E, EM, S> { pub trait Fuzzer<E, EM, S, CS> {
/// Fuzz for a single iteration /// Fuzz for a single iteration
/// Returns the index of the last fuzzed corpus item /// Returns the index of the last fuzzed corpus item
fn fuzz_one(&self, state: &mut S, executor: &mut E, manager: &mut EM) -> Result<usize, Error>; fn fuzz_one(
&mut self,
state: &mut S,
executor: &mut E,
manager: &mut EM,
scheduler: &CS,
) -> Result<usize, Error>;
/// Fuzz forever (or until stopped) /// Fuzz forever (or until stopped)
fn fuzz_loop(&self, state: &mut S, executor: &mut E, manager: &mut EM) -> Result<(), Error> { fn fuzz_loop(
&mut self,
state: &mut S,
executor: &mut E,
manager: &mut EM,
scheduler: &CS,
) -> Result<usize, Error> {
let mut last = current_time(); let mut last = current_time();
let stats_timeout = STATS_TIMEOUT_DEFAULT; let stats_timeout = STATS_TIMEOUT_DEFAULT;
loop { loop {
self.fuzz_one(state, executor, manager)?; self.fuzz_one(state, executor, manager, scheduler)?;
last = Self::maybe_report_stats(state, manager, last, stats_timeout)?; last = Self::maybe_report_stats(state, manager, last, stats_timeout)?;
} }
} }
@ -60,10 +72,11 @@ pub trait Fuzzer<E, EM, S> {
/// Fuzz for n iterations /// Fuzz for n iterations
/// Returns the index of the last fuzzed corpus item /// Returns the index of the last fuzzed corpus item
fn fuzz_loop_for( fn fuzz_loop_for(
&self, &mut self,
state: &mut S, state: &mut S,
executor: &mut E, executor: &mut E,
manager: &mut EM, manager: &mut EM,
scheduler: &CS,
iters: u64, iters: u64,
) -> Result<usize, Error> { ) -> Result<usize, Error> {
if iters == 0 { if iters == 0 {
@ -77,7 +90,7 @@ pub trait Fuzzer<E, EM, S> {
let stats_timeout = STATS_TIMEOUT_DEFAULT; let stats_timeout = STATS_TIMEOUT_DEFAULT;
for _ in 0..iters { for _ in 0..iters {
ret = self.fuzz_one(state, executor, manager)?; ret = self.fuzz_one(state, executor, manager, scheduler)?;
last = Self::maybe_report_stats(state, manager, last, stats_timeout)?; last = Self::maybe_report_stats(state, manager, last, stats_timeout)?;
} }
Ok(ret) Ok(ret)
@ -104,9 +117,8 @@ where
EM: EventManager<I, S>, EM: EventManager<I, S>,
I: Input, I: Input,
{ {
scheduler: CS,
stages: ST, stages: ST,
phantom: PhantomData<(E, EM, I, OT, S)>, phantom: PhantomData<(CS, E, EM, I, OT, S)>,
} }
impl<CS, ST, E, EM, I, OT, S> HasStages<CS, E, EM, I, S, ST> for StdFuzzer<CS, ST, E, EM, I, OT, S> impl<CS, ST, E, EM, I, OT, S> HasStages<CS, E, EM, I, S, ST> for StdFuzzer<CS, ST, E, EM, I, OT, S>
@ -126,6 +138,7 @@ where
} }
} }
/*
impl<CS, ST, E, EM, I, OT, S> HasCorpusScheduler<CS, I, S> for StdFuzzer<CS, ST, E, EM, I, OT, S> impl<CS, ST, E, EM, I, OT, S> HasCorpusScheduler<CS, I, S> for StdFuzzer<CS, ST, E, EM, I, OT, S>
where where
CS: CorpusScheduler<I, S>, CS: CorpusScheduler<I, S>,
@ -142,8 +155,9 @@ where
&mut self.scheduler &mut self.scheduler
} }
} }
*/
impl<CS, ST, E, EM, I, OT, S> Fuzzer<E, EM, S> for StdFuzzer<CS, ST, E, EM, I, OT, S> impl<CS, ST, E, EM, I, OT, S> Fuzzer<E, EM, S, CS> for StdFuzzer<CS, ST, E, EM, I, OT, S>
where where
CS: CorpusScheduler<I, S>, CS: CorpusScheduler<I, S>,
S: HasExecutions, S: HasExecutions,
@ -178,13 +192,19 @@ where
} }
} }
fn fuzz_one(&self, state: &mut S, executor: &mut E, manager: &mut EM) -> Result<usize, Error> { fn fuzz_one(
let idx = self.scheduler().next(state)?; &mut self,
state: &mut S,
executor: &mut E,
manager: &mut EM,
scheduler: &CS,
) -> Result<usize, Error> {
let idx = scheduler.next(state)?;
self.stages() self.stages_mut()
.perform_all(state, executor, manager, self.scheduler(), idx)?; .perform_all(state, executor, manager, scheduler, idx)?;
manager.process(state, executor, self.scheduler())?; manager.process(state, executor, scheduler)?;
Ok(idx) Ok(idx)
} }
} }
@ -197,9 +217,8 @@ where
EM: EventManager<I, S>, EM: EventManager<I, S>,
I: Input, I: Input,
{ {
pub fn new(scheduler: CS, stages: ST) -> Self { pub fn new(stages: ST) -> Self {
Self { Self {
scheduler,
stages, stages,
phantom: PhantomData, phantom: PhantomData,
} }

View File

@ -139,7 +139,7 @@ mod tests {
corpus::{Corpus, InMemoryCorpus, RandCorpusScheduler, Testcase}, corpus::{Corpus, InMemoryCorpus, RandCorpusScheduler, Testcase},
executors::{ExitKind, InProcessExecutor}, executors::{ExitKind, InProcessExecutor},
inputs::BytesInput, inputs::BytesInput,
mutators::{mutation_bitflip, ComposedByMutations, StdScheduledMutator}, mutators::{mutations::BitFlipMutator, StdScheduledMutator},
stages::StdMutationalStage, stages::StdMutationalStage,
state::{HasCorpus, State}, state::{HasCorpus, State},
stats::SimpleStats, stats::SimpleStats,
@ -182,14 +182,14 @@ mod tests {
) )
.unwrap(); .unwrap();
let mut mutator = StdScheduledMutator::new(); let mutator = StdScheduledMutator::new(tuple_list!(BitFlipMutator::new()));
mutator.add_mutation(mutation_bitflip);
let stage = StdMutationalStage::new(mutator); let stage = StdMutationalStage::new(mutator);
let fuzzer = StdFuzzer::new(RandCorpusScheduler::new(), tuple_list!(stage)); let scheduler = RandCorpusScheduler::new();
let mut fuzzer = StdFuzzer::new(tuple_list!(stage));
for i in 0..1000 { for i in 0..1000 {
fuzzer fuzzer
.fuzz_one(&mut state, &mut executor, &mut event_manager) .fuzz_one(&mut state, &mut executor, &mut event_manager, &scheduler)
.expect(&format!("Error in iter {}", i)); .expect(&format!("Error in iter {}", i));
} }

View File

@ -7,11 +7,24 @@ pub use mutations::*;
pub mod token_mutations; pub mod token_mutations;
pub use token_mutations::*; pub use token_mutations::*;
use crate::{inputs::Input, Error}; use crate::{
bolts::tuples::{HasLen, Named},
inputs::Input,
Error,
};
// TODO mutator stats method that produces something that can be sent with the NewTestcase event // TODO mutator stats method that produces something that can be sent with the NewTestcase event
// We can use it to report which mutations generated the testcase in the broker logs // We can use it to report which mutations generated the testcase in the broker logs
/// The result of a mutation.
/// If the mutation got skipped, the target
/// will not be executed with the returned input.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MutationResult {
Mutated,
Skipped,
}
/// A mutator takes input, and mutates it. /// A mutator takes input, and mutates it.
/// Simple as that. /// Simple as that.
pub trait Mutator<I, S> pub trait Mutator<I, S>
@ -19,15 +32,158 @@ where
I: Input, I: Input,
{ {
/// Mutate a given input /// Mutate a given input
fn mutate(&self, state: &mut S, input: &mut I, stage_idx: i32) -> Result<(), Error>; fn mutate(
&mut self,
state: &mut S,
input: &mut I,
stage_idx: i32,
) -> Result<MutationResult, Error>;
/// Post-process given the outcome of the execution /// Post-process given the outcome of the execution
fn post_exec( fn post_exec(
&self, &mut self,
_state: &mut S, _state: &mut S,
_is_interesting: u32,
_stage_idx: i32, _stage_idx: i32,
_corpus_idx: Option<usize>,
) -> Result<(), Error> { ) -> Result<(), Error> {
Ok(()) Ok(())
} }
} }
pub trait MutatorsTuple<I, S>: HasLen
where
I: Input,
{
fn mutate_all(
&mut self,
state: &mut S,
input: &mut I,
stage_idx: i32,
) -> Result<MutationResult, Error>;
fn post_exec_all(
&mut self,
state: &mut S,
stage_idx: i32,
corpus_idx: Option<usize>,
) -> Result<(), Error>;
fn get_and_mutate(
&mut self,
index: usize,
state: &mut S,
input: &mut I,
stage_idx: i32,
) -> Result<MutationResult, Error>;
fn get_and_post_exec(
&mut self,
index: usize,
state: &mut S,
stage_idx: i32,
corpus_idx: Option<usize>,
) -> Result<(), Error>;
}
impl<I, S> MutatorsTuple<I, S> for ()
where
I: Input,
{
fn mutate_all(
&mut self,
_state: &mut S,
_input: &mut I,
_stage_idx: i32,
) -> Result<MutationResult, Error> {
Ok(MutationResult::Skipped)
}
fn post_exec_all(
&mut self,
_state: &mut S,
_stage_idx: i32,
_corpus_idx: Option<usize>,
) -> Result<(), Error> {
Ok(())
}
fn get_and_mutate(
&mut self,
_index: usize,
_state: &mut S,
_input: &mut I,
_stage_idx: i32,
) -> Result<MutationResult, Error> {
Ok(MutationResult::Skipped)
}
fn get_and_post_exec(
&mut self,
_index: usize,
_state: &mut S,
_stage_idx: i32,
_corpus_idx: Option<usize>,
) -> Result<(), Error> {
Ok(())
}
}
impl<Head, Tail, I, S> MutatorsTuple<I, S> for (Head, Tail)
where
Head: Mutator<I, S> + Named,
Tail: MutatorsTuple<I, S>,
I: Input,
{
fn mutate_all(
&mut self,
state: &mut S,
input: &mut I,
stage_idx: i32,
) -> Result<MutationResult, Error> {
let r = self.0.mutate(state, input, stage_idx)?;
if self.1.mutate_all(state, input, stage_idx)? == MutationResult::Mutated {
Ok(MutationResult::Mutated)
} else {
Ok(r)
}
}
fn post_exec_all(
&mut self,
state: &mut S,
stage_idx: i32,
corpus_idx: Option<usize>,
) -> Result<(), Error> {
self.0.post_exec(state, stage_idx, corpus_idx)?;
self.1.post_exec_all(state, stage_idx, corpus_idx)
}
fn get_and_mutate(
&mut self,
index: usize,
state: &mut S,
input: &mut I,
stage_idx: i32,
) -> Result<MutationResult, Error> {
if index == 0 {
self.0.mutate(state, input, stage_idx)
} else {
self.1.get_and_mutate(index - 1, state, input, stage_idx)
}
}
fn get_and_post_exec(
&mut self,
index: usize,
state: &mut S,
stage_idx: i32,
corpus_idx: Option<usize>,
) -> Result<(), Error> {
if index == 0 {
self.0.post_exec(state, stage_idx, corpus_idx)
} else {
self.1
.get_and_post_exec(index - 1, state, stage_idx, corpus_idx)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,108 +1,161 @@
use alloc::string::String;
use alloc::vec::Vec; use alloc::vec::Vec;
use core::{ use core::{
default::Default,
fmt::{self, Debug}, fmt::{self, Debug},
marker::PhantomData, marker::PhantomData,
}; };
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
bolts::tuples::{tuple_list, NamedTuple},
corpus::Corpus, corpus::Corpus,
inputs::{HasBytesVec, Input}, inputs::{HasBytesVec, Input},
mutators::Mutator, mutators::{MutationResult, Mutator, MutatorsTuple},
state::{HasCorpus, HasMaxSize, HasMetadata, HasRand}, state::{HasCorpus, HasMaxSize, HasMetadata, HasRand},
utils::Rand, utils::{AsSlice, Rand},
Error, Error,
}; };
pub use crate::mutators::mutations::*; pub use crate::mutators::mutations::*;
pub use crate::mutators::token_mutations::*; pub use crate::mutators::token_mutations::*;
pub trait ScheduledMutator<I, S>: Mutator<I, S> + ComposedByMutations<I, S> #[derive(Serialize, Deserialize)]
pub struct MutationsMetadata {
pub list: Vec<String>,
}
crate::impl_serdeany!(MutationsMetadata);
impl AsSlice<String> for MutationsMetadata {
fn as_slice(&self) -> &[String] {
self.list.as_slice()
}
}
impl MutationsMetadata {
pub fn new(list: Vec<String>) -> Self {
Self { list }
}
}
pub trait ComposedByMutations<I, MT, S>
where where
I: Input, I: Input,
MT: MutatorsTuple<I, S>,
{
/// Get the mutations
fn mutations(&self) -> &MT;
// Get the mutations (mut)
fn mutations_mut(&mut self) -> &mut MT;
}
pub trait ScheduledMutator<I, MT, S>: ComposedByMutations<I, MT, S> + Mutator<I, S>
where
I: Input,
MT: MutatorsTuple<I, S>,
{ {
/// Compute the number of iterations used to apply stacked mutations /// Compute the number of iterations used to apply stacked mutations
fn iterations(&self, state: &mut S, input: &I) -> u64; fn iterations(&self, state: &mut S, input: &I) -> u64;
/// Get the next mutation to apply /// Get the next mutation to apply
fn schedule(&self, mutations_count: usize, state: &mut S, input: &I) -> usize; fn schedule(&self, state: &mut S, input: &I) -> usize;
/// New default implementation for mutate /// New default implementation for mutate
/// Implementations must forward mutate() to this method /// Implementations must forward mutate() to this method
fn scheduled_mutate(&self, state: &mut S, input: &mut I, _stage_idx: i32) -> Result<(), Error> { fn scheduled_mutate(
&mut self,
state: &mut S,
input: &mut I,
stage_idx: i32,
) -> Result<MutationResult, Error> {
let mut r = MutationResult::Skipped;
let num = self.iterations(state, input); let num = self.iterations(state, input);
for _ in 0..num { for _ in 0..num {
let idx = self.schedule(self.mutations_count(), state, input); let idx = self.schedule(state, input);
self.mutation_by_idx(idx)(state, input)?; let outcome = self
.mutations_mut()
.get_and_mutate(idx, state, input, stage_idx)?;
if outcome == MutationResult::Mutated {
r = MutationResult::Mutated;
} }
Ok(()) }
Ok(r)
} }
} }
pub struct StdScheduledMutator<I, R, S> pub struct StdScheduledMutator<I, MT, R, S>
where where
I: Input, I: Input,
S: HasRand<R>, MT: MutatorsTuple<I, S>,
R: Rand, R: Rand,
S: HasRand<R>,
{ {
mutations: Vec<MutationFunction<I, S>>, mutations: MT,
phantom: PhantomData<R>, phantom: PhantomData<(I, R, S)>,
} }
impl<I, R, S> Debug for StdScheduledMutator<I, R, S> impl<I, MT, R, S> Debug for StdScheduledMutator<I, MT, R, S>
where where
I: Input, I: Input,
S: HasRand<R>, MT: MutatorsTuple<I, S>,
R: Rand, R: Rand,
S: HasRand<R>,
{ {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
"StdScheduledMutator with {} Mutations for Input type {}", "StdScheduledMutator with {} mutations for Input type {}",
self.mutations.len(), self.mutations.len(),
core::any::type_name::<I>() core::any::type_name::<I>()
) )
} }
} }
impl<I, R, S> Mutator<I, S> for StdScheduledMutator<I, R, S> impl<I, MT, R, S> Mutator<I, S> for StdScheduledMutator<I, MT, R, S>
where where
I: Input, I: Input,
S: HasRand<R>, MT: MutatorsTuple<I, S>,
R: Rand, R: Rand,
S: HasRand<R>,
{ {
fn mutate(&self, state: &mut S, input: &mut I, _stage_idx: i32) -> Result<(), Error> { #[inline]
self.scheduled_mutate(state, input, _stage_idx) fn mutate(
&mut self,
state: &mut S,
input: &mut I,
stage_idx: i32,
) -> Result<MutationResult, Error> {
self.scheduled_mutate(state, input, stage_idx)
} }
} }
impl<I, R, S> ComposedByMutations<I, S> for StdScheduledMutator<I, R, S> impl<I, MT, R, S> ComposedByMutations<I, MT, S> for StdScheduledMutator<I, MT, R, S>
where where
I: Input, I: Input,
S: HasRand<R>, MT: MutatorsTuple<I, S>,
R: Rand, R: Rand,
S: HasRand<R>,
{ {
/// Get the mutations
#[inline] #[inline]
fn mutation_by_idx(&self, index: usize) -> MutationFunction<I, S> { fn mutations(&self) -> &MT {
self.mutations[index] &self.mutations
} }
// Get the mutations (mut)
#[inline] #[inline]
fn mutations_count(&self) -> usize { fn mutations_mut(&mut self) -> &mut MT {
self.mutations.len() &mut self.mutations
}
#[inline]
fn add_mutation(&mut self, mutation: MutationFunction<I, S>) {
self.mutations.push(mutation)
} }
} }
impl<I, R, S> ScheduledMutator<I, S> for StdScheduledMutator<I, R, S> impl<I, MT, R, S> ScheduledMutator<I, MT, S> for StdScheduledMutator<I, MT, R, S>
where where
I: Input, I: Input,
S: HasRand<R>, MT: MutatorsTuple<I, S>,
R: Rand, R: Rand,
S: HasRand<R>,
{ {
/// Compute the number of iterations used to apply stacked mutations /// Compute the number of iterations used to apply stacked mutations
fn iterations(&self, state: &mut S, _: &I) -> u64 { fn iterations(&self, state: &mut S, _: &I) -> u64 {
@ -110,161 +163,222 @@ where
} }
/// Get the next mutation to apply /// Get the next mutation to apply
fn schedule(&self, mutations_count: usize, state: &mut S, _: &I) -> usize { fn schedule(&self, state: &mut S, _: &I) -> usize {
debug_assert!(mutations_count > 0); debug_assert!(!self.mutations().is_empty());
state.rand_mut().below(mutations_count as u64) as usize state.rand_mut().below(self.mutations().len() as u64) as usize
} }
} }
impl<I, R, S> StdScheduledMutator<I, R, S> impl<I, MT, R, S> StdScheduledMutator<I, MT, R, S>
where where
I: Input, I: Input,
S: HasRand<R>, MT: MutatorsTuple<I, S>,
R: Rand, R: Rand,
S: HasRand<R>,
{ {
/// Create a new StdScheduledMutator instance without mutations and corpus
pub fn new() -> Self {
Self {
mutations: vec![],
phantom: PhantomData,
}
}
/// Create a new StdScheduledMutator instance specifying mutations /// Create a new StdScheduledMutator instance specifying mutations
pub fn with_mutations(mutations: Vec<MutationFunction<I, S>>) -> Self { pub fn new(mutations: MT) -> Self {
StdScheduledMutator { StdScheduledMutator {
mutations, mutations: mutations,
phantom: PhantomData, phantom: PhantomData,
} }
} }
} }
impl<I, R, S> Default for StdScheduledMutator<I, R, S> /// Get the mutations that compose the Havoc mutator
pub fn havoc_mutations<C, I, R, S>() -> impl MutatorsTuple<I, S>
where where
I: Input,
S: HasRand<R>,
R: Rand,
{
fn default() -> Self {
Self::new()
}
}
/// Schedule some selected byte level mutations given a ScheduledMutator type
#[derive(Clone, Debug)]
pub struct HavocBytesMutator<C, I, R, S, SM>
where
SM: ScheduledMutator<I, S>,
I: Input + HasBytesVec, I: Input + HasBytesVec,
S: HasRand<R> + HasCorpus<C, I> + HasMetadata + HasMaxSize, S: HasRand<R> + HasCorpus<C, I> + HasMetadata + HasMaxSize,
C: Corpus<I>, C: Corpus<I>,
R: Rand, R: Rand,
{
tuple_list!(
BitFlipMutator::new(),
ByteFlipMutator::new(),
ByteIncMutator::new(),
ByteDecMutator::new(),
ByteNegMutator::new(),
ByteRandMutator::new(),
ByteAddMutator::new(),
WordAddMutator::new(),
DwordAddMutator::new(),
QwordAddMutator::new(),
ByteInterestingMutator::new(),
WordInterestingMutator::new(),
DwordInterestingMutator::new(),
BytesDeleteMutator::new(),
BytesDeleteMutator::new(),
BytesDeleteMutator::new(),
BytesDeleteMutator::new(),
BytesExpandMutator::new(),
BytesInsertMutator::new(),
BytesRandInsertMutator::new(),
BytesSetMutator::new(),
BytesRandSetMutator::new(),
BytesCopyMutator::new(),
BytesSwapMutator::new(),
TokenInsert::new(),
TokenReplace::new(),
CrossoverInsertMutator::new(),
CrossoverReplaceMutator::new(),
)
}
//wraps around StdScheduledMutator
pub struct LoggerScheduledMutator<C, I, MT, R, S, SM>
where
C: Corpus<I>,
I: Input,
MT: MutatorsTuple<I, S> + NamedTuple,
R: Rand,
S: HasRand<R> + HasCorpus<C, I>,
SM: ScheduledMutator<I, MT, S>,
{ {
scheduled: SM, scheduled: SM,
phantom: PhantomData<(C, I, R, S)>, mutation_log: Vec<usize>,
phantom: PhantomData<(C, I, MT, R, S)>,
} }
impl<C, I, R, S, SM> Mutator<I, S> for HavocBytesMutator<C, I, R, S, SM> impl<C, I, MT, R, S, SM> Debug for LoggerScheduledMutator<C, I, MT, R, S, SM>
where where
SM: ScheduledMutator<I, S>,
I: Input + HasBytesVec,
S: HasRand<R> + HasCorpus<C, I> + HasMetadata + HasMaxSize,
C: Corpus<I>, C: Corpus<I>,
I: Input,
MT: MutatorsTuple<I, S> + NamedTuple,
R: Rand, R: Rand,
S: HasRand<R> + HasCorpus<C, I>,
SM: ScheduledMutator<I, MT, S>,
{ {
/// Mutate bytes fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn mutate(&self, state: &mut S, input: &mut I, stage_idx: i32) -> Result<(), Error> { write!(
self.scheduled.mutate(state, input, stage_idx)?; f,
/*let num = self.scheduled.iterations(state, input); "LoggerScheduledMutator with {} mutations for Input type {}",
for _ in 0..num { self.scheduled.mutations().len(),
let idx = self.scheduled.schedule(14, state, input); core::any::type_name::<I>()
let mutation = match idx { )
0 => mutation_bitflip, }
1 => mutation_byteflip, }
2 => mutation_byteinc,
3 => mutation_bytedec,
4 => mutation_byteneg,
5 => mutation_byterand,
6 => mutation_byteadd, impl<C, I, MT, R, S, SM> Mutator<I, S> for LoggerScheduledMutator<C, I, MT, R, S, SM>
7 => mutation_wordadd, where
8 => mutation_dwordadd, C: Corpus<I>,
9 => mutation_byteinteresting, I: Input,
10 => mutation_wordinteresting, MT: MutatorsTuple<I, S> + NamedTuple,
11 => mutation_dwordinteresting, R: Rand,
_ => mutation_splice, S: HasRand<R> + HasCorpus<C, I>,
SM: ScheduledMutator<I, MT, S>,
{
fn mutate(
&mut self,
state: &mut S,
input: &mut I,
stage_idx: i32,
) -> Result<MutationResult, Error> {
self.scheduled_mutate(state, input, stage_idx)
}
fn post_exec(
&mut self,
state: &mut S,
_stage_idx: i32,
corpus_idx: Option<usize>,
) -> Result<(), Error> {
if let Some(idx) = corpus_idx {
let mut testcase = (*state.corpus_mut().get(idx)?).borrow_mut();
let mut log = Vec::<String>::new();
while let Some(idx) = self.mutation_log.pop() {
let name = String::from(self.scheduled.mutations().get_name(idx).unwrap()); // TODO maybe return an Error on None
log.push(name)
}
let meta = MutationsMetadata::new(log);
testcase.add_metadata(meta);
}; };
mutation(self, state, input)?; // Always reset the log for each run
}*/ self.mutation_log.clear();
Ok(()) Ok(())
} }
} }
impl<C, I, R, S, SM> HavocBytesMutator<C, I, R, S, SM> impl<C, I, MT, R, S, SM> ComposedByMutations<I, MT, S>
for LoggerScheduledMutator<C, I, MT, R, S, SM>
where where
SM: ScheduledMutator<I, S>,
I: Input + HasBytesVec,
S: HasRand<R> + HasCorpus<C, I> + HasMetadata + HasMaxSize,
C: Corpus<I>, C: Corpus<I>,
I: Input,
MT: MutatorsTuple<I, S> + NamedTuple,
R: Rand, R: Rand,
S: HasRand<R> + HasCorpus<C, I>,
SM: ScheduledMutator<I, MT, S>,
{ {
/// Create a new HavocBytesMutator instance given a ScheduledMutator to wrap #[inline]
pub fn new(mut scheduled: SM) -> Self { fn mutations(&self) -> &MT {
scheduled.add_mutation(mutation_bitflip); self.scheduled.mutations()
scheduled.add_mutation(mutation_splice);
Self {
scheduled,
phantom: PhantomData,
} }
#[inline]
fn mutations_mut(&mut self) -> &mut MT {
self.scheduled.mutations_mut()
} }
} }
impl<C, I, R, S> Default for HavocBytesMutator<C, I, R, S, StdScheduledMutator<I, R, S>> impl<C, I, MT, R, S, SM> ScheduledMutator<I, MT, S> for LoggerScheduledMutator<C, I, MT, R, S, SM>
where where
I: Input + HasBytesVec,
S: HasRand<R> + HasCorpus<C, I> + HasMetadata + HasMaxSize,
C: Corpus<I>, C: Corpus<I>,
I: Input,
MT: MutatorsTuple<I, S> + NamedTuple,
R: Rand, R: Rand,
S: HasRand<R> + HasCorpus<C, I>,
SM: ScheduledMutator<I, MT, S>,
{ {
/// Create a new HavocBytesMutator instance wrapping StdScheduledMutator /// Compute the number of iterations used to apply stacked mutations
fn default() -> Self { fn iterations(&self, state: &mut S, _: &I) -> u64 {
let mut scheduled = StdScheduledMutator::<I, R, S>::new(); 1 << (1 + state.rand_mut().below(6))
scheduled.add_mutation(mutation_bitflip); }
scheduled.add_mutation(mutation_byteflip);
scheduled.add_mutation(mutation_byteinc);
scheduled.add_mutation(mutation_bytedec);
scheduled.add_mutation(mutation_byteneg);
scheduled.add_mutation(mutation_byterand);
scheduled.add_mutation(mutation_byteadd); /// Get the next mutation to apply
scheduled.add_mutation(mutation_wordadd); fn schedule(&self, state: &mut S, _: &I) -> usize {
scheduled.add_mutation(mutation_dwordadd); debug_assert!(!self.scheduled.mutations().is_empty());
scheduled.add_mutation(mutation_qwordadd); state
scheduled.add_mutation(mutation_byteinteresting); .rand_mut()
scheduled.add_mutation(mutation_wordinteresting); .below(self.scheduled.mutations().len() as u64) as usize
scheduled.add_mutation(mutation_dwordinteresting); }
scheduled.add_mutation(mutation_bytesdelete); fn scheduled_mutate(
scheduled.add_mutation(mutation_bytesdelete); &mut self,
scheduled.add_mutation(mutation_bytesdelete); state: &mut S,
scheduled.add_mutation(mutation_bytesdelete); input: &mut I,
scheduled.add_mutation(mutation_bytesexpand); stage_idx: i32,
scheduled.add_mutation(mutation_bytesinsert); ) -> Result<MutationResult, Error> {
scheduled.add_mutation(mutation_bytesrandinsert); let mut r = MutationResult::Skipped;
scheduled.add_mutation(mutation_bytesset); let num = self.iterations(state, input);
scheduled.add_mutation(mutation_bytesrandset); self.mutation_log.clear();
scheduled.add_mutation(mutation_bytescopy); for _ in 0..num {
scheduled.add_mutation(mutation_bytesswap); let idx = self.schedule(state, input);
self.mutation_log.push(idx);
let outcome = self
.mutations_mut()
.get_and_mutate(idx, state, input, stage_idx)?;
if outcome == MutationResult::Mutated {
r = MutationResult::Mutated;
}
}
Ok(r)
}
}
scheduled.add_mutation(mutation_tokeninsert); impl<C, I, MT, R, S, SM> LoggerScheduledMutator<C, I, MT, R, S, SM>
scheduled.add_mutation(mutation_tokenreplace); where
C: Corpus<I>,
scheduled.add_mutation(mutation_crossover_insert); I: Input,
scheduled.add_mutation(mutation_crossover_replace); MT: MutatorsTuple<I, S> + NamedTuple,
//scheduled.add_mutation(mutation_splice); R: Rand,
S: HasRand<R> + HasCorpus<C, I>,
HavocBytesMutator { SM: ScheduledMutator<I, MT, S>,
scheduled, {
/// Create a new StdScheduledMutator instance without mutations and corpus
pub fn new(scheduled: SM) -> Self {
Self {
scheduled: scheduled,
mutation_log: vec![],
phantom: PhantomData, phantom: PhantomData,
} }
} }
@ -276,7 +390,8 @@ mod tests {
corpus::{Corpus, InMemoryCorpus, Testcase}, corpus::{Corpus, InMemoryCorpus, Testcase},
inputs::{BytesInput, HasBytesVec}, inputs::{BytesInput, HasBytesVec},
mutators::{ mutators::{
scheduled::{mutation_splice, HavocBytesMutator, StdScheduledMutator}, mutations::SpliceMutator,
scheduled::{havoc_mutations, StdScheduledMutator},
Mutator, Mutator,
}, },
state::State, state::State,
@ -302,7 +417,8 @@ mod tests {
rand.set_seed(5); rand.set_seed(5);
mutation_splice(&mut state, &mut input).unwrap(); let mut splice = SpliceMutator::new();
splice.mutate(&mut state, &mut input, 0).unwrap();
#[cfg(feature = "std")] #[cfg(feature = "std")]
println!("{:?}", input.bytes()); println!("{:?}", input.bytes());
@ -330,7 +446,7 @@ mod tests {
let mut state = State::new(rand, corpus, (), InMemoryCorpus::new(), ()); let mut state = State::new(rand, corpus, (), InMemoryCorpus::new(), ());
let havoc = HavocBytesMutator::new(StdScheduledMutator::new()); let mut havoc = StdScheduledMutator::new(havoc_mutations());
assert_eq!(input, input_prior); assert_eq!(input, input_prior);

View File

@ -1,6 +1,5 @@
//! Tokens are what afl calls extras or dictionaries. //! Tokens are what afl calls extras or dictionaries.
//! They may be inserted as part of mutations during fuzzing. //! They may be inserted as part of mutations during fuzzing.
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::{ use std::{
fs::File, fs::File,
@ -15,6 +14,7 @@ use crate::{
utils::Rand, utils::Rand,
Error, Error,
}; };
use core::marker::PhantomData;
use alloc::vec::Vec; use alloc::vec::Vec;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -121,13 +121,28 @@ impl Tokens {
} }
} }
/// Insert a dictionary token #[derive(Default)]
pub fn mutation_tokeninsert<I, R, S>(state: &mut S, input: &mut I) -> Result<MutationResult, Error> pub struct TokenInsert<I, R, S>
where where
I: Input + HasBytesVec, I: Input + HasBytesVec,
S: HasMetadata + HasRand<R> + HasMaxSize, S: HasMetadata + HasRand<R> + HasMaxSize,
R: Rand, R: Rand,
{ {
phantom: PhantomData<(I, R, S)>,
}
impl<I, R, S> Mutator<I, S> for TokenInsert<I, R, S>
where
I: Input + HasBytesVec,
S: HasMetadata + HasRand<R> + HasMaxSize,
R: Rand,
{
fn mutate(
&mut self,
state: &mut S,
input: &mut I,
_stage_idx: i32,
) -> Result<MutationResult, Error> {
let max_size = state.max_size(); let max_size = state.max_size();
let tokens_len = { let tokens_len = {
let meta = state.metadata().get::<Tokens>(); let meta = state.metadata().get::<Tokens>();
@ -161,15 +176,55 @@ where
buffer_copy(input.bytes_mut(), token, 0, off, len); buffer_copy(input.bytes_mut(), token, 0, off, len);
Ok(MutationResult::Mutated) Ok(MutationResult::Mutated)
}
} }
/// Overwrite with a dictionary token impl<I, R, S> Named for TokenInsert<I, R, S>
pub fn mutation_tokenreplace<I, R, S>(state: &mut S, input: &mut I) -> Result<MutationResult, Error>
where where
I: Input + HasBytesVec, I: Input + HasBytesVec,
S: HasMetadata + HasRand<R>, S: HasMetadata + HasRand<R> + HasMaxSize,
R: Rand, R: Rand,
{ {
fn name(&self) -> &str {
"TokenInsert"
}
}
impl<I, R, S> TokenInsert<I, R, S>
where
I: Input + HasBytesVec,
S: HasMetadata + HasRand<R> + HasMaxSize,
R: Rand,
{
pub fn new() -> Self {
Self {
phantom: PhantomData,
}
}
}
#[derive(Default)]
pub struct TokenReplace<I, R, S>
where
I: Input + HasBytesVec,
S: HasMetadata + HasRand<R> + HasMaxSize,
R: Rand,
{
phantom: PhantomData<(I, R, S)>,
}
impl<I, R, S> Mutator<I, S> for TokenReplace<I, R, S>
where
I: Input + HasBytesVec,
S: HasMetadata + HasRand<R> + HasMaxSize,
R: Rand,
{
fn mutate(
&mut self,
state: &mut S,
input: &mut I,
_stage_idx: i32,
) -> Result<MutationResult, Error> {
let size = input.bytes().len(); let size = input.bytes().len();
if size == 0 { if size == 0 {
return Ok(MutationResult::Skipped); return Ok(MutationResult::Skipped);
@ -199,6 +254,31 @@ where
buffer_copy(input.bytes_mut(), token, 0, off, len); buffer_copy(input.bytes_mut(), token, 0, off, len);
Ok(MutationResult::Mutated) Ok(MutationResult::Mutated)
}
}
impl<I, R, S> Named for TokenReplace<I, R, S>
where
I: Input + HasBytesVec,
S: HasMetadata + HasRand<R> + HasMaxSize,
R: Rand,
{
fn name(&self) -> &str {
"TokenReplace"
}
}
impl<I, R, S> TokenReplace<I, R, S>
where
I: Input + HasBytesVec,
S: HasMetadata + HasRand<R> + HasMaxSize,
R: Rand,
{
pub fn new() -> Self {
Self {
phantom: PhantomData,
}
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -9,7 +9,7 @@ use core::time::Duration;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
bolts::tuples::{MatchFirstType, MatchNameAndType, MatchType, Named, TupleList}, bolts::tuples::{MatchFirstType, MatchNameAndType, MatchType, Named},
utils::current_time, utils::current_time,
Error, Error,
}; };
@ -75,7 +75,7 @@ impl ObserversTuple for () {
impl<Head, Tail> ObserversTuple for (Head, Tail) impl<Head, Tail> ObserversTuple for (Head, Tail)
where where
Head: Observer, Head: Observer,
Tail: ObserversTuple + TupleList, Tail: ObserversTuple,
{ {
fn pre_exec_all(&mut self) -> Result<(), Error> { fn pre_exec_all(&mut self) -> Result<(), Error> {
self.0.pre_exec()?; self.0.pre_exec()?;
@ -145,7 +145,7 @@ mod tests {
fn test_observer_serde() { fn test_observer_serde() {
let obv = tuple_list!( let obv = tuple_list!(
TimeObserver::new("time"), TimeObserver::new("time"),
StdMapObserver::new("map", unsafe { &mut MAP }) StdMapObserver::new("map", unsafe { &mut MAP }, unsafe { MAP.len() })
); );
let vec = postcard::to_allocvec(&obv).unwrap(); let vec = postcard::to_allocvec(&obv).unwrap();
println!("{:?}", vec); println!("{:?}", vec);

View File

@ -18,7 +18,7 @@ where
{ {
/// Run the stage /// Run the stage
fn perform( fn perform(
&self, &mut self,
state: &mut S, state: &mut S,
executor: &mut E, executor: &mut E,
manager: &mut EM, manager: &mut EM,
@ -34,7 +34,7 @@ where
I: Input, I: Input,
{ {
fn perform_all( fn perform_all(
&self, &mut self,
state: &mut S, state: &mut S,
executor: &mut E, executor: &mut E,
manager: &mut EM, manager: &mut EM,
@ -49,7 +49,14 @@ where
E: Executor<I>, E: Executor<I>,
I: Input, I: Input,
{ {
fn perform_all(&self, _: &mut S, _: &mut E, _: &mut EM, _: &CS, _: usize) -> Result<(), Error> { fn perform_all(
&mut self,
_: &mut S,
_: &mut E,
_: &mut EM,
_: &CS,
_: usize,
) -> Result<(), Error> {
Ok(()) Ok(())
} }
} }
@ -63,7 +70,7 @@ where
I: Input, I: Input,
{ {
fn perform_all( fn perform_all(
&self, &mut self,
state: &mut S, state: &mut S,
executor: &mut E, executor: &mut E,
manager: &mut EM, manager: &mut EM,

View File

@ -40,7 +40,7 @@ where
/// Runs this (mutational) stage for the given testcase /// Runs this (mutational) stage for the given testcase
fn perform_mutational( fn perform_mutational(
&self, &mut self,
state: &mut S, state: &mut S,
executor: &mut E, executor: &mut E,
manager: &mut EM, manager: &mut EM,
@ -55,11 +55,10 @@ where
.borrow_mut() .borrow_mut()
.load_input()? .load_input()?
.clone(); .clone();
self.mutator().mutate(state, &mut input_mut, i as i32)?; self.mutator_mut().mutate(state, &mut input_mut, i as i32)?;
let (_, corpus_idx) = state.evaluate_input(input_mut, executor, manager, scheduler)?;
let fitness = state.evaluate_input(input_mut, executor, manager, scheduler)?; self.mutator_mut().post_exec(state, i as i32, corpus_idx)?;
self.mutator().post_exec(state, fitness, i as i32)?;
} }
Ok(()) Ok(())
} }
@ -131,7 +130,7 @@ where
{ {
#[inline] #[inline]
fn perform( fn perform(
&self, &mut self,
state: &mut S, state: &mut S,
executor: &mut E, executor: &mut E,
manager: &mut EM, manager: &mut EM,

View File

@ -198,7 +198,7 @@ where
executor: &mut E, executor: &mut E,
manager: &mut EM, manager: &mut EM,
scheduler: &CS, scheduler: &CS,
) -> Result<u32, Error> ) -> Result<(u32, Option<usize>), Error>
where where
E: Executor<I> + HasObservers<OT>, E: Executor<I> + HasObservers<OT>,
OT: ObserversTuple, OT: ObserversTuple,
@ -499,7 +499,7 @@ where
executor: &mut E, executor: &mut E,
manager: &mut EM, manager: &mut EM,
scheduler: &CS, scheduler: &CS,
) -> Result<u32, Error> ) -> Result<(u32, Option<usize>), Error>
where where
E: Executor<I> + HasObservers<OT>, E: Executor<I> + HasObservers<OT>,
OT: ObserversTuple, OT: ObserversTuple,
@ -514,11 +514,8 @@ where
// If the input is a solution, add it to the respective corpus // If the input is a solution, add it to the respective corpus
self.solutions_mut().add(Testcase::new(input.clone()))?; self.solutions_mut().add(Testcase::new(input.clone()))?;
} }
let corpus_idx = self.add_if_interesting(&input, fitness, scheduler)?;
if self if corpus_idx.is_some() {
.add_if_interesting(&input, fitness, scheduler)?
.is_some()
{
let observers_buf = manager.serialize_observers(observers)?; let observers_buf = manager.serialize_observers(observers)?;
manager.fire( manager.fire(
self, self,
@ -533,7 +530,7 @@ where
)?; )?;
} }
Ok(fitness) Ok((fitness, corpus_idx))
} }
} }
@ -683,7 +680,7 @@ where
let mut added = 0; let mut added = 0;
for _ in 0..num { for _ in 0..num {
let input = generator.generate(self.rand_mut())?; let input = generator.generate(self.rand_mut())?;
let fitness = self.evaluate_input(input, executor, manager, scheduler)?; let (fitness, _) = self.evaluate_input(input, executor, manager, scheduler)?;
if fitness > 0 { if fitness > 0 {
added += 1; added += 1;
} }

View File

@ -5,7 +5,7 @@ authors = ["Andrea Fioraldi <andreafioraldi@gmail.com>"]
edition = "2018" edition = "2018"
[features] [features]
default = ["sancov", "libfuzzer_compatibility"] default = []
sancov = [] sancov = []
libfuzzer_compatibility = [] libfuzzer_compatibility = []