Coverage Report

Created: 2025-01-06 07:43

/src/wasmtime/crates/fuzzing/src/generators/stacks.rs
Line
Count
Source (jump to first uncovered line)
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
4.92k
    fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
42
4.92k
        let funcs = Self::arbitrary_funcs(u)?;
43
4.92k
        let n = u.len().min(200);
44
4.92k
        let inputs = u.bytes(n)?.to_vec();
45
4.92k
        Ok(Stacks { funcs, inputs })
46
4.92k
    }
47
}
48
49
impl Stacks {
50
4.92k
    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
4.92k
        let nfuncs = u.int_in_range(1..=MAX_FUNCS)?;
54
4.92k
        let mut funcs = (0..nfuncs)
55
60.6k
            .map(|_| {
56
60.6k
                Ok(Function {
57
60.6k
                    ops: Vec::new(), // generated later
58
60.6k
                    params: u.int_in_range(0..=MAX_PARAMS)?,
59
60.6k
                    results: u.int_in_range(0..=MAX_PARAMS)?,
60
                })
61
60.6k
            })
62
4.92k
            .collect::<Result<Vec<_>>>()?;
63
4.92k
        let mut funcs_by_result = vec![Vec::new(); MAX_PARAMS + 1];
64
60.6k
        for (i, func) in funcs.iter().enumerate() {
65
60.6k
            funcs_by_result[func.results].push(i as u32);
66
60.6k
        }
67
68
        // Fill in each function body with various instructions/operations now
69
        // that the set of functions is known.
70
60.6k
        for f in funcs.iter_mut() {
71
60.6k
            let funcs_with_same_results = &funcs_by_result[f.results];
72
60.6k
            for _ in 0..u.arbitrary_len::<usize>()?.min(MAX_OPS) {
73
2.29M
                let op = match u.int_in_range(0..=3)? {
74
336k
                    0 => Op::CheckStackInHost,
75
1.35M
                    1 => Op::Call(u.int_in_range(0..=nfuncs - 1)?),
76
592k
                    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
12.4k
                    3 => Op::ReturnCall(*u.choose(funcs_with_same_results)?),
80
0
                    _ => unreachable!(),
81
                };
82
2.29M
                f.ops.push(op);
83
2.29M
                // once a `return_call` has been generated there's no need to
84
2.29M
                // generate any more instructions, so fall through to below.
85
2.29M
                if let Some(Op::ReturnCall(_)) = f.ops.last() {
86
12.4k
                    break;
87
2.28M
                }
88
            }
89
        }
90
91
4.92k
        Ok(funcs)
92
4.92k
    }
93
94
    /// Get the input values to run the Wasm module with.
