wat2wasm/wast2json: assertion failure (!IsReferenceWithIndex()) parsing a typed reference in a GC struct/array field
Summary
The WAT text parser aborts with an assertion failure when a GC struct or array field has a typed reference element type ((ref N) / (ref null N)). WastParser::ParseField builds the field's Type with the single-argument Type(Type::Enum) constructor, which discards the type index and asserts !IsReferenceWithIndex().
This is reachable from any tool/library entry point that parses WebAssembly text via ParseWatModule / ParseWastScript: wat2wasm, wast2json, spectest-interp, and any application linking libwabt.
- Debug/assert builds:
abort()→ denial of service (CWE-617, reachable assertion). - Release (
NDEBUG) builds: the assertion is compiled out and the field silently gets aTypewithtype_index_ = 0— the referenced type index is lost (incorrect parse result / type confusion).
Tested on main @ 03a00a1, Ubuntu 24.04, clang 18.1.3.
Steps to reproduce
$ echo '(module (type (struct (field (ref 0)))))' | wat2wasm --enable-gc --enable-function-references -
wat2wasm: .../include/wabt/type.h:75: wabt::Type::Type(wabt::Type::Enum): Assertion `!IsReferenceWithIndex()' failed.
Aborted (core dumped)
$ echo '(module (type (array (ref 0))))' | wat2wasm --enable-gc --enable-function-references -
Aborted (core dumped)The same inputs also crash wast2json --enable-all.
Required feature flags
Both --enable-gc (for struct/array) and --enable-function-references (for the (ref N) syntax) are required.
What does / does not trigger it
Input inside (module (type ...)) |
flags | result |
|---|---|---|
(struct (field (ref 0))) |
gc + function-references | abort |
(array (ref 0)) |
gc + function-references | abort |
(struct (field (mut (ref 0)))) |
gc + function-references | abort |
(struct (field (ref 0))) |
gc only | clean parse error (ok) |
(struct (field i32)) |
gc + function-references | ok |
(struct (field funcref)) (non-indexed ref) |
gc + function-references | ok |
Only indexed typed references crash; non-reference and non-indexed reference types are fine.
Affected tools / functions
libwabt — WastParser::ParseField (reached by ParseWatModule and ParseWastScript). Tools: wat2wasm, wast2json, spectest-interp.
Root cause
src/wast-parser.cc, WastParser::ParseField() → lambda parse_mut_valuetype (lines ~1785–1792):
Var type;
CHECK_RESULT(ParseValueType(&type));
field->type = Type(type.opt_type()); // single-arg ctor drops the type indexParseValueType may yield a typed reference (Type::Ref / Type::RefNull) whose opt_type() IsReferenceWithIndex(). Passing it to the single-arg constructor triggers the assert in include/wabt/type.h:74:
Type(Enum e) : enum_(e), type_index_(0) { assert(!IsReferenceWithIndex()); }The sibling code in the same file already handles this correctly with the two-argument constructor:
// src/wast-parser.cc:1031 (ParseValueTypeList)
out_type_list->push_back(Type(type.opt_type(), kInvalidIndex));So ParseField is simply missing the index argument that the rest of the parser uses.
Suggested fix
Preserve the reference's type index instead of dropping it — either mirror line 1031:
- field->type = Type(type.opt_type());
+ field->type = Type(type.opt_type(), kInvalidIndex);(applied to both occurrences), or, better, resolve the parsed Var to a fully-typed reference the way other call sites do (VarToType(...), used for table element types), so that (ref N) keeps its actual target index N. The minimal change stops the crash; the VarToType-based change additionally fixes the silent index loss in release builds.
Discovery
Found via libFuzzer + ASAN/UBSAN with a custom wast2json harness mirroring src/tools/wast2json.cc.
Source: WebAssembly/wabt