/src/wasmtime/fuzz/fuzz_targets/oom.rs
Line | Count | Source |
1 | | #![no_main] |
2 | | |
3 | | use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured}; |
4 | | use wasmtime::{Engine, Module, Store, Trap, Val, error::OutOfMemory}; |
5 | | use wasmtime_core::alloc::TryVec; |
6 | | use wasmtime_fuzzing::generators::Config; |
7 | | use wasmtime_fuzzing::oom::{OomTest, OomTestAllocator}; |
8 | | use wasmtime_fuzzing::oracles::dummy; |
9 | | use wasmtime_fuzzing::single_module_fuzzer::KnownValid; |
10 | | |
11 | | const OOM_TEST_ITERS: u32 = 10; |
12 | | const OOM_TEST_FUEL: u64 = 1000; |
13 | | |
14 | | #[global_allocator] |
15 | | static GLOBAL_ALLOCATOR: OomTestAllocator = OomTestAllocator::new(); |
16 | | |
17 | | wasmtime_fuzzing::single_module_fuzzer!(execute gen_module); |
18 | | |
19 | | #[derive(Debug)] |
20 | | struct OomInput { |
21 | | config: Config, |
22 | | seed: u64, |
23 | | } |
24 | | |
25 | | impl<'a> Arbitrary<'a> for OomInput { |
26 | 9.76k | fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> { |
27 | 9.76k | let mut config: Config = u.arbitrary()?; |
28 | 9.58k | config.module_config.config.exceptions_enabled = false; |
29 | 9.58k | config.module_config.config.gc_enabled = false; |
30 | 9.58k | config.module_config.config.reference_types_enabled = false; |
31 | 9.58k | config.module_config.function_references_enabled = false; |
32 | 9.58k | config.module_config.config.export_everything = true; |
33 | 9.58k | config.wasmtime.strategy = |
34 | 9.58k | wasmtime_fuzzing::generators::InstanceAllocationStrategy::OnDemand; |
35 | 9.58k | let seed = u.arbitrary()?; |
36 | 9.58k | Ok(OomInput { config, seed }) |
37 | 9.76k | } |
38 | | } |
39 | | |
40 | 9.51k | fn compile(config: &Config, wasm: &[u8]) -> wasmtime::Result<Vec<u8>> { |
41 | 9.51k | let mut wasmtime_config = config.to_wasmtime(); |
42 | 9.51k | wasmtime_config.concurrency_support(false); |
43 | 9.51k | wasmtime_config.consume_fuel(true); |
44 | 9.51k | let engine = Engine::new(&wasmtime_config)?; |
45 | 9.51k | let module = Module::new(&engine, wasm)?; |
46 | 9.06k | module.serialize() |
47 | 9.51k | } |
48 | | |
49 | 9.51k | fn execute( |
50 | 9.51k | module: &[u8], |
51 | 9.51k | _known_valid: KnownValid, |
52 | 9.51k | input: OomInput, |
53 | 9.51k | _u: &mut Unstructured<'_>, |
54 | 9.51k | ) -> Result<()> { |
55 | 9.51k | if cfg!(not(arc_try_new)) { |
56 | 0 | panic!( |
57 | | "The OOM fuzzer is disabled because `cfg(arc_try_new)` was not enabled. Build with \ |
58 | | `RUSTFLAGS=--cfg=arc_try_new` to enable." |
59 | | ); |
60 | 9.51k | } |
61 | | |
62 | 9.51k | let module_bytes = match compile(&input.config, module) { |
63 | 9.06k | Ok(bytes) => bytes, |
64 | 446 | Err(_) => return Ok(()), |
65 | | }; |
66 | | |
67 | 9.06k | let mut oom_config = input.config.to_wasmtime(); |
68 | 9.06k | oom_config.enable_compiler(false); |
69 | 9.06k | oom_config.concurrency_support(false); |
70 | 9.06k | oom_config.consume_fuel(true); |
71 | | |
72 | | // Prevent real process-level OOM: fuzzer-generated configs can set |
73 | | // `memory_reservation(0)`, forcing `mmap` to commit real pages that bypass |
74 | | // `OomTestAllocator`. Use large virtual reservations instead. |
75 | 9.06k | oom_config.memory_reservation(1 << 32); |
76 | 9.06k | oom_config.memory_guard_size(1 << 31); |
77 | | |
78 | 9.06k | let oom_engine = match Engine::new(&oom_config) { |
79 | 9.06k | Ok(e) => e, |
80 | 0 | Err(_) => return Ok(()), |
81 | | }; |
82 | | |
83 | 9.06k | let _ = OomTest::new() |
84 | 9.06k | .seed(input.seed) |
85 | 9.06k | .max_iters(OOM_TEST_ITERS) |
86 | 9.06k | .allow_alloc_after_oom(true) |
87 | 9.06k | .alloc_succeeds_after_oom(true) |
88 | 9.06k | .allow_missed_oom_errors(true) |
89 | 9.31k | .fuzz(|| { |
90 | 9.31k | let module = unsafe { Module::deserialize(&oom_engine, &module_bytes)? }; |
91 | | |
92 | 117 | let mut store = Store::try_new(&oom_engine, ())?; |
93 | 97 | store.set_fuel(OOM_TEST_FUEL).unwrap(); |
94 | | |
95 | 97 | let linker = dummy::dummy_linker(&mut store, &module)?; |
96 | 97 | let instance = linker.instantiate(&mut store, &module)?; |
97 | | |
98 | 47 | 'export_loop: for export in module.exports() { |
99 | 14 | let extern_ty = export.ty(); |
100 | 14 | let Some(func_ty) = extern_ty.func() else { |
101 | 14 | continue; |
102 | | }; |
103 | 0 | let func = instance.get_func(&mut store, export.name()).unwrap(); |
104 | | |
105 | | // Build default params; skip if any param type has no default. |
106 | 0 | let mut params: TryVec<Val> = TryVec::with_capacity(func_ty.params().len())?; |
107 | 0 | for p in func_ty.params() { |
108 | 0 | match p.default_value() { |
109 | 0 | Some(v) => params.push(v)?, |
110 | | None => { |
111 | 0 | continue 'export_loop; |
112 | | } |
113 | | } |
114 | | } |
115 | | |
116 | 0 | let mut results: TryVec<Val> = TryVec::with_capacity(func_ty.results().len())?; |
117 | 0 | for _ in 0..func_ty.results().len() { |
118 | 0 | results.push(Val::I32(0))?; |
119 | | } |
120 | | |
121 | 0 | match func.call(&mut store, ¶ms, &mut results) { |
122 | | // OOM; return from this OOM test iteration. |
123 | 0 | Err(e) if e.is::<OutOfMemory>() => return Err(e), |
124 | | |
125 | | // Out of fuel; stop calling exports. |
126 | 0 | Err(e) |
127 | 0 | if e.downcast_ref::<Trap>() |
128 | 0 | .is_some_and(|trap| *trap == Trap::OutOfFuel) => |
129 | | { |
130 | 0 | break; |
131 | | } |
132 | | |
133 | 0 | Err(_) | Ok(_) => {} |
134 | | } |
135 | | } |
136 | | |
137 | 47 | Ok(()) |
138 | 9.31k | }); |
139 | | |
140 | 9.06k | Ok(()) |
141 | 9.51k | } |
142 | | |
143 | 9.58k | fn gen_module(input: &mut OomInput, u: &mut Unstructured<'_>) -> Result<(Vec<u8>, KnownValid)> { |
144 | 9.58k | let module = input.config.generate(u, Some(1000))?; |
145 | 9.51k | Ok((module.to_bytes(), KnownValid::Yes)) |
146 | 9.58k | } |