Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fuzzing/src/oracles/memory.rs
1693 views
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
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
/// ```
261
fn 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)]
393
mod 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
}
412
413