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