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