Path: blob/main/crates/fuzzing/src/oracles/stacks.rs
1693 views
use crate::generators::Stacks;1use anyhow::bail;2use wasmtime::*;34/// Run the given `Stacks` test case and assert that the host's view of the Wasm5/// stack matches the test case's understanding of the Wasm stack.6///7/// Returns the maximum stack depth we checked.8pub fn check_stacks(stacks: Stacks) -> usize {9let wasm = stacks.wasm();10crate::oracles::log_wasm(&wasm);1112let engine = Engine::default();13let module = Module::new(&engine, &wasm).expect("should compile okay");1415let mut linker = Linker::new(&engine);16linker17.func_wrap(18"host",19"check_stack",20|mut caller: Caller<'_, ()>| -> Result<()> {21let fuel = caller22.get_export("fuel")23.expect("should export `fuel`")24.into_global()25.expect("`fuel` export should be a global");2627let fuel_left = fuel.get(&mut caller).unwrap_i32();28if fuel_left == 0 {29bail!(Trap::OutOfFuel);30}3132fuel.set(&mut caller, Val::I32(fuel_left - 1)).unwrap();33Ok(())34},35)36.unwrap()37.func_wrap(38"host",39"call_func",40|mut caller: Caller<'_, ()>, f: Option<Func>| {41let f = f.unwrap();42let ty = f.ty(&caller);43let params = vec![Val::I32(0); ty.params().len()];44let mut results = vec![Val::I32(0); ty.results().len()];45f.call(&mut caller, ¶ms, &mut results)?;46Ok(())47},48)49.unwrap();5051let mut store = Store::new(&engine, ());5253let instance = linker54.instantiate(&mut store, &module)55.expect("should instantiate okay");5657let run = instance58.get_typed_func::<(u32,), ()>(&mut store, "run")59.expect("should export `run` function");6061let mut max_stack_depth = 0;62for input in stacks.inputs().iter().copied() {63log::debug!("input: {input}");64if let Err(trap) = run.call(&mut store, (input.into(),)) {65log::debug!("trap: {trap:?}");66let get_stack = instance67.get_typed_func::<(), (u32, u32)>(&mut store, "get_stack")68.expect("should export `get_stack` function as expected");6970let (ptr, len) = get_stack71.call(&mut store, ())72.expect("`get_stack` should not trap");7374let memory = instance75.get_memory(&mut store, "memory")76.expect("should have `memory` export");7778let host_trace = trap.downcast_ref::<WasmBacktrace>().unwrap().frames();79let trap = trap.downcast_ref::<Trap>().unwrap();80max_stack_depth = max_stack_depth.max(host_trace.len());81assert_stack_matches(&mut store, memory, ptr, len, host_trace, *trap);82}83}84max_stack_depth85}8687/// Assert that the Wasm program's view of the stack matches the host's view.88fn assert_stack_matches(89store: &mut impl AsContextMut,90memory: Memory,91ptr: u32,92len: u32,93host_trace: &[FrameInfo],94trap: Trap,95) {96let mut data = vec![0; len as usize];97memory98.read(&mut *store, ptr as usize, &mut data)99.expect("should be in bounds");100101let mut wasm_trace = vec![];102for entry in data.chunks(4).rev() {103let mut bytes = [0; 4];104bytes.copy_from_slice(entry);105let entry = u32::from_le_bytes(bytes);106wasm_trace.push(entry);107}108109// If the test case here trapped due to stack overflow then the host trace110// will have one more frame than the wasm trace. The wasm didn't actually111// get to the point of pushing onto its own trace stack where the host will112// be able to see the exact function that triggered the stack overflow. In113// this situation the host trace is asserted to be one larger and then the114// top frame (first) of the host trace is discarded.115let host_trace = if trap == Trap::StackOverflow {116assert_eq!(host_trace.len(), wasm_trace.len() + 1);117&host_trace[1..]118} else {119host_trace120};121122log::debug!("Wasm thinks its stack is: {wasm_trace:?}");123log::debug!(124"Host thinks the stack is: {:?}",125host_trace126.iter()127.map(|f| f.func_index())128.collect::<Vec<_>>()129);130131assert_eq!(wasm_trace.len(), host_trace.len());132for (wasm_entry, host_entry) in wasm_trace.into_iter().zip(host_trace) {133assert_eq!(wasm_entry, host_entry.func_index());134}135}136137#[cfg(test)]138mod tests {139use super::*;140use arbitrary::{Arbitrary, Unstructured};141use rand::prelude::*;142143const TARGET_STACK_DEPTH: usize = 10;144145#[test]146fn smoke_test() {147let mut rng = SmallRng::seed_from_u64(0);148let mut buf = vec![0; 2048];149150for _ in 0..1024 {151rng.fill_bytes(&mut buf);152let u = Unstructured::new(&buf);153if let Ok(stacks) = Stacks::arbitrary_take_rest(u) {154let max_stack_depth = check_stacks(stacks);155if max_stack_depth >= TARGET_STACK_DEPTH {156return;157}158}159}160161panic!(162"never generated a `Stacks` test case that reached {TARGET_STACK_DEPTH} \163deep stack frames",164);165}166}167168169