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