/src/wasmtime/crates/fuzzing/src/mutators.rs
Line | Count | Source |
1 | | //! Custom fuzz input mutators. |
2 | | //! |
3 | | //! The functions in this module are intended to be used with [the |
4 | | //! `libfuzzer_sys::fuzz_mutator!` macro][fuzz-mutator]. |
5 | | //! |
6 | | //! [fuzz-mutator]: https://docs.rs/libfuzzer-sys/latest/libfuzzer_sys/macro.fuzz_mutator.html |
7 | | |
8 | | use arbitrary::{Arbitrary, Unstructured}; |
9 | | use std::sync::Arc; |
10 | | |
11 | | /// Use [`wasm-mutate`][wasm-mutate] to mutate a fuzz input. |
12 | | /// |
13 | | /// [wasm-mutate]: https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-mutate |
14 | 0 | pub fn wasm_mutate( |
15 | 0 | data: &mut [u8], |
16 | 0 | size: usize, |
17 | 0 | max_size: usize, |
18 | 0 | seed: u32, |
19 | 0 | libfuzzer_mutate: fn(data: &mut [u8], size: usize, max_size: usize) -> usize, |
20 | 0 | ) -> usize { |
21 | | const MUTATION_FUEL: u64 = 100; |
22 | | const MUTATION_ITERS: usize = 100; |
23 | | |
24 | 0 | let wasm = &data[..size]; |
25 | | |
26 | 0 | if wasmparser::validate(wasm).is_ok() { |
27 | 0 | let mut wasm_mutate = wasm_mutate::WasmMutate::default(); |
28 | 0 | wasm_mutate |
29 | 0 | .seed(seed.into()) |
30 | 0 | .fuel(MUTATION_FUEL) |
31 | 0 | .reduce(max_size < size) |
32 | 0 | .raw_mutate_func(Some(Arc::new(move |data, max_size| { |
33 | 0 | let len = data.len(); |
34 | | |
35 | | // The given max could be very large, so clamp it to no more |
36 | | // than `len * 2` in any single, given mutation. This way we |
37 | | // don't over-allocate a bunch of space. |
38 | 0 | let max_size = std::cmp::min(max_size, len * 2); |
39 | | // Also, the max must always be greater than zero (`libfuzzer` |
40 | | // asserts this). |
41 | 0 | let max_size = std::cmp::max(max_size, 1); |
42 | | |
43 | | // Make sure we have capacity in case `libfuzzer` decides to |
44 | | // grow this data. |
45 | 0 | if max_size > len { |
46 | 0 | data.resize(max_size, 0); |
47 | 0 | } |
48 | | |
49 | | // Finally, have `libfuzzer` mutate the data! |
50 | 0 | let new_len = libfuzzer_mutate(data, len, max_size); |
51 | | |
52 | | // Resize the data to the mutated size, releasing any extra |
53 | | // capacity that we don't need anymore. |
54 | 0 | data.resize(new_len, 0); |
55 | 0 | data.shrink_to_fit(); |
56 | | |
57 | 0 | Ok(()) |
58 | 0 | }))); |
59 | | |
60 | 0 | let wasm = wasm.to_vec(); |
61 | 0 | let mutations = wasm_mutate.run(&wasm); |
62 | 0 | if let Ok(mutations) = mutations { |
63 | 0 | for mutation in mutations.take(MUTATION_ITERS) { |
64 | 0 | if let Ok(mutated_wasm) = mutation { |
65 | 0 | if mutated_wasm.len() <= max_size { |
66 | 0 | data[..mutated_wasm.len()].copy_from_slice(&mutated_wasm); |
67 | 0 | return mutated_wasm.len(); |
68 | 0 | } |
69 | 0 | } |
70 | | } |
71 | 0 | } |
72 | 0 | } |
73 | | |
74 | | // If we can't mutate the input because it isn't valid Wasm or `wasm-mutate` |
75 | | // otherwise fails, try to use `wasm-smith` to generate a new, arbitrary |
76 | | // Wasm module that fits within the max-size limit. |
77 | 0 | let mut u = Unstructured::new(&data[..max_size]); |
78 | 0 | if let Ok(module) = wasm_smith::Module::arbitrary(&mut u) { |
79 | 0 | let wasm = module.to_bytes(); |
80 | 0 | if wasm.len() <= max_size { |
81 | 0 | data[..wasm.len()].copy_from_slice(&wasm); |
82 | 0 | return wasm.len(); |
83 | 0 | } |
84 | 0 | } |
85 | | |
86 | | // Otherwise, try to return an empty Wasm module: |
87 | | // |
88 | | // ``` |
89 | | // (module) |
90 | | // ``` |
91 | | static EMPTY_WASM: &[u8] = &[0x00, b'a', b's', b'm', 0x01, 0x00, 0x00, 0x00]; |
92 | 0 | if EMPTY_WASM.len() <= max_size { |
93 | 0 | data[..EMPTY_WASM.len()].copy_from_slice(EMPTY_WASM); |
94 | 0 | return EMPTY_WASM.len(); |
95 | 0 | } |
96 | | |
97 | | // If the max size is even smaller than an empty Wasm module, then just let |
98 | | // `libfuzzer` mutate the data. |
99 | 0 | libfuzzer_mutate(data, size, max_size) |
100 | 0 | } |