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