Skip to content

Commit 1e2ffb8

Browse files
authored
Ensure that we have the full length of component and module sections (#1492)
* Ensure that we have the full length of component and module sections Fixes the root cause of bytecodealliance/wasmtime#8322 * Extend validation fuzzer to check that payloads are in bounds * Don't check module/component section ranges; rename to `unchecked_range`; document * rustfmt
1 parent fe1307b commit 1e2ffb8

File tree

8 files changed

+150
-31
lines changed

8 files changed

+150
-31
lines changed

crates/wasm-metadata/src/lib.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,8 +418,14 @@ impl Metadata {
418418
}
419419
}
420420
}
421-
ModuleSection { range, .. } => metadata.push(Metadata::empty_module(range)),
422-
ComponentSection { range, .. } => metadata.push(Metadata::empty_component(range)),
421+
ModuleSection {
422+
unchecked_range: range,
423+
..
424+
} => metadata.push(Metadata::empty_module(range)),
425+
ComponentSection {
426+
unchecked_range: range,
427+
..
428+
} => metadata.push(Metadata::empty_component(range)),
423429
End { .. } => {
424430
let finished = metadata.pop().expect("non-empty metadata stack");
425431
if metadata.is_empty() {

crates/wasmparser/src/parser.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,10 @@ pub enum Payload<'a> {
212212
parser: Parser,
213213
/// The range of bytes that represent the nested module in the
214214
/// original byte stream.
215-
range: Range<usize>,
215+
///
216+
/// Note that, to better support streaming parsing and validation, the
217+
/// validator does *not* check that this range is in bounds.
218+
unchecked_range: Range<usize>,
216219
},
217220
/// A core instance section was received and the provided parser can be
218221
/// used to parse the contents of the core instance section.
@@ -241,7 +244,10 @@ pub enum Payload<'a> {
241244
parser: Parser,
242245
/// The range of bytes that represent the nested component in the
243246
/// original byte stream.
244-
range: Range<usize>,
247+
///
248+
/// Note that, to better support streaming parsing and validation, the
249+
/// validator does *not* check that this range is in bounds.
250+
unchecked_range: Range<usize>,
245251
},
246252
/// A component instance section was received and the provided reader can be
247253
/// used to parse the contents of the component instance section.
@@ -679,16 +685,22 @@ impl Parser {
679685
);
680686
}
681687

682-
let range =
683-
reader.original_position()..reader.original_position() + len as usize;
688+
let range = reader.original_position()
689+
..reader.original_position() + usize::try_from(len).unwrap();
684690
self.max_size -= u64::from(len);
685691
self.offset += u64::from(len);
686692
let mut parser = Parser::new(usize_to_u64(reader.original_position()));
687-
parser.max_size = len.into();
693+
parser.max_size = u64::from(len);
688694

689695
Ok(match id {
690-
1 => ModuleSection { parser, range },
691-
4 => ComponentSection { parser, range },
696+
1 => ModuleSection {
697+
parser,
698+
unchecked_range: range,
699+
},
700+
4 => ComponentSection {
701+
parser,
702+
unchecked_range: range,
703+
},
692704
_ => unreachable!(),
693705
})
694706
}
@@ -1098,10 +1110,16 @@ impl Payload<'_> {
10981110
CodeSectionStart { range, .. } => Some((CODE_SECTION, range.clone())),
10991111
CodeSectionEntry(_) => None,
11001112

1101-
ModuleSection { range, .. } => Some((COMPONENT_MODULE_SECTION, range.clone())),
1113+
ModuleSection {
1114+
unchecked_range: range,
1115+
..
1116+
} => Some((COMPONENT_MODULE_SECTION, range.clone())),
11021117
InstanceSection(s) => Some((COMPONENT_CORE_INSTANCE_SECTION, s.range())),
11031118
CoreTypeSection(s) => Some((COMPONENT_CORE_TYPE_SECTION, s.range())),
1104-
ComponentSection { range, .. } => Some((COMPONENT_SECTION, range.clone())),
1119+
ComponentSection {
1120+
unchecked_range: range,
1121+
..
1122+
} => Some((COMPONENT_SECTION, range.clone())),
11051123
ComponentInstanceSection(s) => Some((COMPONENT_INSTANCE_SECTION, s.range())),
11061124
ComponentAliasSection(s) => Some((COMPONENT_ALIAS_SECTION, s.range())),
11071125
ComponentTypeSection(s) => Some((COMPONENT_TYPE_SECTION, s.range())),
@@ -1164,13 +1182,19 @@ impl fmt::Debug for Payload<'_> {
11641182
CodeSectionEntry(_) => f.debug_tuple("CodeSectionEntry").field(&"...").finish(),
11651183

