Skip to content

Commit 6f53aaf

Browse files
committed
fuzzing: Add a fuzz target to check that our stack traces are correct
We generate Wasm modules that keep track of their own stack as they call and return between functions, and then we periodically check that if the host captures a backtrace, it matches what the Wasm module has recorded.
1 parent 46eb29a commit 6f53aaf

6 files changed

Lines changed: 447 additions & 0 deletions

File tree

crates/fuzzing/src/generators.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod memory;
1717
mod module_config;
1818
mod single_inst_module;
1919
mod spec_test;
20+
mod stacks;
2021
pub mod table_ops;
2122

2223
pub use codegen_settings::CodegenSettings;
@@ -27,3 +28,4 @@ pub use memory::{MemoryConfig, NormalMemoryConfig, UnalignedMemory, UnalignedMem
2728
pub use module_config::ModuleConfig;
2829
pub use single_inst_module::SingleInstModule;
2930
pub use spec_test::SpecTest;
31+
pub use stacks::Stacks;
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
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+
}
33+
34+
impl<'a> Arbitrary<'a> for Stacks {
35+
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
36+
let funcs = Self::arbitrary_funcs(u)?;
37+
let n = u.len();
38+
let inputs = u.bytes(n)?.to_vec();
39+
Ok(Stacks { funcs, inputs })
40+
}
41+
}
42+
43+
impl Stacks {
44+
fn arbitrary_funcs(u: &mut Unstructured) -> Result<Vec<Function>> {
45+
let mut funcs = vec![Function::default()];
46+
47+
// The indices of functions within `funcs` that we still need to
48+
// generate.
49+
let mut work_list = vec![0];
50+
51+
while let Some(f) = work_list.pop() {
52+
let mut ops = u.arbitrary::<Vec<Op>>()?;
53+
for op in &mut ops {
54+
if let Op::Call(idx) = op {
55+
if u.is_empty() || funcs.len() >= MAX_FUNCS || u.ratio(4, 5)? {
56+
// Call an existing function.
57+
*idx = *idx % u32::try_from(funcs.len()).unwrap();
58+
} else {
59+
// Call a new function...
60+
*idx = u32::try_from(funcs.len()).unwrap();
61+
// ...which means we also need to eventually define it.
62+
work_list.push(funcs.len());
63+
funcs.push(Function::default());
64+
}
65+
}
66+
}
67+
funcs[f].ops = ops;
68+
}
69+
70+
Ok(funcs)
71+
}
72+
73+
/// Get the input values to run the Wasm module with.
74+
pub fn inputs(&self) -> &[u8] {
75+
&self.inputs
76+
}
77+
78+
/// Get this test case's Wasm module.
79+
///
80+
/// The Wasm module imports a function `host.check_stack: [i32 i32] -> []`
81+
/// from the host. This function is given an array (as pointer and length)
82+
/// of `u32`s. This is the Wasm program's understanding of its current
83+
/// stack. The host can check this against its own understanding of the Wasm
84+
/// stack to find bugs.
85+
///
86+
/// The Wasm module exports two functions:
87+
///
88+
/// 1. `run: [i32] -> []`: This function should be called with each of the
89+
/// input values to run this generated test case.
90+
///
91+
/// 2. `get_stack: [] -> [i32 i32]`: Get the pointer and length of the `u32`
92+
/// array of this Wasm's understanding of its stack. This is useful for
93+
/// checking whether the host's view of the stack at a trap matches the
94+
/// Wasm program's understanding.
95+
pub fn wasm(&self) -> Vec<u8> {
96+
let mut module = wasm_encoder::Module::new();
97+
98+
let mut types = wasm_encoder::TypeSection::new();
99+
let check_stack_type = types.len();
100+
types.function(
101+
vec![wasm_encoder::ValType::I32, wasm_encoder::ValType::I32],
102+
vec![],
103+
);
104+
let run_type = types.len();
105+
types.function(vec![wasm_encoder::ValType::I32], vec![]);
106+
let get_stack_type = types.len();
107+
types.function(
108+
vec![],
109+
vec![wasm_encoder::ValType::I32, wasm_encoder::ValType::I32],
110+
);
111+
let null_type = types.len();
112+
types.function(vec![], vec![]);
113+
section(&mut module, types);
114+
115+
let mut imports = wasm_encoder::ImportSection::new();
116+
let check_stack_func = 0;
117+
imports.import(
118+
"host",
119+
"check_stack",
120+
wasm_encoder::EntityType::Function(check_stack_type),
121+
);
122+
let num_imported_funcs = 1;
123+
section(&mut module, imports);
124+
125+
let mut funcs = wasm_encoder::FunctionSection::new();
126+
for _ in &self.funcs {
127+
funcs.function(null_type);
128+
}
129+
let run_func = funcs.len() + num_imported_funcs;
130+
funcs.function(run_type);
131+
let get_stack_func = funcs.len() + num_imported_funcs;
132+
funcs.function(get_stack_type);
133+
section(&mut module, funcs);
134+
135+
let mut mems = wasm_encoder::MemorySection::new();
136+
let memory = mems.len();
137+
mems.memory(wasm_encoder::MemoryType {
138+
minimum: 1,
139+
maximum: Some(1),
140+
memory64: false,
141+
shared: false,
142+
});
143+
section(&mut module, mems);
144+
145+
let mut globals = wasm_encoder::GlobalSection::new();
146+
let fuel_global = globals.len();
147+
globals.global(
148+
wasm_encoder::GlobalType {
149+
val_type: wasm_encoder::ValType::I32,
150+
mutable: true,
151+
},
152+
&Instruction::I32Const(0),
153+
);
154+
let stack_len_global = globals.len();
155+
globals.global(
156+
wasm_encoder::GlobalType {
157+
val_type: wasm_encoder::ValType::I32,
158+
mutable: true,
159+
},
160+
&Instruction::I32Const(0),
161+
);
162+
section(&mut module, globals);
163+
164+
let mut exports = wasm_encoder::ExportSection::new();
165+
exports.export("run", wasm_encoder::ExportKind::Func, run_func);
166+
exports.export("get_stack", wasm_encoder::ExportKind::Func, get_stack_func);
167+
exports.export("memory", wasm_encoder::ExportKind::Memory, memory);
168+
section(&mut module, exports);
169+
170+
let mut code = wasm_encoder::CodeSection::new();
171+
for (func_index, func) in self.funcs.iter().enumerate() {
172+
let mut body = wasm_encoder::Function::new(vec![]);
173+
174+
// Add this function to our internal stack.
175+
//
176+
// Note that we know our `stack_len_global` can't go beyond memory
177+
// bounds because we limit fuel to at most `u8::MAX` and each stack
178+
// entry is an `i32` and `u8::MAX * size_of(i32)` still fits in one
179+
// page.
180+
body.instruction(&Instruction::GlobalGet(stack_len_global))
181+
.instruction(&Instruction::I32Const(
182+
(num_imported_funcs + u32::try_from(func_index).unwrap()) as i32,
183+
))
184+
.instruction(&Instruction::I32Store(wasm_encoder::MemArg {
185+
offset: 0,
186+
align: 0,
187+
memory_index: memory,
188+
}))
189+
.instruction(&Instruction::GlobalGet(stack_len_global))
190+
.instruction(&Instruction::I32Const(mem::size_of::<i32>() as i32))
191+
.instruction(&Instruction::I32Add)
192+
.instruction(&Instruction::GlobalSet(stack_len_global));
193+
194+
// Trap if we are out of fuel.
195+
body.instruction(&Instruction::GlobalGet(fuel_global))
196+
.instruction(&Instruction::I32Eqz)
197+
.instruction(&Instruction::If(wasm_encoder::BlockType::Empty))
198+
.instruction(&Instruction::Unreachable)
199+
.instruction(&Instruction::End);
200+
201+
// Decrement fuel.
202+
body.instruction(&Instruction::GlobalGet(fuel_global))
203+
.instruction(&Instruction::I32Const(1))
204+
.instruction(&Instruction::I32Sub)
205+
.instruction(&Instruction::GlobalSet(fuel_global));
206+
207+
// Perform our specified operations.
208+
for op in &func.ops {
209+
match op {
210+
Op::CheckStackInHost => {
211+
body.instruction(&Instruction::I32Const(0))
212+
.instruction(&Instruction::GlobalGet(stack_len_global))
213+
.instruction(&Instruction::Call(check_stack_func));
214+
}
215+
Op::Call(f) => {
216+
body.instruction(&Instruction::Call(f + num_imported_funcs));
217+
}
218+
}
219+
}
220+
221+
// Remove this function from our internal stack.
222+
body.instruction(&Instruction::GlobalGet(stack_len_global))
223+
.instruction(&Instruction::I32Const(mem::size_of::<i32>() as i32))
224+
.instruction(&Instruction::I32Sub)
225+
.instruction(&Instruction::GlobalSet(stack_len_global));
226+
227+
body.instruction(&Instruction::End);
228+
function(&mut code, body);
229+
}
230+
231+
let mut run_body = wasm_encoder::Function::new(vec![]);
232+
233+
// Add the `run` function to our internal stack.
234+
//
235+
// See above comments about overflow.
236+
run_body
237+
.instruction(&Instruction::GlobalGet(stack_len_global))
238+
.instruction(&Instruction::I32Const(
239+
u32::try_from(run_func).unwrap() as i32
240+
))
241+
.instruction(&Instruction::I32Store(wasm_encoder::MemArg {
242+
offset: 0,
243+
align: 0,
244+
memory_index: memory,
245+
}))
246+
.instruction(&Instruction::GlobalGet(stack_len_global))
247+
.instruction(&Instruction::I32Const(mem::size_of::<i32>() as i32))
248+
.instruction(&Instruction::I32Add)
249+
.instruction(&Instruction::GlobalSet(stack_len_global));
250+
251+
// Initialize the fuel global and call the first locally defined
252+
// function.
253+
run_body
254+
.instruction(&Instruction::LocalGet(0))
255+
.instruction(&Instruction::GlobalSet(fuel_global))
256+
.instruction(&Instruction::Call(num_imported_funcs));
257+
258+
// Remove the `run` function from our internal stack.
259+
run_body
260+
.instruction(&Instruction::GlobalGet(stack_len_global))
261+
.instruction(&Instruction::I32Const(mem::size_of::<i32>() as i32))
262+
.instruction(&Instruction::I32Sub)
263+
.instruction(&Instruction::GlobalSet(stack_len_global));
264+
265+
run_body.instruction(&Instruction::End);
266+
function(&mut code, run_body);
267+
268+
let mut get_stack_body = wasm_encoder::Function::new(vec![]);
269+
get_stack_body
270+
.instruction(&Instruction::I32Const(0))
271+
.instruction(&Instruction::GlobalGet(stack_len_global))
272+
.instruction(&Instruction::End);
273+
function(&mut code, get_stack_body);
274+
275+
section(&mut module, code);
276+
277+
return module.finish();
278+
279+
// Helper that defines a section in the module and takes ownership of it
280+
// so that it is dropped and its memory reclaimed after adding it to the
281+
// module.
282+
fn section(module: &mut wasm_encoder::Module, section: impl wasm_encoder::Section) {
283+
module.section(&section);
284+
}
285+
286+
// Helper that defines a function body in the code section and takes
287+
// ownership of it so that it is dropped and its memory reclaimed after
288+
// adding it to the module.
289+
fn function(code: &mut wasm_encoder::CodeSection, func: wasm_encoder::Function) {
290+
code.function(&func);
291+
}
292+
}
293+
}
294+
295+
#[cfg(test)]
296+
mod tests {
297+
use super::*;
298+
use rand::prelude::*;
299+
use wasmparser::Validator;
300+
301+
#[test]
302+
fn stacks_generates_valid_wasm_modules() {
303+
let mut rng = SmallRng::seed_from_u64(0);
304+
let mut buf = vec![0; 2048];
305+
for _ in 0..1024 {
306+
rng.fill_bytes(&mut buf);
307+
let u = Unstructured::new(&buf);
308+
if let Ok(stacks) = Stacks::arbitrary_take_rest(u) {
309+
let wasm = stacks.wasm();
310+
validate(&wasm);
311+
}
312+
}
313+
}
314+
315+
fn validate(wasm: &[u8]) {
316+
let mut validator = Validator::new();
317+
let err = match validator.validate_all(wasm) {
318+
Ok(_) => return,
319+
Err(e) => e,
320+
};
321+
drop(std::fs::write("test.wasm", wasm));
322+
if let Ok(text) = wasmprinter::print_bytes(wasm) {
323+
drop(std::fs::write("test.wat", &text));
324+
}
325+
panic!("wasm failed to validate: {}", err);
326+
}
327+
}

crates/fuzzing/src/oracles.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
//! panicking.
1212
1313
pub mod dummy;
14+
mod stacks;
1415

1516
use crate::generators;
1617
use arbitrary::Arbitrary;
1718
use log::debug;
19+
pub use stacks::check_stacks;
1820
use std::cell::Cell;
1921
use std::rc::Rc;
2022
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};

0 commit comments

Comments
 (0)