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