1 //! Generate a configuration for both Wasmtime and the Wasm module to execute. 2 3 use super::{ 4 AsyncConfig, CodegenSettings, InstanceAllocationStrategy, MemoryConfig, ModuleConfig, 5 NormalMemoryConfig, UnalignedMemoryCreator, 6 }; 7 use crate::oracles::{StoreLimits, Timeout}; 8 use anyhow::Result; 9 use arbitrary::{Arbitrary, Unstructured}; 10 use std::sync::Arc; 11 use std::time::Duration; 12 use wasmtime::{Engine, Module, Store}; 13 14 /// Configuration for `wasmtime::Config` and generated modules for a session of 15 /// fuzzing. 16 /// 17 /// This configuration guides what modules are generated, how wasmtime 18 /// configuration is generated, and is typically itself generated through a call 19 /// to `Arbitrary` which allows for a form of "swarm testing". 20 #[derive(Debug, Clone)] 21 pub struct Config { 22 /// Configuration related to the `wasmtime::Config`. 23 pub wasmtime: WasmtimeConfig, 24 /// Configuration related to generated modules. 25 pub module_config: ModuleConfig, 26 } 27 28 impl Config { 29 /// Indicates that this configuration is being used for differential 30 /// execution. 31 /// 32 /// The purpose of this function is to update the configuration which was 33 /// generated to be compatible with execution in multiple engines. The goal 34 /// is to produce the exact same result in all engines so we need to paper 35 /// over things like nan differences and memory/table behavior differences. 36 pub fn set_differential_config(&mut self) { 37 let config = &mut self.module_config.config; 38 39 // Make it more likely that there are types available to generate a 40 // function with. 41 config.min_types = config.min_types.max(1); 42 config.max_types = config.max_types.max(1); 43 44 // Generate at least one function 45 config.min_funcs = config.min_funcs.max(1); 46 config.max_funcs = config.max_funcs.max(1); 47 48 // Allow a memory to be generated, but don't let it get too large. 49 // Additionally require the maximum size to guarantee that the growth 50 // behavior is consistent across engines. 51 config.max_memory32_pages = 10; 52 config.max_memory64_pages = 10; 53 config.memory_max_size_required = true; 54 55 // If tables are generated make sure they don't get too large to avoid 56 // hitting any engine-specific limit. Additionally ensure that the 57 // maximum size is required to guarantee consistent growth across 58 // engines. 59 // 60 // Note that while reference types are disabled below, only allow one 61 // table. 62 config.max_table_elements = 1_000; 63 config.table_max_size_required = true; 64 65 // Don't allow any imports 66 config.max_imports = 0; 67 68 // Try to get the function and the memory exported 69 config.export_everything = true; 70 71 // NaN is canonicalized at the wasm level for differential fuzzing so we 72 // can paper over NaN differences between engines. 73 config.canonicalize_nans = true; 74 75 // If using the pooling allocator, update the instance limits too 76 if let InstanceAllocationStrategy::Pooling(pooling) = &mut self.wasmtime.strategy { 77 // One single-page memory 78 pooling.total_memories = config.max_memories as u32; 79 pooling.memory_pages = 10; 80 pooling.max_memories_per_module = config.max_memories as u32; 81 82 pooling.total_tables = config.max_tables as u32; 83 pooling.table_elements = 1_000; 84 pooling.max_tables_per_module = config.max_tables as u32; 85 86 pooling.core_instance_size = 1_000_000; 87 } 88 } 89 90 /// Uses this configuration and the supplied source of data to generate 91 /// a wasm module. 92 /// 93 /// If a `default_fuel` is provided, the resulting module will be configured 94 /// to ensure termination; as doing so will add an additional global to the module, 95 /// the pooling allocator, if configured, will also have its globals limit updated. 96 pub fn generate( 97 &self, 98 input: &mut Unstructured<'_>, 99 default_fuel: Option<u32>, 100 ) -> arbitrary::Result<wasm_smith::Module> { 101 self.module_config.generate(input, default_fuel) 102 } 103 104 /// Tests whether this configuration is capable of running all wast tests. 105 pub fn is_wast_test_compliant(&self) -> bool { 106 let config = &self.module_config.config; 107 108 // Check for wasm features that must be disabled to run spec tests 109 if config.memory64_enabled { 110 return false; 111 } 112 113 // Check for wasm features that must be enabled to run spec tests 114 if !config.bulk_memory_enabled 115 || !config.reference_types_enabled 116 || !config.multi_value_enabled 117 || !config.simd_enabled 118 || !config.threads_enabled 119 || config.max_memories <= 1 120 { 121 return false; 122 } 123 124 // Make sure the runtime limits allow for the instantiation of all spec 125 // tests. Note that the max memories must be precisely one since 0 won't 126 // instantiate spec tests and more than one is multi-memory which is 127 // disabled for spec tests. 128 if config.max_memories != 1 || config.max_tables < 5 { 129 return false; 130 } 131 132 if let InstanceAllocationStrategy::Pooling(pooling) = &self.wasmtime.strategy { 133 // Check to see if any item limit is less than the required 134 // threshold to execute the spec tests. 135 if pooling.total_memories < 1 136 || pooling.total_tables < 5 137 || pooling.table_elements < 1_000 138 || pooling.memory_pages < 900 139 || pooling.total_core_instances < 500 140 || pooling.core_instance_size < 64 * 1024 141 { 142 return false; 143 } 144 } 145 146 true 147 } 148 149 /// Converts this to a `wasmtime::Config` object 150 pub fn to_wasmtime(&self) -> wasmtime::Config { 151 crate::init_fuzzing(); 152 log::debug!("creating wasmtime config with {:#?}", self.wasmtime); 153 154 let mut cfg = wasmtime::Config::new(); 155 cfg.wasm_bulk_memory(true) 156 .wasm_reference_types(true) 157 .wasm_multi_value(self.module_config.config.multi_value_enabled) 158 .wasm_multi_memory(self.module_config.config.max_memories > 1) 159 .wasm_simd(self.module_config.config.simd_enabled) 160 .wasm_memory64(self.module_config.config.memory64_enabled) 161 .wasm_tail_call(self.module_config.config.tail_call_enabled) 162 .wasm_threads(self.module_config.config.threads_enabled) 163 .native_unwind_info(cfg!(target_os = "windows") || self.wasmtime.native_unwind_info) 164 .cranelift_nan_canonicalization(self.wasmtime.canonicalize_nans) 165 .cranelift_opt_level(self.wasmtime.opt_level.to_wasmtime()) 166 .consume_fuel(self.wasmtime.consume_fuel) 167 .epoch_interruption(self.wasmtime.epoch_interruption) 168 .memory_init_cow(self.wasmtime.memory_init_cow) 169 .memory_guaranteed_dense_image_size(std::cmp::min( 170 // Clamp this at 16MiB so we don't get huge in-memory 171 // images during fuzzing. 172 16 << 20, 173 self.wasmtime.memory_guaranteed_dense_image_size, 174 )) 175 .allocation_strategy(self.wasmtime.strategy.to_wasmtime()) 176 .generate_address_map(self.wasmtime.generate_address_map); 177 178 if !self.module_config.config.simd_enabled { 179 cfg.wasm_relaxed_simd(false); 180 } 181 182 let compiler_strategy = &self.wasmtime.compiler_strategy; 183 let cranelift_strategy = *compiler_strategy == CompilerStrategy::Cranelift; 184 cfg.strategy(self.wasmtime.compiler_strategy.to_wasmtime()); 185 186 self.wasmtime.codegen.configure(&mut cfg); 187 188 // Determine whether we will actually enable PCC -- this is 189 // disabled if the module erquires memory64, which is not yet 190 // compatible (due to the need for dynamic checks). 191 let pcc = cfg!(feature = "fuzz-pcc") 192 && self.wasmtime.pcc 193 && !self.module_config.config.memory64_enabled; 194 195 // Only set cranelift specific flags when the Cranelift strategy is 196 // chosen. 197 if cranelift_strategy { 198 // If the wasm-smith-generated module use nan canonicalization then we 199 // don't need to enable it, but if it doesn't enable it already then we 200 // enable this codegen option. 201 cfg.cranelift_nan_canonicalization(!self.module_config.config.canonicalize_nans); 202 203 // Enabling the verifier will at-least-double compilation time, which 204 // with a 20-30x slowdown in fuzzing can cause issues related to 205 // timeouts. If generated modules can have more than a small handful of 206 // functions then disable the verifier when fuzzing to try to lessen the 207 // impact of timeouts. 208 if self.module_config.config.max_funcs > 10 { 209 cfg.cranelift_debug_verifier(false); 210 } 211 212 if self.wasmtime.force_jump_veneers { 213 unsafe { 214 cfg.cranelift_flag_set("wasmtime_linkopt_force_jump_veneer", "true"); 215 } 216 } 217 218 if let Some(pad) = self.wasmtime.padding_between_functions { 219 unsafe { 220 cfg.cranelift_flag_set( 221 "wasmtime_linkopt_padding_between_functions", 222 &pad.to_string(), 223 ); 224 } 225 } 226 227 cfg.cranelift_pcc(pcc); 228 } 229 230 self.wasmtime.async_config.configure(&mut cfg); 231 232 // Vary the memory configuration, but only if threads are not enabled. 233 // When the threads proposal is enabled we might generate shared memory, 234 // which is less amenable to different memory configurations: 235 // - shared memories are required to be "static" so fuzzing the various 236 // memory configurations will mostly result in uninteresting errors. 237 // The interesting part about shared memories is the runtime so we 238 // don't fuzz non-default settings. 239 // - shared memories are required to be aligned which means that the 240 // `CustomUnaligned` variant isn't actually safe to use with a shared 241 // memory. 242 if !self.module_config.config.threads_enabled { 243 // If PCC is enabled, force other options to be compatible: PCC is currently only 244 // supported when bounds checks are elided. 245 let memory_config = if pcc { 246 MemoryConfig::Normal(NormalMemoryConfig { 247 static_memory_maximum_size: Some(4 << 30), // 4 GiB 248 static_memory_guard_size: Some(2 << 30), // 2 GiB 249 dynamic_memory_guard_size: Some(0), 250 guard_before_linear_memory: false, 251 }) 252 } else { 253 self.wasmtime.memory_config.clone() 254 }; 255 256 match &memory_config { 257 MemoryConfig::Normal(memory_config) => { 258 cfg.static_memory_maximum_size( 259 memory_config.static_memory_maximum_size.unwrap_or(0), 260 ) 261 .static_memory_guard_size(memory_config.static_memory_guard_size.unwrap_or(0)) 262 .dynamic_memory_guard_size(memory_config.dynamic_memory_guard_size.unwrap_or(0)) 263 .guard_before_linear_memory(memory_config.guard_before_linear_memory); 264 } 265 MemoryConfig::CustomUnaligned => { 266 cfg.with_host_memory(Arc::new(UnalignedMemoryCreator)) 267 .static_memory_maximum_size(0) 268 .dynamic_memory_guard_size(0) 269 .static_memory_guard_size(0) 270 .guard_before_linear_memory(false); 271 } 272 } 273 } 274 275 return cfg; 276 } 277 278 /// Convenience function for generating a `Store<T>` using this 279 /// configuration. 280 pub fn to_store(&self) -> Store<StoreLimits> { 281 let engine = Engine::new(&self.to_wasmtime()).unwrap(); 282 let mut store = Store::new(&engine, StoreLimits::new()); 283 self.configure_store(&mut store); 284 store 285 } 286 287 /// Configures a store based on this configuration. 288 pub fn configure_store(&self, store: &mut Store<StoreLimits>) { 289 store.limiter(|s| s as &mut dyn wasmtime::ResourceLimiter); 290 match self.wasmtime.async_config { 291 AsyncConfig::Disabled => { 292 if self.wasmtime.consume_fuel { 293 store.set_fuel(u64::MAX).unwrap(); 294 } 295 if self.wasmtime.epoch_interruption { 296 store.epoch_deadline_trap(); 297 store.set_epoch_deadline(1); 298 } 299 } 300 AsyncConfig::YieldWithFuel(amt) => { 301 assert!(self.wasmtime.consume_fuel); 302 store.fuel_async_yield_interval(Some(amt)).unwrap(); 303 store.set_fuel(amt).unwrap(); 304 } 305 AsyncConfig::YieldWithEpochs { ticks, .. } => { 306 assert!(self.wasmtime.epoch_interruption); 307 store.set_epoch_deadline(ticks); 308 store.epoch_deadline_async_yield_and_update(ticks); 309 } 310 } 311 } 312 313 /// Generates an arbitrary method of timing out an instance, ensuring that 314 /// this configuration supports the returned timeout. 315 pub fn generate_timeout(&mut self, u: &mut Unstructured<'_>) -> arbitrary::Result<Timeout> { 316 let time_duration = Duration::from_millis(100); 317 let timeout = u 318 .choose(&[Timeout::Fuel(100_000), Timeout::Epoch(time_duration)])? 319 .clone(); 320 match &timeout { 321 Timeout::Fuel(..) => { 322 self.wasmtime.consume_fuel = true; 323 } 324 Timeout::Epoch(..) => { 325 self.wasmtime.epoch_interruption = true; 326 } 327 Timeout::None => unreachable!("Not an option given to choose()"), 328 } 329 Ok(timeout) 330 } 331 332 /// Compiles the `wasm` within the `engine` provided. 333 /// 334 /// This notably will use `Module::{serialize,deserialize_file}` to 335 /// round-trip if configured in the fuzzer. 336 pub fn compile(&self, engine: &Engine, wasm: &[u8]) -> Result<Module> { 337 // Propagate this error in case the caller wants to handle 338 // valid-vs-invalid wasm. 339 let module = Module::new(engine, wasm)?; 340 if !self.wasmtime.use_precompiled_cwasm { 341 return Ok(module); 342 } 343 344 // Don't propagate these errors to prevent them from accidentally being 345 // interpreted as invalid wasm, these should never fail on a 346 // well-behaved host system. 347 let dir = tempfile::TempDir::new().unwrap(); 348 let file = dir.path().join("module.wasm"); 349 std::fs::write(&file, module.serialize().unwrap()).unwrap(); 350 unsafe { Ok(Module::deserialize_file(engine, &file).unwrap()) } 351 } 352 353 /// Winch doesn't support the same set of wasm proposal as Cranelift at 354 /// this time, so if winch is selected be sure to disable wasm proposals 355 /// in `Config` to ensure that Winch can compile the module that 356 /// wasm-smith generates. 357 pub fn disable_unimplemented_winch_proposals(&mut self) { 358 self.module_config.config.simd_enabled = false; 359 self.module_config.config.relaxed_simd_enabled = false; 360 self.module_config.config.gc_enabled = false; 361 self.module_config.config.threads_enabled = false; 362 self.module_config.config.tail_call_enabled = false; 363 self.module_config.config.exceptions_enabled = false; 364 self.module_config.config.reference_types_enabled = false; 365 } 366 367 /// Updates this configuration to forcibly enable async support. Only useful 368 /// in fuzzers which do async calls. 369 pub fn enable_async(&mut self, u: &mut Unstructured<'_>) -> arbitrary::Result<()> { 370 if self.wasmtime.consume_fuel || u.arbitrary()? { 371 self.wasmtime.async_config = 372 AsyncConfig::YieldWithFuel(u.int_in_range(1000..=100_000)?); 373 self.wasmtime.consume_fuel = true; 374 } else { 375 self.wasmtime.async_config = AsyncConfig::YieldWithEpochs { 376 dur: Duration::from_millis(u.int_in_range(1..=10)?), 377 ticks: u.int_in_range(1..=10)?, 378 }; 379 self.wasmtime.epoch_interruption = true; 380 } 381 Ok(()) 382 } 383 } 384 385 impl<'a> Arbitrary<'a> for Config { 386 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 387 let mut config = Self { 388 wasmtime: u.arbitrary()?, 389 module_config: u.arbitrary()?, 390 }; 391 392 if let CompilerStrategy::Winch = config.wasmtime.compiler_strategy { 393 config.disable_unimplemented_winch_proposals(); 394 } 395 396 // This is pulled from `u` by default via `wasm-smith`, but Wasmtime 397 // doesn't implement this yet, so forcibly always disable it. 398 config.module_config.config.tail_call_enabled = false; 399 400 // If using the pooling allocator, constrain the memory and module configurations 401 // to the module limits. 402 if let InstanceAllocationStrategy::Pooling(pooling) = &mut config.wasmtime.strategy { 403 let cfg = &mut config.module_config.config; 404 // If the pooling allocator is used, do not allow shared memory to 405 // be created. FIXME: see 406 // https://github.com/bytecodealliance/wasmtime/issues/4244. 407 cfg.threads_enabled = false; 408 409 // Ensure the pooling allocator can support the maximal size of 410 // memory, picking the smaller of the two to win. 411 let min = cfg 412 .max_memory32_pages 413 .min(cfg.max_memory64_pages) 414 .min(pooling.memory_pages); 415 pooling.memory_pages = min; 416 cfg.max_memory32_pages = min; 417 cfg.max_memory64_pages = min; 418 419 // If traps are disallowed then memories must have at least one page 420 // of memory so if we still are only allowing 0 pages of memory then 421 // increase that to one here. 422 if cfg.disallow_traps { 423 if pooling.memory_pages == 0 { 424 pooling.memory_pages = 1; 425 cfg.max_memory32_pages = 1; 426 cfg.max_memory64_pages = 1; 427 } 428 // .. additionally update tables 429 if pooling.table_elements == 0 { 430 pooling.table_elements = 1; 431 } 432 } 433 434 // Forcibly don't use the `CustomUnaligned` memory configuration 435 // with the pooling allocator active. 436 if let MemoryConfig::CustomUnaligned = config.wasmtime.memory_config { 437 config.wasmtime.memory_config = MemoryConfig::Normal(u.arbitrary()?); 438 } 439 440 // Don't allow too many linear memories per instance since massive 441 // virtual mappings can fail to get allocated. 442 cfg.min_memories = cfg.min_memories.min(10); 443 cfg.max_memories = cfg.max_memories.min(10); 444 445 // Force this pooling allocator to always be able to accommodate the 446 // module that may be generated. 447 pooling.total_memories = cfg.max_memories as u32; 448 pooling.total_tables = cfg.max_tables as u32; 449 } 450 451 Ok(config) 452 } 453 } 454 455 /// Configuration related to `wasmtime::Config` and the various settings which 456 /// can be tweaked from within. 457 #[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)] 458 pub struct WasmtimeConfig { 459 opt_level: OptLevel, 460 debug_info: bool, 461 canonicalize_nans: bool, 462 interruptable: bool, 463 pub(crate) consume_fuel: bool, 464 pub(crate) epoch_interruption: bool, 465 /// The Wasmtime memory configuration to use. 466 pub memory_config: MemoryConfig, 467 force_jump_veneers: bool, 468 memory_init_cow: bool, 469 memory_guaranteed_dense_image_size: u64, 470 use_precompiled_cwasm: bool, 471 /// Configuration for the instance allocation strategy to use. 472 pub strategy: InstanceAllocationStrategy, 473 codegen: CodegenSettings, 474 padding_between_functions: Option<u16>, 475 generate_address_map: bool, 476 native_unwind_info: bool, 477 /// Configuration for the compiler to use. 478 pub compiler_strategy: CompilerStrategy, 479 480 /// Whether or not fuzzing should enable PCC. 481 pcc: bool, 482 483 /// Configuration for whether wasm is invoked in an async fashion and how 484 /// it's cooperatively time-sliced. 485 pub async_config: AsyncConfig, 486 } 487 488 impl WasmtimeConfig { 489 /// Force `self` to be a configuration compatible with `other`. This is 490 /// useful for differential execution to avoid unhelpful fuzz crashes when 491 /// one engine has a feature enabled and the other does not. 492 pub fn make_compatible_with(&mut self, other: &Self) { 493 // Use the same allocation strategy between the two configs. 494 // 495 // Ideally this wouldn't be necessary, but, during differential 496 // evaluation, if the `lhs` is using ondemand and the `rhs` is using the 497 // pooling allocator (or vice versa), then the module may have been 498 // generated in such a way that is incompatible with the other 499 // allocation strategy. 500 // 501 // We can remove this in the future when it's possible to access the 502 // fields of `wasm_smith::Module` to constrain the pooling allocator 503 // based on what was actually generated. 504 self.strategy = other.strategy.clone(); 505 if let InstanceAllocationStrategy::Pooling { .. } = &other.strategy { 506 // Also use the same memory configuration when using the pooling 507 // allocator. 508 self.memory_config = other.memory_config.clone(); 509 } 510 } 511 } 512 513 #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] 514 enum OptLevel { 515 None, 516 Speed, 517 SpeedAndSize, 518 } 519 520 impl OptLevel { 521 fn to_wasmtime(&self) -> wasmtime::OptLevel { 522 match self { 523 OptLevel::None => wasmtime::OptLevel::None, 524 OptLevel::Speed => wasmtime::OptLevel::Speed, 525 OptLevel::SpeedAndSize => wasmtime::OptLevel::SpeedAndSize, 526 } 527 } 528 } 529 530 #[derive(Clone, Debug, PartialEq, Eq, Hash)] 531 /// Compiler to use. 532 pub enum CompilerStrategy { 533 /// Cranelift compiler. 534 Cranelift, 535 /// Winch compiler. 536 Winch, 537 } 538 539 impl CompilerStrategy { 540 fn to_wasmtime(&self) -> wasmtime::Strategy { 541 match self { 542 CompilerStrategy::Cranelift => wasmtime::Strategy::Cranelift, 543 CompilerStrategy::Winch => wasmtime::Strategy::Winch, 544 } 545 } 546 } 547 548 impl Arbitrary<'_> for CompilerStrategy { 549 fn arbitrary(_: &mut Unstructured<'_>) -> arbitrary::Result<Self> { 550 // NB: Winch isn't selected here yet as it doesn't yet implement all the 551 // compiler features for things such as trampolines, so it's only used 552 // on fuzz targets that don't need those trampolines. 553 Ok(Self::Cranelift) 554 } 555 } 556