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
check_memory_accesses(input: MemoryAccesses)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                 "bad module compilation error: {e:?}",
38             );
39             return;
40         }
41     };
42 
43     let limits = super::StoreLimits::new();
44     let mut store = Store::new(&engine, limits);
45     input.config.configure_store(&mut store);
46 
47     // If we are using fuel, make sure we add enough that we won't ever run out.
48     if input.config.wasmtime.consume_fuel {
49         store.set_fuel(u64::MAX).unwrap();
50     }
51 
52     let instance = match Instance::new(&mut store, &module, &[]) {
53         Ok(x) => x,
54         Err(e) => {
55             log::info!("Failed to instantiate: {e:?}");
56             assert!(
57                 format!("{e:?}").contains("Cannot allocate memory"),
58                 "bad error: {e:?}",
59             );
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 res = memory.grow(&mut store, u64::from(growth));
239     log::debug!("grow {growth} -> {res:?}");
240     do_accesses(&mut store, "after growing");
241 }
242 
243 /// Build a Wasm module with a single memory in the shape of the given heap
244 /// image, exports that memory, and also exports four functions:
245 /// `load{8,16,32,64}`. Each of these functions takes an `i64` address,
246 /// truncates it to `i32` if the memory is not 64-bit, and loads its associated
247 /// number of bits from memory at `address + offset`.
248 ///
249 /// ```wat
250 /// (module
251 ///   (memory (export "memory") ...)
252 ///   (func (export "load8") (param i64) (result i32)
253 ///     (i32.load8_u offset=${offset} (local.get 0))
254 ///   )
255 ///   ...
256 /// )
257 /// ```
build_wasm(image: &HeapImage, offset: u32) -> Vec<u8>258 fn build_wasm(image: &HeapImage, offset: u32) -> Vec<u8> {
259     let mut module = wasm_encoder::Module::new();
260 
261     {
262         let mut types = wasm_encoder::TypeSection::new();
263         types
264             .ty()
265             .function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I32]);
266         types
267             .ty()
268             .function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I64]);
269         module.section(&types);
270     }
271 
272     {
273         let mut funcs = wasm_encoder::FunctionSection::new();
274         funcs.function(0);
275         funcs.function(0);
276         funcs.function(0);
277         funcs.function(1);
278         module.section(&funcs);
279     }
280 
281     {
282         let mut memories = wasm_encoder::MemorySection::new();
283         memories.memory(wasm_encoder::MemoryType {
284             minimum: u64::from(image.minimum),
285             maximum: image.maximum.map(Into::into),
286             memory64: image.memory64,
287             shared: false,
288             page_size_log2: image.page_size_log2,
289         });
290         module.section(&memories);
291     }
292 
293     {
294         let mut exports = wasm_encoder::ExportSection::new();
295         exports.export("memory", wasm_encoder::ExportKind::Memory, 0);
296         exports.export("load8", wasm_encoder::ExportKind::Func, 0);
297         exports.export("load16", wasm_encoder::ExportKind::Func, 1);
298         exports.export("load32", wasm_encoder::ExportKind::Func, 2);
299         exports.export("load64", wasm_encoder::ExportKind::Func, 3);
300         module.section(&exports);
301     }
302 
303     {
304         let mut code = wasm_encoder::CodeSection::new();
305         {
306             let mut func = wasm_encoder::Function::new([]);
307             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
308             if !image.memory64 {
309                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
310             }
311             func.instruction(&wasm_encoder::Instruction::I32Load8U(
312                 wasm_encoder::MemArg {
313                     offset: u64::from(offset),
314                     align: 0,
315                     memory_index: 0,
316                 },
317             ));
318             func.instruction(&wasm_encoder::Instruction::End);
319             code.function(&func);
320         }
321         {
322             let mut func = wasm_encoder::Function::new([]);
323             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
324             if !image.memory64 {
325                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
326             }
327             func.instruction(&wasm_encoder::Instruction::I32Load16U(
328                 wasm_encoder::MemArg {
329                     offset: u64::from(offset),
330                     align: 0,
331                     memory_index: 0,
332                 },
333             ));
334             func.instruction(&wasm_encoder::Instruction::End);
335             code.function(&func);
336         }
337         {
338             let mut func = wasm_encoder::Function::new([]);
339             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
340             if !image.memory64 {
341                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
342             }
343             func.instruction(&wasm_encoder::Instruction::I32Load(wasm_encoder::MemArg {
344                 offset: u64::from(offset),
345                 align: 0,
346                 memory_index: 0,
347             }));
348             func.instruction(&wasm_encoder::Instruction::End);
349             code.function(&func);
350         }
351         {
352             let mut func = wasm_encoder::Function::new([]);
353             func.instruction(&wasm_encoder::Instruction::LocalGet(0));
354             if !image.memory64 {
355                 func.instruction(&wasm_encoder::Instruction::I32WrapI64);
356             }
357             func.instruction(&wasm_encoder::Instruction::I64Load(wasm_encoder::MemArg {
358                 offset: u64::from(offset),
359                 align: 0,
360                 memory_index: 0,
361             }));
362             func.instruction(&wasm_encoder::Instruction::End);
363             code.function(&func);
364         }
365         module.section(&code);
366     }
367 
368     {
369         let mut datas = wasm_encoder::DataSection::new();
370         for (offset, data) in image.segments.iter() {
371             datas.segment(wasm_encoder::DataSegment {
372                 mode: wasm_encoder::DataSegmentMode::Active {
373                     memory_index: 0,
374                     offset: &if image.memory64 {
375                         wasm_encoder::ConstExpr::i64_const(*offset as i64)
376                     } else {
377                         wasm_encoder::ConstExpr::i32_const(*offset as i32)
378                     },
379                 },
380                 data: data.iter().copied(),
381             });
382         }
383         module.section(&datas);
384     }
385 
386     module.finish()
387 }
388 
389 #[cfg(test)]
390 mod tests {
391     use super::*;
392     use crate::test::test_n_times;
393 
394     #[test]
smoke_test_memory_access()395     fn smoke_test_memory_access() {
396         test_n_times(50, |input: MemoryAccesses, _u| {
397             check_memory_accesses(input);
398             Ok(())
399         })
400     }
401 }
402