Skip to main content

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            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/// ```
258fn 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                    target: Default::default(),
316                    ordering: None,
317                },
318            ));
319            func.instruction(&wasm_encoder::Instruction::End);
320            code.function(&func);
321        }
322        {
323            let mut func = wasm_encoder::Function::new([]);
324            func.instruction(&wasm_encoder::Instruction::LocalGet(0));
325            if !image.memory64 {
326                func.instruction(&wasm_encoder::Instruction::I32WrapI64);
327            }
328            func.instruction(&wasm_encoder::Instruction::I32Load16U(
329                wasm_encoder::MemArg {
330                    offset: u64::from(offset),
331                    align: 0,
332                    target: Default::default(),
333                    ordering: None,
334                },
335            ));
336            func.instruction(&wasm_encoder::Instruction::End);
337            code.function(&func);
338        }
339        {
340            let mut func = wasm_encoder::Function::new([]);
341            func.instruction(&wasm_encoder::Instruction::LocalGet(0));
342            if !image.memory64 {
343                func.instruction(&wasm_encoder::Instruction::I32WrapI64);
344            }
345            func.instruction(&wasm_encoder::Instruction::I32Load(wasm_encoder::MemArg {
346                offset: u64::from(offset),
347                align: 0,
348                target: Default::default(),
349                ordering: None,
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                target: Default::default(),
364                ordering: None,
365            }));
366            func.instruction(&wasm_encoder::Instruction::End);
367            code.function(&func);
368        }
369        module.section(&code);
370    }
371
372    {
373        let mut datas = wasm_encoder::DataSection::new();
374        for (offset, data) in image.segments.iter() {
375            datas.segment(wasm_encoder::DataSegment {
376                mode: wasm_encoder::DataSegmentMode::Active {
377                    memory_index: 0,
378                    offset: &if image.memory64 {
379                        wasm_encoder::ConstExpr::i64_const(*offset as i64)
380                    } else {
381                        wasm_encoder::ConstExpr::i32_const(*offset as i32)
382                    },
383                },
384                data: data.iter().copied(),
385            });
386        }
387        module.section(&datas);
388    }
389
390    module.finish()
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396    use crate::test::test_n_times;
397
398    #[test]
399    fn smoke_test_memory_access() {
400        test_n_times(50, |input: MemoryAccesses, _u| {
401            check_memory_accesses(input);
402            Ok(())
403        })
404    }
405}