95
4.92k
    pub fn inputs(&self) -> &[u8] {
96
4.92k
        &self.inputs
97
4.92k
    }
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
4.92k
    pub fn wasm(&self) -> Vec<u8> {
121
4.92k
        let mut module = wasm_encoder::Module::new();
122
4.92k
123
4.92k
        let mut types = wasm_encoder::TypeSection::new();
124
4.92k
125
4.92k
        let run_type = types.len();
126
4.92k
        types
127
4.92k
            .ty()
128
4.92k
            .function(vec![wasm_encoder::ValType::I32], vec![]);
129
4.92k
130
4.92k
        let get_stack_type = types.len();
131
4.92k
        types.ty().function(
132
4.92k
            vec![],
133
4.92k
            vec![wasm_encoder::ValType::I32, wasm_encoder::ValType::I32],
134
4.92k
        );
135
4.92k
136
4.92k
        let call_func_type = types.len();
137
4.92k
        types
138
4.92k
            .ty()
139
4.92k
            .function(vec![wasm_encoder::ValType::FUNCREF], vec![]);
140
4.92k
141
4.92k
        let check_stack_type = types.len();
142
4.92k
        types.ty().function(vec![], vec![]);
143
4.92k
144
4.92k
        let func_types_start = types.len();
145
60.6k
        for func in self.funcs.iter() {
146
60.6k
            types.ty().function(
147
60.6k
                vec![ValType::I32; func.params],
148
60.6k
                vec![ValType::I32; func.results],
149
60.6k
            );
150
60.6k
        }
151
152
4.92k
        section(&mut module, types);
153
4.92k
154
4.92k
        let mut imports = wasm_encoder::ImportSection::new();
155
4.92k
        let check_stack_func = 0;
156
4.92k
        imports.import(
157
4.92k
            "host",
158
4.92k
            "check_stack",
159
4.92k
            wasm_encoder::EntityType::Function(check_stack_type),
160
4.92k
        );
161
4.92k
        let call_func_func = 1;
162
4.92k
        imports.import(
163
4.92k
            "host",
164
4.92k
            "call_func",
165
4.92k
            wasm_encoder::EntityType::Function(call_func_type),
166
4.92k
        );
167
4.92k
        let num_imported_funcs = 2;
168
4.92k
        section(&mut module, imports);
169
4.92k
170
4.92k
        let mut funcs = wasm_encoder::FunctionSection::new();
171
60.6k
        for (i, _) in self.funcs.iter().enumerate() {
172
60.6k
            funcs.function(func_types_start + (i as u32));
173
60.6k
        }
174
4.92k
        let run_func = funcs.len() + num_imported_funcs;
175
4.92k
        funcs.function(run_type);
176
4.92k
        let get_stack_func = funcs.len() + num_imported_funcs;
177
4.92k
        funcs.function(get_stack_type);
178
4.92k
        section(&mut module, funcs);
179
4.92k
180
4.92k
        let mut mems = wasm_encoder::MemorySection::new();
181
4.92k
        let memory = mems.len();
182
4.92k
        mems.memory(wasm_encoder::MemoryType {
183
4.92k
            minimum: 1,
184
4.92k
            maximum: Some(1),
185
4.92k
            memory64: false,
186
4.92k
            shared: false,
187
4.92k
            page_size_log2: None,
188
4.92k
        });
189
4.92k
        section(&mut module, mems);
190
4.92k
191
4.92k
        let mut globals = wasm_encoder::GlobalSection::new();
192
4.92k
        let fuel_global = globals.len();
193
4.92k
        globals.global(
194
4.92k
            wasm_encoder::GlobalType {
195
4.92k
                val_type: wasm_encoder::ValType::I32,
196
4.92k
                mutable: true,
197
4.92k
                shared: false,
198
4.92k
            },
199
4.92k
            &wasm_encoder::ConstExpr::i32_const(0),
200
4.92k
        );
201
4.92k
        let stack_len_global = globals.len();
202
4.92k
        globals.global(
203
4.92k
            wasm_encoder::GlobalType {
204
4.92k
                val_type: wasm_encoder::ValType::I32,
205
4.92k
                mutable: true,
206
4.92k
                shared: false,
207
4.92k
            },
208
4.92k
            &wasm_encoder::ConstExpr::i32_const(0),
209
4.92k
        );
210
4.92k
        section(&mut module, globals);
211
4.92k
212
4.92k
        let mut exports = wasm_encoder::ExportSection::new();
213
4.92k
        exports.export("run", wasm_encoder::ExportKind::Func, run_func);
214
4.92k
        exports.export("get_stack", wasm_encoder::ExportKind::Func, get_stack_func);
215
4.92k
        exports.export("memory", wasm_encoder::ExportKind::Memory, memory);
216
4.92k
        exports.export("fuel", wasm_encoder::ExportKind::Global, fuel_global);
217
4.92k
        section(&mut module, exports);
218
4.92k
219
4.92k
        let mut elems = wasm_encoder::ElementSection::new();
220
4.92k
        elems.declared(wasm_encoder::Elements::Functions(
221
4.92k
            (0..num_imported_funcs + u32::try_from(self.funcs.len()).unwrap())
222
4.92k
                .collect::<Vec<_>>()
223
4.92k
                .into(),
224
4.92k
        ));
225
4.92k
        section(&mut module, elems);
226
4.92k
227
131k
        let check_fuel = |body: &mut wasm_encoder::Function| {
228
131k
            // Trap if we are out of fuel.
229
131k
            body.instruction(&Instruction::GlobalGet(fuel_global))
230
131k
                .instruction(&Instruction::I32Eqz)
231
131k
                .instruction(&Instruction::If(wasm_encoder::BlockType::Empty))
232
131k
                .instruction(&Instruction::Unreachable)
233
131k
                .instruction(&Instruction::End);
234
131k
235
131k
            // Decrement fuel.
236
131k
            body.instruction(&Instruction::GlobalGet(fuel_global))
237
131k
                .instruction(&Instruction::I32Const(1))
238
131k
                .instruction(&Instruction::I32Sub)
239
131k
                .instruction(&Instruction::GlobalSet(fuel_global));
240
131k
        };
241
242
65.5k
        let push_func_to_stack = |body: &mut wasm_encoder::Function, func: u32| {
243
65.5k
            // Add this function to our internal stack.
244
65.5k
            //
245
65.5k
            // Note that we know our `stack_len_global` can't go beyond memory
246
65.5k
            // bounds because we limit fuel to at most `u8::MAX` and each stack
247
65.5k
            // entry is an `i32` and `u8::MAX * size_of(i32)` still fits in one
248
65.5k
            // Wasm page.
249
65.5k
            body.instruction(&Instruction::GlobalGet(stack_len_global))
250
65.5k
                .instruction(&Instruction::I32Const(func as i32))
251
65.5k
                .instruction(&Instruction::I32Store(wasm_encoder::MemArg {
252
65.5k
                    offset: 0,
253
65.5k
                    align: 0,
254
65.5k
                    memory_index: memory,
255
65.5k
                }))
256
65.5k
                .instruction(&Instruction::GlobalGet(stack_len_global))
257
65.5k
                .instruction(&Instruction::I32Const(mem::size_of::<i32>() as i32))
258
65.5k
                .instruction(&Instruction::I32Add)
259
65.5k
                .instruction(&Instruction::GlobalSet(stack_len_global));
260
65.5k
        };
261
262
65.5k
        let pop_func_from_stack = |body: &mut wasm_encoder::Function| {
263
65.5k
            // Remove this function from our internal stack.
264
65.5k
            body.instruction(&Instruction::GlobalGet(stack_len_global))
265
65.5k
                .instruction(&Instruction::I32Const(mem::size_of::<i32>() as i32))
266
65.5k
                .instruction(&Instruction::I32Sub)
267
65.5k
                .instruction(&Instruction::GlobalSet(stack_len_global));
268
65.5k
        };
269
270
1.37M
        let push_params = |body: &mut wasm_encoder::Function, func: u32| {
271
1.37M
            let func = &self.funcs[func as usize];
272
8.05M
            for _ in 0..func.params {
273
8.05M
                body.instruction(&Instruction::I32Const(0));
274
8.05M
            }
275
1.37M
        };
276
1.36M
        let pop_results = |body: &mut wasm_encoder::Function, func: u32| {
277
1.36M
            let func = &self.funcs[func as usize];
278
8.79M
            for _ in 0..func.results {
279
8.79M
                body.instruction(&Instruction::Drop);
280
8.79M
            }
281
1.36M
        };
282
48.1k
        let push_results = |body: &mut wasm_encoder::Function, func: u32| {
283
48.1k
            let func = &self.funcs[func as usize];
284
179k
            for _ in 0..func.results {
285
179k
                body.instruction(&Instruction::I32Const(0));
286
179k
            }
287
48.1k
        };
288
289
4.92k
        let mut code = wasm_encoder::CodeSection::new();
290
60.6k
        for (func_index, func) in self.funcs.iter().enumerate() {
291
60.6k
            let mut body = wasm_encoder::Function::new(vec![]);
292
60.6k
293
60.6k
            push_func_to_stack(
294
60.6k
                &mut body,
295
60.6k
                num_imported_funcs + u32::try_from(func_index).unwrap(),
296
60.6k
            );
297
60.6k
            check_fuel(&mut body);
298
60.6k
299
60.6k
            let mut check_fuel_and_pop_at_end = true;
300
301
            // Perform our specified operations.
302
2.35M
            for op in &func.ops {
303
2.29M
                assert!(check_fuel_and_pop_at_end);
304
2.29M
                match op {
305
336k
                    Op::CheckStackInHost => {
306
336k
                        body.instruction(&Instruction::Call(check_stack_func));
307
336k
                    }
308
1.35M
                    Op::Call(f) => {
309
1.35M
                        push_params(&mut body, *f);
310
1.35M
                        body.instruction(&Instruction::Call(f + num_imported_funcs));
311
1.35M
                        pop_results(&mut body, *f);
312
1.35M
                    }
313
592k
                    Op::CallThroughHost(f) => {
314
592k
                        body.instruction(&Instruction::RefFunc(f + num_imported_funcs))
315
592k
                            .instruction(&Instruction::Call(call_func_func));
316
592k
                    }
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
12.4k
                    Op::ReturnCall(f) => {
322
12.4k
                        push_params(&mut body, *f);
323
12.4k
                        check_fuel(&mut body);
324
12.4k
                        pop_func_from_stack(&mut body);
325
12.4k
                        check_fuel_and_pop_at_end = false;
326
12.4k
                        body.instruction(&Instruction::ReturnCall(f + num_imported_funcs));
327
12.4k
                    }
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
60.6k
            if check_fuel_and_pop_at_end {
338
48.1k
                check_fuel(&mut body);
339
48.1k
                pop_func_from_stack(&mut body);
340
48.1k
                push_results(&mut body, func_index as u32);
341
48.1k
            }
342
343
60.6k
            function(&mut code, body);
344
        }
345
346
4.92k
        let mut run_body = wasm_encoder::Function::new(vec![]);
347
4.92k
348
4.92k
        // Reset the bump pointer for the internal stack (this allows us to
349
4.92k
        // reuse an instance in the oracle, rather than re-instantiate).
350
4.92k
        run_body
351
4.92k
            .instruction(&Instruction::I32Const(0))
352
4.92k
            .instruction(&Instruction::GlobalSet(stack_len_global));
353
4.92k
354
4.92k
        // Initialize the fuel global.
355
4.92k
        run_body
356
4.92k
            .instruction(&Instruction::LocalGet(0))
357
4.92k
            .instruction(&Instruction::GlobalSet(fuel_global));
358
4.92k
359
4.92k
        push_func_to_stack(&mut run_body, run_func);
360
4.92k
361
4.92k
        // Make sure to check for out-of-fuel in the `run` function as well, so
362
4.92k
        // that we also capture stack traces with only one frame, not just `run`
363
4.92k
        // followed by the first locally-defined function and then zero or more
364
4.92k
        // extra frames.
365
4.92k
        check_fuel(&mut run_body);
366
4.92k
367
4.92k
        // Call the first locally defined function.
368
4.92k
        push_params(&mut run_body, 0);
369
4.92k
        run_body.instruction(&Instruction::Call(num_imported_funcs));
370
4.92k
        pop_results(&mut run_body, 0);
371
4.92k
372
4.92k
        check_fuel(&mut run_body);
373
4.92k
        pop_func_from_stack(&mut run_body);
374
4.92k
375
4.92k
        function(&mut code, run_body);
376
4.92k
377
4.92k
        let mut get_stack_body = wasm_encoder::Function::new(vec![]);
378
4.92k
        get_stack_body
379
4.92k
            .instruction(&Instruction::I32Const(0))
380
4.92k
            .instruction(&Instruction::GlobalGet(stack_len_global));
381
4.92k
        function(&mut code, get_stack_body);
382
4.92k
383
4.92k
        section(&mut module, code);
384
4.92k
385
4.92k
        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
39.4k
        fn section(module: &mut wasm_encoder::Module, section: impl wasm_encoder::Section) {
391
39.4k
            module.section(&section);
392
39.4k
        }
<wasmtime_fuzzing::generators::stacks::Stacks>::wasm::section::<wasm_encoder::core::code::CodeSection>
Line
Count
Source
390
4.92k
        fn section(module: &mut wasm_encoder::Module, section: impl wasm_encoder::Section) {
391
4.92k
            module.section(&section);
392
4.92k
        }
<wasmtime_fuzzing::generators::stacks::Stacks>::wasm::section::<wasm_encoder::core::types::TypeSection>
Line
Count
Source
390
4.92k
        fn section(module: &mut wasm_encoder::Module, section: impl wasm_encoder::Section) {
391
4.92k
            module.section(&section);
392
4.92k
        }
<wasmtime_fuzzing::generators::stacks::Stacks>::wasm::section::<wasm_encoder::core::exports::ExportSection>
Line
Count
Source
390
4.92k
        fn section(module: &mut wasm_encoder::Module, section: impl wasm_encoder::Section) {
391
4.92k
            module.section(&section);
392
4.92k
        }
<wasmtime_fuzzing::generators::stacks::Stacks>::wasm::section::<wasm_encoder::core::globals::GlobalSection>
Line
Count
Source
390
4.92k
        fn section(module: &mut wasm_encoder::Module, section: impl wasm_encoder::Section) {
391
4.92k
            module.section(&section);
392
4.92k
        }
<wasmtime_fuzzing::generators::stacks::Stacks>::wasm::section::<wasm_encoder::core::imports::ImportSection>
Line
Count
Source
390
4.92k
        fn section(module: &mut wasm_encoder::Module, section: impl wasm_encoder::Section) {
391
4.92k
            module.section(&section);
392
4.92k
        }
<wasmtime_fuzzing::generators::stacks::Stacks>::wasm::section::<wasm_encoder::core::elements::ElementSection>
Line
Count
Source
390
4.92k
        fn section(module: &mut wasm_encoder::Module, section: impl wasm_encoder::Section) {
391
4.92k
            module.section(&section);
392
4.92k
        }
<wasmtime_fuzzing::generators::stacks::Stacks>::wasm::section::<wasm_encoder::core::memories::MemorySection>
Line
Count
Source
390
4.92k
        fn section(module: &mut wasm_encoder::Module, section: impl wasm_encoder::Section) {
391
4.92k
            module.section(&section);
392
4.92k
        }
<wasmtime_fuzzing::generators::stacks::Stacks>::wasm::section::<wasm_encoder::core::functions::FunctionSection>
Line
Count
Source
390
4.92k
        fn section(module: &mut wasm_encoder::Module, section: impl wasm_encoder::Section) {
391
4.92k
            module.section(&section);
392
4.92k
        }
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
70.5k
        fn function(code: &mut wasm_encoder::CodeSection, mut func: wasm_encoder::Function) {
398
70.5k
            func.instruction(&Instruction::End);
399
70.5k
            code.function(&func);
400
70.5k
        }
401
4.92k
    }
402
}
403
404
#[cfg(test)]
405
mod tests {
406
    use super::*;
407
    use rand::prelude::*;
408
    use wasmparser::Validator;
409
410
    #[test]
411
    fn stacks_generates_valid_wasm_modules() {
412
        let mut rng = SmallRng::seed_from_u64(0);
413
        let mut buf = vec![0; 2048];
414
        for _ in 0..1024 {
415
            rng.fill_bytes(&mut buf);
416
            let u = Unstructured::new(&buf);
417
            if let Ok(stacks) = Stacks::arbitrary_take_rest(u) {
418
                let wasm = stacks.wasm();
419
                validate(&wasm);
420
            }
421
        }
422
    }
423
424
    fn validate(wasm: &[u8]) {
425
        let mut validator = Validator::new();
426
        let err = match validator.validate_all(wasm) {
427
            Ok(_) => return,
428
            Err(e) => e,
429
        };
430
        drop(std::fs::write("test.wasm", wasm));
431
        if let Ok(text) = wasmprinter::print_bytes(wasm) {
432
            drop(std::fs::write("test.wat", &text));
433
        }
434
        panic!("wasm failed to validate: {err}");
435
    }
436
}