wasmtime_fuzzing/oracles/
memory.rs

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