Coverage Report

Created: 2026-08-02 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasm-tools/fuzz/src/validate.rs
Line
Count
Source
1
use arbitrary::{Result, Unstructured};
2
use wasmparser::{Parser, Validator, WasmFeatures};
3
4
512
pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
5
    // Either use `wasm-smith` to generate a module with possibly invalid
6
    // functions or try validating raw bytes from the input itself.
7
512
    if u.arbitrary()? {
8
508
        validate_maybe_invalid_module(u)?;
9
    } else {
10
4
        validate_raw_bytes(u)?;
11
    }
12
512
    Ok(())
13
512
}
14
15
508
pub fn validate_maybe_invalid_module(u: &mut Unstructured<'_>) -> Result<()> {
16
    // Generate a "valid" module but specifically allow invalid functions which
17
    // means that some functions may be defined from the input bytes raw. This
18
    // means that most of the module is valid but only some functions may be
19
    // invalid which can help stress various bits and pieces of validation.
20
508
    let (wasm, config) = crate::generate_valid_module(u, |config, _| {
21
508
        config.allow_invalid_funcs = true;
22
508
        Ok(())
23
508
    })?;
24
508
    validate_all(u, crate::validator_for_config(&config), &wasm)
25
508
}
26
27
4
pub fn validate_raw_bytes(u: &mut Unstructured<'_>) -> Result<()> {
28
    // Enable arbitrary combinations of features to validate the input bytes.
29
4
    let validator = Validator::new_with_features(WasmFeatures::from_bits_truncate(u.arbitrary()?));
30
4
    let wasm = u.bytes(u.len())?;
31
4
    crate::log_wasm(wasm, "");
32
4
    validate_all(u, validator, wasm)
33
4
}
34
35
512
fn validate_all(u: &mut Unstructured<'_>, mut validator: Validator, wasm: &[u8]) -> Result<()> {
36
    // First try printing this module. Generate a random configuration for
37
    // printing and then see what happens. Mostly making sure nothing panics
38
    // here.
39
512
    let mut cfg = wasmprinter::Config::new();
40
512
    cfg.fold_instructions(u.arbitrary()?);
41
512
    cfg.print_skeleton(u.arbitrary()?);
42
512
    cfg.print_offsets(u.arbitrary()?);
43
512
    cfg.name_unnamed(u.arbitrary()?);
44
512
    log::debug!("print config {cfg:?}");
45
512
    let mut wat = String::new();
46
512
    let _ = cfg.print(wasm, &mut wasmprinter::PrintFmtWrite(&mut wat));
47
48
    // After printing then try to parse and validate the module. See how far we
49
    // get as invalid modules are explicitly allowed here. Generally looking for
50
    // panics and excessive resource usage here.
51
7.76k
    for payload in Parser::new(0).parse_all(wasm) {
52
7.76k
        let payload = match payload {
53
7.76k
            Ok(p) => p,
54
4
            Err(_) => return Ok(()),
55
        };
56
57
7.76k
        if validator.payload(&payload).is_err() {
58
0
            return Ok(());
59
7.76k
        }
60
61
        // Check that the payload's range is in bounds, since the payload is
62
        // supposedly valid.
63
        use wasmparser::Payload::*;
64
7.76k
        match payload {
65
508
            Version { range, .. } => assert!(wasm.get(range).is_some()),
66
432
            TypeSection(s) => assert!(wasm.get(s.range()).is_some()),
67
300
            ImportSection(s) => assert!(wasm.get(s.range()).is_some()),
68
242
            FunctionSection(s) => assert!(wasm.get(s.range()).is_some()),
69
181
            TableSection(s) => assert!(wasm.get(s.range()).is_some()),
70
150
            MemorySection(s) => assert!(wasm.get(s.range()).is_some()),
71
52
            TagSection(s) => assert!(wasm.get(s.range()).is_some()),
72
250
            GlobalSection(s) => assert!(wasm.get(s.range()).is_some()),
73
154
            ExportSection(s) => assert!(wasm.get(s.range()).is_some()),
74
17
            StartSection { range, .. } => assert!(wasm.get(range).is_some()),
75
115
            ElementSection(s) => assert!(wasm.get(s.range()).is_some()),
76
67
            DataCountSection { range, .. } => assert!(wasm.get(range).is_some()),
77
92
            DataSection(s) => assert!(wasm.get(s.range()).is_some()),
78
242
            CodeSectionStart { range, .. } => assert!(wasm.get(range).is_some()),
79
4.45k
            CodeSectionEntry(body) => assert!(wasm.get(body.range()).is_some()),
80
0
            InstanceSection(s) => assert!(wasm.get(s.range()).is_some()),
81
0
            CoreTypeSection(s) => assert!(wasm.get(s.range()).is_some()),
82
0
            ComponentInstanceSection(s) => assert!(wasm.get(s.range()).is_some()),
83
0
            ComponentAliasSection(s) => assert!(wasm.get(s.range()).is_some()),
84
0
            ComponentTypeSection(s) => assert!(wasm.get(s.range()).is_some()),
85
0
            ComponentCanonicalSection(s) => assert!(wasm.get(s.range()).is_some()),
86
0
            ComponentStartSection { range, .. } => assert!(wasm.get(range).is_some()),
87
0
            ComponentImportSection(s) => assert!(wasm.get(s.range()).is_some()),
88
0
            ComponentExportSection(s) => assert!(wasm.get(s.range()).is_some()),
89
0
            CustomSection(s) => assert!(wasm.get(s.range()).is_some()),
90
0
            UnknownSection { range, .. } => assert!(wasm.get(range).is_some()),
91
92
            // In order to support streaming parsing and validation, these
93
            // sections' ranges are not checked during validation, since they
94
            // contain nested sections and we don't want to require all nested
95
            // sections are present before we can parse/validate any of them.
96
            ComponentSection {
97
                unchecked_range: _, ..
98
            }
99
            | ModuleSection {
100
                unchecked_range: _, ..
101
0
            } => {}
102
103
            // No associated range.
104
508
            End(_) => {}
105
106
0
            _ => {}
107
        }
108
    }
109
110
508
    Ok(())
111
512
}
112
113
#[test]
114
fn smoke() {
115
    super::test::test_n_times(100, run);
116
}