Coverage Report

Created: 2026-06-07 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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, &params, &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
}