Skip to content

wat2wasm/wast2json: assertion failure (!IsReferenceWithIndex()) parsing a typed reference in a GC struct/array field #2750

Description

@yejunkim2000

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

libwabtWastParser::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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions