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 a Type with type_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 index
ParseValueType 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.
Summary
The WAT text parser aborts with an assertion failure when a GC
structorarrayfield has a typed reference element type ((ref N)/(ref null N)).WastParser::ParseFieldbuilds the field'sTypewith the single-argumentType(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 linkinglibwabt.abort()→ denial of service (CWE-617, reachable assertion).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
The same inputs also crash
wast2json --enable-all.Required feature flags
Both
--enable-gc(forstruct/array) and--enable-function-references(for the(ref N)syntax) are required.What does / does not trigger it
(module (type ...))(struct (field (ref 0)))(array (ref 0))(struct (field (mut (ref 0))))(struct (field (ref 0)))(struct (field i32))(struct (field funcref))(non-indexed ref)Only indexed typed references crash; non-reference and non-indexed reference types are fine.
Affected tools / functions
libwabt—WastParser::ParseField(reached byParseWatModuleandParseWastScript). Tools:wat2wasm,wast2json,spectest-interp.Root cause
src/wast-parser.cc,WastParser::ParseField()→ lambdaparse_mut_valuetype(lines ~1785–1792):ParseValueTypemay yield a typed reference (Type::Ref/Type::RefNull) whoseopt_type()IsReferenceWithIndex(). Passing it to the single-arg constructor triggers the assert ininclude/wabt/type.h:74:The sibling code in the same file already handles this correctly with the two-argument constructor:
So
ParseFieldis 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:
(applied to both occurrences), or, better, resolve the parsed
Varto a fully-typed reference the way other call sites do (VarToType(...), used for table element types), so that(ref N)keeps its actual target indexN. The minimal change stops the crash; theVarToType-based change additionally fixes the silent index loss in release builds.Discovery
Found via libFuzzer + ASAN/UBSAN with a custom
wast2jsonharness mirroringsrc/tools/wast2json.cc.