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