Path: blob/main/crates/fuzzing/src/oracles/memory.rs
1693 views
//! Oracles related to memory.12use crate::generators::{HeapImage, MemoryAccesses};3use wasmtime::*;45/// Oracle to perform the described memory accesses and check that they are all6/// in- or out-of-bounds as expected7pub fn check_memory_accesses(input: MemoryAccesses) {8crate::init_fuzzing();9log::info!("Testing memory accesses: {input:#x?}");1011let offset = input.offset;12let growth = input.growth;13let wasm = build_wasm(&input.image, offset);14crate::oracles::log_wasm(&wasm);15let offset = u64::from(offset);1617let mut config = input.config.to_wasmtime();1819// Force-enable proposals if the heap image needs them.20if input.image.memory64 {21config.wasm_memory64(true);22}23if input.image.page_size_log2.is_some() {24config.wasm_custom_page_sizes(true);25}2627let engine = Engine::new(&config).unwrap();28let module = match Module::new(&engine, &wasm) {29Ok(m) => m,30Err(e) => {31let e = format!("{e:?}");32log::info!("Failed to create `Module`: {e}");33if cfg!(feature = "fuzz-pcc") && e.contains("Compilation error: Proof-carrying-code") {34return;35}36assert!(37e.contains("bytes which exceeds the configured maximum of")38|| e.contains("exceeds the limit of"),39"bad module compilation error: {e:?}",40);41return;42}43};4445let limits = super::StoreLimits::new();46let mut store = Store::new(&engine, limits);47input.config.configure_store(&mut store);4849// If we are using fuel, make sure we add enough that we won't ever run out.50if input.config.wasmtime.consume_fuel {51store.set_fuel(u64::MAX).unwrap();52}5354let instance = match Instance::new(&mut store, &module, &[]) {55Ok(x) => x,56Err(e) => {57log::info!("Failed to instantiate: {e:?}");58assert!(59format!("{e:?}").contains("Cannot allocate memory"),60"bad error: {e:?}",61);62return;63}64};6566let memory = instance.get_memory(&mut store, "memory").unwrap();67let load8 = instance68.get_typed_func::<u64, u32>(&mut store, "load8")69.unwrap();70let load16 = instance71.get_typed_func::<u64, u32>(&mut store, "load16")72.unwrap();73let load32 = instance74.get_typed_func::<u64, u32>(&mut store, "load32")75.unwrap();76let load64 = instance77.get_typed_func::<u64, u64>(&mut store, "load64")78.unwrap();7980let do_accesses = |store: &mut Store<_>, msg: &str| {81let len = memory.data_size(&mut *store);82let len = u64::try_from(len).unwrap();8384if let Some(n) = len.checked_sub(8).and_then(|n| n.checked_sub(offset)) {85// Test various in-bounds accesses near the bound.86for i in 0..=7 {87let addr = n + i;88assert!(addr + offset + 1 <= len);89let result = load8.call(&mut *store, addr);90assert!(91result.is_ok(),92"{msg}: len={len:#x}, offset={offset:#x}, load8({n:#x} + {i:#x} = {addr:#x}) \93should be in bounds, got {result:?}"94);95}96for i in 0..=6 {97let addr = n + offset + i;98assert!(addr + 2 <= len);99let result = load16.call(&mut *store, n + i);100assert!(101result.is_ok(),102"{msg}: len={len:#x}, offset={offset:#x}, load16({n:#x} + {i:#x} = {addr:#x}) \103should be in bounds, got {result:?}"104);105}106for i in 0..=4 {107let addr = n + offset + i;108assert!(addr + 4 <= len);109let result = load32.call(&mut *store, n + i);110assert!(111result.is_ok(),112"{msg}: len={len:#x}, offset={offset:#x}, load32({n:#x} + {i:#x} = {addr:#x}) \113should be in bounds, got {result:?}"114);115}116assert!(n + offset + 8 <= len);117let result = load64.call(&mut *store, n);118assert!(119result.is_ok(),120"{msg}: len={len:#x}, offset={offset:#x}, load64({n:#x}) should be in bounds, \121got {result:?}"122);123124// Test various out-of-bounds accesses overlapping the memory bound.125for i in 1..2 {126let addr = len - i;127assert!(addr + offset + 2 > len);128let result = load16.call(&mut *store, addr);129assert!(130result.is_err(),131"{msg}: len={len:#x}, offset={offset:#x}, load16({len:#x} - {i:#x} = {addr:#x}) \132should trap, got {result:?}"133);134}135for i in 1..4 {136let addr = len - i;137assert!(addr + offset + 4 > len);138let result = load32.call(&mut *store, addr);139assert!(140result.is_err(),141"{msg}: len={len:#x}, offset={offset:#x}, load32({len:#x} - {i:#x} = {addr:#x}) \142should trap, got {result:?}"143);144}145for i in 1..8 {146let addr = len - i;147assert!(addr + offset + 8 > len);148let result = load64.call(&mut *store, addr);149assert!(150result.is_err(),151"{msg}: len={len:#x}, offset={offset:#x}, load64({len:#x} - {i:#x} = {addr:#x}) \152should trap, got {result:?}"153);154}155}156157// Test that out-of-bounds accesses just after the memory bound trap.158if let Some(n) = len.checked_sub(offset) {159for i in 0..=1 {160let addr = n + i;161assert!(addr + offset + 1 > len);162let result = load8.call(&mut *store, addr);163assert!(164result.is_err(),165"{msg}: len={len:#x}, offset={offset:#x}, load8({n:#x} + {i:#x} = {addr:#x}) \166should trap, got {result:?}"167);168assert!(addr + offset + 2 > len);169let result = load16.call(&mut *store, addr);170assert!(171result.is_err(),172"{msg}: len={len:#x}, offset={offset:#x}, load16({n:#x} + {i:#x} = {addr:#x}) \173should trap, got {result:?}"174);175assert!(addr + offset + 4 > len);176let result = load32.call(&mut *store, addr);177assert!(178result.is_err(),179"{msg}: len={len:#x}, offset={offset:#x}, load32({n:#x} + {i:#x} = {addr:#x}) \180should trap, got {result:?}"181);182assert!(addr + offset + 8 > len);183let result = load64.call(&mut *store, addr);184assert!(185result.is_err(),186"{msg}: len={len:#x}, offset={offset:#x}, load64({n:#x} + {i:#x} = {addr:#x}) \187should trap, got {result:?}"188);189}190}191192// Test out-of-bounds accesses near the end of the index type's range to193// double check our overflow handling inside the bounds checks.194let len_is_4gib = len == u64::from(u32::MAX) + 1;195let end_delta = (input.image.memory64 && len_is_4gib) as u64;196let max = if input.image.memory64 {197u64::MAX198} else {199u64::from(u32::MAX)200};201for i in 0..(1 - end_delta) {202let addr = max - i;203let result = load8.call(&mut *store, addr);204assert!(205result.is_err(),206"{msg}: len={len:#x}, offset={offset:#x}, load8({max:#x} - {i:#x} = {addr:#x}) \207should trap, got {result:?}"208);209}210for i in 0..(2 - end_delta) {211let addr = max - i;212let result = load16.call(&mut *store, addr);213assert!(214result.is_err(),215"{msg}: len={len:#x}, offset={offset:#x}, load16({max:#x} - {i:#x} = {addr:#x}) \216should trap, got {result:?}"217);218}219for i in 0..(4 - end_delta) {220let addr = max - i;221let result = load32.call(&mut *store, addr);222assert!(223result.is_err(),224"{msg}: len={len:#x}, offset={offset:#x}, load32({max:#x} - {i:#x} = {addr:#x}) \225should trap, got {result:?}"226);227}228for i in 0..(8 - end_delta) {229let addr = max - i;230let result = load64.call(&mut *store, addr);231assert!(232result.is_err(),233"{msg}: len={len:#x}, offset={offset:#x}, load64({max:#x} - {i:#x} = {addr:#x}) \234should trap, got {result:?}"235);236}237};238239do_accesses(&mut store, "initial size");240let res = memory.grow(&mut store, u64::from(growth));241log::debug!("grow {growth} -> {res:?}");242do_accesses(&mut store, "after growing");243}244245/// Build a Wasm module with a single memory in the shape of the given heap246/// image, exports that memory, and also exports four functions:247/// `load{8,16,32,64}`. Each of these functions takes an `i64` address,248/// truncates it to `i32` if the memory is not 64-bit, and loads its associated249/// number of bits from memory at `address + offset`.250///251/// ```wat252/// (module253/// (memory (export "memory") ...)254/// (func (export "load8") (param i64) (result i32)255/// (i32.load8_u offset=${offset} (local.get 0))256/// )257/// ...258/// )259/// ```260fn build_wasm(image: &HeapImage, offset: u32) -> Vec<u8> {261let mut module = wasm_encoder::Module::new();262263{264let mut types = wasm_encoder::TypeSection::new();265types266.ty()267.function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I32]);268types269.ty()270.function([wasm_encoder::ValType::I64], [wasm_encoder::ValType::I64]);271module.section(&types);272}273274{275let mut funcs = wasm_encoder::FunctionSection::new();276funcs.function(0);277funcs.function(0);278funcs.function(0);279funcs.function(1);280module.section(&funcs);281}282283{284let mut memories = wasm_encoder::MemorySection::new();285memories.memory(wasm_encoder::MemoryType {286minimum: u64::from(image.minimum),287maximum: image.maximum.map(Into::into),288memory64: image.memory64,289shared: false,290page_size_log2: image.page_size_log2,291});292module.section(&memories);293}294295{296let mut exports = wasm_encoder::ExportSection::new();297exports.export("memory", wasm_encoder::ExportKind::Memory, 0);298exports.export("load8", wasm_encoder::ExportKind::Func, 0);299exports.export("load16", wasm_encoder::ExportKind::Func, 1);300exports.export("load32", wasm_encoder::ExportKind::Func, 2);301exports.export("load64", wasm_encoder::ExportKind::Func, 3);302module.section(&exports);303}304305{306let mut code = wasm_encoder::CodeSection::new();307{308let mut func = wasm_encoder::Function::new([]);309func.instruction(&wasm_encoder::Instruction::LocalGet(0));310if !image.memory64 {311func.instruction(&wasm_encoder::Instruction::I32WrapI64);312}313func.instruction(&wasm_encoder::Instruction::I32Load8U(314wasm_encoder::MemArg {315offset: u64::from(offset),316align: 0,317memory_index: 0,318},319));320func.instruction(&wasm_encoder::Instruction::End);321code.function(&func);322}323{324let mut func = wasm_encoder::Function::new([]);325func.instruction(&wasm_encoder::Instruction::LocalGet(0));326if !image.memory64 {327func.instruction(&wasm_encoder::Instruction::I32WrapI64);328}329func.instruction(&wasm_encoder::Instruction::I32Load16U(330wasm_encoder::MemArg {331offset: u64::from(offset),332align: 0,333memory_index: 0,334},335));336func.instruction(&wasm_encoder::Instruction::End);337code.function(&func);338}339{340let mut func = wasm_encoder::Function::new([]);341func.instruction(&wasm_encoder::Instruction::LocalGet(0));342if !image.memory64 {343func.instruction(&wasm_encoder::Instruction::I32WrapI64);344}345func.instruction(&wasm_encoder::Instruction::I32Load(wasm_encoder::MemArg {346offset: u64::from(offset),347align: 0,348memory_index: 0,349}));350func.instruction(&wasm_encoder::Instruction::End);351code.function(&func);352}353{354let mut func = wasm_encoder::Function::new([]);355func.instruction(&wasm_encoder::Instruction::LocalGet(0));356if !image.memory64 {357func.instruction(&wasm_encoder::Instruction::I32WrapI64);358}359func.instruction(&wasm_encoder::Instruction::I64Load(wasm_encoder::MemArg {360offset: u64::from(offset),361align: 0,362memory_index: 0,363}));364func.instruction(&wasm_encoder::Instruction::End);365code.function(&func);366}367module.section(&code);368}369370{371let mut datas = wasm_encoder::DataSection::new();372for (offset, data) in image.segments.iter() {373datas.segment(wasm_encoder::DataSegment {374mode: wasm_encoder::DataSegmentMode::Active {375memory_index: 0,376offset: &if image.memory64 {377wasm_encoder::ConstExpr::i64_const(*offset as i64)378} else {379wasm_encoder::ConstExpr::i32_const(*offset as i32)380},381},382data: data.iter().copied(),383});384}385module.section(&datas);386}387388module.finish()389}390391#[cfg(test)]392mod tests {393use super::*;394use arbitrary::{Arbitrary, Unstructured};395use rand::prelude::*;396397#[test]398fn smoke_test_memory_access() {399let mut rng = SmallRng::seed_from_u64(0);400let mut buf = vec![0; 1024];401402for _ in 0..1024 {403rng.fill_bytes(&mut buf);404let u = Unstructured::new(&buf);405if let Ok(input) = MemoryAccesses::arbitrary_take_rest(u) {406check_memory_accesses(input);407}408}409}410}411412413