/src/wasm-tools/fuzz/fuzz_targets/mutate.rs
Line | Count | Source (jump to first uncovered line) |
1 | | #![no_main] |
2 | | |
3 | | use arbitrary::Unstructured; |
4 | | use libfuzzer_sys::fuzz_target; |
5 | | use std::sync::atomic::{AtomicU64, Ordering}; |
6 | | use wasmparser::WasmFeatures; |
7 | | |
8 | | static NUM_RUNS: AtomicU64 = AtomicU64::new(0); |
9 | | static NUM_SUCCESSFUL_MUTATIONS: AtomicU64 = AtomicU64::new(0); |
10 | | |
11 | | fuzz_target!(|bytes: &[u8]| { |
12 | | let _ = env_logger::try_init(); |
13 | | |
14 | | // Generate a random Wasm module with `wasm-smith` as well as a RNG seed for |
15 | | // use with `wasm-mutate`. |
16 | | |
17 | | let mut seed = 0; |
18 | | let mut preserve_semantics = false; |
19 | | let mut u = Unstructured::new(bytes); |
20 | 10.9k | let (wasm, config) = match wasm_tools_fuzz::generate_valid_module(&mut u, |config, u| { |
21 | 10.9k | config.exceptions_enabled = false; |
22 | 10.9k | seed = u.arbitrary()?; |
23 | 10.9k | preserve_semantics = u.arbitrary()?; |
24 | 10.9k | Ok(()) |
25 | 10.9k | }) { |
26 | | Ok(m) => m, |
27 | | Err(_) => return, |
28 | | }; |
29 | | log::debug!("seed = {}", seed); |
30 | | |
31 | | // Keep track of how many runs we've done thus far and how many of those |
32 | | // runs had successful mutations. |
33 | | |
34 | | let old_num_runs = NUM_RUNS.fetch_add(1, Ordering::Relaxed); |
35 | | if old_num_runs % 4096 == 4095 && log::log_enabled!(log::Level::Info) { |
36 | | let successful = NUM_SUCCESSFUL_MUTATIONS.load(Ordering::Relaxed); |
37 | | let percent = successful as f64 / old_num_runs as f64 * 100.0; |
38 | | log::info!( |
39 | | "{} / {} ({:.2}%) successful mutations.", |
40 | | successful, |
41 | | old_num_runs, |
42 | | percent |
43 | | ); |
44 | | } |
45 | | |
46 | | // Mutate the Wasm with `wasm-mutate`. Assert that each mutation is still |
47 | | // valid Wasm. |
48 | | |
49 | | let mut wasm_mutate = wasm_mutate::WasmMutate::default(); |
50 | | wasm_mutate.seed(seed); |
51 | | wasm_mutate.fuel(300); |
52 | | wasm_mutate.preserve_semantics( |
53 | | // If we are going to check that we get the same evaluated results |
54 | | // before and after mutation, then we need to preserve semantics. |
55 | | cfg!(feature = "wasmtime") && preserve_semantics, |
56 | | ); |
57 | | |
58 | | let iterator = match wasm_mutate.run(&wasm) { |
59 | | Ok(iterator) => iterator, |
60 | | Err(e) => { |
61 | | log::warn!("Failed to mutate the Wasm: {}", e); |
62 | | return; |
63 | | } |
64 | | }; |
65 | | |
66 | | // Note that on-by-default features in wasmparser are not disabled here if |
67 | | // the feature was disabled in `config` when the module was generated. For |
68 | | // example if the input module doesn't have simd then wasm-mutate may |
69 | | // produce a module that uses simd, which is ok and expected. |
70 | | // |
71 | | // Otherwise only forward some off-by-default features which are affected by |
72 | | // wasm-smith's generation of modules and wasm-mutate otherwise won't add |
73 | | // itself if it doesn't already exist. |
74 | | let mut features = WasmFeatures::default(); |
75 | | features.relaxed_simd = config.relaxed_simd_enabled; |
76 | | features.multi_memory = config.max_memories > 1; |
77 | | features.memory64 = config.memory64_enabled; |
78 | | features.threads = config.threads_enabled; |
79 | | |
80 | | for (i, mutated_wasm) in iterator.take(10).enumerate() { |
81 | | let mutated_wasm = match mutated_wasm { |
82 | | Ok(w) => w, |
83 | | Err(e) => match e.kind() { |
84 | | wasm_mutate::ErrorKind::NoMutationsApplicable => continue, |
85 | | _ => panic!("Unexpected mutation failure: {}", e), |
86 | | }, |
87 | | }; |
88 | | |
89 | | // Increase ony once for the same input Wasm. |
90 | | if i == 0 { |
91 | | NUM_SUCCESSFUL_MUTATIONS.fetch_add(1, Ordering::Relaxed); |
92 | | } |
93 | | |
94 | | let validation_result = |
95 | | wasmparser::Validator::new_with_features(features).validate_all(&mutated_wasm); |
96 | | |
97 | | if log::log_enabled!(log::Level::Debug) { |
98 | | log::debug!("writing mutated Wasm to `mutated.wasm`"); |
99 | | std::fs::write("mutated.wasm", &mutated_wasm) |
100 | | .expect("should write `mutated.wasm` okay"); |
101 | | if let Ok(mutated_wat) = wasmprinter::print_bytes(&mutated_wasm) { |
102 | | log::debug!("writing mutated WAT to `mutated.wat`"); |
103 | | std::fs::write("mutated.wat", &mutated_wat) |
104 | | .expect("should write `mutated.wat` okay"); |
105 | | } |
106 | | } |
107 | | |
108 | | validation_result.expect("`wasm-mutate` should always produce a valid Wasm file"); |
109 | | |
110 | | #[cfg(feature = "wasmtime")] |
111 | | if preserve_semantics { |
112 | | eval::assert_same_evaluation(&wasm, &mutated_wasm); |
113 | | } |
114 | | } |
115 | | }); |
116 | | |
117 | | #[cfg(feature = "wasmtime")] |
118 | | #[path = "../../crates/fuzz-stats/src/lib.rs"] |
119 | | pub mod fuzz_stats; |
120 | | |
121 | | #[cfg(feature = "wasmtime")] |
122 | | mod eval { |
123 | | use super::fuzz_stats::{dummy, limits::StoreLimits}; |
124 | | use std::collections::hash_map::DefaultHasher; |
125 | | use std::hash::{Hash, Hasher}; |
126 | | use wasmtime::{ResourceLimiter, Val}; |
127 | | |
128 | | /// Compile, instantiate, and evaluate both the original and mutated Wasm. |
129 | | /// |
130 | | /// We should get identical results because we told `wasm-mutate` to preserve |
131 | | /// semantics. |
132 | | pub fn assert_same_evaluation(wasm: &[u8], mutated_wasm: &[u8]) { |
133 | | let mut config = wasmtime::Config::default(); |
134 | | config.cranelift_nan_canonicalization(true); |
135 | | config.consume_fuel(true); |
136 | | |
137 | | let engine = wasmtime::Engine::new(&config).unwrap(); |
138 | | |
139 | | let (orig_module, mutated_module) = match ( |
140 | | wasmtime::Module::new(&engine, &wasm), |
141 | | wasmtime::Module::new(&engine, &mutated_wasm), |
142 | | ) { |
143 | | (Ok(o), Ok(m)) => (o, m), |
144 | | // Ideally we would assert that they both errored if either one did, but |
145 | | // it is possible that a mutation bumped some count above/below an |
146 | | // implementation limit. |
147 | | (_, _) => return, |
148 | | }; |
149 | | |
150 | | let limits = StoreLimits { |
151 | | remaining_memory: 1 << 30, |
152 | | oom: false, |
153 | | }; |
154 | | let mut orig_store = wasmtime::Store::new(&engine, limits.clone()); |
155 | | let mut mutated_store = wasmtime::Store::new(&engine, limits); |
156 | | orig_store.limiter(|s| s as &mut dyn ResourceLimiter); |
157 | | mutated_store.limiter(|s| s as &mut dyn ResourceLimiter); |
158 | | let orig_imports = match dummy::dummy_imports(&mut orig_store, &orig_module) { |
159 | | Ok(imps) => imps, |
160 | | Err(_) => return, |
161 | | }; |
162 | | let mutated_imports = match dummy::dummy_imports(&mut mutated_store, &mutated_module) { |
163 | | Ok(imps) => imps, |
164 | | Err(_) => return, |
165 | | }; |
166 | | |
167 | | let (orig_instance, mutated_instance) = match ( |
168 | | wasmtime::Instance::new(&mut orig_store, &orig_module, &orig_imports), |
169 | | wasmtime::Instance::new(&mut mutated_store, &mutated_module, &mutated_imports), |
170 | | ) { |
171 | | (Ok(x), Ok(y)) => (x, y), |
172 | | (_, _) => return, |
173 | | }; |
174 | | |
175 | | assert_same_state( |
176 | | &orig_module, |
177 | | &mut orig_store, |
178 | | orig_instance, |
179 | | &mut mutated_store, |
180 | | mutated_instance, |
181 | | ); |
182 | | assert_same_calls( |
183 | | &orig_module, |
184 | | &mut orig_store, |
185 | | orig_instance, |
186 | | &mut mutated_store, |
187 | | mutated_instance, |
188 | | ); |
189 | | assert_same_state( |
190 | | &orig_module, |
191 | | &mut orig_store, |
192 | | orig_instance, |
193 | | &mut mutated_store, |
194 | | mutated_instance, |
195 | | ); |
196 | | } |
197 | | |
198 | | fn assert_same_state( |
199 | | orig_module: &wasmtime::Module, |
200 | | orig_store: &mut wasmtime::Store<StoreLimits>, |
201 | | orig_instance: wasmtime::Instance, |
202 | | mutated_store: &mut wasmtime::Store<StoreLimits>, |
203 | | mutated_instance: wasmtime::Instance, |
204 | | ) { |
205 | | for export in orig_module.exports() { |
206 | | match export.ty() { |
207 | | wasmtime::ExternType::Global(_) => { |
208 | | let orig = orig_instance |
209 | | .get_export(&mut *orig_store, export.name()) |
210 | | .unwrap() |
211 | | .into_global() |
212 | | .unwrap() |
213 | | .get(&mut *orig_store); |
214 | | let mutated = mutated_instance |
215 | | .get_export(&mut *mutated_store, export.name()) |
216 | | .unwrap() |
217 | | .into_global() |
218 | | .unwrap() |
219 | | .get(&mut *mutated_store); |
220 | | assert_val_eq(&orig, &mutated); |
221 | | } |
222 | | wasmtime::ExternType::Memory(_) => { |
223 | | let orig = orig_instance |
224 | | .get_export(&mut *orig_store, export.name()) |
225 | | .unwrap() |
226 | | .into_memory() |
227 | | .unwrap(); |
228 | | let mut h = DefaultHasher::default(); |
229 | | orig.data(&orig_store).hash(&mut h); |
230 | | let orig = h.finish(); |
231 | | let mutated = mutated_instance |
232 | | .get_export(&mut *mutated_store, export.name()) |
233 | | .unwrap() |
234 | | .into_memory() |
235 | | .unwrap(); |
236 | | let mut h = DefaultHasher::default(); |
237 | | mutated.data(&mutated_store).hash(&mut h); |
238 | | let mutated = h.finish(); |
239 | | assert_eq!(orig, mutated, "original and mutated Wasm memories diverged"); |
240 | | } |
241 | | _ => continue, |
242 | | } |
243 | | } |
244 | | } |
245 | | |
246 | | fn assert_same_calls( |
247 | | orig_module: &wasmtime::Module, |
248 | | orig_store: &mut wasmtime::Store<StoreLimits>, |
249 | | orig_instance: wasmtime::Instance, |
250 | | mutated_store: &mut wasmtime::Store<StoreLimits>, |
251 | | mutated_instance: wasmtime::Instance, |
252 | | ) { |
253 | | for export in orig_module.exports() { |
254 | | let func_ty = match export.ty() { |
255 | | wasmtime::ExternType::Func(func_ty) => func_ty, |
256 | | _ => continue, |
257 | | }; |
258 | | let orig_func = orig_instance |
259 | | .get_func(&mut *orig_store, export.name()) |
260 | | .unwrap(); |
261 | | let mutated_func = mutated_instance |
262 | | .get_func(&mut *mutated_store, export.name()) |
263 | | .unwrap(); |
264 | | let args = dummy::dummy_values(func_ty.params()); |
265 | | let mut orig_results = vec![Val::I32(0); func_ty.results().len()]; |
266 | | let mut mutated_results = orig_results.clone(); |
267 | | log::debug!("invoking `{}`", export.name()); |
268 | | let prev_consumed = orig_store.fuel_consumed().unwrap(); |
269 | | match ( |
270 | | { |
271 | | orig_store.add_fuel(1_000).unwrap(); |
272 | | orig_func.call(&mut *orig_store, &args, &mut orig_results) |
273 | | }, |
274 | | { |
275 | | let consumed = orig_store.fuel_consumed().unwrap() - prev_consumed; |
276 | | // Add in some extra fuel to account for extra code that |
277 | | // may be inserted by the mutation or to handle the case |
278 | | // where the original execution trapped which may not have |
279 | | // fully accounted for the last bit of fuel needed to reach |
280 | | // the trap. |
281 | | log::debug!("consumed {consumed} fuel"); |
282 | | mutated_store.add_fuel(consumed + 10).unwrap(); |
283 | | mutated_func.call(&mut *mutated_store, &args, &mut mutated_results) |
284 | | }, |
285 | | ) { |
286 | | (Ok(()), Ok(())) => { |
287 | | for (orig_val, mutated_val) in orig_results.iter().zip(mutated_results.iter()) { |
288 | | assert_val_eq(orig_val, mutated_val); |
289 | | } |
290 | | } |
291 | | (Err(orig), Err(mutated)) => { |
292 | | log::debug!("original error {orig:?}"); |
293 | | log::debug!("mutated error {mutated:?}"); |
294 | | continue; |
295 | | } |
296 | | (orig, mutated) => panic!( |
297 | | "mutated and original Wasm diverged: orig = {:?}; mutated = {:?}", |
298 | | orig, mutated, |
299 | | ), |
300 | | } |
301 | | } |
302 | | } |
303 | | |
304 | | fn assert_val_eq(orig_val: &wasmtime::Val, mutated_val: &wasmtime::Val) { |
305 | | match (orig_val, mutated_val) { |
306 | | (wasmtime::Val::I32(o), wasmtime::Val::I32(m)) => assert_eq!(o, m), |
307 | | (wasmtime::Val::I64(o), wasmtime::Val::I64(m)) => assert_eq!(o, m), |
308 | | (wasmtime::Val::F32(o), wasmtime::Val::F32(m)) => { |
309 | | let o = f32::from_bits(*o); |
310 | | let m = f32::from_bits(*m); |
311 | | assert!(o == m || (o.is_nan() && m.is_nan())); |
312 | | } |
313 | | (wasmtime::Val::F64(o), wasmtime::Val::F64(m)) => { |
314 | | let o = f64::from_bits(*o); |
315 | | let m = f64::from_bits(*m); |
316 | | assert!(o == m || (o.is_nan() && m.is_nan())); |
317 | | } |
318 | | (wasmtime::Val::V128(o), wasmtime::Val::V128(m)) => { |
319 | | assert_eq!(o, m) |
320 | | } |
321 | | (wasmtime::Val::ExternRef(o), wasmtime::Val::ExternRef(m)) => { |
322 | | assert_eq!(o.is_none(), m.is_none()) |
323 | | } |
324 | | (wasmtime::Val::FuncRef(o), wasmtime::Val::FuncRef(m)) => { |
325 | | assert_eq!(o.is_none(), m.is_none()) |
326 | | } |
327 | | (o, m) => panic!( |
328 | | "mutated and original Wasm diverged: orig = {:?}; mutated = {:?}", |
329 | | o, m, |
330 | | ), |
331 | | } |
332 | | } |
333 | | } |