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