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