1 //! Generate a Wasm program that keeps track of its current stack frames.
2 //!
3 //! We can then compare the stack trace we observe in Wasmtime to what the Wasm
4 //! program believes its stack should be. Any discrepancies between the two
5 //! points to a bug in either this test case generator or Wasmtime's stack
6 //! walker.
7 
8 use std::mem;
9 
10 use arbitrary::{Arbitrary, Result, Unstructured};
11 use wasm_encoder::{Instruction, ValType};
12 
13 const MAX_FUNCS: u32 = 20;
14 const MAX_OPS: usize = 1_000;
15 const MAX_PARAMS: usize = 10;
16 
17 /// Generate a Wasm module that keeps track of its current call stack, to
18 /// compare to the host.
19 #[derive(Debug)]
20 pub struct Stacks {
21     funcs: Vec<Function>,
22     inputs: Vec<u8>,
23 }
24 
25 #[derive(Debug, Default)]
26 struct Function {
27     ops: Vec<Op>,
28     params: usize,
29     results: usize,
30 }
31 
32 #[derive(Debug, Clone, Copy)]
33 enum Op {
34     CheckStackInHost,
35     Call(u32),
36     CallThroughHost(u32),
37     ReturnCall(u32),
38 }
39 
40 impl<'a> Arbitrary<'a> for Stacks {
41     fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
42         let funcs = Self::arbitrary_funcs(u)?;
43         let n = u.len().min(200);
44         let inputs = u.bytes(n)?.to_vec();
45         Ok(Stacks { funcs, inputs })
46     }
47 }
48 
49 impl Stacks {
50     fn arbitrary_funcs(u: &mut Unstructured) -> Result<Vec<Function>> {
51         // Generate a list of functions first with a number of parameters and
52         // results. Bodies are generated afterwards.
53         let nfuncs = u.int_in_range(1..=MAX_FUNCS)?;
54         let mut funcs = (0..nfuncs)
55             .map(|_| {
56                 Ok(Function {
57                     ops: Vec::new(), // generated later
58                     params: u.int_in_range(0..=MAX_PARAMS)?,
59                     results: u.int_in_range(0..=MAX_PARAMS)?,
60                 })
61             })
62             .collect::<Result<Vec<_>>>()?;
63         let mut funcs_by_result = vec![Vec::new(); MAX_PARAMS + 1];
64         for (i, func) in funcs.iter().enumerate() {
65             funcs_by_result[func.results].push(i as u32);
66         }
67 
68         // Fill in each function body with various instructions/operations now
69         // that the set of functions is known.
70         for f in funcs.iter_mut() {
71             let funcs_with_same_results = &funcs_by_result[f.results];
72             for _ in 0..u.arbitrary_len::<usize>()?.min(MAX_OPS) {
73                 let op = match u.int_in_range(0..=3)? {
74                     0 => Op::CheckStackInHost,
75                     1 => Op::Call(u.int_in_range(0..=nfuncs - 1)?),
76                     2 => Op::CallThroughHost(u.int_in_range(0..=nfuncs - 1)?),
77                     // This only works if the target function has the same
78                     // number of results, so choose from a different set here.
79                     3 => Op::ReturnCall(*u.choose(funcs_with_same_results)?),
80                     _ => unreachable!(),
81                 };
82                 f.ops.push(op);
83                 // once a `return_call` has been generated there's no need to
84                 // generate any more instructions, so fall through to below.
85                 if let Some(Op::ReturnCall(_)) = f.ops.last() {
86                     break;
87                 }
88             }
89         }
90 
91         Ok(funcs)
92     }
93 
94     /// Get the input values to run the Wasm module with.
95     pub fn inputs(&self) -> &[u8] {
96         &self.inputs
97     }
98 
99     /// Get this test case's Wasm module.
100     ///
101     /// The Wasm module has the following imports:
102     ///
103     /// * `host.check_stack: [] -> []`: The host can check the Wasm's
104     ///   understanding of its own stack against the host's understanding of the
105     ///   Wasm stack to find discrepancy bugs.
106     ///
107     /// * `host.call_func: [funcref] -> []`: The host should call the given
108     ///   `funcref`, creating a call stack with multiple sequences of contiguous
109     ///   Wasm frames on the stack like `[..., wasm, host, wasm]`.
110     ///
111     /// The Wasm module has the following exports:
112     ///
113     /// * `run: [i32] -> []`: This function should be called with each of the
114     ///   input values to run this generated test case.
115     ///
116     /// * `get_stack: [] -> [i32 i32]`: Get the pointer and length of the `u32`
117     ///   array of this Wasm's understanding of its stack. This is useful for
118     ///   checking whether the host's view of the stack at a trap matches the
119     ///   Wasm program's understanding.
120     pub fn wasm(&self) -> Vec<u8> {
121         let mut module = wasm_encoder::Module::new();
122 
123         let mut types = wasm_encoder::TypeSection::new();
124 
125         let run_type = types.len();
126         types
127             .ty()
128             .function(vec![wasm_encoder::ValType::I32], vec![]);
129 
130         let get_stack_type = types.len();
131         types.ty().function(
132             vec![],
133             vec![wasm_encoder::ValType::I32, wasm_encoder::ValType::I32],
134         );
135 
136         let call_func_type = types.len();
137         types
138             .ty()
139             .function(vec![wasm_encoder::ValType::FUNCREF], vec![]);
140 
141         let check_stack_type = types.len();
142         types.ty().function(vec![], vec![]);
143 
144         let func_types_start = types.len();
145         for func in self.funcs.iter() {
146             types.ty().function(
147                 vec![ValType::I32; func.params],
148                 vec![ValType::I32; func.results],
149             );
150         }
151 
152         section(&mut module, types);
153 
154         let mut imports = wasm_encoder::ImportSection::new();
155         let check_stack_func = 0;
156         imports.import(
157             "host",
158             "check_stack",
159             wasm_encoder::EntityType::Function(check_stack_type),
160         );
161         let call_func_func = 1;
162         imports.import(
163             "host",
164             "call_func",
165             wasm_encoder::EntityType::Function(call_func_type),
166         );
167         let num_imported_funcs = 2;
168         section(&mut module, imports);
169 
170         let mut funcs = wasm_encoder::FunctionSection::new();
171         for (i, _) in self.funcs.iter().enumerate() {
172             funcs.function(func_types_start + (i as u32));
173         }
174         let run_func = funcs.len() + num_imported_funcs;
175         funcs.function(run_type);
176         let get_stack_func = funcs.len() + num_imported_funcs;
177         funcs.function(get_stack_type);
178         section(&mut module, funcs);
179 
180         let mut mems = wasm_encoder::MemorySection::new();
181         let memory = mems.len();
182         mems.memory(wasm_encoder::MemoryType {
183             minimum: 1,
184             maximum: Some(1),
185             memory64: false,
186             shared: false,
187             page_size_log2: None,
188         });
189         section(&mut module, mems);
190 
191         let mut globals = wasm_encoder::GlobalSection::new();
192         let fuel_global = globals.len();
193         globals.global(
194             wasm_encoder::GlobalType {
195                 val_type: wasm_encoder::ValType::I32,
196                 mutable: true,
197                 shared: false,
198             },
199             &wasm_encoder::ConstExpr::i32_const(0),
200         );
201         let stack_len_global = globals.len();
202         globals.global(
203             wasm_encoder::GlobalType {
204                 val_type: wasm_encoder::ValType::I32,
205                 mutable: true,
206                 shared: false,
207             },
208             &wasm_encoder::ConstExpr::i32_const(0),
209         );
210         section(&mut module, globals);
211 
212         let mut exports = wasm_encoder::ExportSection::new();
213         exports.export("run", wasm_encoder::ExportKind::Func, run_func);
214         exports.export("get_stack", wasm_encoder::ExportKind::Func, get_stack_func);
215         exports.export("memory", wasm_encoder::ExportKind::Memory, memory);
216         exports.export("fuel", wasm_encoder::ExportKind::Global, fuel_global);
217         section(&mut module, exports);
218 
219         let mut elems = wasm_encoder::ElementSection::new();
220         elems.declared(wasm_encoder::Elements::Functions(
221             (0..num_imported_funcs + u32::try_from(self.funcs.len()).unwrap())
222                 .collect::<Vec<_>>()
223                 .into(),
224         ));
225         section(&mut module, elems);
226 
227         let check_fuel = |body: &mut wasm_encoder::Function| {
228             // Trap if we are out of fuel.
229             body.instruction(&Instruction::GlobalGet(fuel_global))
230                 .instruction(&Instruction::I32Eqz)
231                 .instruction(&Instruction::If(wasm_encoder::BlockType::Empty))
232                 .instruction(&Instruction::Unreachable)
233                 .instruction(&Instruction::End);
234 
235             // Decrement fuel.
236             body.instruction(&Instruction::GlobalGet(fuel_global))
237                 .instruction(&Instruction::I32Const(1))
238                 .instruction(&Instruction::I32Sub)
239                 .instruction(&Instruction::GlobalSet(fuel_global));
240         };
241 
242         let push_func_to_stack = |body: &mut wasm_encoder::Function, func: u32| {
243             // Add this function to our internal stack.
244             //
245             // Note that we know our `stack_len_global` can't go beyond memory
246             // bounds because we limit fuel to at most `u8::MAX` and each stack
247             // entry is an `i32` and `u8::MAX * size_of(i32)` still fits in one
248             // Wasm page.
249             body.instruction(&Instruction::GlobalGet(stack_len_global))
250                 .instruction(&Instruction::I32Const(func as i32))
251                 .instruction(&Instruction::I32Store(wasm_encoder::MemArg {
252                     offset: 0,
253                     align: 0,
254                     memory_index: memory,
255                 }))
256                 .instruction(&Instruction::GlobalGet(stack_len_global))
257                 .instruction(&Instruction::I32Const(mem::size_of::<i32>() as i32))
258                 .instruction(&Instruction::I32Add)
259                 .instruction(&Instruction::GlobalSet(stack_len_global));
260         };
261 
262         let pop_func_from_stack = |body: &mut wasm_encoder::Function| {
263             // Remove this function from our internal stack.
264             body.instruction(&Instruction::GlobalGet(stack_len_global))
265                 .instruction(&Instruction::I32Const(mem::size_of::<i32>() as i32))
266                 .instruction(&Instruction::I32Sub)
267                 .instruction(&Instruction::GlobalSet(stack_len_global));
268         };
269 
270         let push_params = |body: &mut wasm_encoder::Function, func: u32| {
271             let func = &self.funcs[func as usize];
272             for _ in 0..func.params {
273                 body.instruction(&Instruction::I32Const(0));
274             }
275         };
276         let pop_results = |body: &mut wasm_encoder::Function, func: u32| {
277             let func = &self.funcs[func as usize];
278             for _ in 0..func.results {
279                 body.instruction(&Instruction::Drop);
280             }
281         };
282         let push_results = |body: &mut wasm_encoder::Function, func: u32| {
283             let func = &self.funcs[func as usize];
284             for _ in 0..func.results {
285                 body.instruction(&Instruction::I32Const(0));
286             }
287         };
288 
289         let mut code = wasm_encoder::CodeSection::new();
290         for (func_index, func) in self.funcs.iter().enumerate() {
291             let mut body = wasm_encoder::Function::new(vec![]);
292 
293             push_func_to_stack(
294                 &mut body,
295                 num_imported_funcs + u32::try_from(func_index).unwrap(),
296             );
297             check_fuel(&mut body);
298 
299             let mut check_fuel_and_pop_at_end = true;
300 
301             // Perform our specified operations.
302             for op in &func.ops {
303                 assert!(check_fuel_and_pop_at_end);
304                 match op {
305                     Op::CheckStackInHost => {
306                         body.instruction(&Instruction::Call(check_stack_func));
307                     }
308                     Op::Call(f) => {
309                         push_params(&mut body, *f);
310                         body.instruction(&Instruction::Call(f + num_imported_funcs));
311                         pop_results(&mut body, *f);
312                     }
313                     Op::CallThroughHost(f) => {
314                         body.instruction(&Instruction::RefFunc(f + num_imported_funcs))
315                             .instruction(&Instruction::Call(call_func_func));
316                     }
317 
318                     // For a `return_call` preemptively check fuel to possibly
319                     // trap and then pop our function from the in-wasm managed
320                     // stack. After that execute the `return_call` itself.
321                     Op::ReturnCall(f) => {
322                         push_params(&mut body, *f);
323                         check_fuel(&mut body);
324                         pop_func_from_stack(&mut body);
325                         check_fuel_and_pop_at_end = false;
326                         body.instruction(&Instruction::ReturnCall(f + num_imported_funcs));
327                     }
328                 }
329             }
330 
331             // Potentially trap at the end of our function as well, so that we
332             // exercise the scenario where the Wasm-to-host trampoline
333             // initialized `last_wasm_exit_sp` et al when calling out to a host
334             // function, but then we returned back to Wasm and then trapped
335             // while `last_wasm_exit_sp` et al are still initialized from that
336             // previous host call.
337             if check_fuel_and_pop_at_end {
338                 check_fuel(&mut body);
339                 pop_func_from_stack(&mut body);
340                 push_results(&mut body, func_index as u32);
341             }
342 
343             function(&mut code, body);
344         }
345 
346         let mut run_body = wasm_encoder::Function::new(vec![]);
347 
348         // Reset the bump pointer for the internal stack (this allows us to
349         // reuse an instance in the oracle, rather than re-instantiate).
350         run_body
351             .instruction(&Instruction::I32Const(0))
352             .instruction(&Instruction::GlobalSet(stack_len_global));
353 
354         // Initialize the fuel global.
355         run_body
356             .instruction(&Instruction::LocalGet(0))
357             .instruction(&Instruction::GlobalSet(fuel_global));
358 
359         push_func_to_stack(&mut run_body, run_func);
360 
361         // Make sure to check for out-of-fuel in the `run` function as well, so
362         // that we also capture stack traces with only one frame, not just `run`
363         // followed by the first locally-defined function and then zero or more
364         // extra frames.
365         check_fuel(&mut run_body);
366 
367         // Call the first locally defined function.
368         push_params(&mut run_body, 0);
369         run_body.instruction(&Instruction::Call(num_imported_funcs));
370         pop_results(&mut run_body, 0);
371 
372         check_fuel(&mut run_body);
373         pop_func_from_stack(&mut run_body);
374 
375         function(&mut code, run_body);
376 
377         let mut get_stack_body = wasm_encoder::Function::new(vec![]);
378         get_stack_body
379             .instruction(&Instruction::I32Const(0))
380             .instruction(&Instruction::GlobalGet(stack_len_global));
381         function(&mut code, get_stack_body);
382 
383         section(&mut module, code);
384 
385         return module.finish();
386 
387         // Helper that defines a section in the module and takes ownership of it
388         // so that it is dropped and its memory reclaimed after adding it to the
389         // module.
390         fn section(module: &mut wasm_encoder::Module, section: impl wasm_encoder::Section) {
391             module.section(&section);
392         }
393 
394         // Helper that defines a function body in the code section and takes
395         // ownership of it so that it is dropped and its memory reclaimed after
396         // adding it to the module.
397         fn function(code: &mut wasm_encoder::CodeSection, mut func: wasm_encoder::Function) {
398             func.instruction(&Instruction::End);
399             code.function(&func);
400         }
401     }
402 }
403 
404 #[cfg(test)]
405 mod tests {
406     use super::*;
407     use wasmparser::Validator;
408 
409     #[test]
410     fn stacks_generates_valid_wasm_modules() {
411         crate::test::test_n_times(10, |stacks: Stacks, _u| {
412             let wasm = stacks.wasm();
413             validate(&wasm);
414             Ok(())
415         })
416     }
417 
418     fn validate(wasm: &[u8]) {
419         let mut validator = Validator::new();
420         let err = match validator.validate_all(wasm) {
421             Ok(_) => return,
422             Err(e) => e,
423         };
424         drop(std::fs::write("test.wasm", wasm));
425         if let Ok(text) = wasmprinter::print_bytes(wasm) {
426             drop(std::fs::write("test.wat", &text));
427         }
428         panic!("wasm failed to validate: {err}");
429     }
430 }
431