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             assert!(
35                 e.contains("bytes which exceeds the configured maximum of")
36                     || e.contains("exceeds the limit of")
37             );
38             return;
39         }
40     };
41 
42     let limits = super::StoreLimits::new();
43     let mut store = Store::new(&engine, limits);
44     input.config.configure_store(&mut store);
45 
46     // If we are using fuel, make sure we add enough that we won't ever run out.
47     if input.config.wasmtime.consume_fuel {
48         store.set_fuel(u64::MAX).unwrap();
49     }
50 
51     let instance = match Instance::new(&mut store, &module, &[]) {
52         Ok(x) => x,
53         Err(e) => {
54             log::info!("Failed to instantiate: {e:?}");
55             assert!(format!("{e:?}").contains("Cannot allocate memory"));
56             return;
57         }
58     };
59 
60     let memory = instance.get_memory(&mut store, "memory").unwrap();
61     let load8 = instance
62         .get_typed_func::<u64, u32>(&mut store, "load8")
63         .unwrap();
64     let load16 = instance
65         .get_typed_func::<u64, u32>(&mut store, "load16")
66         .unwrap();
67     let load32 = instance
68         .get_typed_func::<u64, u32>(&mut store, "load32")
69         .unwrap();
70     let load64 = instance
71         .get_typed_func::<u64, u64>(&mut store, "load64")
72         .unwrap();
73 
74     let do_accesses = |store: &mut Store<_>, msg: &str| {
75         let len = memory.data_size(&mut *store);
76         let len = u64::try_from(len).unwrap();
77 
78         if let Some(n) = len.checked_sub(8).and_then(|n| n.checked_sub(offset)) {
79             // Test various in-bounds accesses near the bound.
80             for i in 0..=7 {
81                 let addr = n + i;
82                 assert!(addr + offset + 1 <= len);
83                 let result = load8.call(&mut *store, addr);
84                 assert!(
85                     result.is_ok(),
86                     "{msg}: len={len:#x}, offset={offset:#x}, load8({n:#x} + {i:#x} = {addr:#x}) \
87                      should be in bounds, got {result:?}"
88                 );
89             }
90             for i in 0..=6 {
91                 let addr = n + offset + i;
92                 assert!(addr + 2 <= len);
93                 let result = load16.call(&mut *store, n + i);
94                 assert!(
95                     result.is_ok(),
96                     "{msg}: len={len:#x}, offset={offset:#x}, load16({n:#x} + {i:#x} = {addr:#x}) \
97                      should be in bounds, got {result:?}"
98                 );
99             }
100             for i in 0..=4 {
101                 let addr = n + offset + i;
102                 assert!(addr + 4 <= len);
103                 let result = load32.call(&mut *store, n + i);
104                 assert!(
105                     result.is_ok(),
106                     "{msg}: len={len:#x}, offset={offset:#x}, load32({n:#x} + {i:#x} = {addr:#x}) \
107                      should be in bounds, got {result:?}"
108                 );
109             }
110             assert!(n + offset + 8 <= len);
111             let result = load64.call(&mut *store, n);
112             assert!(
113                 result.is_ok(),
114                 "{msg}: len={len:#x}, offset={offset:#x}, load64({n:#x}) should be in bounds, \
115                  got {result:?}"
116             );
117 
118             // Test various out-of-bounds accesses overlapping the memory bound.
119             for i in 1..2 {
120                 let addr = len - i;
121                 assert!(addr + offset + 2 > len);
122                 let result = load16.call(&mut *store, addr);
123                 assert!(
124                     result.is_err(),
125                     "{msg}: len={len:#x}, offset={offset:#x}, load16({len:#x} - {i:#x} = {addr:#x}) \
126                      should trap, got {result:?}"
127                 );
128             }
129             for i in 1..4 {
130                 let addr = len - i;
131                 assert!(addr + offset + 4 > len);
132                 let result = load32.call(&mut *store, addr);
133                 assert!(
134                     result.is_err(),
135                     "{msg}: len={len:#x}, offset={offset:#x}, load32({len:#x} - {i:#x} = {addr:#x}) \
136                      should trap, got {result:?}"
137                 );
138             }
139             for i in 1..8 {
140                 let addr = len - i;
141                 assert!(addr + offset + 8 > len);
142                 let result = load64.call(&mut *store, addr);
143                 assert!(
144                     result.is_err(),
145                     "{msg}: len={len:#x}, offset={offset:#x}, load64({len:#x} - {i:#x} = {addr:#x}) \
146                      should trap, got {result:?}"
147                 );
148             }
149         }
150 
151         // Test that out-of-bounds accesses just after the memory bound trap.
152         if let Some(n) = len.checked_sub(offset) {
153             for i in 0..=1 {
154                 let addr = n + i;
155                 assert!(addr + offset + 1 > len);
156                 let result = load8.call(&mut *store, addr);
157                 assert!(
158                     result.is_err(),
159                     "{msg}: len={len:#x}, offset={offset:#x}, load8({n:#x} + {i:#x} = {addr:#x}) \
160                      should trap, got {result:?}"
161                 );
162                 assert!(addr + offset + 2 > len);
163                 let result = load16.call(&mut *store, addr);
164                 assert!(
165                     result.is_err(),
166                     "{msg}: len={len:#x}, offset={offset:#x}, load16({n:#x} + {i:#x} = {addr:#x}) \
167                      should trap, got {result:?}"
168                 );
169                 assert!(addr + offset + 4 > len);
170                 let result = load32.call(&mut *store, addr);
171                 assert!(
172                     result.is_err(),
173                     "{msg}: len={len:#x}, offset={offset:#x}, load32({n:#x} + {i:#x} = {addr:#x}) \
174                      should trap, got {result:?}"
175                 );
176                 assert!(addr + offset + 8 > len);
177                 let result = load64.call(&mut *store, addr);
178                 assert!(
179                     result.is_err(),
180                     "{msg}: len={len:#x}, offset={offset:#x}, load64({n:#x} + {i:#x} = {addr:#x}) \
181                      should trap, got {result:?}"
182                 );
183             }
184         }
185 
186         // Test out-of-bounds accesses near the end of the index type's range to
187         // double check our overflow handling inside the bounds checks.
188         let len_is_4gib = len == u64::from(u32::MAX) + 1;
189         let end_delta = (input.image.memory64 && len_is_4gib) as u64;
190         let max = if input.image.memory64 {
191             u64::MAX
192         } else {
193             u64::from(u32::MAX)
194         };
195         for i in 0..(1 - end_delta) {
196             let addr = max - i;
197             let result = load8.call(&mut *store, addr);
198             assert!(
199                 result.is_err(),
200                 "{msg}: len={len:#x}, offset={offset:#x}, load8({max:#x} - {i:#x} = {addr:#x}) \
201                  should trap, got {result:?}"
202             );
203         }
204         for i in 0..(2 - end_delta) {
205             let addr = max - i;
206             let result = load16.call(&mut *store, addr);
207             assert!(
208                 result.is_err(),
209                 "{msg}: len={len:#x}, offset={offset:#x}, load16({max:#x} - {i:#x} = {addr:#x}) \
210                  should trap, got {result:?}"
211             );
212         }
213         for i in 0..(4 - end_delta) {
214             let addr = max - i;
215             let result = load32.call(&mut *store, addr);
216             assert!(
217                 result.is_err(),
218                 "{msg}: len={len:#x}, offset={offset:#x}, load32({max:#x} - {i:#x} = {addr:#x}) \
219                  should trap, got {result:?}"
220             );
221         }
222         for i in 0..(8 - end_delta) {
223             let addr = max - i;
224             let result = load64.call(&mut *store, addr);
225             assert!(
226                 result.is_err(),
227                 "{msg}: len={len:#x}, offset={offset:#x}, load64({max:#x} - {i:#x} = {addr:#x}) \
228                  should trap, got {result:?}"
229             );
230         }
231     };
232 
233     do_accesses(&mut store, "initial size");
234     let _ = memory.grow(&mut store, u64::from(growth));
235     do_accesses(&mut store, "after growing");
236 }
237 
238 /// Build a Wasm module with a single memory in the shape of the given heap
239 /// image, exports that memory, and also exports four functions:
240 /// `load{8,16,32,64}`. Each of these functions takes an `i64` address,
241 /// truncates it to `i32` if the memory is not 64-bit, and loads its associated
242 /// number of bits from memory at `address + offset`.
243 ///
244 /// ```wat
245 /// (module
246 ///   (memory (export "memory") ...)
247 ///   (func (export "load8") (param i64) (result i32)
248 ///     (i32.load8_u offset=${offset} (local.get 0))
249 ///   )
250 ///   ...
251 /// )
252 /// ```
253 fn build_wasm(image: &HeapImage, offset: u32) -> Vec<u8> {
254     let mut module = wasm_encoder::Module::new();
255 
256     {
257         let mut types = wasm_encoder::TypeSection::new();
258         types.function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I32]);
259         types.function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I64]);
260         module.section(&types);
261     }
262 
263     {
264         let mut funcs = wasm_encoder::FunctionSection::new();
265         funcs.function(0);
266         funcs.function(0);
267         funcs.function(0);
268         funcs.function(1);
269         module.section(&funcs);
270     }
271 
272     {
273         let mut memories = wasm_encoder::MemorySection::new();
274         memories.memory(wasm_encoder::MemoryType {
275             minimum: u64::from(image.minimum),
276             maximum: image.maximum.map(Into::into),
277             memory64: image.memory64,
278             shared: false,
279             page_size_log2: image.page_size_log2,
280         });
281         module.section(&memories);
282     }
283 
284     {
285         let mut exports = wasm_encoder::ExportSection::new();
286         exports.export("memory", wasm_encoder::ExportKind::Memory, 0);
287         exports.export("load8", wasm_encoder::ExportKind::Func, 0);
288         exports.export("load16", wasm_encoder::ExportKind::Func, 1);
289         exports.export("load32", wasm_encoder::ExportKind::Func, 2);
290         exports.export("load64", wasm_encoder::ExportKind::Func, 3);
291         module.section(&exports);
292     }
293 
294     {
295         let mut code = wasm_encoder::CodeSection::new();
296         {
297             let mut func = wasm_encoder::Function::new([]);
298             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
299             if !image.memory64 {
300                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
301             }
302             func.instruction(&wasm_encoder::Instruction::I32Load8U(
303                 wasm_encoder::MemArg {
304                     offset: u64::from(offset),
305                     align: 0,
306                     memory_index: 0,
307                 },
308             ));
309             func.instruction(&wasm_encoder::Instruction::End);
310             code.function(&func);
311         }
312         {
313             let mut func = wasm_encoder::Function::new([]);
314             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
315             if !image.memory64 {
316                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
317             }
318             func.instruction(&wasm_encoder::Instruction::I32Load16U(
319                 wasm_encoder::MemArg {
320                     offset: u64::from(offset),
321                     align: 0,
322                     memory_index: 0,
323                 },
324             ));
325             func.instruction(&wasm_encoder::Instruction::End);
326             code.function(&func);
327         }
328         {
329             let mut func = wasm_encoder::Function::new([]);
330             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
331             if !image.memory64 {
332                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
333             }
334             func.instruction(&wasm_encoder::Instruction::I32Load(wasm_encoder::MemArg {
335                 offset: u64::from(offset),
336                 align: 0,
337                 memory_index: 0,
338             }));
339             func.instruction(&wasm_encoder::Instruction::End);
340             code.function(&func);
341         }
342         {
343             let mut func = wasm_encoder::Function::new([]);
344             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
345             if !image.memory64 {
346                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
347             }
348             func.instruction(&wasm_encoder::Instruction::I64Load(wasm_encoder::MemArg {
349                 offset: u64::from(offset),
350                 align: 0,
351                 memory_index: 0,
352             }));
353             func.instruction(&wasm_encoder::Instruction::End);
354             code.function(&func);
355         }
356         module.section(&code);
357     }
358 
359     {
360         let mut datas = wasm_encoder::DataSection::new();
361         for (offset, data) in image.segments.iter() {
362             datas.segment(wasm_encoder::DataSegment {
363                 mode: wasm_encoder::DataSegmentMode::Active {
364                     memory_index: 0,
365                     offset: &if image.memory64 {
366                         wasm_encoder::ConstExpr::i64_const(*offset as i64)
367                     } else {
368                         wasm_encoder::ConstExpr::i32_const(*offset as i32)
369                     },
370                 },
371                 data: data.iter().copied(),
372             });
373         }
374         module.section(&datas);
375     }
376 
377     module.finish()
378 }
379 
380 #[cfg(test)]
381 mod tests {
382     use super::*;
383     use arbitrary::{Arbitrary, Unstructured};
384     use rand::prelude::*;
385 
386     #[test]
387     fn smoke_test_memory_access() {
388         let mut rng = SmallRng::seed_from_u64(0);
389         let mut buf = vec![0; 1024];
390 
391         for _ in 0..1024 {
392             rng.fill_bytes(&mut buf);
393             let u = Unstructured::new(&buf);
394             if let Ok(input) = MemoryAccesses::arbitrary_take_rest(u) {
395                 check_memory_accesses(input);
396             }
397         }
398     }
399 }
400