Breaking change: remove mutator mode/corpus fuzzing state

This commit is contained in:
Lander Brandt 2020-05-28 16:59:58 -07:00
Родитель 3b3dbe6d4a
Коммит 88ef34d91b
8 изменённых файлов: 46 добавлений и 309 удалений

8
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]]

Просмотреть файл

@ -84,15 +84,8 @@ fn fuzzer_routine<R: Rng>(mutator: &mut Mutator<R>, 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();

Просмотреть файл

@ -241,7 +241,7 @@ pub fn start_fuzzer<F: 'static, C: 'static, T: 'static + Send + Sync>(
return;
}
mutator.begin_new_iteration();
mutator.random_flags();
if let Err(_) =
(callback)(&mut mutator, &mut context, thread_driver.global_context())

Просмотреть файл

@ -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<R: Rng>(&mut self, mutator: &mut Mutator<R>, _constraints: Option<&Constraints<Self::RangeType>>) {
mutator.mutate_from_mutation_mode(self);
mutator.mutate(self);
}
}
)*
@ -397,7 +397,7 @@ impl Mutatable for i8 {
_constraints: Option<&Constraints<Self::RangeType>>,
) {
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<Self::RangeType>>,
) {
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<Self::RangeType>>,
) {
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<Self::RangeType>>,
) {
let mut val = *self as u64;
mutator.mutate_from_mutation_mode(&mut val);
mutator.mutate(&mut val);
*self = val as i64;
}
}

Просмотреть файл

@ -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<usize>,
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<R: Rng> {
pub rng: R,
flags: Vec<MutatorFlags>,
flags: MutatorFlags,
corpus_state: CorpusFuzzingState,
}
@ -97,19 +70,11 @@ impl<R: Rng> Mutator<R> {
pub fn new(rng: R) -> Mutator<R> {
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<R: Rng> Mutator<R> {
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<T>(&mut self, mn: &mut T)
where
T: BitXor<Output = T>
+ Add<Output = T>
+ Sub<Output = T>
+ WrappingAdd<Output = T>
+ WrappingSub<Output = T>
+ NumCast
+ Bounded
+ Copy
+ DangerousNumber<T>
+ 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::<T>();
}
/// 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<T: Bounded + NumCast + DangerousNumber<T>>(&mut self) {
let num_bits = (std::mem::size_of::<T>() * 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<R: Rng> Mutator<R> {
+ WrappingAdd<Output = T>
+ WrappingSub<Output = T>,
{
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<R: Rng> Mutator<R> {
/// Generates a number in the range from [min, max) (**note**: non-inclusive). Panics if min >= max.
pub fn gen_range<T, B1>(&mut self, min: B1, max: B1) -> T
where
T: SampleUniform + std::fmt::Display,
B1: SampleBorrow<T>
where T: SampleUniform + std::fmt::Display,
B1: SampleBorrow<T>
+ std::fmt::Display
+ Add
+ Mul
@ -440,18 +292,10 @@ impl<R: Rng> Mutator<R> {
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<R: Rng> Mutator<R> {
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<R: Rng> Mutator<R> {
/// 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();
}
}

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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