macro_rules! fuzz_crossover { ( | $data1:ident : &[u8] , $data2:ident : &[u8] , $out:ident : &mut [u8] , $seed:ident : u32 $(,)* | $body:block ) => { ... }; }
Expand description
Define a custom cross-over function to combine test cases.
This is optional, and libFuzzer will use its own, default cross-over strategy
if this is not provided. (As of the time of writing, this default strategy
takes alternating byte sequences from the two test cases, to construct the
new one) (see FuzzerCrossOver.cpp
)
This could potentially be useful if your input is, for instance, a sequence of fixed sized, multi-byte values and the crossover could then merge discrete values rather than joining parts of a value.
§Implementation Contract
The original, read-only inputs are given in the full slices of data1
, and
data2
(as opposed to the, potentially, partial slice of data
in
the fuzz_mutator!
macro).
You must place the new input merged from the two existing inputs’ data
into out
and return the size of the relevant data written to that slice.
The deterministic requirements from the fuzz_mutator!
macro
apply as well to the seed
parameter
§Example: Floating-Point Sum NaN
#![no_main]
use libfuzzer_sys::{fuzz_crossover, fuzz_mutator, fuzz_target, fuzzer_mutate};
use rand::{rngs::StdRng, Rng, SeedableRng};
use std::mem::size_of;
fuzz_target!(|data: &[u8]| {
let (_, floats, _) = unsafe { data.align_to::<f64>() };
let res = floats
.iter()
.fold(0.0, |a, b| if b.is_nan() { a } else { a + b });
assert!(
!res.is_nan(),
"The sum of the following floats resulted in a NaN: {floats:?}"
);
});
// Inject some ...potentially problematic values to make the example close
// more quickly.
fuzz_mutator!(|data: &mut [u8], size: usize, max_size: usize, seed: u32| {
let mut gen = StdRng::seed_from_u64(seed.into());
let (_, floats, _) = unsafe { data[..size].align_to_mut::<f64>() };
let x = gen.gen_range(0..=1000);
if x == 0 && !floats.is_empty() {
floats[0] = f64::INFINITY;
} else if x == 1000 && floats.len() > 1 {
floats[1] = f64::NEG_INFINITY;
} else {
return fuzzer_mutate(data, size, max_size);
}
size
});
fuzz_crossover!(|data1: &[u8], data2: &[u8], out: &mut [u8], _seed: u32| {
// Decode each source to see how many floats we can pull with proper
// alignment, and destination as to how many will fit with proper alignment
//
// Keep track of the unaligned prefix to `out`, as we will need to remember
// that those bytes will remain prepended to the actual floats that we
// write into the out buffer.
let (out_pref, out_floats, _) = unsafe { out.align_to_mut::<f64>() };
let (_, d1_floats, _) = unsafe { data1.align_to::<f64>() };
let (_, d2_floats, _) = unsafe { data2.align_to::<f64>() };
// Put into the destination, floats first from data1 then from data2, ...if
// possible given the size of `out`
let mut i: usize = 0;
for float in d1_floats.iter().chain(d2_floats).take(out_floats.len()) {
out_floats[i] = *float;
i += 1;
}
// Now that we have written the true floats, report back to the fuzzing
// engine that we left the unaligned `out` prefix bytes at the beginning of
// `out` and also then the floats that we wrote into the aligned float
// section.
out_pref.len() * size_of::<u8>() + i * size_of::<f64>()
});
This example is a minimized version of Erik Rigtorp’s floating point
summation fuzzing example. A more detailed version of this experiment
can be found in the example_crossover
directory.