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 #[cfg(feature = "fuzz-spec-interpreter")] 14 pub mod diff_spec; 15 pub mod diff_wasmi; 16 pub mod diff_wasmtime; 17 pub mod dummy; 18 pub mod engine; 19 pub mod memory; 20 mod stacks; 21 22 use self::diff_wasmtime::WasmtimeInstance; 23 use self::engine::{DiffEngine, DiffInstance}; 24 use crate::generators::{self, DiffValue, DiffValueType}; 25 use crate::single_module_fuzzer::KnownValid; 26 use arbitrary::Arbitrary; 27 pub use stacks::check_stacks; 28 use std::future::Future; 29 use std::pin::Pin; 30 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}; 31 use std::sync::{Arc, Condvar, Mutex}; 32 use std::task::{Context, Poll}; 33 use std::time::{Duration, Instant}; 34 use wasmtime::*; 35 use wasmtime_wast::WastContext; 36 37 #[cfg(not(any(windows, target_arch = "s390x", target_arch = "riscv64")))] 38 mod diff_v8; 39 40 static CNT: AtomicUsize = AtomicUsize::new(0); 41 42 /// Logs a wasm file to the filesystem to make it easy to figure out what wasm 43 /// was used when debugging. 44 pub fn log_wasm(wasm: &[u8]) { 45 super::init_fuzzing(); 46 47 if !log::log_enabled!(log::Level::Debug) { 48 return; 49 } 50 51 let i = CNT.fetch_add(1, SeqCst); 52 let name = format!("testcase{}.wasm", i); 53 std::fs::write(&name, wasm).expect("failed to write wasm file"); 54 log::debug!("wrote wasm file to `{}`", name); 55 let wat = format!("testcase{}.wat", i); 56 match wasmprinter::print_bytes(wasm) { 57 Ok(s) => std::fs::write(&wat, s).expect("failed to write wat file"), 58 // If wasmprinter failed remove a `*.wat` file, if any, to avoid 59 // confusing a preexisting one with this wasm which failed to get 60 // printed. 61 Err(_) => drop(std::fs::remove_file(&wat)), 62 } 63 } 64 65 /// The `T` in `Store<T>` for fuzzing stores, used to limit resource 66 /// consumption during fuzzing. 67 #[derive(Clone)] 68 pub struct StoreLimits(Arc<LimitsState>); 69 70 struct LimitsState { 71 /// Remaining memory, in bytes, left to allocate 72 remaining_memory: AtomicUsize, 73 /// Whether or not an allocation request has been denied 74 oom: AtomicBool, 75 } 76 77 impl StoreLimits { 78 /// Creates the default set of limits for all fuzzing stores. 79 pub fn new() -> StoreLimits { 80 StoreLimits(Arc::new(LimitsState { 81 // Limits tables/memories within a store to at most 1gb for now to 82 // exercise some larger address but not overflow various limits. 83 remaining_memory: AtomicUsize::new(1 << 30), 84 oom: AtomicBool::new(false), 85 })) 86 } 87 88 fn alloc(&mut self, amt: usize) -> bool { 89 log::trace!("alloc {amt:#x} bytes"); 90 match self 91 .0 92 .remaining_memory 93 .fetch_update(SeqCst, SeqCst, |remaining| remaining.checked_sub(amt)) 94 { 95 Ok(_) => true, 96 Err(_) => { 97 self.0.oom.store(true, SeqCst); 98 log::debug!("OOM hit"); 99 false 100 } 101 } 102 } 103 104 fn is_oom(&self) -> bool { 105 self.0.oom.load(SeqCst) 106 } 107 } 108 109 impl ResourceLimiter for StoreLimits { 110 fn memory_growing( 111 &mut self, 112 current: usize, 113 desired: usize, 114 _maximum: Option<usize>, 115 ) -> Result<bool> { 116 Ok(self.alloc(desired - current)) 117 } 118 119 fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> Result<bool> { 120 let delta = (desired - current) as usize * std::mem::size_of::<usize>(); 121 Ok(self.alloc(delta)) 122 } 123 } 124 125 /// Methods of timing out execution of a WebAssembly module 126 #[derive(Clone, Debug)] 127 pub enum Timeout { 128 /// No timeout is used, it should be guaranteed via some other means that 129 /// the input does not infinite loop. 130 None, 131 /// Fuel-based timeouts are used where the specified fuel is all that the 132 /// provided wasm module is allowed to consume. 133 Fuel(u64), 134 /// An epoch-interruption-based timeout is used with a sleeping 135 /// thread bumping the epoch counter after the specified duration. 136 Epoch(Duration), 137 } 138 139 /// Instantiate the Wasm buffer, and implicitly fail if we have an unexpected 140 /// panic or segfault or anything else that can be detected "passively". 141 /// 142 /// The engine will be configured using provided config. 143 pub fn instantiate( 144 wasm: &[u8], 145 known_valid: KnownValid, 146 config: &generators::Config, 147 timeout: Timeout, 148 ) { 149 let mut store = config.to_store(); 150 151 let module = match compile_module(store.engine(), wasm, known_valid, config) { 152 Some(module) => module, 153 None => return, 154 }; 155 156 let mut timeout_state = HelperThread::default(); 157 match timeout { 158 Timeout::Fuel(fuel) => store.set_fuel(fuel).unwrap(), 159 160 // If a timeout is requested then we spawn a helper thread to wait for 161 // the requested time and then send us a signal to get interrupted. We 162 // also arrange for the thread's sleep to get interrupted if we return 163 // early (or the wasm returns within the time limit), which allows the 164 // thread to get torn down. 165 // 166 // This prevents us from creating a huge number of sleeping threads if 167 // this function is executed in a loop, like it does on nightly fuzzing 168 // infrastructure. 169 Timeout::Epoch(timeout) => { 170 let engine = store.engine().clone(); 171 timeout_state.run_periodically(timeout, move || engine.increment_epoch()); 172 } 173 Timeout::None => {} 174 } 175 176 instantiate_with_dummy(&mut store, &module); 177 } 178 179 /// Represents supported commands to the `instantiate_many` function. 180 #[derive(Arbitrary, Debug)] 181 pub enum Command { 182 /// Instantiates a module. 183 /// 184 /// The value is the index of the module to instantiate. 185 /// 186 /// The module instantiated will be this value modulo the number of modules provided to `instantiate_many`. 187 Instantiate(usize), 188 /// Terminates a "running" instance. 189 /// 190 /// The value is the index of the instance to terminate. 191 /// 192 /// The instance terminated will be this value modulo the number of currently running 193 /// instances. 194 /// 195 /// If no instances are running, the command will be ignored. 196 Terminate(usize), 197 } 198 199 /// Instantiates many instances from the given modules. 200 /// 201 /// The engine will be configured using the provided config. 202 /// 203 /// The modules are expected to *not* have start functions as no timeouts are configured. 204 pub fn instantiate_many( 205 modules: &[Vec<u8>], 206 known_valid: KnownValid, 207 config: &generators::Config, 208 commands: &[Command], 209 ) { 210 assert!(!config.module_config.config.allow_start_export); 211 212 let engine = Engine::new(&config.to_wasmtime()).unwrap(); 213 214 let modules = modules 215 .iter() 216 .filter_map(|bytes| compile_module(&engine, bytes, known_valid, config)) 217 .collect::<Vec<_>>(); 218 219 // If no modules were valid, we're done 220 if modules.is_empty() { 221 return; 222 } 223 224 // This stores every `Store` where a successful instantiation takes place 225 let mut stores = Vec::new(); 226 let limits = StoreLimits::new(); 227 228 for command in commands { 229 match command { 230 Command::Instantiate(index) => { 231 let index = *index % modules.len(); 232 log::info!("instantiating {}", index); 233 let module = &modules[index]; 234 let mut store = Store::new(&engine, limits.clone()); 235 config.configure_store(&mut store); 236 237 if instantiate_with_dummy(&mut store, module).is_some() { 238 stores.push(Some(store)); 239 } else { 240 log::warn!("instantiation failed"); 241 } 242 } 243 Command::Terminate(index) => { 244 if stores.is_empty() { 245 continue; 246 } 247 let index = *index % stores.len(); 248 249 log::info!("dropping {}", index); 250 stores.swap_remove(index); 251 } 252 } 253 } 254 } 255 256 fn compile_module( 257 engine: &Engine, 258 bytes: &[u8], 259 known_valid: KnownValid, 260 config: &generators::Config, 261 ) -> Option<Module> { 262 log_wasm(bytes); 263 264 fn is_pcc_error(e: &anyhow::Error) -> bool { 265 // NOTE: please keep this predicate in sync with the display format of CodegenError, 266 // defined in `wasmtime/cranelift/codegen/src/result.rs` 267 e.to_string().to_lowercase().contains("proof-carrying-code") 268 } 269 270 match config.compile(engine, bytes) { 271 Ok(module) => Some(module), 272 Err(e) if is_pcc_error(&e) => { 273 panic!("pcc error in input: {e:#?}"); 274 } 275 Err(_) if known_valid == KnownValid::No => None, 276 Err(e) => { 277 if let generators::InstanceAllocationStrategy::Pooling(c) = &config.wasmtime.strategy { 278 // When using the pooling allocator, accept failures to compile 279 // when arbitrary table element limits have been exceeded as 280 // there is currently no way to constrain the generated module 281 // table types. 282 let string = e.to_string(); 283 if string.contains("minimum element size") { 284 return None; 285 } 286 287 // Allow modules-failing-to-compile which exceed the requested 288 // size for each instance. This is something that is difficult 289 // to control and ensure it always succeeds, so we simply have a 290 // "random" instance size limit and if a module doesn't fit we 291 // move on to the next fuzz input. 292 if string.contains("instance allocation for this module requires") { 293 return None; 294 } 295 296 // If the pooling allocator is more restrictive on the number of 297 // tables and memories than we allowed wasm-smith to generate 298 // then allow compilation errors along those lines. 299 if c.max_tables_per_module < (config.module_config.config.max_tables as u32) 300 && string.contains("defined tables count") 301 && string.contains("exceeds the per-instance limit") 302 { 303 return None; 304 } 305 306 if c.max_memories_per_module < (config.module_config.config.max_memories as u32) 307 && string.contains("defined memories count") 308 && string.contains("exceeds the per-instance limit") 309 { 310 return None; 311 } 312 } 313 314 panic!("failed to compile module: {:?}", e); 315 } 316 } 317 } 318 319 /// Create a Wasmtime [`Instance`] from a [`Module`] and fill in all imports 320 /// with dummy values (e.g., zeroed values, immediately-trapping functions). 321 /// Also, this function catches certain fuzz-related instantiation failures and 322 /// returns `None` instead of panicking. 323 /// 324 /// TODO: we should implement tracing versions of these dummy imports that 325 /// record a trace of the order that imported functions were called in and with 326 /// what values. Like the results of exported functions, calls to imports should 327 /// also yield the same values for each configuration, and we should assert 328 /// that. 329 pub fn instantiate_with_dummy(store: &mut Store<StoreLimits>, module: &Module) -> Option<Instance> { 330 // Creation of imports can fail due to resource limit constraints, and then 331 // instantiation can naturally fail for a number of reasons as well. Bundle 332 // the two steps together to match on the error below. 333 let instance = 334 dummy::dummy_linker(store, module).and_then(|l| l.instantiate(&mut *store, module)); 335 unwrap_instance(store, instance) 336 } 337 338 fn unwrap_instance( 339 store: &Store<StoreLimits>, 340 instance: anyhow::Result<Instance>, 341 ) -> Option<Instance> { 342 let e = match instance { 343 Ok(i) => return Some(i), 344 Err(e) => e, 345 }; 346 347 // If the instantiation hit OOM for some reason then that's ok, it's 348 // expected that fuzz-generated programs try to allocate lots of 349 // stuff. 350 if store.data().is_oom() { 351 log::debug!("failed to instantiate: OOM"); 352 return None; 353 } 354 355 // Allow traps which can happen normally with `unreachable` or a 356 // timeout or such 357 if let Some(trap) = e.downcast_ref::<Trap>() { 358 log::debug!("failed to instantiate: {}", trap); 359 return None; 360 } 361 362 let string = e.to_string(); 363 // Currently we instantiate with a `Linker` which can't instantiate 364 // every single module under the sun due to using name-based resolution 365 // rather than positional-based resolution 366 if string.contains("incompatible import type") { 367 log::debug!("failed to instantiate: {}", string); 368 return None; 369 } 370 371 // Also allow failures to instantiate as a result of hitting pooling limits. 372 if e.is::<wasmtime::PoolConcurrencyLimitError>() { 373 log::debug!("failed to instantiate: {}", string); 374 return None; 375 } 376 377 // Everything else should be a bug in the fuzzer or a bug in wasmtime 378 panic!("failed to instantiate: {:?}", e); 379 } 380 381 /// Evaluate the function identified by `name` in two different engine 382 /// instances--`lhs` and `rhs`. 383 /// 384 /// Returns `Ok(true)` if more evaluations can happen or `Ok(false)` if the 385 /// instances may have drifted apart and no more evaluations can happen. 386 /// 387 /// # Panics 388 /// 389 /// This will panic if the evaluation is different between engines (e.g., 390 /// results are different, hashed instance is different, one side traps, etc.). 391 pub fn differential( 392 lhs: &mut dyn DiffInstance, 393 lhs_engine: &dyn DiffEngine, 394 rhs: &mut WasmtimeInstance, 395 name: &str, 396 args: &[DiffValue], 397 result_tys: &[DiffValueType], 398 ) -> anyhow::Result<bool> { 399 log::debug!("Evaluating: `{}` with {:?}", name, args); 400 let lhs_results = match lhs.evaluate(name, args, result_tys) { 401 Ok(Some(results)) => Ok(results), 402 Err(e) => Err(e), 403 // this engine couldn't execute this type signature, so discard this 404 // execution by returning success. 405 Ok(None) => return Ok(true), 406 }; 407 log::debug!(" -> results on {}: {:?}", lhs.name(), &lhs_results); 408 409 let rhs_results = rhs 410 .evaluate(name, args, result_tys) 411 // wasmtime should be able to invoke any signature, so unwrap this result 412 .map(|results| results.unwrap()); 413 log::debug!(" -> results on {}: {:?}", rhs.name(), &rhs_results); 414 415 // If Wasmtime hit its OOM condition, which is possible since it's set 416 // somewhat low while fuzzing, then don't return an error but return 417 // `false` indicating that differential fuzzing must stop. There's no 418 // guarantee the other engine has the same OOM limits as Wasmtime, and 419 // it's assumed that Wasmtime is configured to have a more conservative 420 // limit than the other engine. 421 if rhs.is_oom() { 422 return Ok(false); 423 } 424 425 match DiffEqResult::new(lhs_engine, lhs_results, rhs_results) { 426 DiffEqResult::Success(lhs, rhs) => assert_eq!(lhs, rhs), 427 DiffEqResult::Poisoned => return Ok(false), 428 DiffEqResult::Failed => {} 429 } 430 431 for (global, ty) in rhs.exported_globals() { 432 log::debug!("Comparing global `{global}`"); 433 let lhs = match lhs.get_global(&global, ty) { 434 Some(val) => val, 435 None => continue, 436 }; 437 let rhs = rhs.get_global(&global, ty).unwrap(); 438 assert_eq!(lhs, rhs); 439 } 440 for (memory, shared) in rhs.exported_memories() { 441 log::debug!("Comparing memory `{memory}`"); 442 let lhs = match lhs.get_memory(&memory, shared) { 443 Some(val) => val, 444 None => continue, 445 }; 446 let rhs = rhs.get_memory(&memory, shared).unwrap(); 447 if lhs == rhs { 448 continue; 449 } 450 eprintln!("differential memory is {} bytes long", lhs.len()); 451 eprintln!("wasmtime memory is {} bytes long", rhs.len()); 452 panic!("memories have differing values"); 453 } 454 455 Ok(true) 456 } 457 458 /// Result of comparing the result of two operations during differential 459 /// execution. 460 pub enum DiffEqResult<T, U> { 461 /// Both engines succeeded. 462 Success(T, U), 463 /// The result has reached the state where engines may have diverged and 464 /// results can no longer be compared. 465 Poisoned, 466 /// Both engines failed with the same error message, and internal state 467 /// should still match between the two engines. 468 Failed, 469 } 470 471 impl<T, U> DiffEqResult<T, U> { 472 /// Computes the differential result from executing in two different 473 /// engines. 474 pub fn new( 475 lhs_engine: &dyn DiffEngine, 476 lhs_result: Result<T>, 477 rhs_result: Result<U>, 478 ) -> DiffEqResult<T, U> { 479 match (lhs_result, rhs_result) { 480 (Ok(lhs_result), Ok(rhs_result)) => DiffEqResult::Success(lhs_result, rhs_result), 481 482 // Both sides failed. If either one hits a stack overflow then that's an 483 // engine defined limit which means we can no longer compare the state 484 // of the two instances, so `None` is returned and nothing else is 485 // compared. 486 (Err(lhs), Err(rhs)) => { 487 let err = rhs.downcast::<Trap>().expect("not a trap"); 488 let poisoned = err == Trap::StackOverflow || lhs_engine.is_stack_overflow(&lhs); 489 490 if poisoned { 491 return DiffEqResult::Poisoned; 492 } 493 lhs_engine.assert_error_match(&err, &lhs); 494 DiffEqResult::Failed 495 } 496 // A real bug is found if only one side fails. 497 (Ok(_), Err(_)) => panic!("only the `rhs` failed for this input"), 498 (Err(_), Ok(_)) => panic!("only the `lhs` failed for this input"), 499 } 500 } 501 } 502 503 /// Invoke the given API calls. 504 pub fn make_api_calls(api: generators::api::ApiCalls) { 505 use crate::generators::api::ApiCall; 506 use std::collections::HashMap; 507 508 let mut store: Option<Store<StoreLimits>> = None; 509 let mut modules: HashMap<usize, Module> = Default::default(); 510 let mut instances: HashMap<usize, Instance> = Default::default(); 511 512 for call in api.calls { 513 match call { 514 ApiCall::StoreNew(config) => { 515 log::trace!("creating store"); 516 assert!(store.is_none()); 517 store = Some(config.to_store()); 518 } 519 520 ApiCall::ModuleNew { id, wasm } => { 521 log::debug!("creating module: {}", id); 522 log_wasm(&wasm); 523 let module = match Module::new(store.as_ref().unwrap().engine(), &wasm) { 524 Ok(m) => m, 525 Err(_) => continue, 526 }; 527 let old = modules.insert(id, module); 528 assert!(old.is_none()); 529 } 530 531 ApiCall::ModuleDrop { id } => { 532 log::trace!("dropping module: {}", id); 533 drop(modules.remove(&id)); 534 } 535 536 ApiCall::InstanceNew { id, module } => { 537 log::trace!("instantiating module {} as {}", module, id); 538 let module = match modules.get(&module) { 539 Some(m) => m, 540 None => continue, 541 }; 542 543 let store = store.as_mut().unwrap(); 544 if let Some(instance) = instantiate_with_dummy(store, module) { 545 instances.insert(id, instance); 546 } 547 } 548 549 ApiCall::InstanceDrop { id } => { 550 log::trace!("dropping instance {}", id); 551 instances.remove(&id); 552 } 553 554 ApiCall::CallExportedFunc { instance, nth } => { 555 log::trace!("calling instance export {} / {}", instance, nth); 556 let instance = match instances.get(&instance) { 557 Some(i) => i, 558 None => { 559 // Note that we aren't guaranteed to instantiate valid 560 // modules, see comments in `InstanceNew` for details on 561 // that. But the API call generator can't know if 562 // instantiation failed, so we might not actually have 563 // this instance. When that's the case, just skip the 564 // API call and keep going. 565 continue; 566 } 567 }; 568 let store = store.as_mut().unwrap(); 569 570 let funcs = instance 571 .exports(&mut *store) 572 .filter_map(|e| match e.into_extern() { 573 Extern::Func(f) => Some(f.clone()), 574 _ => None, 575 }) 576 .collect::<Vec<_>>(); 577 578 if funcs.is_empty() { 579 continue; 580 } 581 582 let nth = nth % funcs.len(); 583 let f = &funcs[nth]; 584 let ty = f.ty(&store); 585 if let Ok(params) = dummy::dummy_values(ty.params()) { 586 let mut results = vec![Val::I32(0); ty.results().len()]; 587 let _ = f.call(store, ¶ms, &mut results); 588 } 589 } 590 } 591 } 592 } 593 594 /// Executes the wast `test` with the `config` specified. 595 /// 596 /// Ensures that wast tests pass regardless of the `Config`. 597 pub fn wast_test(fuzz_config: generators::Config, test: generators::WastTest) { 598 crate::init_fuzzing(); 599 if !fuzz_config.is_wast_test_compliant() { 600 return; 601 } 602 603 // Fuel and epochs don't play well with threads right now, so exclude any 604 // thread-spawning test if it looks like threads are spawned in that case. 605 if fuzz_config.wasmtime.consume_fuel || fuzz_config.wasmtime.epoch_interruption { 606 if test.contents.contains("(thread") { 607 return; 608 } 609 } 610 611 log::debug!("running {:?}", test.file); 612 let mut wast_context = WastContext::new(fuzz_config.to_store()); 613 wast_context 614 .register_spectest(&wasmtime_wast::SpectestConfig { 615 use_shared_memory: false, 616 suppress_prints: true, 617 }) 618 .unwrap(); 619 wast_context 620 .run_buffer(test.file, test.contents.as_bytes()) 621 .unwrap(); 622 } 623 624 /// Execute a series of `table.get` and `table.set` operations. 625 /// 626 /// Returns the number of `gc` operations which occurred throughout the test 627 /// case -- used to test below that gc happens reasonably soon and eventually. 628 pub fn table_ops( 629 mut fuzz_config: generators::Config, 630 ops: generators::table_ops::TableOps, 631 ) -> Result<usize> { 632 let expected_drops = Arc::new(AtomicUsize::new(ops.num_params as usize)); 633 let num_dropped = Arc::new(AtomicUsize::new(0)); 634 635 let num_gcs = Arc::new(AtomicUsize::new(0)); 636 { 637 fuzz_config.wasmtime.consume_fuel = true; 638 let mut store = fuzz_config.to_store(); 639 store.set_fuel(1_000).unwrap(); 640 641 let wasm = ops.to_wasm_binary(); 642 log_wasm(&wasm); 643 let module = match compile_module(store.engine(), &wasm, KnownValid::No, &fuzz_config) { 644 Some(m) => m, 645 None => return Ok(0), 646 }; 647 648 let mut linker = Linker::new(store.engine()); 649 650 // To avoid timeouts, limit the number of explicit GCs we perform per 651 // test case. 652 const MAX_GCS: usize = 5; 653 654 let func_ty = FuncType::new( 655 store.engine(), 656 vec![], 657 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF], 658 ); 659 let func = Func::new(&mut store, func_ty, { 660 let num_dropped = num_dropped.clone(); 661 let expected_drops = expected_drops.clone(); 662 let num_gcs = num_gcs.clone(); 663 move |mut caller: Caller<'_, StoreLimits>, _params, results| { 664 log::info!("table_ops: GC"); 665 if num_gcs.fetch_add(1, SeqCst) < MAX_GCS { 666 caller.gc(); 667 } 668 669 let a = ExternRef::new(&mut caller, CountDrops(num_dropped.clone()))?; 670 let b = ExternRef::new(&mut caller, CountDrops(num_dropped.clone()))?; 671 let c = ExternRef::new(&mut caller, CountDrops(num_dropped.clone()))?; 672 673 log::info!("table_ops: gc() -> ({:?}, {:?}, {:?})", a, b, c); 674 675 expected_drops.fetch_add(3, SeqCst); 676 results[0] = Some(a).into(); 677 results[1] = Some(b).into(); 678 results[2] = Some(c).into(); 679 Ok(()) 680 } 681 }); 682 linker.define(&store, "", "gc", func).unwrap(); 683 684 linker 685 .func_wrap("", "take_refs", { 686 let expected_drops = expected_drops.clone(); 687 move |caller: Caller<'_, StoreLimits>, 688 a: Option<Rooted<ExternRef>>, 689 b: Option<Rooted<ExternRef>>, 690 c: Option<Rooted<ExternRef>>| 691 -> Result<()> { 692 log::info!("table_ops: take_refs({a:?}, {b:?}, {c:?})",); 693 694 // Do the assertion on each ref's inner data, even though it 695 // all points to the same atomic, so that if we happen to 696 // run into a use-after-free bug with one of these refs we 697 // are more likely to trigger a segfault. 698 if let Some(a) = a { 699 let a = a.data(&caller)?.downcast_ref::<CountDrops>().unwrap(); 700 assert!(a.0.load(SeqCst) <= expected_drops.load(SeqCst)); 701 } 702 if let Some(b) = b { 703 let b = b.data(&caller)?.downcast_ref::<CountDrops>().unwrap(); 704 assert!(b.0.load(SeqCst) <= expected_drops.load(SeqCst)); 705 } 706 if let Some(c) = c { 707 let c = c.data(&caller)?.downcast_ref::<CountDrops>().unwrap(); 708 assert!(c.0.load(SeqCst) <= expected_drops.load(SeqCst)); 709 } 710 Ok(()) 711 } 712 }) 713 .unwrap(); 714 715 let func_ty = FuncType::new( 716 store.engine(), 717 vec![], 718 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF], 719 ); 720 let func = Func::new(&mut store, func_ty, { 721 let num_dropped = num_dropped.clone(); 722 let expected_drops = expected_drops.clone(); 723 move |mut caller, _params, results| { 724 log::info!("table_ops: make_refs"); 725 726 let a = ExternRef::new(&mut caller, CountDrops(num_dropped.clone()))?; 727 let b = ExternRef::new(&mut caller, CountDrops(num_dropped.clone()))?; 728 let c = ExternRef::new(&mut caller, CountDrops(num_dropped.clone()))?; 729 expected_drops.fetch_add(3, SeqCst); 730 731 log::info!("table_ops: make_refs() -> ({:?}, {:?}, {:?})", a, b, c); 732 733 results[0] = Some(a).into(); 734 results[1] = Some(b).into(); 735 results[2] = Some(c).into(); 736 737 Ok(()) 738 } 739 }); 740 linker.define(&store, "", "make_refs", func).unwrap(); 741 742 let instance = linker.instantiate(&mut store, &module).unwrap(); 743 let run = instance.get_func(&mut store, "run").unwrap(); 744 745 { 746 let mut scope = RootScope::new(&mut store); 747 let args: Vec<_> = (0..ops.num_params) 748 .map(|_| { 749 Ok(Val::ExternRef(Some(ExternRef::new( 750 &mut scope, 751 CountDrops(num_dropped.clone()), 752 )?))) 753 }) 754 .collect::<Result<_>>()?; 755 756 // The generated function should always return a trap. The only two 757 // valid traps are table-out-of-bounds which happens through `table.get` 758 // and `table.set` generated or an out-of-fuel trap. Otherwise any other 759 // error is unexpected and should fail fuzzing. 760 let trap = run 761 .call(&mut scope, &args, &mut []) 762 .unwrap_err() 763 .downcast::<Trap>() 764 .unwrap(); 765 766 match trap { 767 Trap::TableOutOfBounds | Trap::OutOfFuel => {} 768 _ => panic!("unexpected trap: {trap}"), 769 } 770 } 771 772 // Do a final GC after running the Wasm. 773 store.gc(); 774 } 775 776 assert_eq!(num_dropped.load(SeqCst), expected_drops.load(SeqCst)); 777 return Ok(num_gcs.load(SeqCst)); 778 779 struct CountDrops(Arc<AtomicUsize>); 780 781 impl Drop for CountDrops { 782 fn drop(&mut self) { 783 self.0.fetch_add(1, SeqCst); 784 } 785 } 786 } 787 788 // Test that the `table_ops` fuzzer eventually runs the gc function in the host. 789 // We've historically had issues where this fuzzer accidentally wasn't fuzzing 790 // anything for a long time so this is an attempt to prevent that from happening 791 // again. 792 #[test] 793 fn table_ops_eventually_gcs() { 794 use arbitrary::Unstructured; 795 use rand::prelude::*; 796 797 // Skip if we're under emulation because some fuzz configurations will do 798 // large address space reservations that QEMU doesn't handle well. 799 if std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok() { 800 return; 801 } 802 803 let mut rng = SmallRng::seed_from_u64(0); 804 let mut buf = vec![0; 2048]; 805 let n = 100; 806 for _ in 0..n { 807 rng.fill_bytes(&mut buf); 808 let u = Unstructured::new(&buf); 809 810 if let Ok((config, test)) = Arbitrary::arbitrary_take_rest(u) { 811 if table_ops(config, test).unwrap() > 0 { 812 return; 813 } 814 } 815 } 816 817 panic!("after {n} runs nothing ever gc'd, something is probably wrong"); 818 } 819 820 #[derive(Default)] 821 struct HelperThread { 822 state: Arc<HelperThreadState>, 823 thread: Option<std::thread::JoinHandle<()>>, 824 } 825 826 #[derive(Default)] 827 struct HelperThreadState { 828 should_exit: Mutex<bool>, 829 should_exit_cvar: Condvar, 830 } 831 832 impl HelperThread { 833 fn run_periodically(&mut self, dur: Duration, mut closure: impl FnMut() + Send + 'static) { 834 let state = self.state.clone(); 835 self.thread = Some(std::thread::spawn(move || { 836 // Using our mutex/condvar we wait here for the first of `dur` to 837 // pass or the `HelperThread` instance to get dropped. 838 let mut should_exit = state.should_exit.lock().unwrap(); 839 while !*should_exit { 840 let (lock, result) = state 841 .should_exit_cvar 842 .wait_timeout(should_exit, dur) 843 .unwrap(); 844 should_exit = lock; 845 // If we timed out for sure then there's no need to continue 846 // since we'll just abort on the next `checked_sub` anyway. 847 if result.timed_out() { 848 closure(); 849 } 850 } 851 })); 852 } 853 } 854 855 impl Drop for HelperThread { 856 fn drop(&mut self) { 857 let thread = match self.thread.take() { 858 Some(thread) => thread, 859 None => return, 860 }; 861 // Signal our thread that it should exit and wake it up in case it's 862 // sleeping. 863 *self.state.should_exit.lock().unwrap() = true; 864 self.state.should_exit_cvar.notify_one(); 865 866 // ... and then wait for the thread to exit to ensure we clean up 867 // after ourselves. 868 thread.join().unwrap(); 869 } 870 } 871 872 /// Generate and execute a `crate::generators::component_types::TestCase` using the specified `input` to create 873 /// arbitrary types and values. 874 pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbitrary::Result<()> { 875 use crate::generators::component_types; 876 use component_fuzz_util::{TestCase, Type, EXPORT_FUNCTION, IMPORT_FUNCTION, MAX_TYPE_DEPTH}; 877 use component_test_util::FuncExt; 878 use wasmtime::component::{Component, Linker, Val}; 879 880 crate::init_fuzzing(); 881 882 let mut types = Vec::new(); 883 let mut type_fuel = 500; 884 885 for _ in 0..5 { 886 types.push(Type::generate(input, MAX_TYPE_DEPTH, &mut type_fuel)?); 887 } 888 let params = (0..input.int_in_range(0..=5)?) 889 .map(|_| input.choose(&types)) 890 .collect::<arbitrary::Result<Vec<_>>>()?; 891 let results = (0..input.int_in_range(0..=5)?) 892 .map(|_| input.choose(&types)) 893 .collect::<arbitrary::Result<Vec<_>>>()?; 894 895 let case = TestCase { 896 params, 897 results, 898 encoding1: input.arbitrary()?, 899 encoding2: input.arbitrary()?, 900 }; 901 902 let mut config = component_test_util::config(); 903 config.debug_adapter_modules(input.arbitrary()?); 904 let engine = Engine::new(&config).unwrap(); 905 let mut store = Store::new(&engine, (Vec::new(), None)); 906 let wat = case.declarations().make_component(); 907 let wat = wat.as_bytes(); 908 log_wasm(wat); 909 let component = Component::new(&engine, wat).unwrap(); 910 let mut linker = Linker::new(&engine); 911 912 linker 913 .root() 914 .func_new(IMPORT_FUNCTION, { 915 move |mut cx: StoreContextMut<'_, (Vec<Val>, Option<Vec<Val>>)>, 916 params: &[Val], 917 results: &mut [Val]| 918 -> Result<()> { 919 log::trace!("received params {params:?}"); 920 let (expected_args, expected_results) = cx.data_mut(); 921 assert_eq!(params.len(), expected_args.len()); 922 for (expected, actual) in expected_args.iter().zip(params) { 923 assert_eq!(expected, actual); 924 } 925 results.clone_from_slice(&expected_results.take().unwrap()); 926 log::trace!("returning results {results:?}"); 927 Ok(()) 928 } 929 }) 930 .unwrap(); 931 932 let instance = linker.instantiate(&mut store, &component).unwrap(); 933 let func = instance.get_func(&mut store, EXPORT_FUNCTION).unwrap(); 934 let param_tys = func.params(&store); 935 let result_tys = func.results(&store); 936 937 while input.arbitrary()? { 938 let params = param_tys 939 .iter() 940 .map(|ty| component_types::arbitrary_val(ty, input)) 941 .collect::<arbitrary::Result<Vec<_>>>()?; 942 let results = result_tys 943 .iter() 944 .map(|ty| component_types::arbitrary_val(ty, input)) 945 .collect::<arbitrary::Result<Vec<_>>>()?; 946 947 *store.data_mut() = (params.clone(), Some(results.clone())); 948 949 log::trace!("passing params {params:?}"); 950 let mut actual = vec![Val::Bool(false); results.len()]; 951 func.call_and_post_return(&mut store, ¶ms, &mut actual) 952 .unwrap(); 953 log::trace!("received results {actual:?}"); 954 assert_eq!(actual, results); 955 } 956 957 Ok(()) 958 } 959 960 /// Instantiates a wasm module and runs its exports with dummy values, all in 961 /// an async fashion. 962 /// 963 /// Attempts to stress yields in host functions to ensure that exiting and 964 /// resuming a wasm function call works. 965 pub fn call_async(wasm: &[u8], config: &generators::Config, mut poll_amts: &[u32]) { 966 let mut store = config.to_store(); 967 let module = match compile_module(store.engine(), wasm, KnownValid::Yes, config) { 968 Some(module) => module, 969 None => return, 970 }; 971 972 // Configure a helper thread to periodically increment the epoch to 973 // forcibly enable yields-via-epochs if epochs are in use. Note that this 974 // is required because the wasm isn't otherwise guaranteed to necessarily 975 // call any imports which will also increment the epoch. 976 let mut helper_thread = HelperThread::default(); 977 if let generators::AsyncConfig::YieldWithEpochs { dur, .. } = &config.wasmtime.async_config { 978 let engine = store.engine().clone(); 979 helper_thread.run_periodically(*dur, move || engine.increment_epoch()); 980 } 981 982 // Generate a `Linker` where all function imports are custom-built to yield 983 // periodically and additionally increment the epoch. 984 let mut imports = Vec::new(); 985 for import in module.imports() { 986 let item = match import.ty() { 987 ExternType::Func(ty) => { 988 let poll_amt = take_poll_amt(&mut poll_amts); 989 Func::new_async(&mut store, ty.clone(), move |caller, _, results| { 990 let ty = ty.clone(); 991 Box::new(async move { 992 caller.engine().increment_epoch(); 993 log::info!("yielding {} times in import", poll_amt); 994 YieldN(poll_amt).await; 995 for (ret_ty, result) in ty.results().zip(results) { 996 *result = dummy::dummy_value(ret_ty)?; 997 } 998 Ok(()) 999 }) 1000 }) 1001 .into() 1002 } 1003 other_ty => match dummy::dummy_extern(&mut store, other_ty) { 1004 Ok(item) => item, 1005 Err(e) => { 1006 log::warn!("couldn't create import: {}", e); 1007 return; 1008 } 1009 }, 1010 }; 1011 imports.push(item); 1012 } 1013 1014 // Run the instantiation process, asynchronously, and if everything 1015 // succeeds then pull out the instance. 1016 // log::info!("starting instantiation"); 1017 let instance = run(Timeout { 1018 future: Instance::new_async(&mut store, &module, &imports), 1019 polls: take_poll_amt(&mut poll_amts), 1020 end: Instant::now() + Duration::from_millis(2_000), 1021 }); 1022 let instance = match instance { 1023 Ok(instantiation_result) => match unwrap_instance(&store, instantiation_result) { 1024 Some(instance) => instance, 1025 None => { 1026 log::info!("instantiation hit a nominal error"); 1027 return; // resource exhaustion or limits met 1028 } 1029 }, 1030 Err(_) => { 1031 log::info!("instantiation failed to complete"); 1032 return; // Timed out or ran out of polls 1033 } 1034 }; 1035 1036 // Run each export of the instance in the same manner as instantiation 1037 // above. Dummy values are passed in for argument values here: 1038 // 1039 // TODO: this should probably be more clever about passing in arguments for 1040 // example they might be used as pointers or something and always using 0 1041 // isn't too interesting. 1042 let funcs = instance 1043 .exports(&mut store) 1044 .filter_map(|e| { 1045 let name = e.name().to_string(); 1046 let func = e.into_extern().into_func()?; 1047 Some((name, func)) 1048 }) 1049 .collect::<Vec<_>>(); 1050 for (name, func) in funcs { 1051 let ty = func.ty(&store); 1052 let params = ty 1053 .params() 1054 .map(|ty| dummy::dummy_value(ty).unwrap()) 1055 .collect::<Vec<_>>(); 1056 let mut results = ty 1057 .results() 1058 .map(|ty| dummy::dummy_value(ty).unwrap()) 1059 .collect::<Vec<_>>(); 1060 1061 log::info!("invoking export {:?}", name); 1062 let future = func.call_async(&mut store, ¶ms, &mut results); 1063 match run(Timeout { 1064 future, 1065 polls: take_poll_amt(&mut poll_amts), 1066 end: Instant::now() + Duration::from_millis(2_000), 1067 }) { 1068 // On success or too many polls, try the next export. 1069 Ok(_) | Err(Exhausted::Polls) => {} 1070 1071 // If time ran out then stop the current test case as we might have 1072 // already sucked up a lot of time for this fuzz test case so don't 1073 // keep it going. 1074 Err(Exhausted::Time) => return, 1075 } 1076 } 1077 1078 fn take_poll_amt(polls: &mut &[u32]) -> u32 { 1079 match polls.split_first() { 1080 Some((a, rest)) => { 1081 *polls = rest; 1082 *a 1083 } 1084 None => 0, 1085 } 1086 } 1087 1088 /// Helper future to yield N times before resolving. 1089 struct YieldN(u32); 1090 1091 impl Future for YieldN { 1092 type Output = (); 1093 1094 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { 1095 if self.0 == 0 { 1096 Poll::Ready(()) 1097 } else { 1098 self.0 -= 1; 1099 cx.waker().wake_by_ref(); 1100 Poll::Pending 1101 } 1102 } 1103 } 1104 1105 /// Helper future for applying a timeout to `future` up to either when `end` 1106 /// is the current time or `polls` polls happen. 1107 /// 1108 /// Note that this helps to time out infinite loops in wasm, for example. 1109 struct Timeout<F> { 1110 future: F, 1111 /// If the future isn't ready by this time then the `Timeout<F>` future 1112 /// will return `None`. 1113 end: Instant, 1114 /// If the future doesn't resolve itself in this many calls to `poll` 1115 /// then the `Timeout<F>` future will return `None`. 1116 polls: u32, 1117 } 1118 1119 enum Exhausted { 1120 Time, 1121 Polls, 1122 } 1123 1124 impl<F: Future> Future for Timeout<F> { 1125 type Output = Result<F::Output, Exhausted>; 1126 1127 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { 1128 let (end, polls, future) = unsafe { 1129 let me = self.get_unchecked_mut(); 1130 (me.end, &mut me.polls, Pin::new_unchecked(&mut me.future)) 1131 }; 1132 match future.poll(cx) { 1133 Poll::Ready(val) => Poll::Ready(Ok(val)), 1134 Poll::Pending => { 1135 if Instant::now() >= end { 1136 log::warn!("future operation timed out"); 1137 return Poll::Ready(Err(Exhausted::Time)); 1138 } 1139 if *polls == 0 { 1140 log::warn!("future operation ran out of polls"); 1141 return Poll::Ready(Err(Exhausted::Polls)); 1142 } 1143 *polls -= 1; 1144 Poll::Pending 1145 } 1146 } 1147 } 1148 } 1149 1150 fn run<F: Future>(future: F) -> F::Output { 1151 let mut f = Box::pin(future); 1152 let mut cx = Context::from_waker(futures::task::noop_waker_ref()); 1153 loop { 1154 match f.as_mut().poll(&mut cx) { 1155 Poll::Ready(val) => break val, 1156 Poll::Pending => {} 1157 } 1158 } 1159 } 1160 } 1161