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, ¶ms, &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, ¶ms, &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