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.function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I32]); 263 types.function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I64]); 264 module.section(&types); 265 } 266 267 { 268 let mut funcs = wasm_encoder::FunctionSection::new(); 269 funcs.function(0); 270 funcs.function(0); 271 funcs.function(0); 272 funcs.function(1); 273 module.section(&funcs); 274 } 275 276 { 277 let mut memories = wasm_encoder::MemorySection::new(); 278 memories.memory(wasm_encoder::MemoryType { 279 minimum: u64::from(image.minimum), 280 maximum: image.maximum.map(Into::into), 281 memory64: image.memory64, 282 shared: false, 283 page_size_log2: image.page_size_log2, 284 }); 285 module.section(&memories); 286 } 287 288 { 289 let mut exports = wasm_encoder::ExportSection::new(); 290 exports.export("memory", wasm_encoder::ExportKind::Memory, 0); 291 exports.export("load8", wasm_encoder::ExportKind::Func, 0); 292 exports.export("load16", wasm_encoder::ExportKind::Func, 1); 293 exports.export("load32", wasm_encoder::ExportKind::Func, 2); 294 exports.export("load64", wasm_encoder::ExportKind::Func, 3); 295 module.section(&exports); 296 } 297 298 { 299 let mut code = wasm_encoder::CodeSection::new(); 300 { 301 let mut func = wasm_encoder::Function::new([]); 302 func.instruction(&wasm_encoder::Instruction::LocalGet(0)); 303 if !image.memory64 { 304 func.instruction(&wasm_encoder::Instruction::I32WrapI64); 305 } 306 func.instruction(&wasm_encoder::Instruction::I32Load8U( 307 wasm_encoder::MemArg { 308 offset: u64::from(offset), 309 align: 0, 310 memory_index: 0, 311 }, 312 )); 313 func.instruction(&wasm_encoder::Instruction::End); 314 code.function(&func); 315 } 316 { 317 let mut func = wasm_encoder::Function::new([]); 318 func.instruction(&wasm_encoder::Instruction::LocalGet(0)); 319 if !image.memory64 { 320 func.instruction(&wasm_encoder::Instruction::I32WrapI64); 321 } 322 func.instruction(&wasm_encoder::Instruction::I32Load16U( 323 wasm_encoder::MemArg { 324 offset: u64::from(offset), 325 align: 0, 326 memory_index: 0, 327 }, 328 )); 329 func.instruction(&wasm_encoder::Instruction::End); 330 code.function(&func); 331 } 332 { 333 let mut func = wasm_encoder::Function::new([]); 334 func.instruction(&wasm_encoder::Instruction::LocalGet(0)); 335 if !image.memory64 { 336 func.instruction(&wasm_encoder::Instruction::I32WrapI64); 337 } 338 func.instruction(&wasm_encoder::Instruction::I32Load(wasm_encoder::MemArg { 339 offset: u64::from(offset), 340 align: 0, 341 memory_index: 0, 342 })); 343 func.instruction(&wasm_encoder::Instruction::End); 344 code.function(&func); 345 } 346 { 347 let mut func = wasm_encoder::Function::new([]); 348 func.instruction(&wasm_encoder::Instruction::LocalGet(0)); 349 if !image.memory64 { 350 func.instruction(&wasm_encoder::Instruction::I32WrapI64); 351 } 352 func.instruction(&wasm_encoder::Instruction::I64Load(wasm_encoder::MemArg { 353 offset: u64::from(offset), 354 align: 0, 355 memory_index: 0, 356 })); 357 func.instruction(&wasm_encoder::Instruction::End); 358 code.function(&func); 359 } 360 module.section(&code); 361 } 362 363 { 364 let mut datas = wasm_encoder::DataSection::new(); 365 for (offset, data) in image.segments.iter() { 366 datas.segment(wasm_encoder::DataSegment { 367 mode: wasm_encoder::DataSegmentMode::Active { 368 memory_index: 0, 369 offset: &if image.memory64 { 370 wasm_encoder::ConstExpr::i64_const(*offset as i64) 371 } else { 372 wasm_encoder::ConstExpr::i32_const(*offset as i32) 373 }, 374 }, 375 data: data.iter().copied(), 376 }); 377 } 378 module.section(&datas); 379 } 380 381 module.finish() 382 } 383 384 #[cfg(test)] 385 mod tests { 386 use super::*; 387 use arbitrary::{Arbitrary, Unstructured}; 388 use rand::prelude::*; 389 390 #[test] 391 fn smoke_test_memory_access() { 392 let mut rng = SmallRng::seed_from_u64(0); 393 let mut buf = vec![0; 1024]; 394 395 for _ in 0..1024 { 396 rng.fill_bytes(&mut buf); 397 let u = Unstructured::new(&buf); 398 if let Ok(input) = MemoryAccesses::arbitrary_take_rest(u) { 399 check_memory_accesses(input); 400 } 401 } 402 } 403 } 404