1 //! Oracles related to memory. 2 3 use crate::generators::{HeapImage, MemoryAccesses}; 4 use wasmtime::*; 5 6 /// Oracle to perform the described memory accesses and check that they are all 7 /// in- or out-of-bounds as expected 8 pub fn check_memory_accesses(input: MemoryAccesses) { 9 crate::init_fuzzing(); 10 log::info!("Testing memory accesses: {input:#x?}"); 11 12 let offset = input.offset; 13 let growth = input.growth; 14 let wasm = build_wasm(&input.image, offset); 15 crate::oracles::log_wasm(&wasm); 16 let offset = u64::from(offset); 17 18 let mut config = input.config.to_wasmtime(); 19 20 // Force-enable proposals if the heap image needs them. 21 if input.image.memory64 { 22 config.wasm_memory64(true); 23 } 24 if input.image.page_size_log2.is_some() { 25 config.wasm_custom_page_sizes(true); 26 } 27 28 let engine = Engine::new(&config).unwrap(); 29 let module = match Module::new(&engine, &wasm) { 30 Ok(m) => m, 31 Err(e) => { 32 let e = format!("{e:?}"); 33 log::info!("Failed to create `Module`: {e}"); 34 if cfg!(feature = "fuzz-pcc") && e.contains("Compilation error: Proof-carrying-code") { 35 return; 36 } 37 assert!( 38 e.contains("bytes which exceeds the configured maximum of") 39 || e.contains("exceeds the limit of"), 40 "bad module compilation error: {e:?}", 41 ); 42 return; 43 } 44 }; 45 46 let limits = super::StoreLimits::new(); 47 let mut store = Store::new(&engine, limits); 48 input.config.configure_store(&mut store); 49 50 // If we are using fuel, make sure we add enough that we won't ever run out. 51 if input.config.wasmtime.consume_fuel { 52 store.set_fuel(u64::MAX).unwrap(); 53 } 54 55 let instance = match Instance::new(&mut store, &module, &[]) { 56 Ok(x) => x, 57 Err(e) => { 58 log::info!("Failed to instantiate: {e:?}"); 59 assert!(format!("{e:?}").contains("Cannot allocate memory")); 60 return; 61 } 62 }; 63 64 let memory = instance.get_memory(&mut store, "memory").unwrap(); 65 let load8 = instance 66 .get_typed_func::<u64, u32>(&mut store, "load8") 67 .unwrap(); 68 let load16 = instance 69 .get_typed_func::<u64, u32>(&mut store, "load16") 70 .unwrap(); 71 let load32 = instance 72 .get_typed_func::<u64, u32>(&mut store, "load32") 73 .unwrap(); 74 let load64 = instance 75 .get_typed_func::<u64, u64>(&mut store, "load64") 76 .unwrap(); 77 78 let do_accesses = |store: &mut Store<_>, msg: &str| { 79 let len = memory.data_size(&mut *store); 80 let len = u64::try_from(len).unwrap(); 81 82 if let Some(n) = len.checked_sub(8).and_then(|n| n.checked_sub(offset)) { 83 // Test various in-bounds accesses near the bound. 84 for i in 0..=7 { 85 let addr = n + i; 86 assert!(addr + offset + 1 <= len); 87 let result = load8.call(&mut *store, addr); 88 assert!( 89 result.is_ok(), 90 "{msg}: len={len:#x}, offset={offset:#x}, load8({n:#x} + {i:#x} = {addr:#x}) \ 91 should be in bounds, got {result:?}" 92 ); 93 } 94 for i in 0..=6 { 95 let addr = n + offset + i; 96 assert!(addr + 2 <= len); 97 let result = load16.call(&mut *store, n + i); 98 assert!( 99 result.is_ok(), 100 "{msg}: len={len:#x}, offset={offset:#x}, load16({n:#x} + {i:#x} = {addr:#x}) \ 101 should be in bounds, got {result:?}" 102 ); 103 } 104 for i in 0..=4 { 105 let addr = n + offset + i; 106 assert!(addr + 4 <= len); 107 let result = load32.call(&mut *store, n + i); 108 assert!( 109 result.is_ok(), 110 "{msg}: len={len:#x}, offset={offset:#x}, load32({n:#x} + {i:#x} = {addr:#x}) \ 111 should be in bounds, got {result:?}" 112 ); 113 } 114 assert!(n + offset + 8 <= len); 115 let result = load64.call(&mut *store, n); 116 assert!( 117 result.is_ok(), 118 "{msg}: len={len:#x}, offset={offset:#x}, load64({n:#x}) should be in bounds, \ 119 got {result:?}" 120 ); 121 122 // Test various out-of-bounds accesses overlapping the memory bound. 123 for i in 1..2 { 124 let addr = len - i; 125 assert!(addr + offset + 2 > len); 126 let result = load16.call(&mut *store, addr); 127 assert!( 128 result.is_err(), 129 "{msg}: len={len:#x}, offset={offset:#x}, load16({len:#x} - {i:#x} = {addr:#x}) \ 130 should trap, got {result:?}" 131 ); 132 } 133 for i in 1..4 { 134 let addr = len - i; 135 assert!(addr + offset + 4 > len); 136 let result = load32.call(&mut *store, addr); 137 assert!( 138 result.is_err(), 139 "{msg}: len={len:#x}, offset={offset:#x}, load32({len:#x} - {i:#x} = {addr:#x}) \ 140 should trap, got {result:?}" 141 ); 142 } 143 for i in 1..8 { 144 let addr = len - i; 145 assert!(addr + offset + 8 > len); 146 let result = load64.call(&mut *store, addr); 147 assert!( 148 result.is_err(), 149 "{msg}: len={len:#x}, offset={offset:#x}, load64({len:#x} - {i:#x} = {addr:#x}) \ 150 should trap, got {result:?}" 151 ); 152 } 153 } 154 155 // Test that out-of-bounds accesses just after the memory bound trap. 156 if let Some(n) = len.checked_sub(offset) { 157 for i in 0..=1 { 158 let addr = n + i; 159 assert!(addr + offset + 1 > len); 160 let result = load8.call(&mut *store, addr); 161 assert!( 162 result.is_err(), 163 "{msg}: len={len:#x}, offset={offset:#x}, load8({n:#x} + {i:#x} = {addr:#x}) \ 164 should trap, got {result:?}" 165 ); 166 assert!(addr + offset + 2 > len); 167 let result = load16.call(&mut *store, addr); 168 assert!( 169 result.is_err(), 170 "{msg}: len={len:#x}, offset={offset:#x}, load16({n:#x} + {i:#x} = {addr:#x}) \ 171 should trap, got {result:?}" 172 ); 173 assert!(addr + offset + 4 > len); 174 let result = load32.call(&mut *store, addr); 175 assert!( 176 result.is_err(), 177 "{msg}: len={len:#x}, offset={offset:#x}, load32({n:#x} + {i:#x} = {addr:#x}) \ 178 should trap, got {result:?}" 179 ); 180 assert!(addr + offset + 8 > len); 181 let result = load64.call(&mut *store, addr); 182 assert!( 183 result.is_err(), 184 "{msg}: len={len:#x}, offset={offset:#x}, load64({n:#x} + {i:#x} = {addr:#x}) \ 185 should trap, got {result:?}" 186 ); 187 } 188 } 189 190 // Test out-of-bounds accesses near the end of the index type's range to 191 // double check our overflow handling inside the bounds checks. 192 let len_is_4gib = len == u64::from(u32::MAX) + 1; 193 let end_delta = (input.image.memory64 && len_is_4gib) as u64; 194 let max = if input.image.memory64 { 195 u64::MAX 196 } else { 197 u64::from(u32::MAX) 198 }; 199 for i in 0..(1 - end_delta) { 200 let addr = max - i; 201 let result = load8.call(&mut *store, addr); 202 assert!( 203 result.is_err(), 204 "{msg}: len={len:#x}, offset={offset:#x}, load8({max:#x} - {i:#x} = {addr:#x}) \ 205 should trap, got {result:?}" 206 ); 207 } 208 for i in 0..(2 - end_delta) { 209 let addr = max - i; 210 let result = load16.call(&mut *store, addr); 211 assert!( 212 result.is_err(), 213 "{msg}: len={len:#x}, offset={offset:#x}, load16({max:#x} - {i:#x} = {addr:#x}) \ 214 should trap, got {result:?}" 215 ); 216 } 217 for i in 0..(4 - end_delta) { 218 let addr = max - i; 219 let result = load32.call(&mut *store, addr); 220 assert!( 221 result.is_err(), 222 "{msg}: len={len:#x}, offset={offset:#x}, load32({max:#x} - {i:#x} = {addr:#x}) \ 223 should trap, got {result:?}" 224 ); 225 } 226 for i in 0..(8 - end_delta) { 227 let addr = max - i; 228 let result = load64.call(&mut *store, addr); 229 assert!( 230 result.is_err(), 231 "{msg}: len={len:#x}, offset={offset:#x}, load64({max:#x} - {i:#x} = {addr:#x}) \ 232 should trap, got {result:?}" 233 ); 234 } 235 }; 236 237 do_accesses(&mut store, "initial size"); 238 let _ = memory.grow(&mut store, u64::from(growth)); 239 do_accesses(&mut store, "after growing"); 240 } 241 242 /// Build a Wasm module with a single memory in the shape of the given heap 243 /// image, exports that memory, and also exports four functions: 244 /// `load{8,16,32,64}`. Each of these functions takes an `i64` address, 245 /// truncates it to `i32` if the memory is not 64-bit, and loads its associated 246 /// number of bits from memory at `address + offset`. 247 /// 248 /// ```wat 249 /// (module 250 /// (memory (export "memory") ...) 251 /// (func (export "load8") (param i64) (result i32) 252 /// (i32.load8_u offset=${offset} (local.get 0)) 253 /// ) 254 /// ... 255 /// ) 256 /// ``` 257 fn build_wasm(image: &HeapImage, offset: u32) -> Vec<u8> { 258 let mut module = wasm_encoder::Module::new(); 259 260 { 261 let mut types = wasm_encoder::TypeSection::new(); 262 types 263 .ty() 264 .function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I32]); 265 types 266 .ty() 267 .function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I64]); 268 module.section(&types); 269 } 270 271 { 272 let mut funcs = wasm_encoder::FunctionSection::new(); 273 funcs.function(0); 274 funcs.function(0); 275 funcs.function(0); 276 funcs.function(1); 277 module.section(&funcs); 278 } 279 280 { 281 let mut memories = wasm_encoder::MemorySection::new(); 282 memories.memory(wasm_encoder::MemoryType { 283 minimum: u64::from(image.minimum), 284 maximum: image.maximum.map(Into::into), 285 memory64: image.memory64, 286 shared: false, 287 page_size_log2: image.page_size_log2, 288 }); 289 module.section(&memories); 290 } 291 292 { 293 let mut exports = wasm_encoder::ExportSection::new(); 294 exports.export("memory", wasm_encoder::ExportKind::Memory, 0); 295 exports.export("load8", wasm_encoder::ExportKind::Func, 0); 296 exports.export("load16", wasm_encoder::ExportKind::Func, 1); 297 exports.export("load32", wasm_encoder::ExportKind::Func, 2); 298 exports.export("load64", wasm_encoder::ExportKind::Func, 3); 299 module.section(&exports); 300 } 301 302 { 303 let mut code = wasm_encoder::CodeSection::new(); 304 { 305 let mut func = wasm_encoder::Function::new([]); 306 func.instruction(&wasm_encoder::Instruction::LocalGet(0)); 307 if !image.memory64 { 308 func.instruction(&wasm_encoder::Instruction::I32WrapI64); 309 } 310 func.instruction(&wasm_encoder::Instruction::I32Load8U( 311 wasm_encoder::MemArg { 312 offset: u64::from(offset), 313 align: 0, 314 memory_index: 0, 315 }, 316 )); 317 func.instruction(&wasm_encoder::Instruction::End); 318 code.function(&func); 319 } 320 { 321 let mut func = wasm_encoder::Function::new([]); 322 func.instruction(&wasm_encoder::Instruction::LocalGet(0)); 323 if !image.memory64 { 324 func.instruction(&wasm_encoder::Instruction::I32WrapI64); 325 } 326 func.instruction(&wasm_encoder::Instruction::I32Load16U( 327 wasm_encoder::MemArg { 328 offset: u64::from(offset), 329 align: 0, 330 memory_index: 0, 331 }, 332 )); 333 func.instruction(&wasm_encoder::Instruction::End); 334 code.function(&func); 335 } 336 { 337 let mut func = wasm_encoder::Function::new([]); 338 func.instruction(&wasm_encoder::Instruction::LocalGet(0)); 339 if !image.memory64 { 340 func.instruction(&wasm_encoder::Instruction::I32WrapI64); 341 } 342 func.instruction(&wasm_encoder::Instruction::I32Load(wasm_encoder::MemArg { 343 offset: u64::from(offset), 344 align: 0, 345 memory_index: 0, 346 })); 347 func.instruction(&wasm_encoder::Instruction::End); 348 code.function(&func); 349 } 350 { 351 let mut func = wasm_encoder::Function::new([]); 352 func.instruction(&wasm_encoder::Instruction::LocalGet(0)); 353 if !image.memory64 { 354 func.instruction(&wasm_encoder::Instruction::I32WrapI64); 355 } 356 func.instruction(&wasm_encoder::Instruction::I64Load(wasm_encoder::MemArg { 357 offset: u64::from(offset), 358 align: 0, 359 memory_index: 0, 360 })); 361 func.instruction(&wasm_encoder::Instruction::End); 362 code.function(&func); 363 } 364 module.section(&code); 365 } 366 367 { 368 let mut datas = wasm_encoder::DataSection::new(); 369 for (offset, data) in image.segments.iter() { 370 datas.segment(wasm_encoder::DataSegment { 371 mode: wasm_encoder::DataSegmentMode::Active { 372 memory_index: 0, 373 offset: &if image.memory64 { 374 wasm_encoder::ConstExpr::i64_const(*offset as i64) 375 } else { 376 wasm_encoder::ConstExpr::i32_const(*offset as i32) 377 }, 378 }, 379 data: data.iter().copied(), 380 }); 381 } 382 module.section(&datas); 383 } 384 385 module.finish() 386 } 387 388 #[cfg(test)] 389 mod tests { 390 use super::*; 391 use arbitrary::{Arbitrary, Unstructured}; 392 use rand::prelude::*; 393 394 #[test] 395 fn smoke_test_memory_access() { 396 let mut rng = SmallRng::seed_from_u64(0); 397 let mut buf = vec![0; 1024]; 398 399 for _ in 0..1024 { 400 rng.fill_bytes(&mut buf); 401 let u = Unstructured::new(&buf); 402 if let Ok(input) = MemoryAccesses::arbitrary_take_rest(u) { 403 check_memory_accesses(input); 404 } 405 } 406 } 407 } 408