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