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.function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I32]);
263         types.function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I64]);
264         module.section(&types);
265     }
266 
267     {
268         let mut funcs = wasm_encoder::FunctionSection::new();
269         funcs.function(0);
270         funcs.function(0);
271         funcs.function(0);
272         funcs.function(1);
273         module.section(&funcs);
274     }
275 
276     {
277         let mut memories = wasm_encoder::MemorySection::new();
278         memories.memory(wasm_encoder::MemoryType {
279             minimum: u64::from(image.minimum),
280             maximum: image.maximum.map(Into::into),
281             memory64: image.memory64,
282             shared: false,
283             page_size_log2: image.page_size_log2,
284         });
285         module.section(&memories);
286     }
287 
288     {
289         let mut exports = wasm_encoder::ExportSection::new();
290         exports.export("memory", wasm_encoder::ExportKind::Memory, 0);
291         exports.export("load8", wasm_encoder::ExportKind::Func, 0);
292         exports.export("load16", wasm_encoder::ExportKind::Func, 1);
293         exports.export("load32", wasm_encoder::ExportKind::Func, 2);
294         exports.export("load64", wasm_encoder::ExportKind::Func, 3);
295         module.section(&exports);
296     }
297 
298     {
299         let mut code = wasm_encoder::CodeSection::new();
300         {
301             let mut func = wasm_encoder::Function::new([]);
302             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
303             if !image.memory64 {
304                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
305             }
306             func.instruction(&wasm_encoder::Instruction::I32Load8U(
307                 wasm_encoder::MemArg {
308                     offset: u64::from(offset),
309                     align: 0,
310                     memory_index: 0,
311                 },
312             ));
313             func.instruction(&wasm_encoder::Instruction::End);
314             code.function(&func);
315         }
316         {
317             let mut func = wasm_encoder::Function::new([]);
318             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
319             if !image.memory64 {
320                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
321             }
322             func.instruction(&wasm_encoder::Instruction::I32Load16U(
323                 wasm_encoder::MemArg {
324                     offset: u64::from(offset),
325                     align: 0,
326                     memory_index: 0,
327                 },
328             ));
329             func.instruction(&wasm_encoder::Instruction::End);
330             code.function(&func);
331         }
332         {
333             let mut func = wasm_encoder::Function::new([]);
334             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
335             if !image.memory64 {
336                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
337             }
338             func.instruction(&wasm_encoder::Instruction::I32Load(wasm_encoder::MemArg {
339                 offset: u64::from(offset),
340                 align: 0,
341                 memory_index: 0,
342             }));
343             func.instruction(&wasm_encoder::Instruction::End);
344             code.function(&func);
345         }
346         {
347             let mut func = wasm_encoder::Function::new([]);
348             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
349             if !image.memory64 {
350                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
351             }
352             func.instruction(&wasm_encoder::Instruction::I64Load(wasm_encoder::MemArg {
353                 offset: u64::from(offset),
354                 align: 0,
355                 memory_index: 0,
356             }));
357             func.instruction(&wasm_encoder::Instruction::End);
358             code.function(&func);
359         }
360         module.section(&code);
361     }
362 
363     {
364         let mut datas = wasm_encoder::DataSection::new();
365         for (offset, data) in image.segments.iter() {
366             datas.segment(wasm_encoder::DataSegment {
367                 mode: wasm_encoder::DataSegmentMode::Active {
368                     memory_index: 0,
369                     offset: &if image.memory64 {
370                         wasm_encoder::ConstExpr::i64_const(*offset as i64)
371                     } else {
372                         wasm_encoder::ConstExpr::i32_const(*offset as i32)
373                     },
374                 },
375                 data: data.iter().copied(),
376             });
377         }
378         module.section(&datas);
379     }
380 
381     module.finish()
382 }
383 
384 #[cfg(test)]
385 mod tests {
386     use super::*;
387     use arbitrary::{Arbitrary, Unstructured};
388     use rand::prelude::*;
389 
390     #[test]
391     fn smoke_test_memory_access() {
392         let mut rng = SmallRng::seed_from_u64(0);
393         let mut buf = vec![0; 1024];
394 
395         for _ in 0..1024 {
396             rng.fill_bytes(&mut buf);
397             let u = Unstructured::new(&buf);
398             if let Ok(input) = MemoryAccesses::arbitrary_take_rest(u) {
399                 check_memory_accesses(input);
400             }
401         }
402     }
403 }
404