Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions crates/wasmparser/src/readers/core/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1822,17 +1822,25 @@ impl<'a> FromReader<'a> for RefType {
}
0x62 => Err(crate::Error::new("unexpected exact type", pos)),
_ => {
// Reclassify errors as invalid reference types here because
// that's the "root" of what was being parsed rather than
// heap types.
let hty = reader.read().map_err(|mut e| {
// The short form of a reference type (without a `0x63`/`0x64`
// prefix byte) is only an abbreviation for `ref null <ht>` where
// `<ht>` is an *abstract* heap type; a bare (non-negative) type
// index is not a value type. Parse an abstract heap type
// directly, handling the `0x65` prefix for `shared` variants,
// rather than parsing a full heap type and then rejecting the
// concrete/exact cases that a heap type additionally allows.
let shared = reader.peek()? == 0x65;
if shared {
reader.read_u8()?;
}
let ty = reader.read().map_err(|mut e| {
if let ErrorKind::InvalidHeapType = e.kind() {
e.set_message("malformed reference type");
}
e
})?;
RefType::new(true, hty)
.ok_or_else(|| crate::Error::new("type index too large", pos))
// `RefType::new` is infallible for abstract heap types.
Ok(RefType::new(true, HeapType::Abstract { shared, ty }).unwrap())
}
}
}
Expand Down
43 changes: 39 additions & 4 deletions crates/wast/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,15 +465,17 @@ macro_rules! float {
Some(val) => $int::from_str_radix(val,16).ok()?,
None => 1 << (signif_bits - 1),
};
// If the significand is zero then this is actually infinity
// so we fail to parse it.
if signif & signif_mask == 0 {
// The payload `n` must satisfy `1 <= n < 2^signif_bits`. A
// payload of zero would actually be infinity, and a payload
// with any bits set outside the significand is out of range
// rather than something to silently mask away.
if signif == 0 || signif & !signif_mask != 0 {
return None;
}
return Some(
(neg_bit << neg_offset) |
(exp_bits << exp_offset) |
(signif & signif_mask)
signif
);
}

Expand Down Expand Up @@ -766,4 +768,37 @@ mod tests {
Some(0x26800000)
);
}

#[test]
fn nan_payload_range() {
fn nan(val: &'static str) -> crate::lexer::Float<'static> {
crate::lexer::Float::Nan {
val: Some(val.into()),
negative: false,
}
}

// f32 has a 23-bit significand, so `0x7fffff` is the maximum valid NaN
// payload; `0x800000` and beyond are out of range and must not be
// silently masked. A zero payload would be infinity, not a NaN.
assert_eq!(super::strtof(&nan("1")), Some(0x7f800001));
assert_eq!(super::strtof(&nan("7fffff")), Some(0x7fffffff));
assert_eq!(super::strtof(&nan("800000")), None);
assert_eq!(super::strtof(&nan("ffffff")), None);
assert_eq!(super::strtof(&nan("0")), None);

// f64 has a 52-bit significand, so `0xf_ffff_ffff_ffff` is the maximum
// valid NaN payload; a 14-hex-digit (56-bit) payload is out of range.
assert_eq!(super::strtod(&nan("1")), Some(0x7ff0000000000001));
assert_eq!(
super::strtod(&nan("8000000000000")),
Some(0x7ff8000000000000)
);
assert_eq!(
super::strtod(&nan("fffffffffffff")),
Some(0x7fffffffffffffff)
);
assert_eq!(super::strtod(&nan("ffffffffffffff")), None);
assert_eq!(super::strtod(&nan("0")), None);
}
}
23 changes: 23 additions & 0 deletions tests/cli/bad-nan.wast
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,26 @@
(assert_malformed
(module quote "(func (result f32) (f32.const nan:0xffffffff0))")
"constant out of range")

;; The payload `n` of a NaN literal must satisfy `1 <= n < 2^significand`, where
;; the significand is 52 bits for f64 and 23 bits for f32. Payloads that overflow
;; the significand must be rejected rather than silently masked.

;; f64: `0xf_ffff_ffff_ffff` (13 hex digits) is the maximum valid payload; one bit
;; more is out of range.
(module (func (result f64) f64.const nan:0xfffffffffffff))
(assert_malformed
(module quote "(func (result f64) (f64.const nan:0xffffffffffffff))")
"constant out of range")

;; f32: `0x7fffff` (23 bits) is the maximum valid payload; `0x800000` is out of
;; range.
(module (func (result f32) f32.const nan:0x7fffff))
(assert_malformed
(module quote "(func (result f32) (f32.const nan:0x800000))")
"constant out of range")

;; A zero payload is not a valid NaN (it would be an infinity).
(assert_malformed
(module quote "(func (result f64) (f64.const nan:0x0))")
"constant out of range")
61 changes: 61 additions & 0 deletions tests/cli/bad-reftype.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
;; RUN: wast --assert default --snapshot tests/snapshots %

