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::convert::TryInto; 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 /// Fuel-based timeouts are used where the specified fuel is all that the 112 /// provided wasm module is allowed to consume. 113 Fuel(u64), 114 /// An epoch-interruption-based timeout is used with a sleeping 115 /// thread bumping the epoch counter after the specified duration. 116 Epoch(Duration), 117 } 118 119 /// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected 120 /// panic or segfault or anything else that can be detected "passively". 121 /// 122 /// The engine will be configured using provided config. 123 pub fn instantiate(wasm: &[u8], known_valid: bool, config: &generators::Config, timeout: Timeout) { 124 let mut store = config.to_store(); 125 126 let mut timeout_state = SignalOnDrop::default(); 127 match timeout { 128 Timeout::Fuel(fuel) => { 129 // consume the default fuel in the store ... 130 let remaining = store.consume_fuel(0).unwrap(); 131 store.consume_fuel(remaining - 1).unwrap(); 132 // ... then add back in how much fuel we're allowing here 133 store.add_fuel(fuel).unwrap(); 134 } 135 // If a timeout is requested then we spawn a helper thread to wait for 136 // the requested time and then send us a signal to get interrupted. We 137 // also arrange for the thread's sleep to get interrupted if we return 138 // early (or the wasm returns within the time limit), which allows the 139 // thread to get torn down. 140 // 141 // This prevents us from creating a huge number of sleeping threads if 142 // this function is executed in a loop, like it does on nightly fuzzing 143 // infrastructure. 144 Timeout::Epoch(timeout) => { 145 let engine = store.engine().clone(); 146 timeout_state.spawn_timeout(timeout, move || engine.increment_epoch()); 147 } 148 Timeout::None => {} 149 } 150 151 if let Some(module) = compile_module(store.engine(), wasm, known_valid, config) { 152 instantiate_with_dummy(&mut store, &module); 153 } 154 } 155 156 /// Represents supported commands to the `instantiate_many` function. 157 #[derive(Arbitrary, Debug)] 158 pub enum Command { 159 /// Instantiates a module. 160 /// 161 /// The value is the index of the module to instantiate. 162 /// 163 /// The module instantiated will be this value modulo the number of modules provided to `instantiate_many`. 164 Instantiate(usize), 165 /// Terminates a "running" instance. 166 /// 167 /// The value is the index of the instance to terminate. 168 /// 169 /// The instance terminated will be this value modulo the number of currently running 170 /// instances. 171 /// 172 /// If no instances are running, the command will be ignored. 173 Terminate(usize), 174 } 175 176 /// Instantiates many instances from the given modules. 177 /// 178 /// The engine will be configured using the provided config. 179 /// 180 /// The modules are expected to *not* have start functions as no timeouts are configured. 181 pub fn instantiate_many( 182 modules: &[Vec<u8>], 183 known_valid: bool, 184 config: &generators::Config, 185 commands: &[Command], 186 ) { 187 assert!(!config.module_config.config.allow_start_export); 188 189 let engine = Engine::new(&config.to_wasmtime()).unwrap(); 190 191 let modules = modules 192 .iter() 193 .filter_map(|bytes| compile_module(&engine, bytes, known_valid, config)) 194 .collect::<Vec<_>>(); 195 196 // If no modules were valid, we're done 197 if modules.is_empty() { 198 return; 199 } 200 201 // This stores every `Store` where a successful instantiation takes place 202 let mut stores = Vec::new(); 203 let limits = StoreLimits::new(); 204 205 for command in commands { 206 match command { 207 Command::Instantiate(index) => { 208 let index = *index % modules.len(); 209 log::info!("instantiating {}", index); 210 let module = &modules[index]; 211 let mut store = Store::new(&engine, limits.clone()); 212 config.configure_store(&mut store); 213 214 if instantiate_with_dummy(&mut store, module).is_some() { 215 stores.push(Some(store)); 216 } else { 217 log::warn!("instantiation failed"); 218 } 219 } 220 Command::Terminate(index) => { 221 if stores.is_empty() { 222 continue; 223 } 224 let index = *index % stores.len(); 225 226 log::info!("dropping {}", index); 227 stores.swap_remove(index); 228 } 229 } 230 } 231 } 232 233 fn compile_module( 234 engine: &Engine, 235 bytes: &[u8], 236 known_valid: bool, 237 config: &generators::Config, 238 ) -> Option<Module> { 239 log_wasm(bytes); 240 match config.compile(engine, bytes) { 241 Ok(module) => Some(module), 242 Err(_) if !known_valid => None, 243 Err(e) => { 244 if let generators::InstanceAllocationStrategy::Pooling { .. } = 245 &config.wasmtime.strategy 246 { 247 // When using the pooling allocator, accept failures to compile when arbitrary 248 // table element limits have been exceeded as there is currently no way 249 // to constrain the generated module table types. 250 let string = e.to_string(); 251 if string.contains("minimum element size") { 252 return None; 253 } 254 255 // Allow modules-failing-to-compile which exceed the requested 256 // size for each instance. This is something that is difficult 257 // to control and ensure it always suceeds, so we simply have a 258 // "random" instance size limit and if a module doesn't fit we 259 // move on to the next fuzz input. 260 if string.contains("instance allocation for this module requires") { 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 // Different compilation settings can lead to different amounts 400 // of stack space being consumed, so if either the lhs or the rhs 401 // hit a stack overflow then we discard the result of the other side 402 // since if it ran successfully or trapped that's ok in both 403 // situations. 404 (Err(e), _) | (_, Err(e)) if e.trap_code() == Some(TrapCode::StackOverflow) => {} 405 406 (Err(a), Err(b)) => { 407 if a.trap_code() != b.trap_code() { 408 fail(); 409 } 410 } 411 (Ok(lhs), Ok(rhs)) => { 412 if lhs.len() != rhs.len() { 413 fail(); 414 } 415 for (lhs, rhs) in lhs.iter().zip(rhs.iter()) { 416 match (lhs, rhs) { 417 (Val::I32(lhs), Val::I32(rhs)) if lhs == rhs => continue, 418 (Val::I64(lhs), Val::I64(rhs)) if lhs == rhs => continue, 419 (Val::V128(lhs), Val::V128(rhs)) if lhs == rhs => continue, 420 (Val::F32(lhs), Val::F32(rhs)) if f32_equal(*lhs, *rhs) => continue, 421 (Val::F64(lhs), Val::F64(rhs)) if f64_equal(*lhs, *rhs) => continue, 422 (Val::ExternRef(_), Val::ExternRef(_)) 423 | (Val::FuncRef(_), Val::FuncRef(_)) => continue, 424 _ => fail(), 425 } 426 } 427 } 428 _ => fail(), 429 } 430 } 431 } 432 433 fn f32_equal(a: u32, b: u32) -> bool { 434 let a = f32::from_bits(a); 435 let b = f32::from_bits(b); 436 a == b || (a.is_nan() && b.is_nan()) 437 } 438 439 fn f64_equal(a: u64, b: u64) -> bool { 440 let a = f64::from_bits(a); 441 let b = f64::from_bits(b); 442 a == b || (a.is_nan() && b.is_nan()) 443 } 444 445 /// Invoke the given API calls. 446 pub fn make_api_calls(api: generators::api::ApiCalls) { 447 use crate::generators::api::ApiCall; 448 use std::collections::HashMap; 449 450 let mut store: Option<Store<StoreLimits>> = None; 451 let mut modules: HashMap<usize, Module> = Default::default(); 452 let mut instances: HashMap<usize, Instance> = Default::default(); 453 454 for call in api.calls { 455 match call { 456 ApiCall::StoreNew(config) => { 457 log::trace!("creating store"); 458 assert!(store.is_none()); 459 store = Some(config.to_store()); 460 } 461 462 ApiCall::ModuleNew { id, wasm } => { 463 log::debug!("creating module: {}", id); 464 log_wasm(&wasm); 465 let module = match Module::new(store.as_ref().unwrap().engine(), &wasm) { 466 Ok(m) => m, 467 Err(_) => continue, 468 }; 469 let old = modules.insert(id, module); 470 assert!(old.is_none()); 471 } 472 473 ApiCall::ModuleDrop { id } => { 474 log::trace!("dropping module: {}", id); 475 drop(modules.remove(&id)); 476 } 477 478 ApiCall::InstanceNew { id, module } => { 479 log::trace!("instantiating module {} as {}", module, id); 480 let module = match modules.get(&module) { 481 Some(m) => m, 482 None => continue, 483 }; 484 485 let store = store.as_mut().unwrap(); 486 if let Some(instance) = instantiate_with_dummy(store, module) { 487 instances.insert(id, instance); 488 } 489 } 490 491 ApiCall::InstanceDrop { id } => { 492 log::trace!("dropping instance {}", id); 493 drop(instances.remove(&id)); 494 } 495 496 ApiCall::CallExportedFunc { instance, nth } => { 497 log::trace!("calling instance export {} / {}", instance, nth); 498 let instance = match instances.get(&instance) { 499 Some(i) => i, 500 None => { 501 // Note that we aren't guaranteed to instantiate valid 502 // modules, see comments in `InstanceNew` for details on 503 // that. But the API call generator can't know if 504 // instantiation failed, so we might not actually have 505 // this instance. When that's the case, just skip the 506 // API call and keep going. 507 continue; 508 } 509 }; 510 let store = store.as_mut().unwrap(); 511 512 let funcs = instance 513 .exports(&mut *store) 514 .filter_map(|e| match e.into_extern() { 515 Extern::Func(f) => Some(f.clone()), 516 _ => None, 517 }) 518 .collect::<Vec<_>>(); 519 520 if funcs.is_empty() { 521 continue; 522 } 523 524 let nth = nth % funcs.len(); 525 let f = &funcs[nth]; 526 let ty = f.ty(&store); 527 let params = dummy::dummy_values(ty.params()); 528 let mut results = vec![Val::I32(0); ty.results().len()]; 529 let _ = f.call(store, ¶ms, &mut results); 530 } 531 } 532 } 533 } 534 535 /// Executes the wast `test` spectest with the `config` specified. 536 /// 537 /// Ensures that spec tests pass regardless of the `Config`. 538 pub fn spectest(mut fuzz_config: generators::Config, test: generators::SpecTest) { 539 crate::init_fuzzing(); 540 fuzz_config.set_spectest_compliant(); 541 log::debug!("running {:?}", test.file); 542 let mut wast_context = WastContext::new(fuzz_config.to_store()); 543 wast_context.register_spectest().unwrap(); 544 wast_context 545 .run_buffer(test.file, test.contents.as_bytes()) 546 .unwrap(); 547 } 548 549 /// Execute a series of `table.get` and `table.set` operations. 550 pub fn table_ops(mut fuzz_config: generators::Config, ops: generators::table_ops::TableOps) { 551 let expected_drops = Arc::new(AtomicUsize::new(ops.num_params() as usize)); 552 let num_dropped = Arc::new(AtomicUsize::new(0)); 553 554 { 555 fuzz_config.wasmtime.consume_fuel = true; 556 let mut store = fuzz_config.to_store(); 557 558 // consume the default fuel in the store ... 559 let remaining = store.consume_fuel(0).unwrap(); 560 store.consume_fuel(remaining - 1).unwrap(); 561 // ... then add back in how much fuel we're allowing here 562 store.add_fuel(1_000).unwrap(); 563 564 let wasm = ops.to_wasm_binary(); 565 log_wasm(&wasm); 566 let module = match compile_module(store.engine(), &wasm, false, &fuzz_config) { 567 Some(m) => m, 568 None => return, 569 }; 570 571 let mut linker = Linker::new(store.engine()); 572 573 // To avoid timeouts, limit the number of explicit GCs we perform per 574 // test case. 575 const MAX_GCS: usize = 5; 576 577 let num_gcs = AtomicUsize::new(0); 578 linker 579 .define( 580 "", 581 "gc", 582 // NB: use `Func::new` so that this can still compile on the old x86 583 // backend, where `IntoFunc` isn't implemented for multi-value 584 // returns. 585 Func::new( 586 &mut store, 587 FuncType::new( 588 vec![], 589 vec![ValType::ExternRef, ValType::ExternRef, ValType::ExternRef], 590 ), 591 { 592 let num_dropped = num_dropped.clone(); 593 let expected_drops = expected_drops.clone(); 594 move |mut caller: Caller<'_, StoreLimits>, _params, results| { 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 // Do the assertion on each ref's inner data, even though it 618 // all points to the same atomic, so that if we happen to 619 // run into a use-after-free bug with one of these refs we 620 // are more likely to trigger a segfault. 621 if let Some(a) = a { 622 let a = a.data().downcast_ref::<CountDrops>().unwrap(); 623 assert!(a.0.load(SeqCst) <= expected_drops.load(SeqCst)); 624 } 625 if let Some(b) = b { 626 let b = b.data().downcast_ref::<CountDrops>().unwrap(); 627 assert!(b.0.load(SeqCst) <= expected_drops.load(SeqCst)); 628 } 629 if let Some(c) = c { 630 let c = c.data().downcast_ref::<CountDrops>().unwrap(); 631 assert!(c.0.load(SeqCst) <= expected_drops.load(SeqCst)); 632 } 633 } 634 }) 635 .unwrap(); 636 637 linker 638 .define( 639 "", 640 "make_refs", 641 // NB: use `Func::new` so that this can still compile on the old 642 // x86 backend, where `IntoFunc` isn't implemented for 643 // multi-value returns. 644 Func::new( 645 &mut store, 646 FuncType::new( 647 vec![], 648 vec![ValType::ExternRef, ValType::ExternRef, ValType::ExternRef], 649 ), 650 { 651 let num_dropped = num_dropped.clone(); 652 let expected_drops = expected_drops.clone(); 653 move |_caller, _params, results| { 654 expected_drops.fetch_add(3, SeqCst); 655 results[0] = 656 Some(ExternRef::new(CountDrops(num_dropped.clone()))).into(); 657 results[1] = 658 Some(ExternRef::new(CountDrops(num_dropped.clone()))).into(); 659 results[2] = 660 Some(ExternRef::new(CountDrops(num_dropped.clone()))).into(); 661 Ok(()) 662 } 663 }, 664 ), 665 ) 666 .unwrap(); 667 668 let instance = linker.instantiate(&mut store, &module).unwrap(); 669 let run = instance.get_func(&mut store, "run").unwrap(); 670 671 let args: Vec<_> = (0..ops.num_params()) 672 .map(|_| Val::ExternRef(Some(ExternRef::new(CountDrops(num_dropped.clone()))))) 673 .collect(); 674 let _ = run.call(&mut store, &args, &mut []); 675 676 // Do a final GC after running the Wasm. 677 store.gc(); 678 } 679 680 assert_eq!(num_dropped.load(SeqCst), expected_drops.load(SeqCst)); 681 return; 682 683 struct CountDrops(Arc<AtomicUsize>); 684 685 impl Drop for CountDrops { 686 fn drop(&mut self) { 687 self.0.fetch_add(1, SeqCst); 688 } 689 } 690 } 691 692 /// Perform differential execution between Cranelift and wasmi, diffing the 693 /// resulting memory image when execution terminates. This relies on the 694 /// module-under-test to be instrumented to bound the execution time. Invoke 695 /// with a module generated by `wasm-smith` using the 696 /// `SingleFunctionModuleConfig` configuration type for best results. 697 /// 698 /// May return `None` if we early-out due to a rejected fuzz config; these 699 /// should be rare if modules are generated appropriately. 700 pub fn differential_wasmi_execution(wasm: &[u8], config: &generators::Config) -> Option<()> { 701 crate::init_fuzzing(); 702 log_wasm(wasm); 703 704 // Instantiate wasmi module and instance. 705 let wasmi_module = wasmi::Module::from_buffer(&wasm[..]).ok()?; 706 let wasmi_instance = 707 wasmi::ModuleInstance::new(&wasmi_module, &wasmi::ImportsBuilder::default()).ok()?; 708 let wasmi_instance = wasmi_instance.assert_no_start(); 709 710 // If wasmi succeeded then we assert that wasmtime will also succeed. 711 let (wasmtime_module, mut wasmtime_store) = differential_store(wasm, config); 712 let wasmtime_module = wasmtime_module?; 713 let wasmtime_instance = Instance::new(&mut wasmtime_store, &wasmtime_module, &[]) 714 .expect("Wasmtime can instantiate module"); 715 716 // Introspect wasmtime module to find name of an exported function and of an 717 // exported memory. 718 let (func_name, ty) = first_exported_function(&wasmtime_module)?; 719 let memory_name = first_exported_memory(&wasmtime_module)?; 720 721 let wasmi_mem_export = wasmi_instance.export_by_name(memory_name).unwrap(); 722 let wasmi_mem = wasmi_mem_export.as_memory().unwrap(); 723 let wasmi_main_export = wasmi_instance.export_by_name(func_name).unwrap(); 724 let wasmi_main = wasmi_main_export.as_func().unwrap(); 725 let wasmi_val = wasmi::FuncInstance::invoke(&wasmi_main, &[], &mut wasmi::NopExternals); 726 727 let wasmtime_mem = wasmtime_instance 728 .get_memory(&mut wasmtime_store, memory_name) 729 .expect("memory export is present"); 730 let wasmtime_main = wasmtime_instance 731 .get_func(&mut wasmtime_store, func_name) 732 .expect("function export is present"); 733 let mut wasmtime_results = vec![Val::I32(0); ty.results().len()]; 734 let wasmtime_val = wasmtime_main 735 .call(&mut wasmtime_store, &[], &mut wasmtime_results) 736 .map(|()| wasmtime_results.get(0).cloned()); 737 738 debug!( 739 "Successful execution: wasmi returned {:?}, wasmtime returned {:?}", 740 wasmi_val, wasmtime_val 741 ); 742 743 match (&wasmi_val, &wasmtime_val) { 744 (&Ok(Some(wasmi::RuntimeValue::I32(a))), &Ok(Some(Val::I32(b)))) if a == b => {} 745 (&Ok(Some(wasmi::RuntimeValue::F32(a))), &Ok(Some(Val::F32(b)))) 746 if f32_equal(a.to_bits(), b) => {} 747 (&Ok(Some(wasmi::RuntimeValue::I64(a))), &Ok(Some(Val::I64(b)))) if a == b => {} 748 (&Ok(Some(wasmi::RuntimeValue::F64(a))), &Ok(Some(Val::F64(b)))) 749 if f64_equal(a.to_bits(), b) => {} 750 (&Ok(None), &Ok(None)) => {} 751 (&Err(_), &Err(_)) => {} 752 _ => { 753 panic!( 754 "Values do not match: wasmi returned {:?}; wasmtime returned {:?}", 755 wasmi_val, wasmtime_val 756 ); 757 } 758 } 759 760 if wasmi_mem.current_size().0 != wasmtime_mem.size(&wasmtime_store) as usize { 761 panic!("resulting memories are not the same size"); 762 } 763 764 // Wasmi memory may be stored non-contiguously; copy it out to a contiguous chunk. 765 let mut wasmi_buf: Vec<u8> = vec![0; wasmtime_mem.data_size(&wasmtime_store)]; 766 wasmi_mem 767 .get_into(0, &mut wasmi_buf[..]) 768 .expect("can access wasmi memory"); 769 770 let wasmtime_slice = wasmtime_mem.data(&wasmtime_store); 771 772 if wasmi_buf.len() >= 64 { 773 debug!("-> First 64 bytes of wasmi heap: {:?}", &wasmi_buf[0..64]); 774 debug!( 775 "-> First 64 bytes of Wasmtime heap: {:?}", 776 &wasmtime_slice[0..64] 777 ); 778 } 779 780 if &wasmi_buf[..] != &wasmtime_slice[..] { 781 panic!("memory contents are not equal"); 782 } 783 784 Some(()) 785 } 786 787 /// Perform differential execution between Wasmtime and the official WebAssembly 788 /// specification interpreter. 789 /// 790 /// May return `None` if we early-out due to a rejected fuzz config. 791 #[cfg(feature = "fuzz-spec-interpreter")] 792 pub fn differential_spec_execution(wasm: &[u8], config: &generators::Config) -> Option<()> { 793 use anyhow::Context; 794 795 crate::init_fuzzing(); 796 debug!("config: {:#?}", config); 797 log_wasm(wasm); 798 799 // Run the spec interpreter first, then Wasmtime. The order is important 800 // because both sides (OCaml runtime and Wasmtime) register signal handlers; 801 // Wasmtime uses these signal handlers for catching various WebAssembly 802 // failures. On certain OSes (e.g. Linux x86_64), the signal handlers 803 // interfere, observable as an uncaught `SIGSEGV`--not even caught by 804 // libFuzzer. By running Wasmtime second, its signal handlers are registered 805 // most recently and they catch failures appropriately. 806 // 807 // For now, execute with dummy (zeroed) function arguments. 808 let spec_vals = wasm_spec_interpreter::interpret(wasm, None); 809 debug!("spec interpreter returned: {:?}", &spec_vals); 810 811 let (wasmtime_module, mut wasmtime_store) = differential_store(wasm, config); 812 let wasmtime_module = match wasmtime_module { 813 Some(m) => m, 814 None => return None, 815 }; 816 817 let wasmtime_vals = 818 Instance::new(&mut wasmtime_store, &wasmtime_module, &[]).and_then(|wasmtime_instance| { 819 // Find the first exported function. 820 let (func_name, ty) = first_exported_function(&wasmtime_module) 821 .context("Cannot find exported function")?; 822 let wasmtime_main = wasmtime_instance 823 .get_func(&mut wasmtime_store, &func_name[..]) 824 .expect("function export is present"); 825 826 let dummy_params = dummy::dummy_values(ty.params()); 827 828 // Execute the function and return the values. 829 let mut results = vec![Val::I32(0); ty.results().len()]; 830 wasmtime_main 831 .call(&mut wasmtime_store, &dummy_params, &mut results) 832 .map(|()| Some(results)) 833 }); 834 835 // Match a spec interpreter value against a Wasmtime value. Eventually this 836 // should support references and `v128` (TODO). 837 fn matches(spec_val: &wasm_spec_interpreter::Value, wasmtime_val: &wasmtime::Val) -> bool { 838 match (spec_val, wasmtime_val) { 839 (wasm_spec_interpreter::Value::I32(a), wasmtime::Val::I32(b)) => a == b, 840 (wasm_spec_interpreter::Value::I64(a), wasmtime::Val::I64(b)) => a == b, 841 (wasm_spec_interpreter::Value::F32(a), wasmtime::Val::F32(b)) => { 842 f32_equal(*a as u32, *b) 843 } 844 (wasm_spec_interpreter::Value::F64(a), wasmtime::Val::F64(b)) => { 845 f64_equal(*a as u64, *b) 846 } 847 (wasm_spec_interpreter::Value::V128(a), wasmtime::Val::V128(b)) => { 848 assert_eq!(a.len(), 16); 849 let a_num = u128::from_le_bytes(a.as_slice().try_into().unwrap()); 850 a_num == *b 851 } 852 (_, _) => { 853 unreachable!("TODO: only fuzzing of scalar and vector value types is supported") 854 } 855 } 856 } 857 858 match (&spec_vals, &wasmtime_vals) { 859 // Compare the returned values, failing if they do not match. 860 (Ok(spec_vals), Ok(Some(wasmtime_vals))) => { 861 let all_match = spec_vals 862 .iter() 863 .zip(wasmtime_vals) 864 .all(|(s, w)| matches(s, w)); 865 if !all_match { 866 panic!( 867 "Values do not match: spec returned {:?}; wasmtime returned {:?}", 868 spec_vals, wasmtime_vals 869 ); 870 } 871 } 872 (_, Ok(None)) => { 873 // `run_in_wasmtime` rejected the config 874 return None; 875 } 876 // If both sides fail, skip this fuzz execution. 877 (Err(spec_error), Err(wasmtime_error)) => { 878 // The `None` value returned here indicates that both sides 879 // failed--if we see too many of these we might be failing too often 880 // to check instruction semantics. At some point it would be 881 // beneficial to compare the error messages from both sides (TODO). 882 // It would also be good to keep track of statistics about the 883 // ratios of the kinds of errors the fuzzer sees (TODO). 884 log::warn!( 885 "Both sides failed: spec returned '{}'; wasmtime returned {:?}", 886 spec_error, 887 wasmtime_error 888 ); 889 return None; 890 } 891 // If only one side fails, fail the fuzz the test. 892 _ => { 893 panic!( 894 "Only one side failed: spec returned {:?}; wasmtime returned {:?}", 895 &spec_vals, &wasmtime_vals 896 ); 897 } 898 } 899 900 // TODO Compare memory contents. 901 902 Some(()) 903 } 904 905 fn differential_store( 906 wasm: &[u8], 907 fuzz_config: &generators::Config, 908 ) -> (Option<Module>, Store<StoreLimits>) { 909 let store = fuzz_config.to_store(); 910 let module = compile_module(store.engine(), wasm, true, fuzz_config); 911 (module, store) 912 } 913 914 // Introspect wasmtime module to find the name of the first exported function. 915 fn first_exported_function(module: &wasmtime::Module) -> Option<(&str, FuncType)> { 916 for e in module.exports() { 917 match e.ty() { 918 wasmtime::ExternType::Func(ty) => return Some((e.name(), ty)), 919 _ => {} 920 } 921 } 922 None 923 } 924 925 fn first_exported_memory(module: &Module) -> Option<&str> { 926 for e in module.exports() { 927 match e.ty() { 928 wasmtime::ExternType::Memory(..) => return Some(e.name()), 929 _ => {} 930 } 931 } 932 None 933 } 934 935 #[derive(Default)] 936 struct SignalOnDrop { 937 state: Arc<(Mutex<bool>, Condvar)>, 938 thread: Option<std::thread::JoinHandle<()>>, 939 } 940 941 impl SignalOnDrop { 942 fn spawn_timeout(&mut self, dur: Duration, closure: impl FnOnce() + Send + 'static) { 943 let state = self.state.clone(); 944 let start = Instant::now(); 945 self.thread = Some(std::thread::spawn(move || { 946 // Using our mutex/condvar we wait here for the first of `dur` to 947 // pass or the `SignalOnDrop` instance to get dropped. 948 let (lock, cvar) = &*state; 949 let mut signaled = lock.lock().unwrap(); 950 while !*signaled { 951 // Adjust our requested `dur` based on how much time has passed. 952 let dur = match dur.checked_sub(start.elapsed()) { 953 Some(dur) => dur, 954 None => break, 955 }; 956 let (lock, result) = cvar.wait_timeout(signaled, dur).unwrap(); 957 signaled = lock; 958 // If we timed out for sure then there's no need to continue 959 // since we'll just abort on the next `checked_sub` anyway. 960 if result.timed_out() { 961 break; 962 } 963 } 964 drop(signaled); 965 966 closure(); 967 })); 968 } 969 } 970 971 impl Drop for SignalOnDrop { 972 fn drop(&mut self) { 973 if let Some(thread) = self.thread.take() { 974 let (lock, cvar) = &*self.state; 975 // Signal our thread that we've been dropped and wake it up if it's 976 // blocked. 977 let mut g = lock.lock().unwrap(); 978 *g = true; 979 cvar.notify_one(); 980 drop(g); 981 982 // ... and then wait for the thread to exit to ensure we clean up 983 // after ourselves. 984 thread.join().unwrap(); 985 } 986 } 987 } 988