wasmtime_fuzzing/oracles/
memory.rs1use crate::generators::{HeapImage, MemoryAccesses};
4use wasmtime::*;
5
6pub 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 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 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 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 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 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 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
246fn 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}