Coverage Report

Created: 2026-08-15 07:39

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.48k
    fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
27
9.48k
        let mut config: Config = u.arbitrary()?;
28
9.32k
        config.module_config.config.exceptions_enabled = false;
29
9.32k
        config.module_config.config.gc_enabled = false;
30
9.32k
        config.module_config.config.reference_types_enabled = false;
31
9.32k
        config.module_config.function_references_enabled = false;
32
9.32k
        config.module_config.config.export_everything = true;
33
9.32k
        config.wasmtime.strategy =
34
9.32k
            wasmtime_fuzzing::generators::InstanceAllocationStrategy::OnDemand;
35
9.32k
        let seed = u.arbitrary()?;
36
9.32k
        Ok(OomInput { config, seed })
37
9.48k
    }
38
}
39
40
9.25k
fn compile(config: &Config, wasm: &[u8]) -> wasmtime::Result<Vec<u8>> {
41
9.25k
    let mut wasmtime_config = config.to_wasmtime();
42
9.25k
    wasmtime_config.concurrency_support(false);
43
9.25k
    wasmtime_config.consume_fuel(true);
44
9.25k
    let engine = Engine::new(&wasmtime_config)?;
45
9.25k
    let module = Module::new(&engine, wasm)?;
46
8.90k
    module.serialize()
47
9.25k
}
48
49
9.25k
fn execute(
50
9.25k
    module: &[u8],
51
9.25k
    _known_valid: KnownValid,
52
9.25k
    input: OomInput,
53
9.25k
    _u: &mut Unstructured<'_>,
54
9.25k
) -> Result<()> {
55
9.25k
    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.25k
    }
61
62
9.25k
    let module_bytes = match compile(&input.config, module) {
63
8.90k
        Ok(bytes) => bytes,
64
346
        Err(_) => return Ok(()),
65
    };
66
67
8.90k
    let mut oom_config = input.config.to_wasmtime();
68
8.90k
    oom_config.enable_compiler(false);
69
8.90k
    oom_config.concurrency_support(false);
70
8.90k
    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.90k
    oom_config.memory_reservation(1 << 32);
76
8.90k
    oom_config.memory_guard_size(1 << 31);
77
78
8.90k
    let oom_engine = match Engine::new(&oom_config) {
79
8.90k
        Ok(e) => e,
80
0
        Err(_) => return Ok(()),
81
    };
82
83
8.90k
    let _ = OomTest::new()
84
8.90k
        .seed(input.seed)
85
8.90k
        .max_iters(OOM_TEST_ITERS)
86
8.90k
        .allow_alloc_after_oom(true)
87
8.90k
        .alloc_succeeds_after_oom(true)
88
8.90k
        .allow_missed_oom_errors(true)
89
9.07k
        .fuzz(|| {
90
9.07k
            let module = unsafe { Module::deserialize(&oom_engine, &module_bytes)? };
91
92
100
            let mut store = Store::try_new(&oom_engine, ())?;
93
85
            store.set_fuel(OOM_TEST_FUEL).unwrap();
94
95
85
            let linker = dummy::dummy_linker(&mut store, &module)?;
96
80
            let instance = linker.instantiate(&mut store, &module)?;
97
98
68
            'export_loop: for export in module.exports() {
99
68
                let extern_ty = export.ty();
100
68
                let Some(func_ty) = extern_ty.func() else {
101
62
                    continue;
102
                };
103
6
                let func = instance.get_func(&mut store, export.name()).unwrap();
104
105
                // Build default params; skip if any param type has no default.
106
6
                let mut params: TryVec<Val> = TryVec::with_capacity(func_ty.params().len())?;
107
6
                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
6
                let mut results: TryVec<Val> = TryVec::with_capacity(func_ty.results().len())?;
117
6
                for _ in 0..func_ty.results().len() {
118
0
                    results.push(Val::I32(0))?;
119
                }
120
121
6
                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
6
                    Err(_) | Ok(_) => {}
134
                }
135
            }
136
137
36
            Ok(())
138
9.07k
        });
139
140
8.90k
    Ok(())
141
9.25k
}
142
143
9.32k
fn gen_module(input: &mut OomInput, u: &mut Unstructured<'_>) -> Result<(Vec<u8>, KnownValid)> {
144
9.32k
    let module = input.config.generate(u, Some(1000))?;
145
9.25k
    Ok((module.to_bytes(), KnownValid::Yes))
146
9.32k
}