Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Mutator state lifecycle

Lander Brandt edited this page Jul 16, 2019 · 2 revisions

The mutator in its current form maintains some state about the object it's fuzzing. The bulk of the state machine can be found here: https://github.com/microsoft/lain/blob/f358c5e542aff9346791aafe340bc65e10934b7b/lain/src/mutator.rs#L134-L235

In the example fuzzer, you may notice that there's an odd bit of code in the beginning of the fuzzer_routine function: (link)

    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
            }
        }
        _ => {
            mutator.begin_new_corpus();

            thread_context.last_packet = Some(PacketData::new_fuzzed(mutator, None));
            thread_context.last_packet.as_mut().unwrap()
        }
    };

If the mutator mode is not MutatorMode::Havoc we will create a copy of the packet before mutating it, otherwise we will use the existing packet. Why is this?

The fuzzer's state machine in non-Havoc mode is essentially for doing targeted mutations of individual fields or indices in an array. We don't want these mutations to stack as we continue walking down the fields, hence we make a copy of the struct first, then mutate it. Since mutator operations are recursive and there's no "flat" knowledge about the fields in a structure, we will attempt to mutate (and ignore) the first N-1 fields, where N is the current field being targeted. Once N is reached, the field is mutated and the mutator state is progressed forward. A rough overview of the possible states are:

  1. WalkingBitFlip -> next bit (up to std::mem::size_of::<FIELD_TYPE>() * 8 bits).
  2. Once all bits are exhausted in WalkingBitFlip -> InterestingValues
  3. Once all InterestingValues are exhausted -> Havoc
  4. Havoc is the final state.

Havoc will simply pick a small number of fields to mutate, mutating them with completely random operations. The operations will be one of:

  • BitFlip (single bit)
  • Flip (of more than one bit up to the max number in the given type)
  • Arithmetic

In the inverse case when we don't have an existing packet you'll note that there's the following line of code:

mutator.begin_new_corpus();

This will reset the internal state of the mutator to be used with a new object.

This area of Lain is probably the least intuitive and is subject to change in the future.

Clone this wiki locally