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 discrepencies 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;
12 
13 const MAX_FUNCS: usize = 20;
14 
15 /// Generate a Wasm module that keeps track of its current call stack, to
16 /// compare to the host.
17 #[derive(Debug)]
18 pub struct Stacks {
19     funcs: Vec<Function>,
20     inputs: Vec<u8>,
21 }
22 
23 #[derive(Debug, Default)]
24 struct Function {
25     ops: Vec<Op>,
26 }
27 
28 #[derive(Arbitrary, Debug, Clone, Copy)]
29 enum Op {
30     CheckStackInHost,
31     Call(u32),
32     CallThroughHost(u32),
33 }
34 
35 impl<'a> Arbitrary<'a> for Stacks {
36     fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
37         let funcs = Self::arbitrary_funcs(u)?;
38         let n = u.len().min(200);
39         let inputs = u.bytes(n)?.to_vec();
40         Ok(Stacks { funcs, inputs })
41     }
42 }
43 
44 impl Stacks {
45     fn arbitrary_funcs(u: &mut Unstructured) -> Result<Vec<Function>> {
46         let mut funcs = vec![Function::default()];
47 
48         // The indices of functions within `funcs` that we still need to
49         // generate.
50         let mut work_list = vec![0];
51 
52         while let Some(f) = work_list.pop() {
53             let mut ops = u.arbitrary::<Vec<Op>>()?;
54             for op in &mut ops {
55                 match op {
56                     Op::CallThroughHost(idx) | Op::Call(idx) => {
57                         if u.is_empty() || funcs.len() >= MAX_FUNCS || u.ratio(4, 5)? {
58                             // Call an existing function.
59                             *idx = *idx % u32::try_from(funcs.len()).unwrap();
60                         } else {
61                             // Call a new function...
62                             *idx = u32::try_from(funcs.len()).unwrap();
63                             // ...which means we also need to eventually define it.
64                             work_list.push(funcs.len());
65                             funcs.push(Function::default());
66                         }
67                     }
68                     Op::CheckStackInHost => {}
69                 }
70             }
71             funcs[f].ops = ops;
72         }
73 
74         Ok(funcs)
75     }
76 
77     /// Get the input values to run the Wasm module with.
78     pub fn inputs(&self) -> &[u8] {
79         &self.inputs
80     }
81 
82     /// Get this test case's Wasm module.
83     ///
84     /// The Wasm module has the following imports:
85     ///
86     /// * `host.check_stack: [] -> []`: The host can check the Wasm's
87     ///   understanding of its own stack against the host's understanding of the
88     ///   Wasm stack to find discrepency bugs.
89     ///
90     /// * `host.call_func: [funcref] -> []`: The host should call the given
91     ///   `funcref`, creating a call stack with multiple sequences of contiguous
92     ///   Wasm frames on the stack like `[..., wasm, host, wasm]`.
93     ///
94     /// The Wasm module has the following exports:
95     ///
96     /// * `run: [i32] -> []`: This function should be called with each of the
97     ///   input values to run this generated test case.
98     ///
99     /// * `get_stack: [] -> [i32 i32]`: Get the pointer and length of the `u32`
100     ///   array of this Wasm's understanding of its stack. This is useful for
101     ///   checking whether the host's view of the stack at a trap matches the
102     ///   Wasm program's understanding.
103     pub fn wasm(&self) -> Vec<u8> {
104         let mut module = wasm_encoder::Module::new();
105 
106         let mut types = wasm_encoder::TypeSection::new();
107 
108         let run_type = types.len();
109         types.function(vec![wasm_encoder::ValType::I32], vec![]);
110 
111         let get_stack_type = types.len();
112         types.function(
113             vec![],
114             vec![wasm_encoder::ValType::I32, wasm_encoder::ValType::I32],
115         );
116 
117         let null_type = types.len();
118         types.function(vec![], vec![]);
119 
120         let call_func_type = types.len();
121         types.function(vec![wasm_encoder::ValType::FuncRef], vec![]);
122 
123         section(&mut module, types);
124 
125         let mut imports = wasm_encoder::ImportSection::new();
126         let check_stack_func = 0;
127         imports.import(
128             "host",
129             "check_stack",
130             wasm_encoder::EntityType::Function(null_type),
131         );
132         let call_func_func = 1;
133         imports.import(
134             "host",
135             "call_func",
136             wasm_encoder::EntityType::Function(call_func_type),
137         );
138         let num_imported_funcs = 2;
139         section(&mut module, imports);
140 
141         let mut funcs = wasm_encoder::FunctionSection::new();
142         for _ in &self.funcs {
143             funcs.function(null_type);
144         }
145         let run_func = funcs.len() + num_imported_funcs;
146         funcs.function(run_type);
147         let get_stack_func = funcs.len() + num_imported_funcs;
148         funcs.function(get_stack_type);
149         section(&mut module, funcs);
150 
151         let mut mems = wasm_encoder::MemorySection::new();
152         let memory = mems.len();
153         mems.memory(wasm_encoder::MemoryType {
154             minimum: 1,
155             maximum: Some(1),
156             memory64: false,
157             shared: false,
158         });
159         section(&mut module, mems);
160 
161         let mut globals = wasm_encoder::GlobalSection::new();
162         let fuel_global = globals.len();
163         globals.global(
164             wasm_encoder::GlobalType {
165                 val_type: wasm_encoder::ValType::I32,
166                 mutable: true,
167             },
168             &wasm_encoder::ConstExpr::i32_const(0),
169         );
170         let stack_len_global = globals.len();
171         globals.global(
172             wasm_encoder::GlobalType {
173                 val_type: wasm_encoder::ValType::I32,
174                 mutable: true,
175             },
176             &wasm_encoder::ConstExpr::i32_const(0),
177         );
178         section(&mut module, globals);
179 
180         let mut exports = wasm_encoder::ExportSection::new();
181         exports.export("run", wasm_encoder::ExportKind::Func, run_func);
182         exports.export("get_stack", wasm_encoder::ExportKind::Func, get_stack_func);
183         exports.export("memory", wasm_encoder::ExportKind::Memory, memory);
184         exports.export("fuel", wasm_encoder::ExportKind::Global, fuel_global);
185         section(&mut module, exports);
186 
187         let mut elems = wasm_encoder::ElementSection::new();
188         elems.declared(
189             wasm_encoder::ValType::FuncRef,
190             wasm_encoder::Elements::Functions(
191                 &(0..num_imported_funcs + u32::try_from(self.funcs.len()).unwrap())
192                     .collect::<Vec<_>>(),
193             ),
194         );
195         section(&mut module, elems);
196 
197         let check_fuel = |body: &mut wasm_encoder::Function| {
198             // Trap if we are out of fuel.
199             body.instruction(&Instruction::GlobalGet(fuel_global))
200                 .instruction(&Instruction::I32Eqz)
201                 .instruction(&Instruction::If(wasm_encoder::BlockType::Empty))
202                 .instruction(&Instruction::Unreachable)
203                 .instruction(&Instruction::End);
204 
205             // Decrement fuel.
206             body.instruction(&Instruction::GlobalGet(fuel_global))
207                 .instruction(&Instruction::I32Const(1))
208                 .instruction(&Instruction::I32Sub)
209                 .instruction(&Instruction::GlobalSet(fuel_global));
210         };
211 
212         let push_func_to_stack = |body: &mut wasm_encoder::Function, func: u32| {
213             // Add this function to our internal stack.
214             //
215             // Note that we know our `stack_len_global` can't go beyond memory
216             // bounds because we limit fuel to at most `u8::MAX` and each stack
217             // entry is an `i32` and `u8::MAX * size_of(i32)` still fits in one
218             // Wasm page.
219             body.instruction(&Instruction::GlobalGet(stack_len_global))
220                 .instruction(&Instruction::I32Const(func as i32))
221                 .instruction(&Instruction::I32Store(wasm_encoder::MemArg {
222                     offset: 0,
223                     align: 0,
224                     memory_index: memory,
225                 }))
226                 .instruction(&Instruction::GlobalGet(stack_len_global))
227                 .instruction(&Instruction::I32Const(mem::size_of::<i32>() as i32))
228                 .instruction(&Instruction::I32Add)
229                 .instruction(&Instruction::GlobalSet(stack_len_global));
230         };
231 
232         let pop_func_from_stack = |body: &mut wasm_encoder::Function| {
233             // Remove this function from our internal stack.
234             body.instruction(&Instruction::GlobalGet(stack_len_global))
235                 .instruction(&Instruction::I32Const(mem::size_of::<i32>() as i32))
236                 .instruction(&Instruction::I32Sub)
237                 .instruction(&Instruction::GlobalSet(stack_len_global));
238         };
239 
240         let mut code = wasm_encoder::CodeSection::new();
241         for (func_index, func) in self.funcs.iter().enumerate() {
242             let mut body = wasm_encoder::Function::new(vec![]);
243 
244             push_func_to_stack(
245                 &mut body,
246                 num_imported_funcs + u32::try_from(func_index).unwrap(),
247             );
248             check_fuel(&mut body);
249 
250             // Perform our specified operations.
251             for op in &func.ops {
252                 match op {
253                     Op::CheckStackInHost => {
254                         body.instruction(&Instruction::Call(check_stack_func));
255                     }
256                     Op::Call(f) => {
257                         body.instruction(&Instruction::Call(f + num_imported_funcs));
258                     }
259                     Op::CallThroughHost(f) => {
260                         body.instruction(&Instruction::RefFunc(f + num_imported_funcs))
261                             .instruction(&Instruction::Call(call_func_func));
262                     }
263                 }
264             }
265 
266             // Potentially trap at the end of our function as well, so that we
267             // exercise the scenario where the Wasm-to-host trampoline
268             // initialized `last_wasm_exit_sp` et al when calling out to a host
269             // function, but then we returned back to Wasm and then trapped
270             // while `last_wasm_exit_sp` et al are still initialized from that
271             // previous host call.
272             check_fuel(&mut body);
273 
274             pop_func_from_stack(&mut body);
275 
276             function(&mut code, body);
277         }
278 
279         let mut run_body = wasm_encoder::Function::new(vec![]);
280 
281         // Reset the bump pointer for the internal stack (this allows us to
282         // reuse an instance in the oracle, rather than re-instantiate).
283         run_body
284             .instruction(&Instruction::I32Const(0))
285             .instruction(&Instruction::GlobalSet(stack_len_global));
286 
287         // Initialize the fuel global.
288         run_body
289             .instruction(&Instruction::LocalGet(0))
290             .instruction(&Instruction::GlobalSet(fuel_global));
291 
292         push_func_to_stack(&mut run_body, run_func);
293 
294         // Make sure to check for out-of-fuel in the `run` function as well, so
295         // that we also capture stack traces with only one frame, not just `run`
296         // followed by the first locally-defined function and then zero or more
297         // extra frames.
298         check_fuel(&mut run_body);
299 
300         // Call the first locally defined function.
301         run_body.instruction(&Instruction::Call(num_imported_funcs));
302 
303         check_fuel(&mut run_body);
304         pop_func_from_stack(&mut run_body);
305 
306         function(&mut code, run_body);
307 
308         let mut get_stack_body = wasm_encoder::Function::new(vec![]);
309         get_stack_body
310             .instruction(&Instruction::I32Const(0))
311             .instruction(&Instruction::GlobalGet(stack_len_global));
312         function(&mut code, get_stack_body);
313 
314         section(&mut module, code);
315 
316         return module.finish();
317 
318         // Helper that defines a section in the module and takes ownership of it
319         // so that it is dropped and its memory reclaimed after adding it to the
320         // module.
321         fn section(module: &mut wasm_encoder::Module, section: impl wasm_encoder::Section) {
322             module.section(&section);
323         }
324 
325         // Helper that defines a function body in the code section and takes
326         // ownership of it so that it is dropped and its memory reclaimed after
327         // adding it to the module.
328         fn function(code: &mut wasm_encoder::CodeSection, mut func: wasm_encoder::Function) {
329             func.instruction(&Instruction::End);
330             code.function(&func);
331         }
332     }
333 }
334 
335 #[cfg(test)]
336 mod tests {
337     use super::*;
338     use rand::prelude::*;
339     use wasmparser::Validator;
340 
341     #[test]
342     fn stacks_generates_valid_wasm_modules() {
343         let mut rng = SmallRng::seed_from_u64(0);
344         let mut buf = vec![0; 2048];
345         for _ in 0..1024 {
346             rng.fill_bytes(&mut buf);
347             let u = Unstructured::new(&buf);
348             if let Ok(stacks) = Stacks::arbitrary_take_rest(u) {
349                 let wasm = stacks.wasm();
350                 validate(&wasm);
351             }
352         }
353     }
354 
355     fn validate(wasm: &[u8]) {
356         let mut validator = Validator::new();
357         let err = match validator.validate_all(wasm) {
358             Ok(_) => return,
359             Err(e) => e,
360         };
361         drop(std::fs::write("test.wasm", wasm));
362         if let Ok(text) = wasmprinter::print_bytes(wasm) {
363             drop(std::fs::write("test.wat", &text));
364         }
365         panic!("wasm failed to validate: {}", err);
366     }
367 }
368