From 88ef34d91b785564739e85392b8c8bca3ea99f33 Mon Sep 17 00:00:00 2001 From: Lander Brandt Date: Thu, 28 May 2020 16:59:58 -0700 Subject: [PATCH] Breaking change: remove mutator mode/corpus fuzzing state --- Cargo.lock | 8 +- examples/example_fuzzer/src/main.rs | 11 +- lain/src/driver.rs | 2 +- lain/src/mutatable.rs | 18 +- lain/src/mutator.rs | 257 +++------------------------- lain/src/prelude.rs | 2 +- lain_derive/src/mutations.rs | 3 +- testsuite/src/lib.rs | 54 ------ 8 files changed, 46 insertions(+), 309 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e198d5f..f025f4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -211,11 +211,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lain" -version = "0.2.5" +version = "0.3.0" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "field-offset 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lain_derive 0.2.5", + "lain_derive 0.3.0", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "mashup 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -228,7 +228,7 @@ dependencies = [ [[package]] name = "lain_derive" -version = "0.2.5" +version = "0.3.0" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -632,7 +632,7 @@ name = "testsuite" version = "0.1.0" dependencies = [ "criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "lain 0.2.5", + "lain 0.3.0", ] [[package]] diff --git a/examples/example_fuzzer/src/main.rs b/examples/example_fuzzer/src/main.rs index 9e89897..8dbaa7e 100644 --- a/examples/example_fuzzer/src/main.rs +++ b/examples/example_fuzzer/src/main.rs @@ -84,15 +84,8 @@ fn fuzzer_routine(mutator: &mut Mutator, thread_context: &mut FuzzerT let packet = match thread_context.last_packet { Some(ref mut last_packet) => { - if mutator.mode() == MutatorMode::Havoc { - last_packet.mutate(mutator, None); - last_packet - } else { - // We want to do fuzzing of every field separately - thread_context.scratch_packet = last_packet.clone(); - thread_context.scratch_packet.mutate(mutator, None); - &thread_context.scratch_packet - } + last_packet.mutate(mutator, None); + last_packet } _ => { mutator.begin_new_corpus(); diff --git a/lain/src/driver.rs b/lain/src/driver.rs index 2184f30..3933783 100644 --- a/lain/src/driver.rs +++ b/lain/src/driver.rs @@ -241,7 +241,7 @@ pub fn start_fuzzer( return; } - mutator.begin_new_iteration(); + mutator.random_flags(); if let Err(_) = (callback)(&mut mutator, &mut context, thread_driver.global_context()) diff --git a/lain/src/mutatable.rs b/lain/src/mutatable.rs index 0514616..c9bf119 100644 --- a/lain/src/mutatable.rs +++ b/lain/src/mutatable.rs @@ -1,4 +1,4 @@ -use crate::mutator::{Mutator, MutatorMode}; +use crate::mutator::{Mutator}; use crate::rand::seq::index; use crate::rand::Rng; use crate::traits::*; @@ -204,7 +204,7 @@ where const CHANCE_TO_RESIZE_VEC: f64 = 0.01; // 1% chance to resize this vec - if mutator.mode() == MutatorMode::Havoc && mutator.gen_chance(CHANCE_TO_RESIZE_VEC) { + if mutator.gen_chance(CHANCE_TO_RESIZE_VEC) { shrink_vec(self, mutator); } else { // Recreate the constraints so that the min/max types match @@ -244,7 +244,7 @@ where return; } - if mutator.mode() == MutatorMode::Havoc && mutator.gen_chance(CHANCE_TO_RESIZE_VEC) { + if mutator.gen_chance(CHANCE_TO_RESIZE_VEC) { let resize_type = VecResizeType::new_fuzzed(mutator, None); if resize_type == VecResizeType::Grow { grow_vec(self, mutator, constraints.and_then(|c| c.max_size)); @@ -327,7 +327,7 @@ where match *self { UnsafeEnum::Invalid(ref mut value) => { - mutator.mutate_from_mutation_mode(value); + mutator.mutate(value); } _ => unreachable!(), } @@ -378,7 +378,7 @@ macro_rules! impl_mutatable { #[inline(always)] fn mutate(&mut self, mutator: &mut Mutator, _constraints: Option<&Constraints>) { - mutator.mutate_from_mutation_mode(self); + mutator.mutate(self); } } )* @@ -397,7 +397,7 @@ impl Mutatable for i8 { _constraints: Option<&Constraints>, ) { let mut val = *self as u8; - mutator.mutate_from_mutation_mode(&mut val); + mutator.mutate(&mut val); *self = val as i8; } } @@ -412,7 +412,7 @@ impl Mutatable for i16 { _constraints: Option<&Constraints>, ) { let mut val = *self as u16; - mutator.mutate_from_mutation_mode(&mut val); + mutator.mutate(&mut val); *self = val as i16; } } @@ -427,7 +427,7 @@ impl Mutatable for i32 { _constraints: Option<&Constraints>, ) { let mut val = *self as u32; - mutator.mutate_from_mutation_mode(&mut val); + mutator.mutate(&mut val); *self = val as i32; } } @@ -442,7 +442,7 @@ impl Mutatable for i64 { _constraints: Option<&Constraints>, ) { let mut val = *self as u64; - mutator.mutate_from_mutation_mode(&mut val); + mutator.mutate(&mut val); *self = val as i64; } } diff --git a/lain/src/mutator.rs b/lain/src/mutator.rs index 84e2513..f37fc1e 100644 --- a/lain/src/mutator.rs +++ b/lain/src/mutator.rs @@ -30,23 +30,11 @@ enum MutatorOperation { Arithmetic, } -#[derive(PartialEq, Clone, Debug)] -enum MutatorFlags { - FuzzUpToNFields(usize), - ShouldAlwaysPerformPostMutation(bool), - AllChancesSucceedOrFail(bool), -} - -/// Represents the mode of the mutator -#[derive(PartialEq, Clone, Copy, Debug)] -#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))] -pub enum MutatorMode { - /// Performs a linear bit flip from index 0 up to the max bit index, flipping `current_idx` number of bits - WalkingBitFlip { bits: u8, current_idx: u8 }, - /// Selects interesting values for the current data type - InterestingValues { current_idx: u8 }, - /// All-out random mutation - Havoc, +#[derive(Clone, Debug, Default)] +struct MutatorFlags { + field_count: Option, + always_fixup: bool, + all_chances_succeed: bool, } /// Represents the state of the current corpus item being fuzzed. @@ -54,22 +42,12 @@ pub enum MutatorMode { #[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))] pub struct CorpusFuzzingState { fields_fuzzed: usize, - mode: MutatorMode, - targeted_field_idx: usize, - pub target_total_fields: usize, - target_total_passes: usize, - finished_iteration: bool, } impl Default for CorpusFuzzingState { fn default() -> Self { CorpusFuzzingState { fields_fuzzed: 0, - mode: MutatorMode::Havoc, - targeted_field_idx: 0, - target_total_fields: 0, - target_total_passes: 0, - finished_iteration: false, } } } @@ -77,11 +55,6 @@ impl Default for CorpusFuzzingState { impl CorpusFuzzingState { pub fn reset(&mut self) { self.fields_fuzzed = 0; - self.target_total_passes = 0; - self.targeted_field_idx = 0; - self.target_total_fields = 0; - self.mode = MutatorMode::Havoc; - self.finished_iteration = false; } } @@ -89,7 +62,7 @@ impl CorpusFuzzingState { #[derive(Debug)] pub struct Mutator { pub rng: R, - flags: Vec, + flags: MutatorFlags, corpus_state: CorpusFuzzingState, } @@ -97,19 +70,11 @@ impl Mutator { pub fn new(rng: R) -> Mutator { Mutator { rng, - flags: Vec::new(), + flags: MutatorFlags::default(), corpus_state: CorpusFuzzingState::default(), } } - pub fn set_mode(&mut self, mode: MutatorMode) { - self.corpus_state.mode = mode; - } - - pub fn mode(&self) -> MutatorMode { - self.corpus_state.mode - } - pub fn get_corpus_state(&self) -> CorpusFuzzingState { self.corpus_state.clone() } @@ -126,110 +91,6 @@ impl Mutator { T::new_fuzzed(self, None) } - /// TODO: Change function name. Mutates `mn` while taking into consideration the current mutator mode. - /// - pub fn mutate_from_mutation_mode(&mut self, mn: &mut T) - where - T: BitXor - + Add - + Sub - + WrappingAdd - + WrappingSub - + NumCast - + Bounded - + Copy - + DangerousNumber - + std::fmt::Display, - { - // info!("{:?}", self.mode()); - // info!("num is: {}", mn); - // info!("{}, {}, {}, {}", self.corpus_state.fields_fuzzed, self.corpus_state.targeted_field_idx, self.corpus_state.target_total_fields, self.corpus_state.target_total_passes); - self.corpus_state.fields_fuzzed += 1; - - if self.mode() != MutatorMode::Havoc && self.corpus_state.finished_iteration - || self.corpus_state.targeted_field_idx != self.corpus_state.fields_fuzzed - 1 - { - return; - } - //println!("should be changing mode"); - - match self.mode() { - MutatorMode::WalkingBitFlip { bits, current_idx } => { - for i in current_idx..current_idx + bits { - *mn = *mn ^ num::cast(1u64 << i).unwrap(); - } - } - MutatorMode::InterestingValues { current_idx } => { - *mn = T::dangerous_number_at_index(current_idx as usize); - } - // Do nothing for havoc mode -- we let the individual mutators handle that - MutatorMode::Havoc => { - self.mutate(mn); - } - } - - self.corpus_state.finished_iteration = true; - self.next_mode::(); - } - - /// Manages the mutator mode state machine. - /// - /// This will basically: - /// - /// - Swap between the different [MutatorMode]s. This will transition from walking bit flips to dangerous numbers, to havoc mode. - /// - For each mode, determine if there are any any other substates to exhaust (e.g. more bits to flip, more dangerous numbers to select), and update - /// the state accordingly for the next iteration. If no other substates exist, the hard [MutatorMode] state will move to the next enum variant. Before reaching - /// the [MutatorMode::Havoc] state, each subsequent mode will check if the last field has been mutated yet. If not, the state will reset to [MutatorMode::WalkingBitFlip] - /// and adjust the current member index being fuzzed. - /// - Once all members have been fuzzed in all [MutatorMode]s, the mode is set to [MutatorMode::Havoc]. - pub fn next_mode>(&mut self) { - let num_bits = (std::mem::size_of::() * 8) as u8; - //println!("previous: {:?}", self.mode()); - //println!("num bits: {}", num_bits); - match self.mode() { - MutatorMode::WalkingBitFlip { bits, current_idx } => { - // current_idx + bits + 1 == num_bits - if bits == num_bits { - // if we are at the max bits, move on to the next state - self.corpus_state.mode = MutatorMode::InterestingValues { current_idx: 0 }; - } else if current_idx + bits == num_bits { - self.corpus_state.mode = MutatorMode::WalkingBitFlip { - bits: bits + 1, - current_idx: 0, - }; - } else { - self.corpus_state.mode = MutatorMode::WalkingBitFlip { - bits, - current_idx: current_idx + 1, - }; - } - } - MutatorMode::InterestingValues { current_idx } => { - if (current_idx as usize) + 1 < T::dangerous_numbers_len() { - self.corpus_state.mode = MutatorMode::InterestingValues { - current_idx: current_idx + 1, - }; - } else { - self.corpus_state.targeted_field_idx += 1; - if self.corpus_state.targeted_field_idx == self.corpus_state.target_total_fields - { - self.corpus_state.mode = MutatorMode::Havoc; - } else { - self.corpus_state.mode = MutatorMode::WalkingBitFlip { - bits: 1, - current_idx: 0, - }; - } - } - } - MutatorMode::Havoc => { - // stay in havoc mode since we've exhausted all other states - } - } - - //println!("new: {:?}", self.mode()); - } - /// Mutates a number after randomly selecting a mutation strategy (see [MutatorOperation] for a list of strategies) /// If a min/max is specified then a new number in this range is chosen instead of performing /// a bit/arithmetic mutation @@ -244,22 +105,14 @@ impl Mutator { + WrappingAdd + WrappingSub, { - if self.mode() != MutatorMode::Havoc { - panic!("Mutate called in non-havoc mode"); - } - // dirty but needs to be done so we can call self.gen_chance_ignore_flags - let flags = self.flags.clone(); - for flag in flags { - if let MutatorFlags::FuzzUpToNFields(num_fields) = flag { - if self.corpus_state.fields_fuzzed == num_fields - || !self.gen_chance_ignore_flags(50.0) - { - return; - } else { - self.corpus_state.fields_fuzzed += 1; - } + if let Some(count) = self.flags.field_count.clone() { + // 75% chance to ignore this field + if self.corpus_state.fields_fuzzed == count || self.gen_chance_ignore_flags(0.75) { + return; } + + self.corpus_state.fields_fuzzed += 1; } let operation = MutatorOperation::new_fuzzed(self, None); @@ -334,9 +187,8 @@ impl Mutator { /// Generates a number in the range from [min, max) (**note**: non-inclusive). Panics if min >= max. pub fn gen_range(&mut self, min: B1, max: B1) -> T - where - T: SampleUniform + std::fmt::Display, - B1: SampleBorrow + where T: SampleUniform + std::fmt::Display, + B1: SampleBorrow + std::fmt::Display + Add + Mul @@ -440,18 +292,10 @@ impl Mutator { num } - /// Generates the chance to mutate a field. This will always return `true` if the current mode is - /// [MutatorMode::Havoc]. - pub fn gen_chance_to_mutate_field(&mut self, chance_percentage: f64) -> bool { - self.mode() != MutatorMode::Havoc || !self.gen_chance(chance_percentage) - } - /// Helper function for quitting the recursive mutation early if the target field has already /// been mutated. pub fn should_early_bail_mutation(&self) -> bool { - self.mode() != MutatorMode::Havoc - && self.corpus_state.finished_iteration - && self.corpus_state.target_total_passes > 0 + self.flags.field_count.clone().map(|count| count == self.corpus_state.fields_fuzzed).unwrap_or(false) } /// Returns a boolean value indicating whether or not the chance event occurred @@ -460,14 +304,12 @@ impl Mutator { return false; } - if chance_percentage >= 100.0 { + if chance_percentage >= 1.0 { return true; } - for flag in self.flags.iter() { - if let MutatorFlags::AllChancesSucceedOrFail(should_succeed) = flag { - return *should_succeed; - } + if self.flags.all_chances_succeed { + return true; } self.gen_chance_ignore_flags(chance_percentage) @@ -480,65 +322,20 @@ impl Mutator { /// Returns a boolean indicating whether or not post mutation steps should be taken pub fn should_fixup(&mut self) -> bool { - self.mode() == MutatorMode::Havoc && !self.gen_chance(CHANCE_TO_IGNORE_POST_MUTATION) - // for flag in self.flags.iter() { - // if let MutatorFlags::ShouldAlwaysPerformPostMutation(should_perform) = flag { - // return *should_perform; - // } - // } - - // !self.gen_chance(CHANCE_TO_IGNORE_POST_MUTATION) + self.flags.always_fixup || !self.gen_chance(CHANCE_TO_IGNORE_POST_MUTATION) } /// Client code should call this to signal to the mutator that a new fuzzer iteration is beginning /// and that the mutator should reset internal state. - pub fn begin_new_iteration(&mut self) { - let mut set_flags = [false, false, false]; - self.flags.clear(); - let temp_fields_fuzzed = self.corpus_state.fields_fuzzed; - self.corpus_state.fields_fuzzed = 0; + pub fn random_flags(&mut self) { + self.flags = MutatorFlags::default(); - if self.corpus_state.target_total_fields == 0 { - self.corpus_state.target_total_fields = temp_fields_fuzzed; - - if self.corpus_state.targeted_field_idx > self.corpus_state.target_total_fields { - panic!( - "somehow got targeted field index {} with {} total fields", - self.corpus_state.targeted_field_idx, self.corpus_state.target_total_fields - ); - } + // each flag has a 10% chance of being active + if self.gen_chance_ignore_flags(0.10) { + self.flags.field_count = Some(self.gen_range(1, 10)); } - self.corpus_state.target_total_passes += 1; - self.corpus_state.finished_iteration = false; - - if self.mode() == MutatorMode::Havoc && self.corpus_state.target_total_fields > 0 { - // only 2 flags can be concurrently set - for _i in 0..self.gen_range(0, 2) { - let flag_num = self.gen_range(0, 3); - let flag = match flag_num { - 0 => MutatorFlags::FuzzUpToNFields( - self.gen_range(1, self.corpus_state.target_total_fields + 1), - ), - 1 => MutatorFlags::ShouldAlwaysPerformPostMutation(self.gen_range(0, 2) != 0), - 2 => MutatorFlags::AllChancesSucceedOrFail(self.gen_range(0, 2) != 0), - _ => unreachable!(), - }; - - if !set_flags[flag_num] { - set_flags[flag_num] = true; - self.flags.push(flag); - } - } - } - } - - /// Resets the corpus state and current mutation mode. - pub fn begin_new_corpus(&mut self) { - self.corpus_state.reset(); - self.set_mode(MutatorMode::WalkingBitFlip { - bits: 1, - current_idx: 0, - }); + self.flags.all_chances_succeed = self.rng.gen(); + self.flags.always_fixup = self.rng.gen(); } } diff --git a/lain/src/prelude.rs b/lain/src/prelude.rs index 2cbab92..9d00ff9 100644 --- a/lain/src/prelude.rs +++ b/lain/src/prelude.rs @@ -9,7 +9,7 @@ pub use crate::byteorder::{BigEndian, LittleEndian}; #[doc(no_inline)] pub use crate::log::*; #[doc(no_inline)] -pub use crate::mutator::{Mutator, MutatorMode}; +pub use crate::mutator::{Mutator}; #[doc(no_inline)] pub use crate::traits::*; #[doc(no_inline)] diff --git a/lain_derive/src/mutations.rs b/lain_derive/src/mutations.rs index 6ffc0fa..8525bab 100644 --- a/lain_derive/src/mutations.rs +++ b/lain_derive/src/mutations.rs @@ -103,7 +103,8 @@ fn mutatable_enum(variants: &[Variant], cont_ident: &syn::Ident) -> TokenStream } quote! { - if mutator.mode() == _lain::mutator::MutatorMode::Havoc { + // 10% chance to re-generate this field + if mutator.gen_chance(0.10) { *self = Self::new_fuzzed(mutator, parent_constraints); return; } diff --git a/testsuite/src/lib.rs b/testsuite/src/lib.rs index 93b65c0..11b751e 100644 --- a/testsuite/src/lib.rs +++ b/testsuite/src/lib.rs @@ -577,60 +577,6 @@ mod test { assert!(instance.post_mutation_called); } - #[test] - fn test_mutator_switches_modes_properly() { - let mut mutator = get_mutator(); - #[derive(Default, NewFuzzed, Mutatable, BinarySerialize, Clone)] - struct S { - first: u64, - second: u8, - } - - let mut instance = S::new_fuzzed(&mut mutator, None); - mutator.begin_new_corpus(); - - let mut counter = 0usize; - loop { - println!("{:?}", mutator.mode()); - instance.mutate(&mut mutator, None); - mutator.begin_new_iteration(); - counter += 1; - - match counter { - 2078 => assert_eq!( - mutator.mode(), - MutatorMode::WalkingBitFlip { - bits: 63, - current_idx: 1 - } - ), - 2079 => assert_eq!( - mutator.mode(), - MutatorMode::WalkingBitFlip { - bits: 64, - current_idx: 0 - } - ), - 2085 => assert_eq!( - mutator.mode(), - MutatorMode::InterestingValues { current_idx: 5 } - ), - 2100 => assert_eq!( - mutator.mode(), - MutatorMode::WalkingBitFlip { - bits: 2, - current_idx: 6 - } - ), - 2134 => { - assert_eq!(mutator.mode(), MutatorMode::Havoc); - break; - } - _ => {} - } - } - } - #[test] fn test_string_mutation() { // this test mostly ensures that the string generation does not panic