1 //! Generate a configuration for both Wasmtime and the Wasm module to execute. 2 3 use super::{ 4 CodegenSettings, InstanceAllocationStrategy, MemoryConfig, ModuleConfig, NormalMemoryConfig, 5 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 // Disable the start function for now. 40 // 41 // TODO: should probably allow this after testing it works with the new 42 // differential setup in all engines. 43 config.allow_start_export = false; 44 45 // Make it more likely that there are types available to generate a 46 // function with. 47 config.min_types = 1; 48 config.max_types = config.max_types.max(1); 49 50 // Generate at least one function 51 config.min_funcs = 1; 52 config.max_funcs = config.max_funcs.max(1); 53 54 // Allow a memory to be generated, but don't let it get too large. 55 // Additionally require the maximum size to guarantee that the growth 56 // behavior is consistent across engines. 57 config.max_memories = 1; 58 config.max_memory_pages = 10; 59 config.memory_max_size_required = true; 60 61 // If tables are generated make sure they don't get too large to avoid 62 // hitting any engine-specific limit. Additionally ensure that the 63 // maximum size is required to guarantee consistent growth across 64 // engines. 65 // 66 // Note that while reference types are disabled below, only allow one 67 // table. 68 config.max_tables = 1; 69 config.max_table_elements = 1_000; 70 config.table_max_size_required = true; 71 72 // Don't allow any imports 73 config.max_imports = 0; 74 75 // Try to get the function and the memory exported 76 config.export_everything = true; 77 78 // NaN is canonicalized at the wasm level for differential fuzzing so we 79 // can paper over NaN differences between engines. 80 config.canonicalize_nans = true; 81 82 // If using the pooling allocator, update the instance limits too 83 if let InstanceAllocationStrategy::Pooling { 84 instance_limits: limits, 85 .. 86 } = &mut self.wasmtime.strategy 87 { 88 // One single-page memory 89 limits.memories = 1; 90 limits.memory_pages = 10; 91 92 limits.tables = 1; 93 limits.table_elements = 1_000; 94 95 limits.size = 1_000_000; 96 97 match &mut self.wasmtime.memory_config { 98 MemoryConfig::Normal(config) => { 99 config.static_memory_maximum_size = Some(limits.memory_pages * 0x10000); 100 } 101 MemoryConfig::CustomUnaligned => unreachable!(), // Arbitrary impl for `Config` should have prevented this 102 } 103 } 104 } 105 106 /// Uses this configuration and the supplied source of data to generate 107 /// a wasm module. 108 /// 109 /// If a `default_fuel` is provided, the resulting module will be configured 110 /// to ensure termination; as doing so will add an additional global to the module, 111 /// the pooling allocator, if configured, will also have its globals limit updated. 112 pub fn generate( 113 &mut self, 114 input: &mut Unstructured<'_>, 115 default_fuel: Option<u32>, 116 ) -> arbitrary::Result<wasm_smith::Module> { 117 self.module_config.generate(input, default_fuel) 118 } 119 120 /// Indicates that this configuration should be spec-test-compliant, 121 /// disabling various features the spec tests assert are disabled. 122 pub fn set_spectest_compliant(&mut self) { 123 let config = &mut self.module_config.config; 124 config.memory64_enabled = false; 125 config.bulk_memory_enabled = true; 126 config.reference_types_enabled = true; 127 config.multi_value_enabled = true; 128 config.simd_enabled = true; 129 config.threads_enabled = false; 130 config.max_memories = 1; 131 config.max_tables = 5; 132 133 if let InstanceAllocationStrategy::Pooling { 134 instance_limits: limits, 135 .. 136 } = &mut self.wasmtime.strategy 137 { 138 // Configure the lower bound of a number of limits to what's 139 // required to actually run the spec tests. Fuzz-generated inputs 140 // may have limits less than these thresholds which would cause the 141 // spec tests to fail which isn't particularly interesting. 142 limits.memories = limits.memories.max(1); 143 limits.tables = limits.memories.max(5); 144 limits.table_elements = limits.memories.max(1_000); 145 limits.memory_pages = limits.memory_pages.max(900); 146 limits.count = limits.count.max(500); 147 limits.size = limits.size.max(64 * 1024); 148 149 match &mut self.wasmtime.memory_config { 150 MemoryConfig::Normal(config) => { 151 config.static_memory_maximum_size = Some(limits.memory_pages * 0x10000); 152 } 153 MemoryConfig::CustomUnaligned => unreachable!(), // Arbitrary impl for `Config` should have prevented this 154 } 155 } 156 } 157 158 /// Converts this to a `wasmtime::Config` object 159 pub fn to_wasmtime(&self) -> wasmtime::Config { 160 crate::init_fuzzing(); 161 log::debug!("creating wasmtime config with {:#?}", self.wasmtime); 162 163 let mut cfg = wasmtime::Config::new(); 164 cfg.wasm_bulk_memory(true) 165 .wasm_reference_types(true) 166 .wasm_multi_value(self.module_config.config.multi_value_enabled) 167 .wasm_multi_memory(self.module_config.config.max_memories > 1) 168 .wasm_simd(self.module_config.config.simd_enabled) 169 .wasm_memory64(self.module_config.config.memory64_enabled) 170 .wasm_threads(self.module_config.config.threads_enabled) 171 .native_unwind_info(self.wasmtime.native_unwind_info) 172 .cranelift_nan_canonicalization(self.wasmtime.canonicalize_nans) 173 .cranelift_opt_level(self.wasmtime.opt_level.to_wasmtime()) 174 .consume_fuel(self.wasmtime.consume_fuel) 175 .epoch_interruption(self.wasmtime.epoch_interruption) 176 .memory_init_cow(self.wasmtime.memory_init_cow) 177 .memory_guaranteed_dense_image_size(std::cmp::min( 178 // Clamp this at 16MiB so we don't get huge in-memory 179 // images during fuzzing. 180 16 << 20, 181 self.wasmtime.memory_guaranteed_dense_image_size, 182 )) 183 .allocation_strategy(self.wasmtime.strategy.to_wasmtime()) 184 .generate_address_map(self.wasmtime.generate_address_map); 185 186 self.wasmtime.codegen.configure(&mut cfg); 187 188 // If the wasm-smith-generated module use nan canonicalization then we 189 // don't need to enable it, but if it doesn't enable it already then we 190 // enable this codegen option. 191 cfg.cranelift_nan_canonicalization(!self.module_config.config.canonicalize_nans); 192 193 // Enabling the verifier will at-least-double compilation time, which 194 // with a 20-30x slowdown in fuzzing can cause issues related to 195 // timeouts. If generated modules can have more than a small handful of 196 // functions then disable the verifier when fuzzing to try to lessen the 197 // impact of timeouts. 198 if self.module_config.config.max_funcs > 10 { 199 cfg.cranelift_debug_verifier(false); 200 } 201 202 if self.wasmtime.force_jump_veneers { 203 unsafe { 204 cfg.cranelift_flag_set("wasmtime_linkopt_force_jump_veneer", "true"); 205 } 206 } 207 208 if let Some(pad) = self.wasmtime.padding_between_functions { 209 unsafe { 210 cfg.cranelift_flag_set( 211 "wasmtime_linkopt_padding_between_functions", 212 &pad.to_string(), 213 ); 214 } 215 } 216 217 // Vary the memory configuration, but only if threads are not enabled. 218 // When the threads proposal is enabled we might generate shared memory, 219 // which is less amenable to different memory configurations: 220 // - shared memories are required to be "static" so fuzzing the various 221 // memory configurations will mostly result in uninteresting errors. 222 // The interesting part about shared memories is the runtime so we 223 // don't fuzz non-default settings. 224 // - shared memories are required to be aligned which means that the 225 // `CustomUnaligned` variant isn't actually safe to use with a shared 226 // memory. 227 if !self.module_config.config.threads_enabled { 228 match &self.wasmtime.memory_config { 229 MemoryConfig::Normal(memory_config) => { 230 cfg.static_memory_maximum_size( 231 memory_config.static_memory_maximum_size.unwrap_or(0), 232 ) 233 .static_memory_guard_size(memory_config.static_memory_guard_size.unwrap_or(0)) 234 .dynamic_memory_guard_size(memory_config.dynamic_memory_guard_size.unwrap_or(0)) 235 .guard_before_linear_memory(memory_config.guard_before_linear_memory); 236 } 237 MemoryConfig::CustomUnaligned => { 238 cfg.with_host_memory(Arc::new(UnalignedMemoryCreator)) 239 .static_memory_maximum_size(0) 240 .dynamic_memory_guard_size(0) 241 .static_memory_guard_size(0) 242 .guard_before_linear_memory(false); 243 } 244 } 245 } 246 247 return cfg; 248 } 249 250 /// Convenience function for generating a `Store<T>` using this 251 /// configuration. 252 pub fn to_store(&self) -> Store<StoreLimits> { 253 let engine = Engine::new(&self.to_wasmtime()).unwrap(); 254 let mut store = Store::new(&engine, StoreLimits::new()); 255 self.configure_store(&mut store); 256 store 257 } 258 259 /// Configures a store based on this configuration. 260 pub fn configure_store(&self, store: &mut Store<StoreLimits>) { 261 store.limiter(|s| s as &mut dyn wasmtime::ResourceLimiter); 262 if self.wasmtime.consume_fuel { 263 store.add_fuel(u64::max_value()).unwrap(); 264 } 265 if self.wasmtime.epoch_interruption { 266 // Without fuzzing of async execution, we can't test the 267 // "update deadline and continue" behavior, but we can at 268 // least test the codegen paths and checks with the 269 // trapping behavior, which works synchronously too. We'll 270 // set the deadline one epoch tick in the future; then 271 // this works exactly like an interrupt flag. We expect no 272 // traps/interrupts unless we bump the epoch, which we do 273 // as one particular Timeout mode (`Timeout::Epoch`). 274 store.epoch_deadline_trap(); 275 store.set_epoch_deadline(1); 276 } 277 } 278 279 /// Generates an arbitrary method of timing out an instance, ensuring that 280 /// this configuration supports the returned timeout. 281 pub fn generate_timeout(&mut self, u: &mut Unstructured<'_>) -> arbitrary::Result<Timeout> { 282 let time_duration = Duration::from_secs(20); 283 let timeout = u 284 .choose(&[Timeout::Fuel(100_000), Timeout::Epoch(time_duration)])? 285 .clone(); 286 match &timeout { 287 Timeout::Fuel(..) => { 288 self.wasmtime.consume_fuel = true; 289 } 290 Timeout::Epoch(..) => { 291 self.wasmtime.epoch_interruption = true; 292 } 293 Timeout::None => unreachable!("Not an option given to choose()"), 294 } 295 Ok(timeout) 296 } 297 298 /// Compiles the `wasm` within the `engine` provided. 299 /// 300 /// This notably will use `Module::{serialize,deserialize_file}` to 301 /// round-trip if configured in the fuzzer. 302 pub fn compile(&self, engine: &Engine, wasm: &[u8]) -> Result<Module> { 303 // Propagate this error in case the caller wants to handle 304 // valid-vs-invalid wasm. 305 let module = Module::new(engine, wasm)?; 306 if !self.wasmtime.use_precompiled_cwasm { 307 return Ok(module); 308 } 309 310 // Don't propagate these errors to prevent them from accidentally being 311 // interpreted as invalid wasm, these should never fail on a 312 // well-behaved host system. 313 let file = tempfile::NamedTempFile::new().unwrap(); 314 std::fs::write(file.path(), module.serialize().unwrap()).unwrap(); 315 unsafe { Ok(Module::deserialize_file(engine, file.path()).unwrap()) } 316 } 317 } 318 319 impl<'a> Arbitrary<'a> for Config { 320 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { 321 let mut config = Self { 322 wasmtime: u.arbitrary()?, 323 module_config: u.arbitrary()?, 324 }; 325 326 // If using the pooling allocator, constrain the memory and module configurations 327 // to the module limits. 328 if let InstanceAllocationStrategy::Pooling { 329 instance_limits: limits, 330 .. 331 } = &config.wasmtime.strategy 332 { 333 // If the pooling allocator is used, do not allow shared memory to 334 // be created. FIXME: see 335 // https://github.com/bytecodealliance/wasmtime/issues/4244. 336 config.module_config.config.threads_enabled = false; 337 338 // Force the use of a normal memory config when using the pooling allocator and 339 // limit the static memory maximum to be the same as the pooling allocator's memory 340 // page limit. 341 config.wasmtime.memory_config = match config.wasmtime.memory_config { 342 MemoryConfig::Normal(mut config) => { 343 config.static_memory_maximum_size = Some(limits.memory_pages * 0x10000); 344 MemoryConfig::Normal(config) 345 } 346 MemoryConfig::CustomUnaligned => { 347 let mut config: NormalMemoryConfig = u.arbitrary()?; 348 config.static_memory_maximum_size = Some(limits.memory_pages * 0x10000); 349 MemoryConfig::Normal(config) 350 } 351 }; 352 353 let cfg = &mut config.module_config.config; 354 cfg.max_memories = limits.memories as usize; 355 cfg.max_tables = limits.tables as usize; 356 cfg.max_memory_pages = limits.memory_pages; 357 358 // Force no aliases in any generated modules as they might count against the 359 // import limits above. 360 cfg.max_aliases = 0; 361 } 362 363 Ok(config) 364 } 365 } 366 367 /// Configuration related to `wasmtime::Config` and the various settings which 368 /// can be tweaked from within. 369 #[derive(Arbitrary, Clone, Debug, Eq, Hash, PartialEq)] 370 pub struct WasmtimeConfig { 371 opt_level: OptLevel, 372 debug_info: bool, 373 canonicalize_nans: bool, 374 interruptable: bool, 375 pub(crate) consume_fuel: bool, 376 epoch_interruption: bool, 377 /// The Wasmtime memory configuration to use. 378 pub memory_config: MemoryConfig, 379 force_jump_veneers: bool, 380 memory_init_cow: bool, 381 memory_guaranteed_dense_image_size: u64, 382 use_precompiled_cwasm: bool, 383 /// Configuration for the instance allocation strategy to use. 384 pub strategy: InstanceAllocationStrategy, 385 codegen: CodegenSettings, 386 padding_between_functions: Option<u16>, 387 generate_address_map: bool, 388 native_unwind_info: bool, 389 } 390 391 impl WasmtimeConfig { 392 /// Force `self` to be a configuration compatible with `other`. This is 393 /// useful for differential execution to avoid unhelpful fuzz crashes when 394 /// one engine has a feature enabled and the other does not. 395 pub fn make_compatible_with(&mut self, other: &Self) { 396 // Use the same allocation strategy between the two configs. 397 // 398 // Ideally this wouldn't be necessary, but, during differential 399 // evaluation, if the `lhs` is using ondemand and the `rhs` is using the 400 // pooling allocator (or vice versa), then the module may have been 401 // generated in such a way that is incompatible with the other 402 // allocation strategy. 403 // 404 // We can remove this in the future when it's possible to access the 405 // fields of `wasm_smith::Module` to constrain the pooling allocator 406 // based on what was actually generated. 407 self.strategy = other.strategy.clone(); 408 if let InstanceAllocationStrategy::Pooling { .. } = &other.strategy { 409 // Also use the same memory configuration when using the pooling 410 // allocator. 411 self.memory_config = other.memory_config.clone(); 412 } 413 } 414 } 415 416 #[derive(Arbitrary, Clone, Debug, PartialEq, Eq, Hash)] 417 enum OptLevel { 418 None, 419 Speed, 420 SpeedAndSize, 421 } 422 423 impl OptLevel { 424 fn to_wasmtime(&self) -> wasmtime::OptLevel { 425 match self { 426 OptLevel::None => wasmtime::OptLevel::None, 427 OptLevel::Speed => wasmtime::OptLevel::Speed, 428 OptLevel::SpeedAndSize => wasmtime::OptLevel::SpeedAndSize, 429 } 430 } 431 } 432