;; A value type is `numtype | vectype | reftype`, and a reference type is either
;; an abstract heap type shorthand byte or the two-byte `0x64 heaptype` (`ref ht`)
;; / `0x63 heaptype` (`ref null ht`) forms. There is no bare type-index value
;; type, so a leading byte of `0x00` (a non-negative sLEB) is malformed.

;; A bare index used as a value type. In value-type position the error is
;; reclassified as "invalid value type".
(assert_malformed
(module binary
"\00asm" "\01\00\00\00" ;; module header

"\01\08\02" ;; type section, 2 types
"\60\00\00" ;; type 0: (func)
"\60\01" ;; type 1: (func (param ...)), 1 param
"\00" ;; param 0: bare type index 0x00 (invalid value type)
"\00" ;; 0 results
)
"invalid value type")

;; The same bug with an over-long LEB encoding of the bare index (`\ff\00` = 127).
;; The reftype discriminator is a single byte, so this must be rejected at decode
;; time rather than re-interpreted as a type index.
(assert_malformed
(module binary
"\00asm" "\01\00\00\00" ;; module header

"\01\09\02" ;; type section, 2 types
"\60\00\00" ;; type 0: (func)
"\60\01" ;; type 1: (func (param ...)), 1 param
"\ff\00" ;; param 0: bare index 0x00 with an over-long LEB
"\00" ;; 0 results
)
"invalid value type")

;; The same bare index in a reference-type-only position (a table's element type)
;; is reported as a malformed reference type.
(assert_malformed
(module binary
"\00asm" "\01\00\00\00" ;; module header

"\04\04\01" ;; table section, 1 table
"\00" ;; element type: bare index 0x00 (malformed reftype)
"\00\00" ;; limits: no max, minimum 0
)
"malformed reference type")

;; Boundary: the legitimate concrete reftypes `0x63 <idx>` (`ref null $t`) and
;; `0x64 <idx>` (`ref $t`), where the index is an sLEB after the prefix byte, must
;; still be accepted.
(module binary
"\00asm" "\01\00\00\00" ;; module header

"\01\0b\02" ;; type section, 2 types
"\60\00\00" ;; type 0: (func)
"\60\02" ;; type 1: (func (param ...)), 2 params, 0 results
"\63\00" ;; param 0: (ref null 0)
"\64\00" ;; param 1: (ref 0)
"\00" ;; 0 results
)
33 changes: 33 additions & 0 deletions tests/snapshots/cli/bad-nan.wast.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,39 @@
"filename": "bad-nan.2.wat",
"module_type": "text",
"text": "constant out of range"
},
{
"type": "module",
"line": 21,
"filename": "bad-nan.3.wasm",
"module_type": "binary"
},
{
"type": "assert_malformed",
"line": 23,
"filename": "bad-nan.4.wat",
"module_type": "text",
"text": "constant out of range"
},
{
"type": "module",
"line": 28,
"filename": "bad-nan.5.wasm",
"module_type": "binary"
},
{
"type": "assert_malformed",
"line": 30,
"filename": "bad-nan.6.wat",
"module_type": "text",
"text": "constant out of range"
},
{
"type": "assert_malformed",
"line": 35,
"filename": "bad-nan.7.wat",
"module_type": "text",
"text": "constant out of range"
}
]
}
6 changes: 6 additions & 0 deletions tests/snapshots/cli/bad-nan.wast/3.print
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(module
(type (;0;) (func (result f64)))
(func (;0;) (type 0) (result f64)
f64.const nan:0xfffffffffffff (;=NaN;)
)
)
6 changes: 6 additions & 0 deletions tests/snapshots/cli/bad-nan.wast/5.print
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(module
(type (;0;) (func (result f32)))
(func (;0;) (type 0) (result f32)
f32.const nan:0x7fffff (;=NaN;)
)
)
32 changes: 32 additions & 0 deletions tests/snapshots/cli/bad-reftype.wast.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"source_filename": "tests/cli/bad-reftype.wast",
"commands": [
{
"type": "assert_malformed",
"line": 11,
"filename": "bad-reftype.0.wasm",
"module_type": "binary",
"text": "invalid value type"
},
{
"type": "assert_malformed",
"line": 26,
"filename": "bad-reftype.1.wasm",
"module_type": "binary",
"text": "invalid value type"
},
{
"type": "assert_malformed",
"line": 40,
"filename": "bad-reftype.2.wasm",
"module_type": "binary",
"text": "malformed reference type"
},
{
"type": "module",
"line": 52,
"filename": "bad-reftype.3.wasm",
"module_type": "binary"
}
]
}
4 changes: 4 additions & 0 deletions tests/snapshots/cli/bad-reftype.wast/3.print
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(module
(type (;0;) (func))
(type (;1;) (func (param (ref null 0) (ref 0))))
)
Loading