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