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
check_memory_accesses(input: MemoryAccesses)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 /// ```
build_wasm(image: &HeapImage, offset: u32) -> Vec<u8>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]
smoke_test_memory_access()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