macro_rules! fuzz_mutator { ( | $data:ident : &mut [u8] , $size:ident : usize , $max_size:ident : usize , $seed:ident : u32 $(,)* | $body:block ) => { ... }; }
Expand description
Define a custom mutator.
This is optional, and libFuzzer will use its own, default mutation strategy if this is not provided.
You might consider using a custom mutator when your fuzz target is very particular about the shape of its input:
- You want to fuzz “deeper” than just the parser.
- The input contains checksums that have to match the hash of some subset of the data or else the whole thing is invalid, and therefore mutating any of that subset means you need to recompute the checksums.
- Small random changes to the input buffer make it invalid.
That is, a custom mutator is useful in similar situations where a T: Arbitrary
input type is
useful. Note that the two approaches are not mutually exclusive; you can use
whichever is easier for your problem domain or both!
§Implementation Contract
The original, unmodified input is given in data[..size]
.
You must modify the data in place and return the new size.
The new size should not be greater than max_size
. If this is not the case,
then the data
will be truncated to fit within max_size
. Note that
max_size < size
is possible when shrinking test cases.
You must produce the same mutation given the same seed
. Generally, when
choosing what kind of mutation to make or where to mutate, you should start
by creating a random number generator (RNG) that is seeded with the given
seed
and then consult the RNG whenever making a decision:
#![no_main]
use rand::{rngs::StdRng, Rng, SeedableRng};
libfuzzer_sys::fuzz_mutator!(|data: &mut [u8], size: usize, max_size: usize, seed: u32| {
let mut rng = StdRng::seed_from_u64(seed as u64);
// Choose which of our four supported kinds of mutations we want to make.
match rng.gen_range(0..4) {
0 => first_mutation(rng, data, size, max_size),
1 => second_mutation(rng, data, size, max_size),
2 => third_mutation(rng, data, size, max_size),
3 => fourth_mutation(rng, data, size, max_size),
_ => unreachable!()
}
});
§Example: Compression
Consider a simple fuzz target that takes compressed data as input,
decompresses it, and then asserts that the decompressed data doesn’t begin
with “boom”. It is difficult for libFuzzer
(or any other fuzzer) to crash
this fuzz target because nearly all mutations it makes will invalidate the
compression format. Therefore, we use a custom mutator that decompresses the
raw input, mutates the decompressed data, and then recompresses it. This
allows libFuzzer
to quickly discover crashing inputs.
#![no_main]
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use libfuzzer_sys::{fuzz_mutator, fuzz_target};
use std::io::{Read, Write};
fuzz_target!(|data: &[u8]| {
// Decompress the input data and crash if it starts with "boom".
if let Some(data) = decompress(data) {
if data.starts_with(b"boom") {
panic!();
}
}
});
fuzz_mutator!(
|data: &mut [u8], size: usize, max_size: usize, _seed: u32| {
// Decompress the input data. If that fails, use a dummy value.
let mut decompressed = decompress(&data[..size]).unwrap_or_else(|| b"hi".to_vec());
// Mutate the decompressed data with `libFuzzer`'s default mutator. Make
// the `decompressed` vec's extra capacity available for insertion
// mutations via `resize`.
let len = decompressed.len();
let cap = decompressed.capacity();
decompressed.resize(cap, 0);
let new_decompressed_size = libfuzzer_sys::fuzzer_mutate(&mut decompressed, len, cap);
// Recompress the mutated data.
let compressed = compress(&decompressed[..new_decompressed_size]);
// Copy the recompressed mutated data into `data` and return the new size.
let new_size = std::cmp::min(max_size, compressed.len());
data[..new_size].copy_from_slice(&compressed[..new_size]);
new_size
}
);
fn decompress(compressed_data: &[u8]) -> Option<Vec<u8>> {
let mut decoder = GzDecoder::new(compressed_data);
let mut decompressed = Vec::new();
if decoder.read_to_end(&mut decompressed).is_ok() {
Some(decompressed)
} else {
None
}
}
fn compress(data: &[u8]) -> Vec<u8> {
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder
.write_all(data)
.expect("writing into a vec is infallible");
encoder.finish().expect("writing into a vec is infallible")
}
This example is inspired by a similar example from the official libFuzzer
docs.
§More Example Ideas
-
A PNG custom mutator that decodes a PNG, mutates the image, and then re-encodes the mutated image as a new PNG.
-
A
serde
custom mutator that deserializes your structure, mutates it, and then reserializes it. -
A Wasm binary custom mutator that inserts, replaces, and removes a bytecode instruction in a function’s body.
-
An HTTP request custom mutator that inserts, replaces, and removes a header from an HTTP request.