From 006dcac00c9e7c7805362ef9f149d3b24a71fa5b Mon Sep 17 00:00:00 2001 From: Dominik Maier Date: Tue, 1 Aug 2023 16:58:40 +0200 Subject: [PATCH] Named Mutators and MultiMutator API change (#1387) * Mutators need names (alternative to #1379) * Signature of MultiMutator shouldn't be the same as the normal mutator * Named for python, remove mutator for multi_mutator * fmt * clippy edition warning * clippy * mac_count doc fix, return cleanup --- Cargo.toml | 1 + .../fuzzbench_forkserver_cmplog/src/main.rs | 6 +- libafl/src/mutators/mod.rs | 59 ++++++++++++--- libafl/src/mutators/mopt_mutator.rs | 22 +++++- libafl/src/mutators/scheduled.rs | 31 +++++++- libafl/src/mutators/token_mutations.rs | 73 ++++++++++--------- libafl/src/mutators/tuneable.rs | 16 +++- libafl/src/stages/mutational.rs | 25 +++---- 8 files changed, 166 insertions(+), 67 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ef58e3fa83..a56246ffdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "libafl", "libafl_derive", diff --git a/fuzzers/fuzzbench_forkserver_cmplog/src/main.rs b/fuzzers/fuzzbench_forkserver_cmplog/src/main.rs index 249c3d448f..bb7ed78e0a 100644 --- a/fuzzers/fuzzbench_forkserver_cmplog/src/main.rs +++ b/fuzzers/fuzzbench_forkserver_cmplog/src/main.rs @@ -26,7 +26,7 @@ use libafl::{ monitors::SimpleMonitor, mutators::{ scheduled::havoc_mutations, token_mutations::AFLppRedQueen, tokens_mutations, - MutationResult, StdMOptMutator, Tokens, + StdMOptMutator, Tokens, }, observers::{ AFLppCmpMap, AFLppForkserverCmpObserver, HitcountsMapObserver, StdMapObserver, TimeObserver, @@ -35,7 +35,7 @@ use libafl::{ powersched::PowerSchedule, IndexesLenTimeMinimizerScheduler, StdWeightedScheduler, }, stages::{ - calibrate::CalibrationStage, mutational::MultipleMutationalStage, + calibrate::CalibrationStage, mutational::MultiMutationalStage, power::StdPowerMutationalStage, tracing::AFLppCmplogTracingStage, ColorizationStage, IfStage, }, @@ -368,7 +368,7 @@ fn fuzz( let tracing = AFLppCmplogTracingStage::with_cmplog_observer_name(cmplog_executor, "cmplog"); // Setup a randomic Input2State stage - let rq = MultipleMutationalStage::new(AFLppRedQueen::with_cmplog_options(true, true)); + let rq = MultiMutationalStage::new(AFLppRedQueen::with_cmplog_options(true, true)); let cb = |_fuzzer: &mut _, _executor: &mut _, diff --git a/libafl/src/mutators/mod.rs b/libafl/src/mutators/mod.rs index 070ac3e574..70dd76eeb2 100644 --- a/libafl/src/mutators/mod.rs +++ b/libafl/src/mutators/mod.rs @@ -80,7 +80,7 @@ pub enum MutationResult { /// A mutator takes input, and mutates it. /// Simple as that. -pub trait Mutator { +pub trait Mutator: Named { /// Mutate a given input fn mutate( &mut self, @@ -90,6 +90,7 @@ pub trait Mutator { ) -> Result; /// Post-process given the outcome of the execution + #[inline] fn post_exec( &mut self, _state: &mut S, @@ -102,18 +103,20 @@ pub trait Mutator { /// A mutator that takes input, and returns a vector of mutated inputs. /// Simple as that. -pub trait MultipleMutator { - /// Mutate a given input - fn mutate( +pub trait MultiMutator: Named { + /// Mutate a given input up to `max_count` times, + /// or as many times as appropriate, if no `max_count` is given + fn multi_mutate( &mut self, state: &mut S, input: &I, - vec: &mut Vec, stage_idx: i32, - ) -> Result; + max_count: Option, + ) -> Result, Error>; /// Post-process given the outcome of the execution - fn post_exec( + #[inline] + fn multi_post_exec( &mut self, _state: &mut S, _stage_idx: i32, @@ -158,9 +161,13 @@ pub trait MutatorsTuple: HasConstLen { stage_idx: i32, corpus_idx: Option, ) -> Result<(), Error>; + + /// Gets all names of the wrapped [`Mutator`]`s`. + fn names(&self) -> Vec<&str>; } impl MutatorsTuple for () { + #[inline] fn mutate_all( &mut self, _state: &mut S, @@ -170,6 +177,7 @@ impl MutatorsTuple for () { Ok(MutationResult::Skipped) } + #[inline] fn post_exec_all( &mut self, _state: &mut S, @@ -179,6 +187,7 @@ impl MutatorsTuple for () { Ok(()) } + #[inline] fn get_and_mutate( &mut self, _index: MutationId, @@ -189,6 +198,7 @@ impl MutatorsTuple for () { Ok(MutationResult::Skipped) } + #[inline] fn get_and_post_exec( &mut self, _index: usize, @@ -198,11 +208,16 @@ impl MutatorsTuple for () { ) -> Result<(), Error> { Ok(()) } + + #[inline] + fn names(&self) -> Vec<&str> { + Vec::new() + } } impl MutatorsTuple for (Head, Tail) where - Head: Mutator + Named, + Head: Mutator, Tail: MutatorsTuple, { fn mutate_all( @@ -258,16 +273,25 @@ where .get_and_post_exec(index - 1, state, stage_idx, corpus_idx) } } + + fn names(&self) -> Vec<&str> { + let mut ret = self.1.names(); + ret.insert(0, self.0.name()); + ret + } } /// `Mutator` Python bindings #[cfg(feature = "python")] #[allow(missing_docs)] pub mod pybind { - use pyo3::prelude::*; + use core::ffi::CStr; + + use pyo3::{prelude::*, AsPyPointer}; use super::{MutationResult, Mutator}; use crate::{ + bolts::tuples::Named, corpus::CorpusId, inputs::{BytesInput, HasBytesVec}, mutators::scheduled::pybind::PythonStdHavocMutator, @@ -287,6 +311,14 @@ pub mod pybind { } } + impl Named for PyObjectMutator { + fn name(&self) -> &str { + unsafe { CStr::from_ptr((*(*self.inner.as_ptr()).ob_type).tp_name) } + .to_str() + .unwrap() + } + } + impl Mutator for PyObjectMutator { fn mutate( &mut self, @@ -386,6 +418,15 @@ pub mod pybind { } } + impl Named for PythonMutator { + fn name(&self) -> &str { + match &self.wrapper { + PythonMutatorWrapper::Python(pyo) => pyo.name(), + PythonMutatorWrapper::StdHavoc(_) => "StdHavocPythonMutator", + } + } + } + impl Mutator for PythonMutator { fn mutate( &mut self, diff --git a/libafl/src/mutators/mopt_mutator.rs b/libafl/src/mutators/mopt_mutator.rs index d4e01290b5..d08c9350c2 100644 --- a/libafl/src/mutators/mopt_mutator.rs +++ b/libafl/src/mutators/mopt_mutator.rs @@ -1,5 +1,8 @@ //! The `MOpt` mutator scheduler, see and -use alloc::{string::ToString, vec::Vec}; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; use core::{ fmt::{self, Debug}, marker::PhantomData, @@ -9,7 +12,10 @@ use serde::{Deserialize, Serialize}; use super::MutationId; use crate::{ - bolts::rands::{Rand, StdRand}, + bolts::{ + rands::{Rand, StdRand}, + tuples::Named, + }, corpus::{Corpus, CorpusId}, mutators::{ComposedByMutations, MutationResult, Mutator, MutatorsTuple, ScheduledMutator}, state::{HasCorpus, HasMetadata, HasRand, HasSolutions}, @@ -365,6 +371,7 @@ where MT: MutatorsTuple, S: HasRand + HasMetadata + HasCorpus + HasSolutions, { + name: String, mode: MOptMode, finds_before: usize, mutations: MT, @@ -539,6 +546,7 @@ where state.add_metadata::(MOpt::new(mutations.len(), swarm_num, rand_seed)?); } Ok(Self { + name: format!("StdMOptMutator[{}]", mutations.names().join(",")), mode: MOptMode::Pilotfuzzing, finds_before: 0, mutations, @@ -632,6 +640,16 @@ where } } +impl Named for StdMOptMutator +where + MT: MutatorsTuple, + S: HasRand + HasMetadata + HasCorpus + HasSolutions, +{ + fn name(&self) -> &str { + &self.name + } +} + impl ScheduledMutator for StdMOptMutator where MT: MutatorsTuple, diff --git a/libafl/src/mutators/scheduled.rs b/libafl/src/mutators/scheduled.rs index 894f53de04..a2a739187c 100644 --- a/libafl/src/mutators/scheduled.rs +++ b/libafl/src/mutators/scheduled.rs @@ -13,7 +13,7 @@ pub use crate::mutators::{mutations::*, token_mutations::*}; use crate::{ bolts::{ rands::Rand, - tuples::{tuple_list, tuple_list_type, NamedTuple}, + tuples::{tuple_list, tuple_list_type, Named, NamedTuple}, AsMutSlice, AsSlice, }, corpus::{Corpus, CorpusId}, @@ -106,6 +106,7 @@ where MT: MutatorsTuple, S: HasRand, { + name: String, mutations: MT, max_stack_pow: u64, phantom: PhantomData<(I, S)>, @@ -126,6 +127,16 @@ where } } +impl Named for StdScheduledMutator +where + MT: MutatorsTuple, + S: HasRand, +{ + fn name(&self) -> &str { + &self.name + } +} + impl Mutator for StdScheduledMutator where MT: MutatorsTuple, @@ -185,6 +196,7 @@ where /// Create a new [`StdScheduledMutator`] instance specifying mutations pub fn new(mutations: MT) -> Self { StdScheduledMutator { + name: format!("StdScheduledMutator[{}]", mutations.names().join(", ")), mutations, max_stack_pow: 7, phantom: PhantomData, @@ -194,6 +206,7 @@ where /// Create a new [`StdScheduledMutator`] instance specifying mutations and the maximun number of iterations pub fn with_max_stack_pow(mutations: MT, max_stack_pow: u64) -> Self { StdScheduledMutator { + name: format!("StdScheduledMutator[{}]", mutations.names().join(", ")), mutations, max_stack_pow, phantom: PhantomData, @@ -279,6 +292,7 @@ where S: HasRand + HasCorpus, SM: ScheduledMutator, { + name: String, scheduled: SM, mutation_log: Vec, phantom: PhantomData<(I, MT, S)>, @@ -300,6 +314,17 @@ where } } +impl Named for LoggerScheduledMutator +where + MT: MutatorsTuple + NamedTuple, + S: HasRand + HasCorpus, + SM: ScheduledMutator, +{ + fn name(&self) -> &str { + &self.name + } +} + impl Mutator for LoggerScheduledMutator where MT: MutatorsTuple + NamedTuple, @@ -403,9 +428,11 @@ where S: HasRand + HasCorpus, SM: ScheduledMutator, { - /// Create a new [`StdScheduledMutator`] instance without mutations and corpus + /// Create a new [`LoggerScheduledMutator`] instance without mutations and corpus + /// This mutator logs all mutators. pub fn new(scheduled: SM) -> Self { Self { + name: format!("LoggerScheduledMutator[{}]", scheduled.name()), scheduled, mutation_log: vec![], phantom: PhantomData, diff --git a/libafl/src/mutators/token_mutations.rs b/libafl/src/mutators/token_mutations.rs index 321a9d7cda..ce4c34ed18 100644 --- a/libafl/src/mutators/token_mutations.rs +++ b/libafl/src/mutators/token_mutations.rs @@ -25,7 +25,7 @@ use crate::{ bolts::{rands::Rand, AsSlice}, inputs::{HasBytesVec, UsesInput}, mutators::{ - buffer_self_copy, mutations::buffer_copy, MultipleMutator, MutationResult, Mutator, Named, + buffer_self_copy, mutations::buffer_copy, MultiMutator, MutationResult, Mutator, Named, }, observers::cmp::{AFLppCmpValuesMetadata, CmpValues, CmpValuesMetadata}, stages::TaintMetadata, @@ -1090,37 +1090,37 @@ impl AFLppRedQueen { } } -impl MultipleMutator for AFLppRedQueen +impl MultiMutator for AFLppRedQueen where S: UsesInput + HasMetadata + HasRand + HasMaxSize + HasCorpus, I: HasBytesVec + From>, { #[allow(clippy::needless_range_loop)] #[allow(clippy::too_many_lines)] - fn mutate( + fn multi_mutate( &mut self, state: &mut S, input: &I, - ret: &mut Vec, stage_idx: i32, - ) -> Result { + max_count: Option, + ) -> Result, Error> { // TODO // handle 128-bits logs let size = input.bytes().len(); if size == 0 { - return Ok(MutationResult::Skipped); + return Ok(vec![]); } let (cmp_len, cmp_meta, taint_meta) = { let cmp_meta = state.metadata_map().get::(); let taint_meta = state.metadata_map().get::(); if cmp_meta.is_none() || taint_meta.is_none() { - return Ok(MutationResult::Skipped); + return Ok(vec![]); } let cmp_len = cmp_meta.unwrap().headers().len(); if cmp_len == 0 { - return Ok(MutationResult::Skipped); + return Ok(vec![]); } (cmp_len, cmp_meta.unwrap(), taint_meta.unwrap()) }; @@ -1135,7 +1135,7 @@ where let orig_bytes = input.bytes(); let taint = taint_meta.ranges(); - let mut vec = vec![]; + let mut ret = max_count.map_or_else(Vec::new, Vec::with_capacity); let mut gathered_tokens = Tokens::new(); // println!("orig: {:#?} new: {:#?}", orig_cmpvals, new_cmpvals); @@ -1172,6 +1172,13 @@ where } for cmp_buf_idx in 0..input_len { + if let Some(max_count) = max_count { + if ret.len() >= max_count { + // TODO: does this bias towards earlier mutations? + break; + } + } + let taint_len = match taint.get(taint_idx) { Some(t) => { if cmp_buf_idx < t.start { @@ -1213,7 +1220,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); // Swapped @@ -1229,7 +1236,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); } @@ -1247,7 +1254,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); // Swapped @@ -1263,7 +1270,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); } */ @@ -1301,7 +1308,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); // Swapped @@ -1318,7 +1325,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); } @@ -1336,7 +1343,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); // Swapped @@ -1352,7 +1359,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); } @@ -1390,7 +1397,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); // swapped @@ -1407,7 +1414,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); } @@ -1425,7 +1432,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); // Swapped @@ -1442,7 +1449,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); } @@ -1483,7 +1490,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); // Swapped @@ -1500,7 +1507,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); } @@ -1518,7 +1525,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); // Swapped @@ -1535,7 +1542,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); } @@ -1574,7 +1581,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); // Compare v1 against v0 @@ -1589,7 +1596,7 @@ where taint_len, input_len, hshape, - &mut vec, + &mut ret, ); let is_ascii_or_utf8 = self.text_type.is_ascii_or_utf8(); @@ -1663,16 +1670,10 @@ where } } - let mut mutated = false; - for item in vec { - ret.push(I::from(item)); - mutated = true; - } - - if mutated { - Ok(MutationResult::Mutated) + if let Some(max_count) = max_count { + Ok(ret.into_iter().take(max_count).map(I::from).collect()) } else { - Ok(MutationResult::Skipped) + Ok(ret.into_iter().map(I::from).collect()) } } } diff --git a/libafl/src/mutators/tuneable.rs b/libafl/src/mutators/tuneable.rs index 2e122d7ec0..67dbac46a1 100644 --- a/libafl/src/mutators/tuneable.rs +++ b/libafl/src/mutators/tuneable.rs @@ -2,7 +2,7 @@ //! Instead of a random mutator for a random amount of iterations, we can run //! a specific mutator for a specified amount of iterations -use alloc::vec::Vec; +use alloc::{string::String, vec::Vec}; use core::{ fmt::{self, Debug}, marker::PhantomData, @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; pub use crate::mutators::{mutations::*, token_mutations::*}; use crate::{ - bolts::{calculate_cumulative_sum_in_place, rands::Rand}, + bolts::{calculate_cumulative_sum_in_place, rands::Rand, tuples::Named}, impl_serdeany, mutators::{ ComposedByMutations, MutationId, MutationResult, Mutator, MutatorsTuple, ScheduledMutator, @@ -80,6 +80,7 @@ where MT: MutatorsTuple, S: HasRand, { + name: String, mutations: MT, max_stack_pow: u64, phantom: PhantomData<(I, S)>, @@ -134,6 +135,16 @@ where } } +impl Named for TuneableScheduledMutator +where + MT: MutatorsTuple, + S: HasRand, +{ + fn name(&self) -> &str { + &self.name + } +} + impl ScheduledMutator for TuneableScheduledMutator where MT: MutatorsTuple, @@ -215,6 +226,7 @@ where state.add_metadata(TuneableScheduledMutatorMetadata::default()); } TuneableScheduledMutator { + name: format!("TuneableMutator[{}]", mutations.names().join(", ")), mutations, max_stack_pow: 7, phantom: PhantomData, diff --git a/libafl/src/stages/mutational.rs b/libafl/src/stages/mutational.rs index 2f85925bff..fe28e99a39 100644 --- a/libafl/src/stages/mutational.rs +++ b/libafl/src/stages/mutational.rs @@ -11,7 +11,7 @@ use crate::{ fuzzer::Evaluator, inputs::Input, mark_feature_time, - mutators::{MultipleMutator, MutationResult, Mutator}, + mutators::{MultiMutator, MutationResult, Mutator}, stages::Stage, start_timer, state::{HasClientPerfMonitor, HasCorpus, HasRand, UsesState}, @@ -260,28 +260,28 @@ where /// The default mutational stage #[derive(Clone, Debug)] -pub struct MultipleMutationalStage { +pub struct MultiMutationalStage { mutator: M, #[allow(clippy::type_complexity)] phantom: PhantomData<(E, EM, I, Z)>, } -impl UsesState for MultipleMutationalStage +impl UsesState for MultiMutationalStage where E: UsesState, EM: UsesState, - M: MultipleMutator, + M: MultiMutator, Z: Evaluator, Z::State: HasClientPerfMonitor + HasCorpus + HasRand, { type State = Z::State; } -impl Stage for MultipleMutationalStage +impl Stage for MultiMutationalStage where E: UsesState, EM: UsesState, - M: MultipleMutator, + M: MultiMutator, Z: Evaluator, Z::State: HasClientPerfMonitor + HasCorpus + HasRand, I: MutatedTransform + Clone, @@ -303,14 +303,13 @@ where }; drop(testcase); - let mut generated = vec![]; - let _ = self.mutator.mutate(state, &input, &mut generated, 0)?; + let generated = self.mutator.multi_mutate(state, &input, 0, None)?; // println!("Generated {}", generated.len()); for (i, new_input) in generated.into_iter().enumerate() { // Time is measured directly the `evaluate_input` function let (untransformed, post) = new_input.try_transform_into(state)?; let (_, corpus_idx) = fuzzer.evaluate_input(state, executor, manager, untransformed)?; - self.mutator.post_exec(state, i as i32, corpus_idx)?; + self.mutator.multi_post_exec(state, i as i32, corpus_idx)?; post.post_exec(state, i as i32, corpus_idx)?; } // println!("Found {}", found); @@ -319,11 +318,11 @@ where } } -impl MultipleMutationalStage +impl MultiMutationalStage where E: UsesState, EM: UsesState, - M: MultipleMutator, + M: MultiMutator, Z: Evaluator, Z::State: HasClientPerfMonitor + HasCorpus + HasRand, { @@ -333,11 +332,11 @@ where } } -impl MultipleMutationalStage +impl MultiMutationalStage where E: UsesState, EM: UsesState, - M: MultipleMutator, + M: MultiMutator, Z: Evaluator, Z::State: HasClientPerfMonitor + HasCorpus + HasRand, {