libfuzzer_sys

Macro fuzz_crossover

Source
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.