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