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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions