1 //! Oracles.
2 //!
3 //! Oracles take a test case and determine whether we have a bug. For example,
4 //! one of the simplest oracles is to take a Wasm binary as our input test case,
5 //! validate and instantiate it, and (implicitly) check that no assertions
6 //! failed or segfaults happened. A more complicated oracle might compare the
7 //! result of executing a Wasm file with and without optimizations enabled, and
8 //! make sure that the two executions are observably identical.
9 //!
10 //! When an oracle finds a bug, it should report it to the fuzzing engine by
11 //! panicking.
12 
13 pub mod dummy;
14 
15 use crate::generators;
16 use arbitrary::Arbitrary;
17 use log::debug;
18 use std::cell::Cell;
19 use std::rc::Rc;
20 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
21 use std::sync::{Arc, Condvar, Mutex};
22 use std::time::{Duration, Instant};
23 use wasmtime::*;
24 use wasmtime_wast::WastContext;
25 
26 #[cfg(not(any(windows, target_arch = "s390x")))]
27 pub use self::v8::*;
28 #[cfg(not(any(windows, target_arch = "s390x")))]
29 mod v8;
30 
31 static CNT: AtomicUsize = AtomicUsize::new(0);
32 
33 /// Logs a wasm file to the filesystem to make it easy to figure out what wasm
34 /// was used when debugging.
35 pub fn log_wasm(wasm: &[u8]) {
36     super::init_fuzzing();
37 
38     if !log::log_enabled!(log::Level::Debug) {
39         return;
40     }
41 
42     let i = CNT.fetch_add(1, SeqCst);
43     let name = format!("testcase{}.wasm", i);
44     std::fs::write(&name, wasm).expect("failed to write wasm file");
45     log::debug!("wrote wasm file to `{}`", name);
46     let wat = format!("testcase{}.wat", i);
47     match wasmprinter::print_bytes(wasm) {
48         Ok(s) => std::fs::write(&wat, s).expect("failed to write wat file"),
49         // If wasmprinter failed remove a `*.wat` file, if any, to avoid
50         // confusing a preexisting one with this wasm which failed to get
51         // printed.
52         Err(_) => drop(std::fs::remove_file(&wat)),
53     }
54 }
55 
56 /// The `T` in `Store<T>` for fuzzing stores, used to limit resource
57 /// consumption during fuzzing.
58 #[derive(Clone)]
59 pub struct StoreLimits(Rc<LimitsState>);
60 
61 struct LimitsState {
62     /// Remaining memory, in bytes, left to allocate
63     remaining_memory: Cell<usize>,
64     /// Whether or not an allocation request has been denied
65     oom: Cell<bool>,
66 }
67 
68 impl StoreLimits {
69     /// Creates the default set of limits for all fuzzing stores.
70     pub fn new() -> StoreLimits {
71         StoreLimits(Rc::new(LimitsState {
72             // Limits tables/memories within a store to at most 1gb for now to
73             // exercise some larger address but not overflow various limits.
74             remaining_memory: Cell::new(1 << 30),
75             oom: Cell::new(false),
76         }))
77     }
78 
79     fn alloc(&mut self, amt: usize) -> bool {
80         match self.0.remaining_memory.get().checked_sub(amt) {
81             Some(mem) => {
82                 self.0.remaining_memory.set(mem);
83                 true
84             }
85             None => {
86                 self.0.oom.set(true);
87                 false
88             }
89         }
90     }
91 }
92 
93 impl ResourceLimiter for StoreLimits {
94     fn memory_growing(&mut self, current: usize, desired: usize, _maximum: Option<usize>) -> bool {
95         self.alloc(desired - current)
96     }
97 
98     fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> bool {
99         let delta = (desired - current) as usize * std::mem::size_of::<usize>();
100         self.alloc(delta)
101     }
102 }
103 
104 /// Methods of timing out execution of a WebAssembly module
105 #[derive(Clone, Debug)]
106 pub enum Timeout {
107     /// No timeout is used, it should be guaranteed via some other means that
108     /// the input does not infinite loop.
109     None,
110     /// Fuel-based timeouts are used where the specified fuel is all that the
111     /// provided wasm module is allowed to consume.
112     Fuel(u64),
113     /// An epoch-interruption-based timeout is used with a sleeping
114     /// thread bumping the epoch counter after the specified duration.
115     Epoch(Duration),
116 }
117 
118 /// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected
119 /// panic or segfault or anything else that can be detected "passively".
120 ///
121 /// The engine will be configured using provided config.
122 pub fn instantiate(wasm: &[u8], known_valid: bool, config: &generators::Config, timeout: Timeout) {
123     let mut store = config.to_store();
124 
125     let mut timeout_state = SignalOnDrop::default();
126     match timeout {
127         Timeout::Fuel(fuel) => {
128             // consume the default fuel in the store ...
129             let remaining = store.consume_fuel(0).unwrap();
130             store.consume_fuel(remaining - 1).unwrap();
131             // ... then add back in how much fuel we're allowing here
132             store.add_fuel(fuel).unwrap();
133         }
134         // If a timeout is requested then we spawn a helper thread to wait for
135         // the requested time and then send us a signal to get interrupted. We
136         // also arrange for the thread's sleep to get interrupted if we return
137         // early (or the wasm returns within the time limit), which allows the
138         // thread to get torn down.
139         //
140         // This prevents us from creating a huge number of sleeping threads if
141         // this function is executed in a loop, like it does on nightly fuzzing
142         // infrastructure.
143         Timeout::Epoch(timeout) => {
144             let engine = store.engine().clone();
145             timeout_state.spawn_timeout(timeout, move || engine.increment_epoch());
146         }
147         Timeout::None => {}
148     }
149 
150     if let Some(module) = compile_module(store.engine(), wasm, known_valid, config) {
151         instantiate_with_dummy(&mut store, &module);
152     }
153 }
154 
155 /// Represents supported commands to the `instantiate_many` function.
156 #[derive(Arbitrary, Debug)]
157 pub enum Command {
158     /// Instantiates a module.
159     ///
160     /// The value is the index of the module to instantiate.
161     ///
162     /// The module instantiated will be this value modulo the number of modules provided to `instantiate_many`.
163     Instantiate(usize),
164     /// Terminates a "running" instance.
165     ///
166     /// The value is the index of the instance to terminate.
167     ///
168     /// The instance terminated will be this value modulo the number of currently running
169     /// instances.
170     ///
171     /// If no instances are running, the command will be ignored.
172     Terminate(usize),
173 }
174 
175 /// Instantiates many instances from the given modules.
176 ///
177 /// The engine will be configured using the provided config.
178 ///
179 /// The modules are expected to *not* have start functions as no timeouts are configured.
180 pub fn instantiate_many(
181     modules: &[Vec<u8>],
182     known_valid: bool,
183     config: &generators::Config,
184     commands: &[Command],
185 ) {
186     assert!(!config.module_config.config.allow_start_export);
187 
188     let engine = Engine::new(&config.to_wasmtime()).unwrap();
189 
190     let modules = modules
191         .iter()
192         .filter_map(|bytes| compile_module(&engine, bytes, known_valid, config))
193         .collect::<Vec<_>>();
194 
195     // If no modules were valid, we're done
196     if modules.is_empty() {
197         return;
198     }
199 
200     // This stores every `Store` where a successful instantiation takes place
201     let mut stores = Vec::new();
202     let limits = StoreLimits::new();
203 
204     for command in commands {
205         match command {
206             Command::Instantiate(index) => {
207                 let index = *index % modules.len();
208                 log::info!("instantiating {}", index);
209                 let module = &modules[index];
210                 let mut store = Store::new(&engine, limits.clone());
211                 config.configure_store(&mut store);
212 
213                 if instantiate_with_dummy(&mut store, module).is_some() {
214                     stores.push(Some(store));
215                 } else {
216                     log::warn!("instantiation failed");
217                 }
218             }
219             Command::Terminate(index) => {
220                 if stores.is_empty() {
221                     continue;
222                 }
223                 let index = *index % stores.len();
224 
225                 log::info!("dropping {}", index);
226                 stores.swap_remove(index);
227             }
228         }
229     }
230 }
231 
232 fn compile_module(
233     engine: &Engine,
234     bytes: &[u8],
235     known_valid: bool,
236     config: &generators::Config,
237 ) -> Option<Module> {
238     log_wasm(bytes);
239     match config.compile(engine, bytes) {
240         Ok(module) => Some(module),
241         Err(_) if !known_valid => None,
242         Err(e) => {
243             if let generators::InstanceAllocationStrategy::Pooling { .. } =
244                 &config.wasmtime.strategy
245             {
246                 // When using the pooling allocator, accept failures to compile when arbitrary
247                 // table element limits have been exceeded as there is currently no way
248                 // to constrain the generated module table types.
249                 let string = e.to_string();
250                 if string.contains("minimum element size") {
251                     return None;
252                 }
253 
254                 // Allow modules-failing-to-compile which exceed the requested
255                 // size for each instance. This is something that is difficult
256                 // to control and ensure it always suceeds, so we simply have a
257                 // "random" instance size limit and if a module doesn't fit we
258                 // move on to the next fuzz input.
259                 if string.contains("instance allocation for this module requires") {
260                     return None;
261                 }
262             }
263 
264             panic!("failed to compile module: {:?}", e);
265         }
266     }
267 }
268 
269 fn instantiate_with_dummy(store: &mut Store<StoreLimits>, module: &Module) -> Option<Instance> {
270     // Creation of imports can fail due to resource limit constraints, and then
271     // instantiation can naturally fail for a number of reasons as well. Bundle
272     // the two steps together to match on the error below.
273     let instance =
274         dummy::dummy_linker(store, module).and_then(|l| l.instantiate(&mut *store, module));
275 
276     let e = match instance {
277         Ok(i) => return Some(i),
278         Err(e) => e,
279     };
280 
281     // If the instantiation hit OOM for some reason then that's ok, it's
282     // expected that fuzz-generated programs try to allocate lots of
283     // stuff.
284     if store.data().0.oom.get() {
285         return None;
286     }
287 
288     // Allow traps which can happen normally with `unreachable` or a
289     // timeout or such
290     if e.downcast_ref::<Trap>().is_some() {
291         return None;
292     }
293 
294     let string = e.to_string();
295     // Also allow errors related to fuel consumption
296     if string.contains("all fuel consumed")
297         // Currently we instantiate with a `Linker` which can't instantiate
298         // every single module under the sun due to using name-based resolution
299         // rather than positional-based resolution
300         || string.contains("incompatible import type")
301     {
302         return None;
303     }
304 
305     // Also allow failures to instantiate as a result of hitting instance limits
306     if string.contains("concurrent instances has been reached") {
307         return None;
308     }
309 
310     // Everything else should be a bug in the fuzzer or a bug in wasmtime
311     panic!("failed to instantiate: {:?}", e);
312 }
313 
314 /// Instantiate the given Wasm module with each `Config` and call all of its
315 /// exports. Modulo OOM, non-canonical NaNs, and usage of Wasm features that are
316 /// or aren't enabled for different configs, we should get the same results when
317 /// we call the exported functions for all of our different configs.
318 ///
319 /// Returns `None` if a fuzz configuration was rejected (should happen rarely).
320 pub fn differential_execution(
321     wasm: &[u8],
322     module_config: &generators::ModuleConfig,
323     configs: &[generators::WasmtimeConfig],
324 ) -> Option<()> {
325     use std::collections::{HashMap, HashSet};
326 
327     // We need at least two configs.
328     if configs.len() < 2
329         // And all the configs should be unique.
330         || configs.iter().collect::<HashSet<_>>().len() != configs.len()
331     {
332         return None;
333     }
334 
335     let mut export_func_results: HashMap<String, Result<Box<[Val]>, Trap>> = Default::default();
336     log_wasm(&wasm);
337 
338     for fuzz_config in configs {
339         let fuzz_config = generators::Config {
340             module_config: module_config.clone(),
341             wasmtime: fuzz_config.clone(),
342         };
343         log::debug!("fuzz config: {:?}", fuzz_config);
344 
345         let mut store = fuzz_config.to_store();
346         let module = compile_module(store.engine(), &wasm, true, &fuzz_config)?;
347 
348         // TODO: we should implement tracing versions of these dummy imports
349         // that record a trace of the order that imported functions were called
350         // in and with what values. Like the results of exported functions,
351         // calls to imports should also yield the same values for each
352         // configuration, and we should assert that.
353         let instance = match instantiate_with_dummy(&mut store, &module) {
354             Some(instance) => instance,
355             None => continue,
356         };
357 
358         let exports = instance
359             .exports(&mut store)
360             .filter_map(|e| {
361                 let name = e.name().to_string();
362                 e.into_func().map(|f| (name, f))
363             })
364             .collect::<Vec<_>>();
365         for (name, f) in exports {
366             log::debug!("invoke export {:?}", name);
367             let ty = f.ty(&store);
368             let params = dummy::dummy_values(ty.params());
369             let mut results = vec![Val::I32(0); ty.results().len()];
370             let this_result = f
371                 .call(&mut store, &params, &mut results)
372                 .map(|()| results.into())
373                 .map_err(|e| e.downcast::<Trap>().unwrap());
374 
375             let existing_result = export_func_results
376                 .entry(name.to_string())
377                 .or_insert_with(|| this_result.clone());
378             assert_same_export_func_result(&existing_result, &this_result, &name);
379         }
380     }
381 
382     return Some(());
383 
384     fn assert_same_export_func_result(
385         lhs: &Result<Box<[Val]>, Trap>,
386         rhs: &Result<Box<[Val]>, Trap>,
387         func_name: &str,
388     ) {
389         let fail = || {
390             panic!(
391                 "differential fuzzing failed: exported func {} returned two \
392                  different results: {:?} != {:?}",
393                 func_name, lhs, rhs
394             )
395         };
396 
397         match (lhs, rhs) {
398             // Different compilation settings can lead to different amounts
399             // of stack space being consumed, so if either the lhs or the rhs
400             // hit a stack overflow then we discard the result of the other side
401             // since if it ran successfully or trapped that's ok in both
402             // situations.
403             (Err(e), _) | (_, Err(e)) if e.trap_code() == Some(TrapCode::StackOverflow) => {}
404 
405             (Err(a), Err(b)) => {
406                 if a.trap_code() != b.trap_code() {
407                     fail();
408                 }
409             }
410             (Ok(lhs), Ok(rhs)) => {
411                 if lhs.len() != rhs.len() {
412                     fail();
413                 }
414                 for (lhs, rhs) in lhs.iter().zip(rhs.iter()) {
415                     match (lhs, rhs) {
416                         (Val::I32(lhs), Val::I32(rhs)) if lhs == rhs => continue,
417                         (Val::I64(lhs), Val::I64(rhs)) if lhs == rhs => continue,
418                         (Val::V128(lhs), Val::V128(rhs)) if lhs == rhs => continue,
419                         (Val::F32(lhs), Val::F32(rhs)) if f32_equal(*lhs, *rhs) => continue,
420                         (Val::F64(lhs), Val::F64(rhs)) if f64_equal(*lhs, *rhs) => continue,
421                         (Val::ExternRef(_), Val::ExternRef(_))
422                         | (Val::FuncRef(_), Val::FuncRef(_)) => continue,
423                         _ => fail(),
424                     }
425                 }
426             }
427             _ => fail(),
428         }
429     }
430 }
431 
432 fn f32_equal(a: u32, b: u32) -> bool {
433     let a = f32::from_bits(a);
434     let b = f32::from_bits(b);
435     a == b || (a.is_nan() && b.is_nan())
436 }
437 
438 fn f64_equal(a: u64, b: u64) -> bool {
439     let a = f64::from_bits(a);
440     let b = f64::from_bits(b);
441     a == b || (a.is_nan() && b.is_nan())
442 }
443 
444 /// Invoke the given API calls.
445 pub fn make_api_calls(api: generators::api::ApiCalls) {
446     use crate::generators::api::ApiCall;
447     use std::collections::HashMap;
448 
449     let mut store: Option<Store<StoreLimits>> = None;
450     let mut modules: HashMap<usize, Module> = Default::default();
451     let mut instances: HashMap<usize, Instance> = Default::default();
452 
453     for call in api.calls {
454         match call {
455             ApiCall::StoreNew(config) => {
456                 log::trace!("creating store");
457                 assert!(store.is_none());
458                 store = Some(config.to_store());
459             }
460 
461             ApiCall::ModuleNew { id, wasm } => {
462                 log::debug!("creating module: {}", id);
463                 log_wasm(&wasm);
464                 let module = match Module::new(store.as_ref().unwrap().engine(), &wasm) {
465                     Ok(m) => m,
466                     Err(_) => continue,
467                 };
468                 let old = modules.insert(id, module);
469                 assert!(old.is_none());
470             }
471 
472             ApiCall::ModuleDrop { id } => {
473                 log::trace!("dropping module: {}", id);
474                 drop(modules.remove(&id));
475             }
476 
477             ApiCall::InstanceNew { id, module } => {
478                 log::trace!("instantiating module {} as {}", module, id);
479                 let module = match modules.get(&module) {
480                     Some(m) => m,
481                     None => continue,
482                 };
483 
484                 let store = store.as_mut().unwrap();
485                 if let Some(instance) = instantiate_with_dummy(store, module) {
486                     instances.insert(id, instance);
487                 }
488             }
489 
490             ApiCall::InstanceDrop { id } => {
491                 log::trace!("dropping instance {}", id);
492                 drop(instances.remove(&id));
493             }
494 
495             ApiCall::CallExportedFunc { instance, nth } => {
496                 log::trace!("calling instance export {} / {}", instance, nth);
497                 let instance = match instances.get(&instance) {
498                     Some(i) => i,
499                     None => {
500                         // Note that we aren't guaranteed to instantiate valid
501                         // modules, see comments in `InstanceNew` for details on
502                         // that. But the API call generator can't know if
503                         // instantiation failed, so we might not actually have
504                         // this instance. When that's the case, just skip the
505                         // API call and keep going.
506                         continue;
507                     }
508                 };
509                 let store = store.as_mut().unwrap();
510 
511                 let funcs = instance
512                     .exports(&mut *store)
513                     .filter_map(|e| match e.into_extern() {
514                         Extern::Func(f) => Some(f.clone()),
515                         _ => None,
516                     })
517                     .collect::<Vec<_>>();
518 
519                 if funcs.is_empty() {
520                     continue;
521                 }
522 
523                 let nth = nth % funcs.len();
524                 let f = &funcs[nth];
525                 let ty = f.ty(&store);
526                 let params = dummy::dummy_values(ty.params());
527                 let mut results = vec![Val::I32(0); ty.results().len()];
528                 let _ = f.call(store, &params, &mut results);
529             }
530         }
531     }
532 }
533 
534 /// Executes the wast `test` spectest with the `config` specified.
535 ///
536 /// Ensures that spec tests pass regardless of the `Config`.
537 pub fn spectest(mut fuzz_config: generators::Config, test: generators::SpecTest) {
538     crate::init_fuzzing();
539     fuzz_config.set_spectest_compliant();
540     log::debug!("running {:?}", test.file);
541     let mut wast_context = WastContext::new(fuzz_config.to_store());
542     wast_context.register_spectest().unwrap();
543     wast_context
544         .run_buffer(test.file, test.contents.as_bytes())
545         .unwrap();
546 }
547 
548 /// Execute a series of `table.get` and `table.set` operations.
549 pub fn table_ops(mut fuzz_config: generators::Config, ops: generators::table_ops::TableOps) {
550     let expected_drops = Arc::new(AtomicUsize::new(ops.num_params as usize));
551     let num_dropped = Arc::new(AtomicUsize::new(0));
552 
553     {
554         fuzz_config.wasmtime.consume_fuel = true;
555         let mut store = fuzz_config.to_store();
556 
557         // consume the default fuel in the store ...
558         let remaining = store.consume_fuel(0).unwrap();
559         store.consume_fuel(remaining - 1).unwrap();
560         // ... then add back in how much fuel we're allowing here
561         store.add_fuel(1_000).unwrap();
562 
563         let wasm = ops.to_wasm_binary();
564         log_wasm(&wasm);
565         let module = match compile_module(store.engine(), &wasm, false, &fuzz_config) {
566             Some(m) => m,
567             None => return,
568         };
569 
570         let mut linker = Linker::new(store.engine());
571 
572         // To avoid timeouts, limit the number of explicit GCs we perform per
573         // test case.
574         const MAX_GCS: usize = 5;
575 
576         let num_gcs = AtomicUsize::new(0);
577         linker
578             .define(
579                 "",
580                 "gc",
581                 // NB: use `Func::new` so that this can still compile on the old x86
582                 // backend, where `IntoFunc` isn't implemented for multi-value
583                 // returns.
584                 Func::new(
585                     &mut store,
586                     FuncType::new(
587                         vec![],
588                         vec![ValType::ExternRef, ValType::ExternRef, ValType::ExternRef],
589                     ),
590                     {
591                         let num_dropped = num_dropped.clone();
592                         let expected_drops = expected_drops.clone();
593                         move |mut caller: Caller<'_, StoreLimits>, _params, results| {
594                             log::info!("table_ops: GC");
595                             if num_gcs.fetch_add(1, SeqCst) < MAX_GCS {
596                                 caller.gc();
597                             }
598 
599                             expected_drops.fetch_add(3, SeqCst);
600                             results[0] =
601                                 Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
602                             results[1] =
603                                 Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
604                             results[2] =
605                                 Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
606                             Ok(())
607                         }
608                     },
609                 ),
610             )
611             .unwrap();
612 
613         linker
614             .func_wrap("", "take_refs", {
615                 let expected_drops = expected_drops.clone();
616                 move |a: Option<ExternRef>, b: Option<ExternRef>, c: Option<ExternRef>| {
617                     log::info!("table_ops: take_refs");
618                     // Do the assertion on each ref's inner data, even though it
619                     // all points to the same atomic, so that if we happen to
620                     // run into a use-after-free bug with one of these refs we
621                     // are more likely to trigger a segfault.
622                     if let Some(a) = a {
623                         let a = a.data().downcast_ref::<CountDrops>().unwrap();
624                         assert!(a.0.load(SeqCst) <= expected_drops.load(SeqCst));
625                     }
626                     if let Some(b) = b {
627                         let b = b.data().downcast_ref::<CountDrops>().unwrap();
628                         assert!(b.0.load(SeqCst) <= expected_drops.load(SeqCst));
629                     }
630                     if let Some(c) = c {
631                         let c = c.data().downcast_ref::<CountDrops>().unwrap();
632                         assert!(c.0.load(SeqCst) <= expected_drops.load(SeqCst));
633                     }
634                 }
635             })
636             .unwrap();
637 
638         linker
639             .define(
640                 "",
641                 "make_refs",
642                 // NB: use `Func::new` so that this can still compile on the old
643                 // x86 backend, where `IntoFunc` isn't implemented for
644                 // multi-value returns.
645                 Func::new(
646                     &mut store,
647                     FuncType::new(
648                         vec![],
649                         vec![ValType::ExternRef, ValType::ExternRef, ValType::ExternRef],
650                     ),
651                     {
652                         let num_dropped = num_dropped.clone();
653                         let expected_drops = expected_drops.clone();
654                         move |_caller, _params, results| {
655                             log::info!("table_ops: make_refs");
656                             expected_drops.fetch_add(3, SeqCst);
657                             results[0] =
658                                 Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
659                             results[1] =
660                                 Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
661                             results[2] =
662                                 Some(ExternRef::new(CountDrops(num_dropped.clone()))).into();
663                             Ok(())
664                         }
665                     },
666                 ),
667             )
668             .unwrap();
669 
670         let instance = linker.instantiate(&mut store, &module).unwrap();
671         let run = instance.get_func(&mut store, "run").unwrap();
672 
673         let args: Vec<_> = (0..ops.num_params)
674             .map(|_| Val::ExternRef(Some(ExternRef::new(CountDrops(num_dropped.clone())))))
675             .collect();
676         let _ = run.call(&mut store, &args, &mut []);
677 
678         // Do a final GC after running the Wasm.
679         store.gc();
680     }
681 
682     assert_eq!(num_dropped.load(SeqCst), expected_drops.load(SeqCst));
683     return;
684 
685     struct CountDrops(Arc<AtomicUsize>);
686 
687     impl Drop for CountDrops {
688         fn drop(&mut self) {
689             self.0.fetch_add(1, SeqCst);
690         }
691     }
692 }
693 
694 /// Perform differential execution between Cranelift and wasmi, diffing the
695 /// resulting memory image when execution terminates. This relies on the
696 /// module-under-test to be instrumented to bound the execution time. Invoke
697 /// with a module generated by `wasm-smith` using the
698 /// `SingleFunctionModuleConfig` configuration type for best results.
699 ///
700 /// May return `None` if we early-out due to a rejected fuzz config; these
701 /// should be rare if modules are generated appropriately.
702 pub fn differential_wasmi_execution(wasm: &[u8], config: &generators::Config) -> Option<()> {
703     crate::init_fuzzing();
704     log_wasm(wasm);
705 
706     // Instantiate wasmi module and instance.
707     let wasmi_module = wasmi::Module::from_buffer(&wasm[..]).ok()?;
708     let wasmi_instance =
709         wasmi::ModuleInstance::new(&wasmi_module, &wasmi::ImportsBuilder::default()).ok()?;
710     let wasmi_instance = wasmi_instance.assert_no_start();
711 
712     // If wasmi succeeded then we assert that wasmtime will also succeed.
713     let (wasmtime_module, mut wasmtime_store) = differential_store(wasm, config);
714     let wasmtime_module = wasmtime_module?;
715     let wasmtime_instance = Instance::new(&mut wasmtime_store, &wasmtime_module, &[])
716         .expect("Wasmtime can instantiate module");
717 
718     // Introspect wasmtime module to find name of an exported function and of an
719     // exported memory.
720     let (func_name, ty) = first_exported_function(&wasmtime_module)?;
721     let memory_name = first_exported_memory(&wasmtime_module)?;
722 
723     let wasmi_mem_export = wasmi_instance.export_by_name(memory_name).unwrap();
724     let wasmi_mem = wasmi_mem_export.as_memory().unwrap();
725     let wasmi_main_export = wasmi_instance.export_by_name(func_name).unwrap();
726     let wasmi_main = wasmi_main_export.as_func().unwrap();
727     let wasmi_val = wasmi::FuncInstance::invoke(&wasmi_main, &[], &mut wasmi::NopExternals);
728 
729     let wasmtime_mem = wasmtime_instance
730         .get_memory(&mut wasmtime_store, memory_name)
731         .expect("memory export is present");
732     let wasmtime_main = wasmtime_instance
733         .get_func(&mut wasmtime_store, func_name)
734         .expect("function export is present");
735     let mut wasmtime_results = vec![Val::I32(0); ty.results().len()];
736     let wasmtime_val = wasmtime_main
737         .call(&mut wasmtime_store, &[], &mut wasmtime_results)
738         .map(|()| wasmtime_results.get(0).cloned());
739 
740     debug!(
741         "Successful execution: wasmi returned {:?}, wasmtime returned {:?}",
742         wasmi_val, wasmtime_val
743     );
744 
745     match (&wasmi_val, &wasmtime_val) {
746         (&Ok(Some(wasmi::RuntimeValue::I32(a))), &Ok(Some(Val::I32(b)))) if a == b => {}
747         (&Ok(Some(wasmi::RuntimeValue::F32(a))), &Ok(Some(Val::F32(b))))
748             if f32_equal(a.to_bits(), b) => {}
749         (&Ok(Some(wasmi::RuntimeValue::I64(a))), &Ok(Some(Val::I64(b)))) if a == b => {}
750         (&Ok(Some(wasmi::RuntimeValue::F64(a))), &Ok(Some(Val::F64(b))))
751             if f64_equal(a.to_bits(), b) => {}
752         (&Ok(None), &Ok(None)) => {}
753         (&Err(_), &Err(_)) => {}
754         _ => {
755             panic!(
756                 "Values do not match: wasmi returned {:?}; wasmtime returned {:?}",
757                 wasmi_val, wasmtime_val
758             );
759         }
760     }
761 
762     if wasmi_mem.current_size().0 != wasmtime_mem.size(&wasmtime_store) as usize {
763         panic!("resulting memories are not the same size");
764     }
765 
766     // Wasmi memory may be stored non-contiguously; copy it out to a contiguous chunk.
767     let mut wasmi_buf: Vec<u8> = vec![0; wasmtime_mem.data_size(&wasmtime_store)];
768     wasmi_mem
769         .get_into(0, &mut wasmi_buf[..])
770         .expect("can access wasmi memory");
771 
772     let wasmtime_slice = wasmtime_mem.data(&wasmtime_store);
773 
774     if wasmi_buf.len() >= 64 {
775         debug!("-> First 64 bytes of wasmi heap: {:?}", &wasmi_buf[0..64]);
776         debug!(
777             "-> First 64 bytes of Wasmtime heap: {:?}",
778             &wasmtime_slice[0..64]
779         );
780     }
781 
782     if &wasmi_buf[..] != &wasmtime_slice[..] {
783         panic!("memory contents are not equal");
784     }
785 
786     Some(())
787 }
788 
789 /// Perform differential execution between Wasmtime and the official WebAssembly
790 /// specification interpreter.
791 ///
792 /// May return `None` if we early-out due to a rejected fuzz config.
793 #[cfg(feature = "fuzz-spec-interpreter")]
794 pub fn differential_spec_execution(wasm: &[u8], config: &generators::Config) -> Option<()> {
795     use anyhow::Context;
796 
797     crate::init_fuzzing();
798     debug!("config: {:#?}", config);
799     log_wasm(wasm);
800 
801     // Run the spec interpreter first, then Wasmtime. The order is important
802     // because both sides (OCaml runtime and Wasmtime) register signal handlers;
803     // Wasmtime uses these signal handlers for catching various WebAssembly
804     // failures. On certain OSes (e.g. Linux x86_64), the signal handlers
805     // interfere, observable as an uncaught `SIGSEGV`--not even caught by
806     // libFuzzer. By running Wasmtime second, its signal handlers are registered
807     // most recently and they catch failures appropriately.
808     //
809     // For now, execute with dummy (zeroed) function arguments.
810     let spec_vals = wasm_spec_interpreter::interpret(wasm, None);
811     debug!("spec interpreter returned: {:?}", &spec_vals);
812 
813     let (wasmtime_module, mut wasmtime_store) = differential_store(wasm, config);
814     let wasmtime_module = match wasmtime_module {
815         Some(m) => m,
816         None => return None,
817     };
818 
819     let wasmtime_vals =
820         Instance::new(&mut wasmtime_store, &wasmtime_module, &[]).and_then(|wasmtime_instance| {
821             // Find the first exported function.
822             let (func_name, ty) = first_exported_function(&wasmtime_module)
823                 .context("Cannot find exported function")?;
824             let wasmtime_main = wasmtime_instance
825                 .get_func(&mut wasmtime_store, &func_name[..])
826                 .expect("function export is present");
827 
828             let dummy_params = dummy::dummy_values(ty.params());
829 
830             // Execute the function and return the values.
831             let mut results = vec![Val::I32(0); ty.results().len()];
832             wasmtime_main
833                 .call(&mut wasmtime_store, &dummy_params, &mut results)
834                 .map(|()| Some(results))
835         });
836 
837     // Match a spec interpreter value against a Wasmtime value. Eventually this
838     // should support references and `v128` (TODO).
839     fn matches(spec_val: &wasm_spec_interpreter::Value, wasmtime_val: &wasmtime::Val) -> bool {
840         match (spec_val, wasmtime_val) {
841             (wasm_spec_interpreter::Value::I32(a), wasmtime::Val::I32(b)) => a == b,
842             (wasm_spec_interpreter::Value::I64(a), wasmtime::Val::I64(b)) => a == b,
843             (wasm_spec_interpreter::Value::F32(a), wasmtime::Val::F32(b)) => {
844                 f32_equal(*a as u32, *b)
845             }
846             (wasm_spec_interpreter::Value::F64(a), wasmtime::Val::F64(b)) => {
847                 f64_equal(*a as u64, *b)
848             }
849             (wasm_spec_interpreter::Value::V128(a), wasmtime::Val::V128(b)) => {
850                 assert_eq!(a.len(), 16);
851                 let a_num = u128::from_le_bytes(a.as_slice().try_into().unwrap());
852                 a_num == *b
853             }
854             (_, _) => {
855                 unreachable!("TODO: only fuzzing of scalar and vector value types is supported")
856             }
857         }
858     }
859 
860     match (&spec_vals, &wasmtime_vals) {
861         // Compare the returned values, failing if they do not match.
862         (Ok(spec_vals), Ok(Some(wasmtime_vals))) => {
863             let all_match = spec_vals
864                 .iter()
865                 .zip(wasmtime_vals)
866                 .all(|(s, w)| matches(s, w));
867             if !all_match {
868                 panic!(
869                     "Values do not match: spec returned {:?}; wasmtime returned {:?}",
870                     spec_vals, wasmtime_vals
871                 );
872             }
873         }
874         (_, Ok(None)) => {
875             // `run_in_wasmtime` rejected the config
876             return None;
877         }
878         // If both sides fail, skip this fuzz execution.
879         (Err(spec_error), Err(wasmtime_error)) => {
880             // The `None` value returned here indicates that both sides
881             // failed--if we see too many of these we might be failing too often
882             // to check instruction semantics. At some point it would be
883             // beneficial to compare the error messages from both sides (TODO).
884             // It would also be good to keep track of statistics about the
885             // ratios of the kinds of errors the fuzzer sees (TODO).
886             log::warn!(
887                 "Both sides failed: spec returned '{}'; wasmtime returned {:?}",
888                 spec_error,
889                 wasmtime_error
890             );
891             return None;
892         }
893         // If only one side fails, fail the fuzz the test.
894         _ => {
895             panic!(
896                 "Only one side failed: spec returned {:?}; wasmtime returned {:?}",
897                 &spec_vals, &wasmtime_vals
898             );
899         }
900     }
901 
902     // TODO Compare memory contents.
903 
904     Some(())
905 }
906 
907 fn differential_store(
908     wasm: &[u8],
909     fuzz_config: &generators::Config,
910 ) -> (Option<Module>, Store<StoreLimits>) {
911     let store = fuzz_config.to_store();
912     let module = compile_module(store.engine(), wasm, true, fuzz_config);
913     (module, store)
914 }
915 
916 // Introspect wasmtime module to find the name of the first exported function.
917 fn first_exported_function(module: &wasmtime::Module) -> Option<(&str, FuncType)> {
918     for e in module.exports() {
919         match e.ty() {
920             wasmtime::ExternType::Func(ty) => return Some((e.name(), ty)),
921             _ => {}
922         }
923     }
924     None
925 }
926 
927 fn first_exported_memory(module: &Module) -> Option<&str> {
928     for e in module.exports() {
929         match e.ty() {
930             wasmtime::ExternType::Memory(..) => return Some(e.name()),
931             _ => {}
932         }
933     }
934     None
935 }
936 
937 #[derive(Default)]
938 struct SignalOnDrop {
939     state: Arc<(Mutex<bool>, Condvar)>,
940     thread: Option<std::thread::JoinHandle<()>>,
941 }
942 
943 impl SignalOnDrop {
944     fn spawn_timeout(&mut self, dur: Duration, closure: impl FnOnce() + Send + 'static) {
945         let state = self.state.clone();
946         let start = Instant::now();
947         self.thread = Some(std::thread::spawn(move || {
948             // Using our mutex/condvar we wait here for the first of `dur` to
949             // pass or the `SignalOnDrop` instance to get dropped.
950             let (lock, cvar) = &*state;
951             let mut signaled = lock.lock().unwrap();
952             while !*signaled {
953                 // Adjust our requested `dur` based on how much time has passed.
954                 let dur = match dur.checked_sub(start.elapsed()) {
955                     Some(dur) => dur,
956                     None => break,
957                 };
958                 let (lock, result) = cvar.wait_timeout(signaled, dur).unwrap();
959                 signaled = lock;
960                 // If we timed out for sure then there's no need to continue
961                 // since we'll just abort on the next `checked_sub` anyway.
962                 if result.timed_out() {
963                     break;
964                 }
965             }
966             drop(signaled);
967 
968             closure();
969         }));
970     }
971 }
972 
973 impl Drop for SignalOnDrop {
974     fn drop(&mut self) {
975         if let Some(thread) = self.thread.take() {
976             let (lock, cvar) = &*self.state;
977             // Signal our thread that we've been dropped and wake it up if it's
978             // blocked.
979             let mut g = lock.lock().unwrap();
980             *g = true;
981             cvar.notify_one();
982             drop(g);
983 
984             // ... and then wait for the thread to exit to ensure we clean up
985             // after ourselves.
986             thread.join().unwrap();
987         }
988     }
989 }
990