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