1 //! Check that we get the same result whether we
2 //!
3 //! 1. Call the initialization function
4 //! 2. Call the main function
5 //!
6 //! or
7 //!
8 //! 1. Call the initialization function
9 //! 2. Snapshot with Wizer
10 //! 3. Instantiate the snapshot
11 //! 4. Call the instantiated snapshot's main function
12 //!
13 //! When checking that we get the same result, we don't just consider the main
14 //! function's results: we also consider memories and globals.
15 
16 #![no_main]
17 
18 use libfuzzer_sys::{
19     arbitrary::{Arbitrary, Unstructured},
20     fuzz_target,
21 };
22 use std::pin::pin;
23 use std::task::{Context, Poll, Waker};
24 use wasm_smith::MemoryOffsetChoices;
25 use wasmtime::*;
26 
27 const FUEL: u32 = 1_000;
28 
29 fuzz_target!(|data: &[u8]| {
30     let _ = env_logger::try_init();
31 
32     let mut u = Unstructured::new(data);
33 
34     let mut config = wasm_smith::Config::arbitrary(&mut u).unwrap();
35     config.max_memories = 10;
36 
37     // We want small memories that are quick to compare, but we also want to
38     // allow memories to grow so we can shake out any memory-growth-related
39     // bugs, so we choose `2` instead of `1`.
40     config.max_memory32_bytes = 2 * 65536;
41     config.max_memory64_bytes = 2 * 65536;
42 
43     // Always generate at least one function that we can hopefully use as an
44     // initialization function.
45     config.min_funcs = 1;
46 
47     config.max_funcs = 10;
48 
49     // Always at least one export, hopefully a function we can use as an
50     // initialization routine.
51     config.min_exports = 1;
52 
53     config.max_exports = 10;
54 
55     // Always use an offset immediate that is within the memory's minimum
56     // size. This should make trapping on loads/stores a little less
57     // frequent.
58     config.memory_offset_choices = MemoryOffsetChoices(1, 0, 0);
59 
60     config.reference_types_enabled = false;
61     config.bulk_memory_enabled = false;
62 
63     // Disable all imports
64     config.min_imports = 0;
65     config.max_imports = 0;
66 
67     let Ok(mut module) = wasm_smith::Module::new(config, &mut u) else {
68         return;
69     };
70     module.ensure_termination(FUEL).unwrap();
71     let wasm = module.to_bytes();
72 
73     if log::log_enabled!(log::Level::Debug) {
74         log::debug!("Writing test case to `test.wasm`");
75         std::fs::write("test.wasm", &wasm).unwrap();
76         if let Ok(wat) = wasmprinter::print_bytes(&wasm) {
77             log::debug!("Writing disassembly to `test.wat`");
78             std::fs::write("test.wat", wat).unwrap();
79         }
80     }
81 
82     let config = Config::new();
83     let engine = Engine::new(&config).unwrap();
84     let module = Module::new(&engine, &wasm).unwrap();
85 
86     let mut main_funcs = vec![];
87     let mut init_funcs = vec![];
88     for exp in module.exports() {
89         if let ExternType::Func(ty) = exp.ty() {
90             main_funcs.push(exp.name());
91             if ty.params().len() == 0 && ty.results().len() == 0 {
92                 init_funcs.push(exp.name());
93             }
94         }
95     }
96 
97     'init_loop: for init_func in init_funcs {
98         log::debug!("Using initialization function: {init_func:?}");
99 
100         // Create a wizened snapshot of the given Wasm using `init_func` as the
101         // initialization routine.
102         let snapshot_wasm = {
103             let mut wizer = wasmtime_wizer::Wizer::new();
104             let mut store = Store::new(&engine, ());
105             wizer.init_func(init_func);
106 
107             match assert_ready(wizer.run(&mut store, &wasm, async |store, module| {
108                 Instance::new(store, module, &[])
109             })) {
110                 Err(_) => continue 'init_loop,
111                 Ok(s) => s,
112             }
113         };
114         let snapshot_module =
115             Module::new(&engine, &snapshot_wasm).expect("snapshot should be valid wasm");
116 
117         // Now check that each "main" function behaves the same whether we call
118         // it on an instantiated snapshot or if we instantiate the original
119         // Wasm, call the initialization routine, and then call the "main"
120         // function.
121         'main_loop: for main_func in &main_funcs {
122             if *main_func == init_func {
123                 // Wizer un-exports the initialization function, so we can't use
124                 // it as a main function.
125                 continue 'main_loop;
126             }
127             log::debug!("Using main function: {main_func:?}");
128 
129             let mut store = Store::new(&engine, ());
130 
131             // Instantiate the snapshot and call the main function.
132             let snapshot_instance = Instance::new(&mut store, &snapshot_module, &[]).unwrap();
133             let snapshot_main_func = snapshot_instance.get_func(&mut store, main_func).unwrap();
134             let main_args = snapshot_main_func
135                 .ty(&store)
136                 .params()
137                 .map(|t| t.default_value().unwrap())
138                 .collect::<Vec<_>>();
139             let mut snapshot_result =
140                 vec![wasmtime::Val::I32(0); snapshot_main_func.ty(&store).results().len()];
141             let snapshot_call_result =
142                 snapshot_main_func.call(&mut store, &main_args, &mut snapshot_result);
143 
144             // Instantiate the original Wasm and then call the initialization
145             // and main functions back to back.
146             let instance = Instance::new(&mut store, &module, &[]).unwrap();
147             let init_func = instance
148                 .get_typed_func::<(), ()>(&mut store, init_func)
149                 .unwrap();
150             init_func.call(&mut store, ()).unwrap();
151             let main_func = instance.get_func(&mut store, main_func).unwrap();
152             let mut result = vec![wasmtime::Val::I32(0); main_func.ty(&store).results().len()];
153             let call_result = main_func.call(&mut store, &main_args, &mut result);
154 
155             // Check that the function return values / traps are the same.
156             match (snapshot_call_result, call_result) {
157                 // Both did not trap.
158                 (Ok(()), Ok(())) => {
159                     assert_eq!(snapshot_result.len(), result.len());
160                     for (s, r) in snapshot_result.iter().zip(result.iter()) {
161                         assert_val_eq(s, r);
162                     }
163                 }
164 
165                 // Both trapped.
166                 (Err(_), Err(_)) => {}
167 
168                 // Divergence.
169                 (s, r) => {
170                     panic!(
171                         "divergence between whether the main function traps or not!\n\n\
172                          no snapshotting result = {r:?}\n\n\
173                          snapshotted result = {s:?}",
174                     );
175                 }
176             }
177 
178             // Assert that all other exports have the same state as well.
179             let exports = snapshot_instance
180                 .exports(&mut store)
181                 .map(|export| export.name().to_string())
182                 .collect::<Vec<_>>();
183             for name in exports.iter() {
184                 let export = snapshot_instance.get_export(&mut store, &name).unwrap();
185                 match export {
186                     Extern::Global(snapshot_global) => {
187                         let global = instance.get_global(&mut store, &name).unwrap();
188                         assert_val_eq(&snapshot_global.get(&mut store), &global.get(&mut store));
189                     }
190                     Extern::Memory(snapshot_memory) => {
191                         let memory = instance.get_memory(&mut store, &name).unwrap();
192                         let snapshot_memory = snapshot_memory.data(&store);
193                         let memory = memory.data(&store);
194                         assert_eq!(snapshot_memory.len(), memory.len());
195                         // NB: Don't use `assert_eq` here so that we don't
196                         // try to print the full memories' debug
197                         // representations on failure.
198                         if snapshot_memory != memory {
199                             panic!("divergence between snapshot and non-snapshot memories");
200                         }
201                     }
202                     Extern::SharedMemory(_)
203                     | Extern::Func(_)
204                     | Extern::Table(_)
205                     | Extern::Tag(_) => continue,
206                 }
207             }
208         }
209     }
210 });
211 
assert_ready<F: Future>(f: F) -> F::Output212 fn assert_ready<F: Future>(f: F) -> F::Output {
213     let mut context = Context::from_waker(Waker::noop());
214     match pin!(f).poll(&mut context) {
215         Poll::Ready(ret) => ret,
216         Poll::Pending => panic!("future wasn't ready"),
217     }
218 }
219 
assert_val_eq(a: &Val, b: &Val)220 fn assert_val_eq(a: &Val, b: &Val) {
221     match (a, b) {
222         (Val::I32(a), Val::I32(b)) => assert_eq!(a, b),
223         (Val::I64(a), Val::I64(b)) => assert_eq!(a, b),
224         (Val::F32(a), Val::F32(b)) => assert!({
225             let a = f32::from_bits(*a);
226             let b = f32::from_bits(*b);
227             a == b || (a.is_nan() && b.is_nan())
228         }),
229         (Val::F64(a), Val::F64(b)) => assert!({
230             let a = f64::from_bits(*a);
231             let b = f64::from_bits(*b);
232             a == b || (a.is_nan() && b.is_nan())
233         }),
234         (Val::V128(a), Val::V128(b)) => assert_eq!(a, b),
235         _ => panic!("{a:?} != {b:?}"),
236     }
237 }
238