Skip to content

Reject out-of-range NaN payloads and bare type-index value types#2563

Merged
alexcrichton merged 2 commits into
bytecodealliance:mainfrom
vouillon:fix-nan-payload-and-reftype-decode
Jul 10, 2026
Merged

Reject out-of-range NaN payloads and bare type-index value types#2563
alexcrichton merged 2 commits into
bytecodealliance:mainfrom
vouillon:fix-nan-payload-and-reftype-decode

Conversation

@vouillon

Copy link
Copy Markdown
Contributor

Fixes two cases where wasm-tools accepts malformed input that the spec grammar and the reference interpreter (wasm -d) reject.

  • Text parser silently masked over-long NaN payloads (wast)

    A NaN literal's payload n must satisfy 1 ≤ n < 2^signif_bits (52 bits for f64, 23 for f32). The parser masked the payload to the significand width instead of range-checking it, so an over-long payload was silently truncated and accepted. The spec testsuite asserts this (const.wast, float_literals.wast) but only with the value nan:0x80_0000 — which is
    exactly 2^23 and masks to 0, so the old buggy code rejected it by coincidence.

  • Binary decoder accepted a bare type index as a value type (wasmparser)

    The short form of a reference type (no 0x63/0x64 prefix) is only an abbreviation for ref null <abstract-heap-type>. The decoder instead read a full heap type there, so a bare non-negative type index was accepted as a value type: Now concrete/exact heap types reached through the short-form path are rejected with malformed reference type at decode time. This also covers the over-long LEB variant (ff 00).

@vouillon vouillon requested a review from a team as a code owner July 10, 2026 08:32
@vouillon vouillon requested review from alexcrichton and removed request for a team July 10, 2026 08:32
A NaN literal's payload `n` must satisfy `1 <= n < 2^signif_bits` (52 bits
for f64, 23 for f32). The parser masked the payload to the significand width
instead of range-checking it, so an over-long payload such as
`nan:0xffffffffffffff` (56 bits for f64) was silently truncated and accepted
rather than rejected as the reference interpreter does.

Range-check the payload against the significand width instead of masking.

The spec testsuite only exercised the power-of-two boundary `nan:0x80_0000`,
which the old masking rejected by coincidence (it masks to 0, hitting the
"payload is zero => infinity" check), so out-of-range payloads that mask to a
nonzero value went unnoticed. Regression tests cover both the invalid cases
and the adjacent valid boundaries.
@vouillon vouillon force-pushed the fix-nan-payload-and-reftype-decode branch from 715f590 to 4c40f51 Compare July 10, 2026 08:43

@alexcrichton alexcrichton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, and good catch! I've got some thoughts below on minor tweaks, but otherwise looks good to me 👍

Comment on lines +1834 to +1842
// 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, so reject concrete/exact heap types
// reached through this path even though they parse as heap types
// in their own right.
if let HeapType::Concrete(_) | HeapType::Exact(_) = hty {
return Err(crate::Error::new("malformed reference type", pos));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of catching the result of parsing here, could this directly parse AbstractHeapType above? That way there's no need to reclassify errors or recover from an otherwise-successful parse that should have failed. I think in that case this'll have to handle the 0x65 case for shared: true abstract heap types, but that's a pretty small amount of duplication

Comment thread tests/cli/bad-reftype.wast Outdated
;; type 0 = (func), type 1 = (func (param <0x00>)) — bare index used as a value type.
(assert_malformed
(module binary
"\00asm\01\00\00\00\01\08\02\60\00\00\60\01\00\00")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you be ok splitting this up like a number of other module binary tests throughout the test suite with annotations of what each byte or set of bytes are? For example annotations like this

@vouillon vouillon force-pushed the fix-nan-payload-and-reftype-decode branch from 4c40f51 to b004290 Compare July 10, 2026 15:09
The short form of a reference type (without a `0x63`/`0x64` prefix) is only an
abbreviation for `ref null <abstract-heap-type>`. The decoder instead parsed a
full heap type there, which additionally accepts a bare non-negative type
index, so `0x00` was wrongly decoded as the value type `(ref null 0)`.

Parse an abstract heap type directly in the short-form path (handling the
`0x65` prefix for `shared` variants) so a bare index is rejected at decode
time. This also covers the over-long-LEB variant (`ff 00`). In value-type
position the error surfaces as `invalid value type`; in a reference-type-only
position (e.g. a table element type) as `malformed reference type`. The
legitimate `0x63 <idx>` / `0x64 <idx>` concrete reftypes and the `shared`
abstract shorthands (`0x65 ...`) are unaffected.
@vouillon vouillon force-pushed the fix-nan-payload-and-reftype-decode branch from b004290 to 37f2233 Compare July 10, 2026 16:13
@alexcrichton alexcrichton added this pull request to the merge queue Jul 10, 2026
Merged via the queue into bytecodealliance:main with commit 6575033 Jul 10, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants