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