/src/wasm-tools/crates/wasm-mutate/src/lib.rs
Line | Count | Source |
1 | | //! A WebAssembly test case mutator. |
2 | | //! |
3 | | //! `wasm-mutate` takes in an existing Wasm module and then applies a |
4 | | //! pseudo-random transformation to it, producing a new, mutated Wasm |
5 | | //! module. This new, mutated Wasm module can be fed as a test input to your |
6 | | //! Wasm parser, validator, compiler, or any other Wasm-consuming |
7 | | //! tool. `wasm-mutate` can serve as a custom mutator for mutation-based |
8 | | //! fuzzing. |
9 | | |
10 | | #![cfg_attr(not(feature = "clap"), deny(missing_docs))] |
11 | | |
12 | | mod error; |
13 | | mod info; |
14 | | mod module; |
15 | | mod mutators; |
16 | | |
17 | | pub use error::*; |
18 | | |
19 | | use crate::mutators::{ |
20 | | Item, add_function::AddFunctionMutator, add_type::AddTypeMutator, |
21 | | codemotion::CodemotionMutator, custom::AddCustomSectionMutator, custom::CustomSectionMutator, |
22 | | custom::ReorderCustomSectionMutator, function_body_unreachable::FunctionBodyUnreachable, |
23 | | modify_const_exprs::ConstExpressionMutator, modify_data::ModifyDataMutator, |
24 | | peephole::PeepholeMutator, remove_export::RemoveExportMutator, remove_item::RemoveItemMutator, |
25 | | remove_section::RemoveSection, rename_export::RenameExportMutator, snip_function::SnipMutator, |
26 | | start::RemoveStartSection, |
27 | | }; |
28 | | use info::ModuleInfo; |
29 | | use mutators::Mutator; |
30 | | use rand::{Rng, SeedableRng, rngs::SmallRng}; |
31 | | use std::sync::Arc; |
32 | | |
33 | | #[cfg(feature = "clap")] |
34 | | use clap::Parser; |
35 | | |
36 | | // NB: only add this doc comment if we are not building the CLI, since otherwise |
37 | | // it will override the main CLI's about text. |
38 | | #[cfg_attr( |
39 | | not(feature = "clap"), |
40 | | doc = r###" |
41 | | A WebAssembly test case mutator. |
42 | | |
43 | | This is the main entry point into this crate. It provides various methods for |
44 | | configuring what kinds of mutations might be applied to the input Wasm. Once |
45 | | configured, you can apply a transformation to the input Wasm via the |
46 | | [`run`][crate::WasmMutate::run] method. |
47 | | |
48 | | # Example |
49 | | |
50 | | ``` |
51 | | # fn _foo() -> anyhow::Result<()> { |
52 | | use wasm_mutate::WasmMutate; |
53 | | |
54 | | let input_wasm = wat::parse_str(r#" |
55 | | (module |
56 | | (func (export "hello") (result i32) |
57 | | (i32.const 1234) |
58 | | ) |
59 | | ) |
60 | | "#)?; |
61 | | |
62 | | // Create a `WasmMutate` builder and configure it. |
63 | | let mut mutate = WasmMutate::default(); |
64 | | mutate |
65 | | // Set the RNG seed. |
66 | | .seed(42) |
67 | | // Allow mutations that change the semantics of the Wasm module. |
68 | | .preserve_semantics(false) |
69 | | // Use at most this much "fuel" when trying to mutate the Wasm module before |
70 | | // giving up. |
71 | | .fuel(1_000); |
72 | | |
73 | | // Run the configured `WasmMutate` to get a sequence of mutations of the input |
74 | | // Wasm! |
75 | | for mutated_wasm in mutate.run(&input_wasm)? { |
76 | | let mutated_wasm = mutated_wasm?; |
77 | | // Feed `mutated_wasm` into your tests... |
78 | | } |
79 | | # Ok(()) |
80 | | # } |
81 | | ``` |
82 | | "### |
83 | | )] |
84 | | #[cfg_attr(feature = "clap", derive(Parser))] |
85 | | #[derive(Clone)] |
86 | | pub struct WasmMutate<'wasm> { |
87 | | /// The RNG seed used to choose which transformation to apply. Given the |
88 | | /// same input Wasm and same seed, `wasm-mutate` will always generate the |
89 | | /// same output Wasm. |
90 | | #[cfg_attr(feature = "clap", clap(short, long, default_value = "42"))] |
91 | | seed: u64, |
92 | | |
93 | | /// Only perform semantics-preserving transformations on the Wasm module. |
94 | | #[cfg_attr(feature = "clap", clap(long))] |
95 | | preserve_semantics: bool, |
96 | | |
97 | | /// Fuel to control the time of the mutation. |
98 | | #[cfg_attr( |
99 | | feature = "clap", |
100 | | clap( |
101 | | short, |
102 | | long, |
103 | | default_value = "18446744073709551615", // u64::MAX |
104 | | ) |
105 | | )] |
106 | | fuel: u64, |
107 | | |
108 | | /// Only perform size-reducing transformations on the Wasm module. This |
109 | | /// allows `wasm-mutate` to be used as a test case reducer. |
110 | | #[cfg_attr(feature = "clap", clap(long))] |
111 | | reduce: bool, |
112 | | |
113 | | // Note: this is only exposed via the programmatic interface, not via the |
114 | | // CLI. |
115 | | #[cfg_attr(feature = "clap", clap(skip = None))] |
116 | | raw_mutate_func: Option<Arc<dyn Fn(&mut Vec<u8>, usize) -> Result<()>>>, |
117 | | |
118 | | #[cfg_attr(feature = "clap", clap(skip = None))] |
119 | | rng: Option<SmallRng>, |
120 | | |
121 | | #[cfg_attr(feature = "clap", clap(skip = None))] |
122 | | info: Option<ModuleInfo<'wasm>>, |
123 | | } |
124 | | |
125 | | impl Default for WasmMutate<'_> { |
126 | 5.77k | fn default() -> Self { |
127 | 5.77k | let seed = 3; |
128 | 5.77k | WasmMutate { |
129 | 5.77k | seed, |
130 | 5.77k | preserve_semantics: false, |
131 | 5.77k | reduce: false, |
132 | 5.77k | raw_mutate_func: None, |
133 | 5.77k | fuel: u64::MAX, |
134 | 5.77k | rng: None, |
135 | 5.77k | info: None, |
136 | 5.77k | } |
137 | 5.77k | } |
138 | | } |
139 | | |
140 | | impl<'wasm> WasmMutate<'wasm> { |
141 | | /// Set the RNG seed used to choose which transformation to apply. |
142 | | /// |
143 | | /// Given the same input Wasm and same seed, `wasm-mutate` will always |
144 | | /// generate the same output Wasm. |
145 | 5.77k | pub fn seed(&mut self, seed: u64) -> &mut Self { |
146 | 5.77k | self.seed = seed; |
147 | 5.77k | self |
148 | 5.77k | } |
149 | | |
150 | | /// Configure whether we will only perform semantics-preserving |
151 | | /// transformations on the Wasm module. |
152 | 5.77k | pub fn preserve_semantics(&mut self, preserve_semantics: bool) -> &mut Self { |
153 | 5.77k | self.preserve_semantics = preserve_semantics; |
154 | 5.77k | self |
155 | 5.77k | } |
156 | | |
157 | | /// Configure the fuel used during the mutation |
158 | 5.77k | pub fn fuel(&mut self, fuel: u64) -> &mut Self { |
159 | 5.77k | self.fuel = fuel; |
160 | 5.77k | self |
161 | 5.77k | } |
162 | | |
163 | | /// Configure whether we will only perform size-reducing transformations on |
164 | | /// the Wasm module. |
165 | | /// |
166 | | /// Setting this to `true` allows `wasm-mutate` to be used as a test case |
167 | | /// reducer. |
168 | 0 | pub fn reduce(&mut self, reduce: bool) -> &mut Self { |
169 | 0 | self.reduce = reduce; |
170 | 0 | self |
171 | 0 | } |
172 | | |
173 | | /// Set a custom raw mutation function. |
174 | | /// |
175 | | /// This is used when we need some underlying raw bytes, for example when |
176 | | /// mutating the contents of a data segment. |
177 | | /// |
178 | | /// You can override this to use `libFuzzer`'s `LLVMFuzzerMutate` function |
179 | | /// to get raw bytes from `libFuzzer`, for example. |
180 | | /// |
181 | | /// The function is given the raw data buffer and the maximum size the |
182 | | /// mutated data should be. After mutating the data, the function should |
183 | | /// `resize` the data to its final, mutated size, which should be less than |
184 | | /// or equal to the maximum size. |
185 | 0 | pub fn raw_mutate_func( |
186 | 0 | &mut self, |
187 | 0 | raw_mutate_func: Option<Arc<dyn Fn(&mut Vec<u8>, usize) -> Result<()>>>, |
188 | 0 | ) -> &mut Self { |
189 | 0 | self.raw_mutate_func = raw_mutate_func; |
190 | 0 | self |
191 | 0 | } |
192 | | |
193 | 26.4k | pub(crate) fn consume_fuel(&mut self, qt: u64) -> Result<()> { |
194 | 26.4k | if qt > self.fuel { |
195 | 6 | log::info!("Out of fuel"); |
196 | 6 | return Err(Error::out_of_fuel()); |
197 | 26.4k | } |
198 | 26.4k | self.fuel -= qt; |
199 | 26.4k | Ok(()) |
200 | 26.4k | } |
201 | | |
202 | | /// Run this configured `WasmMutate` on the given input Wasm. |
203 | 5.77k | pub fn run<'a>( |
204 | 5.77k | &'a mut self, |
205 | 5.77k | input_wasm: &'wasm [u8], |
206 | 5.77k | ) -> Result<Box<dyn Iterator<Item = Result<Vec<u8>>> + 'a>> { |
207 | 5.77k | self.setup(input_wasm)?; |
208 | | |
209 | | const MUTATORS: &[&dyn Mutator] = &[ |
210 | | &PeepholeMutator::new(2), |
211 | | &RemoveExportMutator, |
212 | | &RenameExportMutator { max_name_size: 100 }, |
213 | | &SnipMutator, |
214 | | &CodemotionMutator, |
215 | | &FunctionBodyUnreachable, |
216 | | &AddCustomSectionMutator, |
217 | | &ReorderCustomSectionMutator, |
218 | | &CustomSectionMutator, |
219 | | &AddTypeMutator { |
220 | | max_params: 20, |
221 | | max_results: 20, |
222 | | }, |
223 | | &AddFunctionMutator, |
224 | | &RemoveSection::Custom, |
225 | | &RemoveSection::Empty, |
226 | | &ConstExpressionMutator::Global, |
227 | | &ConstExpressionMutator::ElementOffset, |
228 | | &ConstExpressionMutator::ElementFunc, |
229 | | &RemoveItemMutator(Item::Function), |
230 | | &RemoveItemMutator(Item::Global), |
231 | | &RemoveItemMutator(Item::Memory), |
232 | | &RemoveItemMutator(Item::Table), |
233 | | &RemoveItemMutator(Item::Type), |
234 | | &RemoveItemMutator(Item::Data), |
235 | | &RemoveItemMutator(Item::Element), |
236 | | &RemoveItemMutator(Item::Tag), |
237 | | &ModifyDataMutator { |
238 | | max_data_size: 10 << 20, // 10MB |
239 | | }, |
240 | | &RemoveStartSection, |
241 | | ]; |
242 | | |
243 | | // Attempt all mutators, but start at an arbitrary index. |
244 | 4.79k | let start = self.rng().random_range(0..MUTATORS.len()); |
245 | 14.9k | for m in MUTATORS.iter().cycle().skip(start).take(MUTATORS.len()) { |
246 | 14.9k | let can_mutate = m.can_mutate(self); |
247 | 14.9k | log::trace!("Can `{}` mutate? {}", m.name(), can_mutate); |
248 | 14.9k | if !can_mutate { |
249 | 10.1k | continue; |
250 | 4.79k | } |
251 | 4.79k | log::debug!("attempting to mutate with `{}`", m.name()); |
252 | 4.79k | match m.mutate(self) { |
253 | 3.86k | Ok(iter) => { |
254 | 3.86k | log::debug!("mutator `{}` succeeded", m.name()); |
255 | 12.0k | return Ok(Box::new(iter.into_iter().map(|r| r.map(|m| m.finish())))); |
256 | | } |
257 | 931 | Err(e) => { |
258 | 931 | log::debug!("mutator `{}` failed: {}", m.name(), e); |
259 | 931 | return Err(e); |
260 | | } |
261 | | } |
262 | | } |
263 | | |
264 | 0 | Err(Error::no_mutations_applicable()) |
265 | 5.77k | } |
266 | | |
267 | 5.77k | fn setup(&mut self, input_wasm: &'wasm [u8]) -> Result<()> { |
268 | 5.77k | self.info = Some(ModuleInfo::new(input_wasm)?); |
269 | 4.79k | self.rng = Some(SmallRng::seed_from_u64(self.seed)); |
270 | 4.79k | Ok(()) |
271 | 5.77k | } |
272 | | |
273 | 42.3k | pub(crate) fn rng(&mut self) -> &mut SmallRng { |
274 | 42.3k | self.rng.as_mut().unwrap() |
275 | 42.3k | } |
276 | | |
277 | 194k | pub(crate) fn info(&self) -> &ModuleInfo<'wasm> { |
278 | 194k | self.info.as_ref().unwrap() |
279 | 194k | } |
280 | | |
281 | 993 | fn raw_mutate(&mut self, data: &mut Vec<u8>, max_size: usize) -> Result<()> { |
282 | | // If a raw mutation function is configured then that's prioritized. |
283 | 993 | if let Some(mutate) = &self.raw_mutate_func { |
284 | 0 | return mutate(data, max_size); |
285 | 993 | } |
286 | | |
287 | | // If no raw mutation function is configured then we apply a naive |
288 | | // default heuristic. For now that heuristic is to simply replace a |
289 | | // subslice of data with a random slice of other data. |
290 | | // |
291 | | // First up start/end indices are picked. |
292 | 993 | let a = self.rng().random_range(0..=data.len()); |
293 | 993 | let b = self.rng().random_range(0..=data.len()); |
294 | 993 | let start = a.min(b); |
295 | 993 | let end = a.max(b); |
296 | | |
297 | | // Next a length of the replacement is chosen. Note that the replacement |
298 | | // is always smaller than the input if reduction is requested, otherwise |
299 | | // we choose some arbitrary length of bytes to insert. |
300 | 993 | let max_size = if self.reduce || self.rng().random() { |
301 | 480 | 0 |
302 | | } else { |
303 | 513 | max_size |
304 | | }; |
305 | 993 | let len = self |
306 | 993 | .rng() |
307 | 993 | .random_range(0..=end - start + max_size.saturating_sub(data.len())); |
308 | | |
309 | | // With parameters chosen the `Vec::splice` method is used to replace |
310 | | // the data in the input. |
311 | 993 | data.splice(start..end, self.rng().random_iter().take(len)); |
312 | | |
313 | 993 | Ok(()) |
314 | 993 | } |
315 | | } |
316 | | |
317 | | #[cfg(test)] |
318 | | pub(crate) fn validate(bytes: &[u8]) { |
319 | | use wasmparser::WasmFeatures; |
320 | | |
321 | | let mut validator = wasmparser::Validator::new_with_features( |
322 | | WasmFeatures::default() | WasmFeatures::MEMORY64 | WasmFeatures::MULTI_MEMORY, |
323 | | ); |
324 | | let err = match validator.validate_all(bytes) { |
325 | | Ok(_) => return, |
326 | | Err(e) => e, |
327 | | }; |
328 | | drop(std::fs::write("test.wasm", &bytes)); |
329 | | if let Ok(text) = wasmprinter::print_bytes(bytes) { |
330 | | drop(std::fs::write("test.wat", &text)); |
331 | | } |
332 | | |
333 | | panic!("wasm failed to validate: {err} (written to test.wasm)"); |
334 | | } |