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