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!(format!("{e:?}").contains("Cannot allocate memory"));
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 _ = memory.grow(&mut store, u64::from(growth));
239     do_accesses(&mut store, "after growing");
240 }
241 
242 /// Build a Wasm module with a single memory in the shape of the given heap
243 /// image, exports that memory, and also exports four functions:
244 /// `load{8,16,32,64}`. Each of these functions takes an `i64` address,
245 /// truncates it to `i32` if the memory is not 64-bit, and loads its associated
246 /// number of bits from memory at `address + offset`.
247 ///
248 /// ```wat
249 /// (module
250 ///   (memory (export "memory") ...)
251 ///   (func (export "load8") (param i64) (result i32)
252 ///     (i32.load8_u offset=${offset} (local.get 0))
253 ///   )
254 ///   ...
255 /// )
256 /// ```
257 fn build_wasm(image: &HeapImage, offset: u32) -> Vec<u8> {
258     let mut module = wasm_encoder::Module::new();
259 
260     {
261         let mut types = wasm_encoder::TypeSection::new();
262         types
263             .ty()
264             .function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I32]);
265         types
266             .ty()
267             .function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I64]);
268         module.section(&types);
269     }
270 
271     {
272         let mut funcs = wasm_encoder::FunctionSection::new();
273         funcs.function(0);
274         funcs.function(0);
275         funcs.function(0);
276         funcs.function(1);
277         module.section(&funcs);
278     }
279 
280     {
281         let mut memories = wasm_encoder::MemorySection::new();
282         memories.memory(wasm_encoder::MemoryType {
283             minimum: u64::from(image.minimum),
284             maximum: image.maximum.map(Into::into),
285             memory64: image.memory64,
286             shared: false,
287             page_size_log2: image.page_size_log2,
288         });
289         module.section(&memories);
290     }
291 
292     {
293         let mut exports = wasm_encoder::ExportSection::new();
294         exports.export("memory", wasm_encoder::ExportKind::Memory, 0);
295         exports.export("load8", wasm_encoder::ExportKind::Func, 0);
296         exports.export("load16", wasm_encoder::ExportKind::Func, 1);
297         exports.export("load32", wasm_encoder::ExportKind::Func, 2);
298         exports.export("load64", wasm_encoder::ExportKind::Func, 3);
299         module.section(&exports);
300     }
301 
302     {
303         let mut code = wasm_encoder::CodeSection::new();
304         {
305             let mut func = wasm_encoder::Function::new([]);
306             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
307             if !image.memory64 {
308                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
309             }
310             func.instruction(&wasm_encoder::Instruction::I32Load8U(
311                 wasm_encoder::MemArg {
312                     offset: u64::from(offset),
313                     align: 0,
314                     memory_index: 0,
315                 },
316             ));
317             func.instruction(&wasm_encoder::Instruction::End);
318             code.function(&func);
319         }
320         {
321             let mut func = wasm_encoder::Function::new([]);
322             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
323             if !image.memory64 {
324                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
325             }
326             func.instruction(&wasm_encoder::Instruction::I32Load16U(
327                 wasm_encoder::MemArg {
328                     offset: u64::from(offset),
329                     align: 0,
330                     memory_index: 0,
331                 },
332             ));
333             func.instruction(&wasm_encoder::Instruction::End);
334             code.function(&func);
335         }
336         {
337             let mut func = wasm_encoder::Function::new([]);
338             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
339             if !image.memory64 {
340                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
341             }
342             func.instruction(&wasm_encoder::Instruction::I32Load(wasm_encoder::MemArg {
343                 offset: u64::from(offset),
344                 align: 0,
345                 memory_index: 0,
346             }));
347             func.instruction(&wasm_encoder::Instruction::End);
348             code.function(&func);
349         }
350         {
351             let mut func = wasm_encoder::Function::new([]);
352             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
353             if !image.memory64 {
354                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
355             }
356             func.instruction(&wasm_encoder::Instruction::I64Load(wasm_encoder::MemArg {
357                 offset: u64::from(offset),
358                 align: 0,
359                 memory_index: 0,
360             }));
361             func.instruction(&wasm_encoder::Instruction::End);
362             code.function(&func);
363         }
364         module.section(&code);
365     }
366 
367     {
368         let mut datas = wasm_encoder::DataSection::new();
369         for (offset, data) in image.segments.iter() {
370             datas.segment(wasm_encoder::DataSegment {
371                 mode: wasm_encoder::DataSegmentMode::Active {
372                     memory_index: 0,
373                     offset: &if image.memory64 {
374                         wasm_encoder::ConstExpr::i64_const(*offset as i64)
375                     } else {
376                         wasm_encoder::ConstExpr::i32_const(*offset as i32)
377                     },
378                 },
379                 data: data.iter().copied(),
380             });
381         }
382         module.section(&datas);
383     }
384 
385     module.finish()
386 }
387 
388 #[cfg(test)]
389 mod tests {
390     use super::*;
391     use arbitrary::{Arbitrary, Unstructured};
392     use rand::prelude::*;
393 
394     #[test]
395     fn smoke_test_memory_access() {
396         let mut rng = SmallRng::seed_from_u64(0);
397         let mut buf = vec![0; 1024];
398 
399         for _ in 0..1024 {
400             rng.fill_bytes(&mut buf);
401             let u = Unstructured::new(&buf);
402             if let Ok(input) = MemoryAccesses::arbitrary_take_rest(u) {
403                 check_memory_accesses(input);
404             }
405         }
406     }
407 }
408