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