11661184
// Component sections
1167-
ModuleSection { parser: _, range } => f
1185+
ModuleSection {
1186+
parser: _,
1187+
unchecked_range: range,
1188+
} => f
11681189
.debug_struct("ModuleSection")
11691190
.field("range", range)
11701191
.finish(),
11711192
InstanceSection(_) => f.debug_tuple("InstanceSection").field(&"...").finish(),
11721193
CoreTypeSection(_) => f.debug_tuple("CoreTypeSection").field(&"...").finish(),
1173-
ComponentSection { parser: _, range } => f
1194+
ComponentSection {
1195+
parser: _,
1196+
unchecked_range: range,
1197+
} => f
11741198
.debug_struct("ComponentSection")
11751199
.field("range", range)
11761200
.finish(),

crates/wasmparser/src/validator.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,13 +556,21 @@ impl Validator {
556556
DataSection(s) => self.data_section(s)?,
557557

558558
// Component sections
559-
ModuleSection { parser, range, .. } => {
559+
ModuleSection {
560+
parser,
561+
unchecked_range: range,
562+
..
563+
} => {
560564
self.module_section(range)?;
561565
return Ok(ValidPayload::Parser(parser.clone()));
562566
}
563567
InstanceSection(s) => self.instance_section(s)?,
564568
CoreTypeSection(s) => self.core_type_section(s)?,
565-
ComponentSection { parser, range, .. } => {
569+
ComponentSection {
570+
parser,
571+
unchecked_range: range,
572+
..
573+
} => {
566574
self.component_section(range)?;
567575
return Ok(ValidPayload::Parser(parser.clone()));
568576
}

crates/wasmprinter/src/lib.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,14 @@ impl Printer {
267267
Payload::CodeSectionEntry(f) => {
268268
code.push(f);
269269
}
270-
Payload::ModuleSection { range, .. } | Payload::ComponentSection { range, .. } => {
270+
Payload::ModuleSection {
271+
unchecked_range: range,
272+
..
273+
}
274+
| Payload::ComponentSection {
275+
unchecked_range: range,
276+
..
277+
} => {
271278
let offset = range.end - range.start;
272279
if offset > bytes.len() {
273280
bail!("invalid module or component section range");
@@ -514,7 +521,7 @@ impl Printer {
514521

515522
Payload::ModuleSection {
516523
parser: inner,
517-
range,
524+
unchecked_range: range,
518525
} => {
519526
Self::ensure_component(&states)?;
520527
expected = Some(Encoding::Module);
@@ -529,7 +536,7 @@ impl Printer {
529536
Payload::CoreTypeSection(s) => self.print_core_types(&mut states, s)?,
530537
Payload::ComponentSection {
531538
parser: inner,
532-
range,
539+
unchecked_range: range,
533540
} => {
534541
Self::ensure_component(&states)?;
535542
expected = Some(Encoding::Component);

fuzz/src/incremental_parse.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,11 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
146146
(
147147
ModuleSection {
148148
parser: p,
149-
range: a,
149+
unchecked_range: a,
150+
},
151+
ModuleSection {
152+
unchecked_range: b, ..
150153
},
151-
ModuleSection { range: b, .. },
152154
) => {
153155
assert_eq!(a, b);
154156
stack.push(parser);
@@ -158,9 +160,11 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
158160
(
159161
ComponentSection {
160162
parser: p,
161-
range: a,
163+
unchecked_range: a,
164+
},
165+
ComponentSection {
166+
unchecked_range: b, ..
162167
},
163-
ComponentSection { range: b, .. },
164168
) => {
165169
assert_eq!(a, b);
166170
stack.push(parser);

fuzz/src/validate.rs

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use arbitrary::{Result, Unstructured};
2-
use wasmparser::{Validator, WasmFeatures};
2+
use wasmparser::{Parser, Validator, WasmFeatures};
33

44
pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
55
// Either use `wasm-smith` to generate a module with possibly invalid
@@ -21,14 +21,13 @@ pub fn validate_maybe_invalid_module(u: &mut Unstructured<'_>) -> Result<()> {
2121
config.allow_invalid_funcs = true;
2222
Ok(())
2323
})?;
24-
let mut validator = crate::validator_for_config(&config);
25-
drop(validator.validate_all(&wasm));
24+
validate_all(crate::validator_for_config(&config), &wasm);
2625
Ok(())
2726
}
2827

2928
pub fn validate_raw_bytes(u: &mut Unstructured<'_>) -> Result<()> {
3029
// Enable arbitrary combinations of features to validate the input bytes.
31-
let mut validator = Validator::new_with_features(WasmFeatures {
30+
let validator = Validator::new_with_features(WasmFeatures {
3231
reference_types: u.arbitrary()?,
3332
multi_value: u.arbitrary()?,
3433
threads: u.arbitrary()?,
@@ -55,6 +54,65 @@ pub fn validate_raw_bytes(u: &mut Unstructured<'_>) -> Result<()> {
5554
});
5655
let wasm = u.bytes(u.len())?;
5756
crate::log_wasm(wasm, "");
58-
drop(validator.validate_all(wasm));
57+
validate_all(validator, wasm);
5958
Ok(())
6059
}
60+
61+
fn validate_all(mut validator: Validator, wasm: &[u8]) {
62+
for payload in Parser::new(0).parse_all(wasm) {
63+
let payload = match payload {
64+
Ok(p) => p,
65+
Err(_) => return,
66+
};
67+
68+
if validator.payload(&payload).is_err() {
69+
return;
70+
}
71+
72+
// Check that the payload's range is in bounds, since the payload is
73+
// supposedly valid.
74+
use wasmparser::Payload::*;
75+
match payload {
76+
Version { range, .. } => assert!(wasm.get(range).is_some()),
77+
TypeSection(s) => assert!(wasm.get(s.range()).is_some()),
78+
ImportSection(s) => assert!(wasm.get(s.range()).is_some()),
79+
FunctionSection(s) => assert!(wasm.get(s.range()).is_some()),
80+
TableSection(s) => assert!(wasm.get(s.range()).is_some()),
81+
MemorySection(s) => assert!(wasm.get(s.range()).is_some()),
82+
TagSection(s) => assert!(wasm.get(s.range()).is_some()),
83+
GlobalSection(s) => assert!(wasm.get(s.range()).is_some()),
84+
ExportSection(s) => assert!(wasm.get(s.range()).is_some()),
85+
StartSection { range, .. } => assert!(wasm.get(range).is_some()),
86+
ElementSection(s) => assert!(wasm.get(s.range()).is_some()),
87+
DataCountSection { range, .. } => assert!(wasm.get(range).is_some()),
88+
DataSection(s) => assert!(wasm.get(s.range()).is_some()),
89+
CodeSectionStart { range, .. } => assert!(wasm.get(range).is_some()),
90+
CodeSectionEntry(body) => assert!(wasm.get(body.range()).is_some()),
91+
InstanceSection(s) => assert!(wasm.get(s.range()).is_some()),
92+
CoreTypeSection(s) => assert!(wasm.get(s.range()).is_some()),
93+
ComponentInstanceSection(s) => assert!(wasm.get(s.range()).is_some()),
94+
ComponentAliasSection(s) => assert!(wasm.get(s.range()).is_some()),
95+
ComponentTypeSection(s) => assert!(wasm.get(s.range()).is_some()),
96+
ComponentCanonicalSection(s) => assert!(wasm.get(s.range()).is_some()),
97+
ComponentStartSection { range, .. } => assert!(wasm.get(range).is_some()),
98+
ComponentImportSection(s) => assert!(wasm.get(s.range()).is_some()),
99+
ComponentExportSection(s) => assert!(wasm.get(s.range()).is_some()),
100+
CustomSection(s) => assert!(wasm.get(s.range()).is_some()),
101+
UnknownSection { range, .. } => assert!(wasm.get(range).is_some()),
102+
103+
// In order to support streaming parsing and validation, these
104+
// sections' ranges are not checked during validation, since they
105+
// contain nested sections and we don't want to require all nested
106+
// sections are present before we can parse/validate any of them.
107+
ComponentSection {
108+
unchecked_range: _, ..
109+
}
110+
| ModuleSection {
111+
unchecked_range: _, ..
112+
} => {}
113+
114+
// No associated range.
115+
End(_) => {}
116+
}
117+
}
118+
}

src/bin/wasm-tools/dump.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,10 @@ impl<'a> Dump<'a> {
270270
}
271271

272272
// Component sections
273-
Payload::ModuleSection { range, .. } => {
273+
Payload::ModuleSection {
274+
unchecked_range: range,
275+
..
276+
} => {
274277
write!(
275278
self.state,
276279
"[core module {}] inline size",
@@ -297,7 +300,10 @@ impl<'a> Dump<'a> {
297300
me.print(end)
298301
})?,
299302

300-
Payload::ComponentSection { range, .. } => {
303+
Payload::ComponentSection {
304+
unchecked_range: range,
305+
..
306+
} => {
301307
write!(
302308
self.state,
303309
"[component {}] inline size",

src/bin/wasm-tools/objdump.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,19 @@ impl Opts {
4949
}
5050
CodeSectionEntry(_) => {}
5151

52-
ModuleSection { range, .. } => {
52+
ModuleSection {
53+
unchecked_range: range,
54+
..
55+
} => {
5356
printer.section_raw(range, 1, "module")?;
5457
printer.start(Encoding::Module)?;
5558
}
5659
InstanceSection(s) => printer.section(s, "core instances")?,
5760
CoreTypeSection(s) => printer.section(s, "core types")?,
58-
ComponentSection { range, .. } => {
61+
ComponentSection {
62+
unchecked_range: range,
63+
..
64+
} => {
5965
printer.section_raw(range, 1, "component")?;
6066
printer.indices.push(IndexSpace::default());
6167
printer.start(Encoding::Component)?;

0 commit comments

Comments
 (0)