Coverage Report

Created: 2026-09-14 07:40

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.09k
    fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
27
9.09k
        let mut config: Config = u.arbitrary()?;
28
8.93k
        config.module_config.config.exceptions_enabled = false;
29
8.93k
        config.module_config.config.gc_enabled = false;
30
8.93k
        config.module_config.config.reference_types_enabled = false;
31
8.93k
        config.module_config.function_references_enabled = false;
32
8.93k
        config.module_config.config.export_everything = true;
33
8.93k
        config.wasmtime.strategy =
34
8.93k
            wasmtime_fuzzing::generators::InstanceAllocationStrategy::OnDemand;
35
8.93k
        let seed = u.arbitrary()?;
36
8.93k
        Ok(OomInput { config, seed })
37
9.09k
    }
38
}
39
40
8.85k
fn compile(config: &Config, wasm: &[u8]) -> wasmtime::Result<Vec<u8>> {
41
8.85k
    let mut wasmtime_config = config.to_wasmtime();
42
8.85k
    wasmtime_config.concurrency_support(false);
43
8.85k
    wasmtime_config.consume_fuel(true);
44
8.85k
    let engine = Engine::new(&wasmtime_config)?;
45
8.85k
    let module = Module::new(&engine, wasm)?;
46
8.62k
    module.serialize()
47
8.85k
}
48
49
8.85k
fn execute(
50
8.85k
    module: &[u8],
51
8.85k
    _known_valid: KnownValid,
52
8.85k
    input: OomInput,
53
8.85k
    _u: &mut Unstructured<'_>,
54
8.85k
) -> Result<()> {
55
8.85k
    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
8.85k
    }
61
62
8.85k
    let module_bytes = match compile(&input.config, module) {
63
8.62k
        Ok(bytes) => bytes,
64
229
        Err(_) => return Ok(()),
65
    };
66
67
8.62k
    let mut oom_config = input.config.to_wasmtime();
68
8.62k
    oom_config.enable_compiler(false);
69
8.62k
    oom_config.concurrency_support(false);
70
8.62k
    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
8.62k
    oom_config.memory_reservation(1 << 32);
76
8.62k
    oom_config.memory_guard_size(1 << 31);
77
78
8.62k
    let oom_engine = match Engine::new(&oom_config) {
79
8.62k
        Ok(e) => e,
80
0
        Err(_) => return Ok(()),
81
    };
82
83
8.62k
    let _ = OomTest::new()
84
8.62k
        .seed(input.seed)
85
8.62k
        .max_iters(OOM_TEST_ITERS)
86
8.62k
        .allow_alloc_after_oom(true)
87
8.62k
        .alloc_succeeds_after_oom(true)
88
8.62k
        .allow_missed_oom_errors(true)
89
8.88k
        .fuzz(|| {
90
8.88k
            let module = unsafe { Module::deserialize(&oom_engine, &module_bytes)? };
91
92
144
            let mut store = Store::try_new(&oom_engine, ())?;
93
124
            store.set_fuel(OOM_TEST_FUEL).unwrap();
94
95
124
            let linker = dummy::dummy_linker(&mut store, &module)?;
96
119
            let instance = linker.instantiate(&mut store, &module)?;
97
98
90
            'export_loop: for export in module.exports() {
99
90
                let extern_ty = export.ty();
100
90
                let Some(func_ty) = extern_ty.func() else {
101
81
                    continue;
102
                };
103
9
                let func = instance.get_func(&mut store, export.name()).unwrap();
104
105
                // Build default params; skip if any param type has no default.
106
9
                let mut params: TryVec<Val> = TryVec::with_capacity(func_ty.params().len())?;
107
63
                for p in func_ty.params() {
108
63
                    match p.default_value() {
109
63
                        Some(v) => params.push(v)?,
110
                        None => {
111
0
                            continue 'export_loop;
112
                        }
113
                    }
114
                }
115
116
9
                let mut results: TryVec<Val> = TryVec::with_capacity(func_ty.results().len())?;
117
9
                for _ in 0..func_ty.results().len() {
118
24
                    results.push(Val::I32(0))?;
119
                }
120
121
9
                match func.call(&mut store, &params, &mut results) {
122
                    // OOM; return from this OOM test iteration.
123
3
                    Err(e) if e.is::<OutOfMemory>() => return Err(e),
124
125
                    // Out of fuel; stop calling exports.
126
0
                    Err(e)
127
3
                        if e.downcast_ref::<Trap>()
128
3
                            .is_some_and(|trap| *trap == Trap::OutOfFuel) =>
129
                    {
130
0
                        break;
131
                    }
132
133
9
                    Err(_) | Ok(_) => {}
134
                }
135
            }
136
137
58
            Ok(())
138
8.88k
        });
139
140
8.62k
    Ok(())
141
8.85k
}
142
143
8.93k
fn gen_module(input: &mut OomInput, u: &mut Unstructured<'_>) -> Result<(Vec<u8>, KnownValid)> {
144
8.93k
    let module = input.config.generate(u, Some(1000))?;
145
8.85k
    Ok((module.to_bytes(), KnownValid::Yes))
146
8.93k
}