Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fuzzing/src/oracles/stacks.rs
1693 views
1
use crate::generators::Stacks;
2
use anyhow::bail;
3
use wasmtime::*;
4
5
/// Run the given `Stacks` test case and assert that the host's view of the Wasm
6
/// stack matches the test case's understanding of the Wasm stack.
7
///
8
/// Returns the maximum stack depth we checked.
9
pub fn check_stacks(stacks: Stacks) -> usize {
10
let wasm = stacks.wasm();
11
crate::oracles::log_wasm(&wasm);
12
13
let engine = Engine::default();
14
let module = Module::new(&engine, &wasm).expect("should compile okay");
15
16
let mut linker = Linker::new(&engine);
17
linker
18
.func_wrap(
19
"host",
20
"check_stack",
21
|mut caller: Caller<'_, ()>| -> Result<()> {
22
let fuel = caller
23
.get_export("fuel")
24
.expect("should export `fuel`")
25
.into_global()
26
.expect("`fuel` export should be a global");
27
28
let fuel_left = fuel.get(&mut caller).unwrap_i32();
29
if fuel_left == 0 {
30
bail!(Trap::OutOfFuel);
31
}
32
33
fuel.set(&mut caller, Val::I32(fuel_left - 1)).unwrap();
34
Ok(())
35
},
36
)
37
.unwrap()
38
.func_wrap(
39
"host",
40
"call_func",
41
|mut caller: Caller<'_, ()>, f: Option<Func>| {
42
let f = f.unwrap();
43
let ty = f.ty(&caller);
44
let params = vec![Val::I32(0); ty.params().len()];
45
let mut results = vec![Val::I32(0); ty.results().len()];
46
f.call(&mut caller, &params, &mut results)?;
47
Ok(())
48
},
49
)
50
.unwrap();
51
52
let mut store = Store::new(&engine, ());
53
54
let instance = linker
55
.instantiate(&mut store, &module)
56
.expect("should instantiate okay");
57
58
let run = instance
59
.get_typed_func::<(u32,), ()>(&mut store, "run")
60
.expect("should export `run` function");
61
62
let mut max_stack_depth = 0;
63
for input in stacks.inputs().iter().copied() {
64
log::debug!("input: {input}");
65
if let Err(trap) = run.call(&mut store, (input.into(),)) {
66
log::debug!("trap: {trap:?}");
67
let get_stack = instance
68
.get_typed_func::<(), (u32, u32)>(&mut store, "get_stack")
69
.expect("should export `get_stack` function as expected");
70
71
let (ptr, len) = get_stack
72
.call(&mut store, ())
73
.expect("`get_stack` should not trap");
74
75
let memory = instance
76
.get_memory(&mut store, "memory")
77
.expect("should have `memory` export");
78
79
let host_trace = trap.downcast_ref::<WasmBacktrace>().unwrap().frames();
80
let trap = trap.downcast_ref::<Trap>().unwrap();
81
max_stack_depth = max_stack_depth.max(host_trace.len());
82
assert_stack_matches(&mut store, memory, ptr, len, host_trace, *trap);
83
}
84
}
85
max_stack_depth
86
}
87
88
/// Assert that the Wasm program's view of the stack matches the host's view.
89
fn assert_stack_matches(
90
store: &mut impl AsContextMut,
91
memory: Memory,
92
ptr: u32,
93
len: u32,
94
host_trace: &[FrameInfo],
95
trap: Trap,
96
) {
97
let mut data = vec![0; len as usize];
98
memory
99
.read(&mut *store, ptr as usize, &mut data)
100
.expect("should be in bounds");
101
102
let mut wasm_trace = vec![];
103
for entry in data.chunks(4).rev() {
104
let mut bytes = [0; 4];
105
bytes.copy_from_slice(entry);
106
let entry = u32::from_le_bytes(bytes);
107
wasm_trace.push(entry);
108
}
109
110
// If the test case here trapped due to stack overflow then the host trace
111
// will have one more frame than the wasm trace. The wasm didn't actually
112
// get to the point of pushing onto its own trace stack where the host will
113
// be able to see the exact function that triggered the stack overflow. In
114
// this situation the host trace is asserted to be one larger and then the
115
// top frame (first) of the host trace is discarded.
116
let host_trace = if trap == Trap::StackOverflow {
117
assert_eq!(host_trace.len(), wasm_trace.len() + 1);
118
&host_trace[1..]
119
} else {
120
host_trace
121
};
122
123
log::debug!("Wasm thinks its stack is: {wasm_trace:?}");
124
log::debug!(
125
"Host thinks the stack is: {:?}",
126
host_trace
127
.iter()
128
.map(|f| f.func_index())
129
.collect::<Vec<_>>()
130
);
131
132
assert_eq!(wasm_trace.len(), host_trace.len());
133
for (wasm_entry, host_entry) in wasm_trace.into_iter().zip(host_trace) {
134
assert_eq!(wasm_entry, host_entry.func_index());
135
}
136
}
137
138
#[cfg(test)]
139
mod tests {
140
use super::*;
141
use arbitrary::{Arbitrary, Unstructured};
142
use rand::prelude::*;
143
144
const TARGET_STACK_DEPTH: usize = 10;
145
146
#[test]
147
fn smoke_test() {
148
let mut rng = SmallRng::seed_from_u64(0);
149
let mut buf = vec![0; 2048];
150
151
for _ in 0..1024 {
152
rng.fill_bytes(&mut buf);
153
let u = Unstructured::new(&buf);
154
if let Ok(stacks) = Stacks::arbitrary_take_rest(u) {
155
let max_stack_depth = check_stacks(stacks);
156
if max_stack_depth >= TARGET_STACK_DEPTH {
157
return;
158
}
159
}
160
}
161
162
panic!(
163
"never generated a `Stacks` test case that reached {TARGET_STACK_DEPTH} \
164
deep stack frames",
165
);
166
}
167
}
168
169