Path: blob/main/crates/fuzzing/src/mutators.rs
1693 views
//! Custom fuzz input mutators.1//!2//! The functions in this module are intended to be used with [the3//! `libfuzzer_sys::fuzz_mutator!` macro][fuzz-mutator].4//!5//! [fuzz-mutator]: https://docs.rs/libfuzzer-sys/latest/libfuzzer_sys/macro.fuzz_mutator.html67use arbitrary::{Arbitrary, Unstructured};8use std::sync::Arc;910/// Use [`wasm-mutate`][wasm-mutate] to mutate a fuzz input.11///12/// [wasm-mutate]: https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-mutate13pub fn wasm_mutate(14data: &mut [u8],15size: usize,16max_size: usize,17seed: u32,18libfuzzer_mutate: fn(data: &mut [u8], size: usize, max_size: usize) -> usize,19) -> usize {20const MUTATION_FUEL: u64 = 100;21const MUTATION_ITERS: usize = 100;2223let wasm = &data[..size];2425if wasmparser::validate(wasm).is_ok() {26let mut wasm_mutate = wasm_mutate::WasmMutate::default();27wasm_mutate28.seed(seed.into())29.fuel(MUTATION_FUEL)30.reduce(max_size < size)31.raw_mutate_func(Some(Arc::new(move |data, max_size| {32let len = data.len();3334// The given max could be very large, so clamp it to no more35// than `len * 2` in any single, given mutation. This way we36// don't over-allocate a bunch of space.37let max_size = std::cmp::min(max_size, len * 2);38// Also, the max must always be greater than zero (`libfuzzer`39// asserts this).40let max_size = std::cmp::max(max_size, 1);4142// Make sure we have capacity in case `libfuzzer` decides to43// grow this data.44if max_size > len {45data.resize(max_size, 0);46}4748// Finally, have `libfuzzer` mutate the data!49let new_len = libfuzzer_mutate(data, len, max_size);5051// Resize the data to the mutated size, releasing any extra52// capacity that we don't need anymore.53data.resize(new_len, 0);54data.shrink_to_fit();5556Ok(())57})));5859let wasm = wasm.to_vec();60let mutations = wasm_mutate.run(&wasm);61if let Ok(mutations) = mutations {62for mutation in mutations.take(MUTATION_ITERS) {63if let Ok(mutated_wasm) = mutation {64if mutated_wasm.len() <= max_size {65data[..mutated_wasm.len()].copy_from_slice(&mutated_wasm);66return mutated_wasm.len();67}68}69}70}71}7273// If we can't mutate the input because it isn't valid Wasm or `wasm-mutate`74// otherwise fails, try to use `wasm-smith` to generate a new, arbitrary75// Wasm module that fits within the max-size limit.76let mut u = Unstructured::new(&data[..max_size]);77if let Ok(module) = wasm_smith::Module::arbitrary(&mut u) {78let wasm = module.to_bytes();79if wasm.len() <= max_size {80data[..wasm.len()].copy_from_slice(&wasm);81return wasm.len();82}83}8485// Otherwise, try to return an empty Wasm module:86//87// ```88// (module)89// ```90static EMPTY_WASM: &[u8] = &[0x00, b'a', b's', b'm', 0x01, 0x00, 0x00, 0x00];91if EMPTY_WASM.len() <= max_size {92data[..EMPTY_WASM.len()].copy_from_slice(EMPTY_WASM);93return EMPTY_WASM.len();94}9596// If the max size is even smaller than an empty Wasm module, then just let97// `libfuzzer` mutate the data.98libfuzzer_mutate(data, size, max_size)99}100101102