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