CVE-2024-44185
Overview
Background
- ref.as_non_null
- A WebAssembly typed-function-references instruction that converts a nullable reference into a non-nullable one, trapping if the value is null; it requires a reference-typed operand.
- Typed function references
- A Wasm proposal (gated by Options::useWasmTypedFunctionReferences) adding non-null and typed reference types, expanding the reference-type system beyond funcref/externref.
- Wasm validation
- The static type-checking pass over a module’s bytecode; a soundness gap here lets ill-typed operands reach compilation and execution.
- isRefType
- A predicate that returns whether a Wasm value type is a reference type, the exact check that was missing on the ref.as_non_null operand.
- WASM_VALIDATOR_FAIL_IF
- A macro that aborts validation with an error message when its condition holds, the mechanism used to reject the malformed instruction.
Root Cause Analysis
The patch adds a missing validation to the WebAssembly function parser/validator in Source/JavaScriptCore/wasm/WasmFunctionParser.h, in the handler for the ref.as_non_null instruction (part of the typed function references / GC proposal, gated by Options::useWasmTypedFunctionReferences). The handler pops one operand off the expression stack via WASM_TRY_POP_EXPRESSION_STACK_INTO(ref, …) and then calls addRefAsNonNull(ref, result), which is meant to take a nullable reference and produce its non-null form. The added line is WASM_VALIDATOR_FAIL_IF(!isRefType(ref.type()), "ref.as_non_null ref to type ", ref.type(), " expected a reference type"). The violated invariant is that ref.as_non_null must be applied only to a value of reference type; before the fix the parser accepted whatever type happened to be on top of the stack (e.g. a numeric type such as i32/i64/f32/f64/v128) and passed it to addRefAsNonNull, which assumes a reference operand. Feeding a non-reference value into reference-typed handling is a type confusion: downstream code (the compiler tiers and the runtime representation of the produced non-null reference) treats raw numeric bits as a pointer/reference, so the value’s type tag and its physical interpretation disagree. The added JSTests/wasm/stress/funcref-types.js ships a hand-crafted module that exercises this path (with the typed-function-references option enabled) and previously drove the engine into the unchecked ref.as_non_null, producing the unexpected process crash in the advisory.
The fix restores the invariant by making the validator reject any ref.as_non_null whose operand is not a reference type, failing module validation with a clear error before any code is generated or run.
Attack Path
- Require the feature flag Target environments where useWasmTypedFunctionReferences is enabled (the test uses //@ requireOptions(’–useWasmTypedFunctionReferences=1’)); reachability depends on that option being on.
- Craft a malformed module From JavaScript, build a WebAssembly.Module whose bytecode contains a ref.as_non_null instruction whose operand on the value stack is a non-reference (numeric) type rather than a nullable reference.
- Slip past validation Before the fix the parser only popped the operand and did not check isRefType, so the malformed module validated successfully and was compiled.
- Trigger type confusion Instantiate and call the exported function; addRefAsNonNull and the compiled code treat the numeric operand as a reference, so raw bits are interpreted as a pointer/reference, corrupting the typed value.
- Crash (established) / escalate (background) The commit establishes an unexpected WebContent crash. Standard background: a controllable type confusion between an integer and a GC/function reference is a strong lever for a fake-object/arbitrary-read-write primitive, but this diff does not demonstrate that reliability.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ref.as_non_null instruction handler (FunctionParser)Source/JavaScriptCore/wasm/WasmFunctionParser.h |
modified | Adds WASM_VALIDATOR_FAIL_IF(!isRefType(ref.type()), ...) so ref.as_non_null rejects a non-reference operand before addRefAsNonNull runs, closing a validation gap that allowed type-confused input. |
funcref-types stress testJSTests/wasm/stress/funcref-types.js |
added | New regression test with a crafted module (typed function references enabled) that previously reached the unchecked ref.as_non_null path. |
Audit Directions
- Audit sibling reference instructionsIn WasmFunctionParser.h review every typed-reference / GC opcode handler (ref.as_non_null, ref.cast, ref.test, br_on_null, br_on_non_null, ref.func, struct/array.get/new) for a matching isRefType or exact-type check before the addToContext call; grep WASM_TRY_POP_EXPRESSION_STACK_INTO followed by addRef / addStruct* / addArray* with no WASM_VALIDATOR_FAIL_IF in between.
- Grep for unchecked operand type useSearch WasmFunctionParser.h for handlers that call ref.type() or pass a popped TypedExpression into a context method without first validating isRefType / the expected type, focusing on opcodes added with the typed-function-references and GC proposals.
- Compare with the reference interpreterCross-reference each reference-type opcode’s validation rules against the Wasm typed-references/GC spec to find any WebKit handler whose static checks are weaker than the spec mandates.
- Fuzz under the feature flagsDifferentially fuzz WebKit Wasm validation with –useWasmTypedFunctionReferences=1 (and GC flags) using type-scrambled operands on reference opcodes to surface further accept-then-confuse gaps.