1 //! Test case generators. 2 //! 3 //! Test case generators take raw, unstructured input from a fuzzer 4 //! (e.g. libFuzzer) and translate that into a structured test case (e.g. a 5 //! valid Wasm binary). 6 //! 7 //! These are generally implementations of the `Arbitrary` trait, or some 8 //! wrapper over an external tool, such that the wrapper implements the 9 //! `Arbitrary` trait for the wrapped external tool. 10 11 pub mod api; 12 pub mod table_ops; 13 14 use crate::oracles::{StoreLimits, Timeout}; 15 use anyhow::Result; 16 use arbitrary::{Arbitrary, Unstructured}; 17 use std::sync::Arc; 18 use std::time::Duration; 19 use wasm_smith::SwarmConfig; 20 use wasmtime::{Engine, LinearMemory, MemoryCreator, MemoryType, Module, Store}; 21 22 #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] 23 enum OptLevel { 24 None, 25 Speed, 26 SpeedAndSize, 27 } 28 29 impl OptLevel { 30 fn to_wasmtime(&self) -> wasmtime::OptLevel { 31 match self { 32 OptLevel::None => wasmtime::OptLevel::None, 33 OptLevel::Speed => wasmtime::OptLevel::Speed, 34 OptLevel::SpeedAndSize => wasmtime::OptLevel::SpeedAndSize, 35 } 36 } 37 } 38 39 /// Configuration for `wasmtime::PoolingAllocationStrategy`. 40 #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] 41 pub enum PoolingAllocationStrategy { 42 /// Use next available instance slot. 43 NextAvailable, 44 /// Use random instance slot. 45 Random, 46 /// Use an affinity-based strategy. 47 ReuseAffinity, 48 } 49 50 impl PoolingAllocationStrategy { 51 fn to_wasmtime(&self) -> wasmtime::PoolingAllocationStrategy { 52 match self { 53 PoolingAllocationStrategy::NextAvailable => { 54 wasmtime::PoolingAllocationStrategy::NextAvailable 55 } 56 PoolingAllocationStrategy::Random => wasmtime::PoolingAllocationStrategy::Random, 57 PoolingAllocationStrategy::ReuseAffinity => { 58 wasmtime::PoolingAllocationStrategy::ReuseAffinity 59 } 60 } 61 } 62 } 63 /// Configuration for `wasmtime::PoolingAllocationStrategy`. 64 #[derive(Debug, Clone, Eq, PartialEq, Hash)] 65 #[allow(missing_docs)] 66 pub struct InstanceLimits { 67 pub count: u32, 68 pub memories: u32, 69 pub tables: u32, 70 pub memory_pages: u64, 71 pub table_elements: u32, 72 pub size: usize, 73 } 74 75 impl InstanceLimits { 76 fn to_wasmtime(&self) -> wasmtime::InstanceLimits { 77 wasmtime::InstanceLimits { 78 count: self.count, 79 memories: self.memories, 80 tables: self.tables, 81 memory_pages: self.memory_pages, 82 table_elements: self.table_elements, 83 size: self.size, 84 } 85 } 86 } 87 88 impl<'a> Arbitrary<'a> for InstanceLimits { 89 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 90 const MAX_COUNT: u32 = 100; 91 92 const MAX_TABLES: u32 = 10; 93 const MAX_MEMORIES: u32 = 10; 94 const MAX_ELEMENTS: u32 = 1000; 95 const MAX_MEMORY_PAGES: u64 = 160; // 10 MiB 96 const MAX_SIZE: usize = 1 << 20; // 1 MiB 97 98 Ok(Self { 99 tables: u.int_in_range(0..=MAX_TABLES)?, 100 memories: u.int_in_range(0..=MAX_MEMORIES)?, 101 table_elements: u.int_in_range(0..=MAX_ELEMENTS)?, 102 memory_pages: u.int_in_range(0..=MAX_MEMORY_PAGES)?, 103 count: u.int_in_range(1..=MAX_COUNT)?, 104 size: u.int_in_range(0..=MAX_SIZE)?, 105 }) 106 } 107 } 108 109 /// Configuration for `wasmtime::InstanceAllocationStrategy`. 110 #[derive(Arbitrary, Clone, Debug, Eq, PartialEq, Hash)] 111 pub enum InstanceAllocationStrategy { 112 /// Use the on-demand instance allocation strategy. 113 OnDemand, 114 /// Use the pooling instance allocation strategy. 115 Pooling { 116 /// The pooling strategy to use. 117 strategy: PoolingAllocationStrategy, 118 /// The instance limits. 119 instance_limits: InstanceLimits, 120 }, 121 } 122 123 impl InstanceAllocationStrategy { 124 fn to_wasmtime(&self) -> wasmtime::InstanceAllocationStrategy { 125 match self { 126 InstanceAllocationStrategy::OnDemand => wasmtime::InstanceAllocationStrategy::OnDemand, 127 InstanceAllocationStrategy::Pooling { 128 strategy, 129 instance_limits, 130 } => wasmtime::InstanceAllocationStrategy::Pooling { 131 strategy: strategy.to_wasmtime(), 132 instance_limits: instance_limits.to_wasmtime(), 133 }, 134 } 135 } 136 } 137 138 /// Configuration for `wasmtime::Config` and generated modules for a session of 139 /// fuzzing. 140 /// 141 /// This configuration guides what modules are generated, how wasmtime 142 /// configuration is generated, and is typically itself generated through a call 143 /// to `Arbitrary` which allows for a form of "swarm testing". 144 #[derive(Debug, Clone)] 145 pub struct Config { 146 /// Configuration related to the `wasmtime::Config`. 147 pub wasmtime: WasmtimeConfig, 148 /// Configuration related to generated modules. 149 pub module_config: ModuleConfig, 150 } 151 152 impl<'a> Arbitrary<'a> for Config { 153 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 154 let mut config = Self { 155 wasmtime: u.arbitrary()?, 156 module_config: u.arbitrary()?, 157 }; 158 159 // If using the pooling allocator, constrain the memory and module configurations 160 // to the module limits. 161 if let InstanceAllocationStrategy::Pooling { 162 instance_limits: limits, 163 .. 164 } = &config.wasmtime.strategy 165 { 166 // If the pooling allocator is used, do not allow shared memory to 167 // be created. FIXME: see 168 // https://github.com/bytecodealliance/wasmtime/issues/4244. 169 config.module_config.config.threads_enabled = false; 170 171 // Force the use of a normal memory config when using the pooling allocator and 172 // limit the static memory maximum to be the same as the pooling allocator's memory 173 // page limit. 174 config.wasmtime.memory_config = match config.wasmtime.memory_config { 175 MemoryConfig::Normal(mut config) => { 176 config.static_memory_maximum_size = Some(limits.memory_pages * 0x10000); 177 MemoryConfig::Normal(config) 178 } 179 MemoryConfig::CustomUnaligned => { 180 let mut config: NormalMemoryConfig = u.arbitrary()?; 181 config.static_memory_maximum_size = Some(limits.memory_pages * 0x10000); 182 MemoryConfig::Normal(config) 183 } 184 }; 185 186 let cfg = &mut config.module_config.config; 187 cfg.max_memories = limits.memories as usize; 188 cfg.max_tables = limits.tables as usize; 189 cfg.max_memory_pages = limits.memory_pages; 190 191 // Force no aliases in any generated modules as they might count against the 192 // import limits above. 193 cfg.max_aliases = 0; 194 } 195 196 Ok(config) 197 } 198 } 199 200 /// Configuration related to `wasmtime::Config` and the various settings which 201 /// can be tweaked from within. 202 #[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)] 203 pub struct WasmtimeConfig { 204 opt_level: OptLevel, 205 debug_info: bool, 206 canonicalize_nans: bool, 207 interruptable: bool, 208 pub(crate) consume_fuel: bool, 209 epoch_interruption: bool, 210 /// The Wasmtime memory configuration to use. 211 pub memory_config: MemoryConfig, 212 force_jump_veneers: bool, 213 memory_init_cow: bool, 214 memory_guaranteed_dense_image_size: u64, 215 use_precompiled_cwasm: bool, 216 /// Configuration for the instance allocation strategy to use. 217 pub strategy: InstanceAllocationStrategy, 218 codegen: CodegenSettings, 219 padding_between_functions: Option<u16>, 220 generate_address_map: bool, 221 wasm_backtraces: bool, 222 } 223 224 /// Configuration for linear memories in Wasmtime. 225 #[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)] 226 pub enum MemoryConfig { 227 /// Configuration for linear memories which correspond to normal 228 /// configuration settings in `wasmtime` itself. This will tweak various 229 /// parameters about static/dynamic memories. 230 Normal(NormalMemoryConfig), 231 232 /// Configuration to force use of a linear memory that's unaligned at its 233 /// base address to force all wasm addresses to be unaligned at the hardware 234 /// level, even if the wasm itself correctly aligns everything internally. 235 CustomUnaligned, 236 } 237 238 /// Represents a normal memory configuration for Wasmtime with the given 239 /// static and dynamic memory sizes. 240 #[derive(Clone, Debug, Eq, Hash, PartialEq)] 241 pub struct NormalMemoryConfig { 242 static_memory_maximum_size: Option<u64>, 243 static_memory_guard_size: Option<u64>, 244 dynamic_memory_guard_size: Option<u64>, 245 guard_before_linear_memory: bool, 246 } 247 248 impl<'a> Arbitrary<'a> for NormalMemoryConfig { 249 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 250 // This attempts to limit memory and guard sizes to 32-bit ranges so 251 // we don't exhaust a 64-bit address space easily. 252 Ok(Self { 253 static_memory_maximum_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into), 254 static_memory_guard_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into), 255 dynamic_memory_guard_size: <Option<u32> as Arbitrary>::arbitrary(u)?.map(Into::into), 256 guard_before_linear_memory: u.arbitrary()?, 257 }) 258 } 259 } 260 261 impl Config { 262 /// Indicates that this configuration is being used for differential 263 /// execution so only a single function should be generated since that's all 264 /// that's going to be exercised. 265 pub fn set_differential_config(&mut self) { 266 let config = &mut self.module_config.config; 267 268 config.allow_start_export = false; 269 // Make sure there's a type available for the function. 270 config.min_types = 1; 271 config.max_types = 1; 272 273 // Generate one and only one function 274 config.min_funcs = 1; 275 config.max_funcs = 1; 276 277 // Give the function a memory, but keep it small 278 config.min_memories = 1; 279 config.max_memories = 1; 280 config.max_memory_pages = 1; 281 config.memory_max_size_required = true; 282 283 // While reference types are disabled below, only allow one table 284 config.max_tables = 1; 285 286 // Don't allow any imports 287 config.max_imports = 0; 288 289 // Try to get the function and the memory exported 290 config.min_exports = 2; 291 config.max_exports = 4; 292 293 // NaN is canonicalized at the wasm level for differential fuzzing so we 294 // can paper over NaN differences between engines. 295 config.canonicalize_nans = true; 296 297 // When diffing against a non-wasmtime engine then disable wasm 298 // features to get selectively re-enabled against each differential 299 // engine. 300 config.bulk_memory_enabled = false; 301 config.reference_types_enabled = false; 302 config.simd_enabled = false; 303 config.memory64_enabled = false; 304 config.threads_enabled = false; 305 306 // If using the pooling allocator, update the instance limits too 307 if let InstanceAllocationStrategy::Pooling { 308 instance_limits: limits, 309 .. 310 } = &mut self.wasmtime.strategy 311 { 312 // One single-page memory 313 limits.memories = 1; 314 limits.memory_pages = 1; 315 316 limits.tables = 1; 317 318 match &mut self.wasmtime.memory_config { 319 MemoryConfig::Normal(config) => { 320 config.static_memory_maximum_size = Some(limits.memory_pages * 0x10000); 321 } 322 MemoryConfig::CustomUnaligned => unreachable!(), // Arbitrary impl for `Config` should have prevented this 323 } 324 } 325 } 326 327 /// Uses this configuration and the supplied source of data to generate 328 /// a wasm module. 329 /// 330 /// If a `default_fuel` is provided, the resulting module will be configured 331 /// to ensure termination; as doing so will add an additional global to the module, 332 /// the pooling allocator, if configured, will also have its globals limit updated. 333 pub fn generate( 334 &mut self, 335 input: &mut Unstructured<'_>, 336 default_fuel: Option<u32>, 337 ) -> arbitrary::Result<wasm_smith::Module> { 338 let mut module = wasm_smith::Module::new(self.module_config.config.clone(), input)?; 339 340 if let Some(default_fuel) = default_fuel { 341 module.ensure_termination(default_fuel); 342 } 343 344 Ok(module) 345 } 346 347 /// Indicates that this configuration should be spec-test-compliant, 348 /// disabling various features the spec tests assert are disabled. 349 pub fn set_spectest_compliant(&mut self) { 350 let config = &mut self.module_config.config; 351 config.memory64_enabled = false; 352 config.bulk_memory_enabled = true; 353 config.reference_types_enabled = true; 354 config.multi_value_enabled = true; 355 config.simd_enabled = true; 356 config.threads_enabled = false; 357 config.max_memories = 1; 358 config.max_tables = 5; 359 360 if let InstanceAllocationStrategy::Pooling { 361 instance_limits: limits, 362 .. 363 } = &mut self.wasmtime.strategy 364 { 365 // Configure the lower bound of a number of limits to what's 366 // required to actually run the spec tests. Fuzz-generated inputs 367 // may have limits less than these thresholds which would cause the 368 // spec tests to fail which isn't particularly interesting. 369 limits.memories = limits.memories.max(1); 370 limits.tables = limits.memories.max(5); 371 limits.table_elements = limits.memories.max(1_000); 372 limits.memory_pages = limits.memory_pages.max(900); 373 limits.count = limits.count.max(500); 374 limits.size = limits.size.max(64 * 1024); 375 376 match &mut self.wasmtime.memory_config { 377 MemoryConfig::Normal(config) => { 378 config.static_memory_maximum_size = Some(limits.memory_pages * 0x10000); 379 } 380 MemoryConfig::CustomUnaligned => unreachable!(), // Arbitrary impl for `Config` should have prevented this 381 } 382 } 383 } 384 385 /// Converts this to a `wasmtime::Config` object 386 pub fn to_wasmtime(&self) -> wasmtime::Config { 387 crate::init_fuzzing(); 388 log::debug!("creating wasmtime config with {:#?}", self.wasmtime); 389 390 let mut cfg = wasmtime::Config::new(); 391 cfg.wasm_bulk_memory(true) 392 .wasm_reference_types(true) 393 .wasm_multi_value(self.module_config.config.multi_value_enabled) 394 .wasm_multi_memory(self.module_config.config.max_memories > 1) 395 .wasm_simd(self.module_config.config.simd_enabled) 396 .wasm_memory64(self.module_config.config.memory64_enabled) 397 .wasm_threads(self.module_config.config.threads_enabled) 398 .wasm_backtrace(self.wasmtime.wasm_backtraces) 399 .cranelift_nan_canonicalization(self.wasmtime.canonicalize_nans) 400 .cranelift_opt_level(self.wasmtime.opt_level.to_wasmtime()) 401 .consume_fuel(self.wasmtime.consume_fuel) 402 .epoch_interruption(self.wasmtime.epoch_interruption) 403 .memory_init_cow(self.wasmtime.memory_init_cow) 404 .memory_guaranteed_dense_image_size(std::cmp::min( 405 // Clamp this at 16MiB so we don't get huge in-memory 406 // images during fuzzing. 407 16 << 20, 408 self.wasmtime.memory_guaranteed_dense_image_size, 409 )) 410 .allocation_strategy(self.wasmtime.strategy.to_wasmtime()) 411 .generate_address_map(self.wasmtime.generate_address_map); 412 413 self.wasmtime.codegen.configure(&mut cfg); 414 415 // If the wasm-smith-generated module use nan canonicalization then we 416 // don't need to enable it, but if it doesn't enable it already then we 417 // enable this codegen option. 418 cfg.cranelift_nan_canonicalization(!self.module_config.config.canonicalize_nans); 419 420 // Enabling the verifier will at-least-double compilation time, which 421 // with a 20-30x slowdown in fuzzing can cause issues related to 422 // timeouts. If generated modules can have more than a small handful of 423 // functions then disable the verifier when fuzzing to try to lessen the 424 // impact of timeouts. 425 if self.module_config.config.max_funcs > 10 { 426 cfg.cranelift_debug_verifier(false); 427 } 428 429 if self.wasmtime.force_jump_veneers { 430 unsafe { 431 cfg.cranelift_flag_set("wasmtime_linkopt_force_jump_veneer", "true"); 432 } 433 } 434 435 if let Some(pad) = self.wasmtime.padding_between_functions { 436 unsafe { 437 cfg.cranelift_flag_set( 438 "wasmtime_linkopt_padding_between_functions", 439 &pad.to_string(), 440 ); 441 } 442 } 443 444 // Vary the memory configuration, but only if threads are not enabled. 445 // When the threads proposal is enabled we might generate shared memory, 446 // which is less amenable to different memory configurations: 447 // - shared memories are required to be "static" so fuzzing the various 448 // memory configurations will mostly result in uninteresting errors. 449 // The interesting part about shared memories is the runtime so we 450 // don't fuzz non-default settings. 451 // - shared memories are required to be aligned which means that the 452 // `CustomUnaligned` variant isn't actually safe to use with a shared 453 // memory. 454 if !self.module_config.config.threads_enabled { 455 match &self.wasmtime.memory_config { 456 MemoryConfig::Normal(memory_config) => { 457 cfg.static_memory_maximum_size( 458 memory_config.static_memory_maximum_size.unwrap_or(0), 459 ) 460 .static_memory_guard_size(memory_config.static_memory_guard_size.unwrap_or(0)) 461 .dynamic_memory_guard_size(memory_config.dynamic_memory_guard_size.unwrap_or(0)) 462 .guard_before_linear_memory(memory_config.guard_before_linear_memory); 463 } 464 MemoryConfig::CustomUnaligned => { 465 cfg.with_host_memory(Arc::new(UnalignedMemoryCreator)) 466 .static_memory_maximum_size(0) 467 .dynamic_memory_guard_size(0) 468 .static_memory_guard_size(0) 469 .guard_before_linear_memory(false); 470 } 471 } 472 } 473 474 return cfg; 475 } 476 477 /// Convenience function for generating a `Store<T>` using this 478 /// configuration. 479 pub fn to_store(&self) -> Store<StoreLimits> { 480 let engine = Engine::new(&self.to_wasmtime()).unwrap(); 481 let mut store = Store::new(&engine, StoreLimits::new()); 482 self.configure_store(&mut store); 483 store 484 } 485 486 /// Configures a store based on this configuration. 487 pub fn configure_store(&self, store: &mut Store<StoreLimits>) { 488 store.limiter(|s| s as &mut dyn wasmtime::ResourceLimiter); 489 if self.wasmtime.consume_fuel { 490 store.add_fuel(u64::max_value()).unwrap(); 491 } 492 if self.wasmtime.epoch_interruption { 493 // Without fuzzing of async execution, we can't test the 494 // "update deadline and continue" behavior, but we can at 495 // least test the codegen paths and checks with the 496 // trapping behavior, which works synchronously too. We'll 497 // set the deadline one epoch tick in the future; then 498 // this works exactly like an interrupt flag. We expect no 499 // traps/interrupts unless we bump the epoch, which we do 500 // as one particular Timeout mode (`Timeout::Epoch`). 501 store.epoch_deadline_trap(); 502 store.set_epoch_deadline(1); 503 } 504 } 505 506 /// Generates an arbitrary method of timing out an instance, ensuring that 507 /// this configuration supports the returned timeout. 508 pub fn generate_timeout(&mut self, u: &mut Unstructured<'_>) -> arbitrary::Result<Timeout> { 509 let time_duration = Duration::from_secs(20); 510 let timeout = u 511 .choose(&[Timeout::Fuel(100_000), Timeout::Epoch(time_duration)])? 512 .clone(); 513 match &timeout { 514 Timeout::Fuel(..) => { 515 self.wasmtime.consume_fuel = true; 516 } 517 Timeout::Epoch(..) => { 518 self.wasmtime.epoch_interruption = true; 519 } 520 Timeout::None => unreachable!("Not an option given to choose()"), 521 } 522 Ok(timeout) 523 } 524 525 /// Compiles the `wasm` within the `engine` provided. 526 /// 527 /// This notably will use `Module::{serialize,deserialize_file}` to 528 /// round-trip if configured in the fuzzer. 529 pub fn compile(&self, engine: &Engine, wasm: &[u8]) -> Result<Module> { 530 // Propagate this error in case the caller wants to handle 531 // valid-vs-invalid wasm. 532 let module = Module::new(engine, wasm)?; 533 if !self.wasmtime.use_precompiled_cwasm { 534 return Ok(module); 535 } 536 537 // Don't propagate these errors to prevent them from accidentally being 538 // interpreted as invalid wasm, these should never fail on a 539 // well-behaved host system. 540 let file = tempfile::NamedTempFile::new().unwrap(); 541 std::fs::write(file.path(), module.serialize().unwrap()).unwrap(); 542 unsafe { Ok(Module::deserialize_file(engine, file.path()).unwrap()) } 543 } 544 } 545 546 struct UnalignedMemoryCreator; 547 548 unsafe impl MemoryCreator for UnalignedMemoryCreator { 549 fn new_memory( 550 &self, 551 _ty: MemoryType, 552 minimum: usize, 553 maximum: Option<usize>, 554 reserved_size_in_bytes: Option<usize>, 555 guard_size_in_bytes: usize, 556 ) -> Result<Box<dyn LinearMemory>, String> { 557 assert_eq!(guard_size_in_bytes, 0); 558 assert!(reserved_size_in_bytes.is_none() || reserved_size_in_bytes == Some(0)); 559 Ok(Box::new(UnalignedMemory { 560 src: vec![0; minimum + 1], 561 maximum, 562 })) 563 } 564 } 565 566 /// A custom "linear memory allocator" for wasm which only works with the 567 /// "dynamic" mode of configuration where wasm always does explicit bounds 568 /// checks. 569 /// 570 /// This memory attempts to always use unaligned host addresses for the base 571 /// address of linear memory with wasm. This means that all jit loads/stores 572 /// should be unaligned, which is a "big hammer way" of testing that all our JIT 573 /// code works with unaligned addresses since alignment is not required for 574 /// correctness in wasm itself. 575 struct UnalignedMemory { 576 /// This memory is always one byte larger than the actual size of linear 577 /// memory. 578 src: Vec<u8>, 579 maximum: Option<usize>, 580 } 581 582 unsafe impl LinearMemory for UnalignedMemory { 583 fn byte_size(&self) -> usize { 584 // Chop off the extra byte reserved for the true byte size of this 585 // linear memory. 586 self.src.len() - 1 587 } 588 589 fn maximum_byte_size(&self) -> Option<usize> { 590 self.maximum 591 } 592 593 fn grow_to(&mut self, new_size: usize) -> Result<()> { 594 // Make sure to allocate an extra byte for our "unalignment" 595 self.src.resize(new_size + 1, 0); 596 Ok(()) 597 } 598 599 fn as_ptr(&self) -> *mut u8 { 600 // Return our allocated memory, offset by one, so that the base address 601 // of memory is always unaligned. 602 self.src[1..].as_ptr() as *mut _ 603 } 604 } 605 606 include!(concat!(env!("OUT_DIR"), "/spectests.rs")); 607 608 /// A spec test from the upstream wast testsuite, arbitrarily chosen from the 609 /// list of known spec tests. 610 #[derive(Debug)] 611 pub struct SpecTest { 612 /// The filename of the spec test 613 pub file: &'static str, 614 /// The `*.wast` contents of the spec test 615 pub contents: &'static str, 616 } 617 618 impl<'a> Arbitrary<'a> for SpecTest { 619 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 620 // NB: this does get a uniform value in the provided range. 621 let i = u.int_in_range(0..=FILES.len() - 1)?; 622 let (file, contents) = FILES[i]; 623 Ok(SpecTest { file, contents }) 624 } 625 626 fn size_hint(_depth: usize) -> (usize, Option<usize>) { 627 (1, Some(std::mem::size_of::<usize>())) 628 } 629 } 630 631 /// Default module-level configuration for fuzzing Wasmtime. 632 /// 633 /// Internally this uses `wasm-smith`'s own `SwarmConfig` but we further refine 634 /// the defaults here as well. 635 #[derive(Debug, Clone)] 636 pub struct ModuleConfig { 637 #[allow(missing_docs)] 638 pub config: SwarmConfig, 639 } 640 641 impl<'a> Arbitrary<'a> for ModuleConfig { 642 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<ModuleConfig> { 643 let mut config = SwarmConfig::arbitrary(u)?; 644 645 // Allow multi-memory by default. 646 config.max_memories = config.max_memories.max(2); 647 648 // Allow multi-table by default. 649 config.max_tables = config.max_tables.max(4); 650 651 // Allow enabling some various wasm proposals by default. Note that 652 // these are all unconditionally turned off even with 653 // `SwarmConfig::arbitrary`. 654 config.memory64_enabled = u.arbitrary()?; 655 656 // Allow the threads proposal if memory64 is not already enabled. FIXME: 657 // to allow threads and memory64 to coexist, see 658 // https://github.com/bytecodealliance/wasmtime/issues/4267. 659 config.threads_enabled = !config.memory64_enabled && u.arbitrary()?; 660 661 Ok(ModuleConfig { config }) 662 } 663 } 664 665 #[derive(Clone, Debug, Eq, Hash, PartialEq)] 666 enum CodegenSettings { 667 Native, 668 #[allow(dead_code)] 669 Target { 670 target: String, 671 flags: Vec<(String, String)>, 672 }, 673 } 674 675 impl CodegenSettings { 676 fn configure(&self, config: &mut wasmtime::Config) { 677 match self { 678 CodegenSettings::Native => {} 679 CodegenSettings::Target { target, flags } => { 680 config.target(target).unwrap(); 681 for (key, value) in flags { 682 unsafe { 683 config.cranelift_flag_set(key, value); 684 } 685 } 686 } 687 } 688 } 689 } 690 691 impl<'a> Arbitrary<'a> for CodegenSettings { 692 #[allow(unused_macros, unused_variables)] 693 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 694 // Helper macro to enable clif features based on what the native host 695 // supports. If the input says to enable a feature and the host doesn't 696 // support it then that test case is rejected with a warning. 697 // 698 // Note that this specifically consumes bytes from the fuzz input for 699 // features for all targets, discarding anything which isn't applicable 700 // to the current target. The theory behind this is that most fuzz bugs 701 // won't be related to this feature selection so by consistently 702 // consuming input irrespective of the current platform reproducing fuzz 703 // bugs should be easier between different architectures. 704 macro_rules! target_features { 705 ( 706 $( 707 $arch:tt => { 708 test:$test:ident, 709 $(std: $std:tt => clif: $clif:tt $(ratio: $a:tt in $b:tt)?,)* 710 }, 711 )* 712 ) => ({ 713 let mut flags = Vec::new(); 714 $( // for each `$arch` 715 $( // for each `$std`/`$clif` pair 716 // Use the input to generate whether `$clif` will be 717 // enabled. By default this is a 1 in 2 chance but each 718 // feature supports a custom ratio as well which shadows 719 // the (low, hi) 720 let (low, hi) = (1, 2); 721 $(let (low, hi) = ($a, $b);)? 722 let enable = u.ratio(low, hi)?; 723 724 // If we're actually on the relevant platform and the 725 // feature is enabled be sure to check that this host 726 // supports it. If the host doesn't support it then 727 // print a warning and return an error because this fuzz 728 // input must be discarded. 729 #[cfg(target_arch = $arch)] 730 if enable && !std::arch::$test!($std) { 731 log::warn!("want to enable clif `{}` but host doesn't support it", 732 $clif); 733 return Err(arbitrary::Error::EmptyChoose) 734 } 735 736 // And finally actually push the feature into the set of 737 // flags to enable, but only if we're on the right 738 // architecture. 739 if cfg!(target_arch = $arch) { 740 flags.push(( 741 $clif.to_string(), 742 enable.to_string(), 743 )); 744 } 745 )* 746 )* 747 flags 748 }) 749 } 750 if u.ratio(1, 10)? { 751 let flags = target_features! { 752 "x86_64" => { 753 test: is_x86_feature_detected, 754 755 // These features are considered to be baseline required by 756 // Wasmtime. Currently some SIMD code generation will 757 // fail if these features are disabled, so unconditionally 758 // enable them as we're not interested in fuzzing without 759 // them. 760 std:"sse3" => clif:"has_sse3" ratio: 1 in 1, 761 std:"ssse3" => clif:"has_ssse3" ratio: 1 in 1, 762 std:"sse4.1" => clif:"has_sse41" ratio: 1 in 1, 763 std:"sse4.2" => clif:"has_sse42" ratio: 1 in 1, 764 765 std:"popcnt" => clif:"has_popcnt", 766 std:"avx" => clif:"has_avx", 767 std:"avx2" => clif:"has_avx2", 768 std:"bmi1" => clif:"has_bmi1", 769 std:"bmi2" => clif:"has_bmi2", 770 std:"lzcnt" => clif:"has_lzcnt", 771 772 // not a lot of of cpus support avx512 so these are weighted 773 // to get enabled much less frequently. 774 std:"avx512bitalg" => clif:"has_avx512bitalg" ratio:1 in 1000, 775 std:"avx512dq" => clif:"has_avx512dq" ratio: 1 in 1000, 776 std:"avx512f" => clif:"has_avx512f" ratio: 1 in 1000, 777 std:"avx512vl" => clif:"has_avx512vl" ratio: 1 in 1000, 778 std:"avx512vbmi" => clif:"has_avx512vbmi" ratio: 1 in 1000, 779 }, 780 "aarch64" => { 781 test: is_aarch64_feature_detected, 782 783 std: "lse" => clif: "has_lse", 784 }, 785 }; 786 return Ok(CodegenSettings::Target { 787 target: target_lexicon::Triple::host().to_string(), 788 flags, 789 }); 790 } 791 Ok(CodegenSettings::Native) 792 } 793 } 794