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 "bad module compilation error: {e:?}", 38 ); 39 return; 40 } 41 }; 42 43 let limits = super::StoreLimits::new(); 44 let mut store = Store::new(&engine, limits); 45 input.config.configure_store(&mut store); 46 47 // If we are using fuel, make sure we add enough that we won't ever run out. 48 if input.config.wasmtime.consume_fuel { 49 store.set_fuel(u64::MAX).unwrap(); 50 } 51 52 let instance = match Instance::new(&mut store, &module, &[]) { 53 Ok(x) => x, 54 Err(e) => { 55 log::info!("Failed to instantiate: {e:?}"); 56 assert!( 57 format!("{e:?}").contains("Cannot allocate memory"), 58 "bad error: {e:?}", 59 ); 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 res = memory.grow(&mut store, u64::from(growth)); 239 log::debug!("grow {growth} -> {res:?}"); 240 do_accesses(&mut store, "after growing"); 241 } 242 243 /// Build a Wasm module with a single memory in the shape of the given heap 244 /// image, exports that memory, and also exports four functions: 245 /// `load{8,16,32,64}`. Each of these functions takes an `i64` address, 246 /// truncates it to `i32` if the memory is not 64-bit, and loads its associated 247 /// number of bits from memory at `address + offset`. 248 /// 249 /// ```wat 250 /// (module 251 /// (memory (export "memory") ...) 252 /// (func (export "load8") (param i64) (result i32) 253 /// (i32.load8_u offset=${offset} (local.get 0)) 254 /// ) 255 /// ... 256 /// ) 257 /// ``` 258 fn build_wasm(image: &HeapImage, offset: u32) -> Vec<u8> { 259 let mut module = wasm_encoder::Module::new(); 260 261 { 262 let mut types = wasm_encoder::TypeSection::new(); 263 types 264 .ty() 265 .function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I32]); 266 types 267 .ty() 268 .function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I64]); 269 module.section(&types); 270 } 271 272 { 273 let mut funcs = wasm_encoder::FunctionSection::new(); 274 funcs.function(0); 275 funcs.function(0); 276 funcs.function(0); 277 funcs.function(1); 278 module.section(&funcs); 279 } 280 281 { 282 let mut memories = wasm_encoder::MemorySection::new(); 283 memories.memory(wasm_encoder::MemoryType { 284 minimum: u64::from(image.minimum), 285 maximum: image.maximum.map(Into::into), 286 memory64: image.memory64, 287 shared: false, 288 page_size_log2: image.page_size_log2, 289 }); 290 module.section(&memories); 291 } 292 293 { 294 let mut exports = wasm_encoder::ExportSection::new(); 295 exports.export("memory", wasm_encoder::ExportKind::Memory, 0); 296 exports.export("load8", wasm_encoder::ExportKind::Func, 0); 297 exports.export("load16", wasm_encoder::ExportKind::Func, 1); 298 exports.export("load32", wasm_encoder::ExportKind::Func, 2); 299 exports.export("load64", wasm_encoder::ExportKind::Func, 3); 300 module.section(&exports); 301 } 302 303 { 304 let mut code = wasm_encoder::CodeSection::new(); 305 { 306 let mut func = wasm_encoder::Function::new([]); 307 func.instruction(&wasm_encoder::Instruction::LocalGet(0)); 308 if !image.memory64 { 309 func.instruction(&wasm_encoder::Instruction::I32WrapI64); 310 } 311 func.instruction(&wasm_encoder::Instruction::I32Load8U( 312 wasm_encoder::MemArg { 313 offset: u64::from(offset), 314 align: 0, 315 memory_index: 0, 316 }, 317 )); 318 func.instruction(&wasm_encoder::Instruction::End); 319 code.function(&func); 320 } 321 { 322 let mut func = wasm_encoder::Function::new([]); 323 func.instruction(&wasm_encoder::Instruction::LocalGet(0)); 324 if !image.memory64 { 325 func.instruction(&wasm_encoder::Instruction::I32WrapI64); 326 } 327 func.instruction(&wasm_encoder::Instruction::I32Load16U( 328 wasm_encoder::MemArg { 329 offset: u64::from(offset), 330 align: 0, 331 memory_index: 0, 332 }, 333 )); 334 func.instruction(&wasm_encoder::Instruction::End); 335 code.function(&func); 336 } 337 { 338 let mut func = wasm_encoder::Function::new([]); 339 func.instruction(&wasm_encoder::Instruction::LocalGet(0)); 340 if !image.memory64 { 341 func.instruction(&wasm_encoder::Instruction::I32WrapI64); 342 } 343 func.instruction(&wasm_encoder::Instruction::I32Load(wasm_encoder::MemArg { 344 offset: u64::from(offset), 345 align: 0, 346 memory_index: 0, 347 })); 348 func.instruction(&wasm_encoder::Instruction::End); 349 code.function(&func); 350 } 351 { 352 let mut func = wasm_encoder::Function::new([]); 353 func.instruction(&wasm_encoder::Instruction::LocalGet(0)); 354 if !image.memory64 { 355 func.instruction(&wasm_encoder::Instruction::I32WrapI64); 356 } 357 func.instruction(&wasm_encoder::Instruction::I64Load(wasm_encoder::MemArg { 358 offset: u64::from(offset), 359 align: 0, 360 memory_index: 0, 361 })); 362 func.instruction(&wasm_encoder::Instruction::End); 363 code.function(&func); 364 } 365 module.section(&code); 366 } 367 368 { 369 let mut datas = wasm_encoder::DataSection::new(); 370 for (offset, data) in image.segments.iter() { 371 datas.segment(wasm_encoder::DataSegment { 372 mode: wasm_encoder::DataSegmentMode::Active { 373 memory_index: 0, 374 offset: &if image.memory64 { 375 wasm_encoder::ConstExpr::i64_const(*offset as i64) 376 } else { 377 wasm_encoder::ConstExpr::i32_const(*offset as i32) 378 }, 379 }, 380 data: data.iter().copied(), 381 }); 382 } 383 module.section(&datas); 384 } 385 386 module.finish() 387 } 388 389 #[cfg(test)] 390 mod tests { 391 use super::*; 392 use crate::test::test_n_times; 393 394 #[test] 395 fn smoke_test_memory_access() { 396 test_n_times(50, |input: MemoryAccesses, _u| { 397 check_memory_accesses(input); 398 Ok(()) 399 }) 400 } 401 } 402