[10] Wasm nonnullable reference type confusion at JS-to-Wasm boundary
Severity: Medium | Component: JSC WebAssembly | 2d64f53
Rated Medium because the observable effect is a type system invariant violation (null smuggled into nonnullable reference) confirmed at confidence 0.92 by the regression test, with the immediate consequence being a null dereference — escalation to controlled memory access via struct.get at large offsets is projected but depends on unverified claims about the Wasm compiler eliding null checks for nonnullable types.
Adds null checks at the JS-to-Wasm return boundary for nonnullable reference types across all four marshalling paths: three C++ slow paths and one JIT-compiled stub.
Source/JavaScriptCore/wasm/WasmOperations.cpp
default: {
if (Wasm::isRefType(returnType)) {
+ JSValue value = JSValue::decode(std::bit_cast<EncodedJSValue>(returned));
+ if (value.isNull() && !returnType.isNullable()) [[unlikely]] {
+ throwTypeError(globalObject, scope, "Host function incorrectly returned null for a nonnullable reference type"_s);
+ OPERATION_RETURN(scope);
+ }
+
if (Wasm::isExternref(returnType)) {
// Do nothing.
} else if (Wasm::isFuncref(returnType)) {
// operationConvertToFuncref
- JSValue value = JSValue::decode(std::bit_cast<EncodedJSValue>(returned));
Source/JavaScriptCore/wasm/js/WasmToJS.cpp
default: {
if (Wasm::isRefType(returnType)) {
+ if (!returnType.isNullable()) {
+ auto isNotNull = jit.branchIfNotNull(JSRInfo::returnValueJSR);
+ jit.move(GPRInfo::wasmContextInstancePointer, GPRInfo::argumentGPR0);
+ emitThrowWasmToJSException(jit, GPRInfo::argumentGPR0, ExceptionType::TypeErrorUnexpectedNullReference);
+ isNotNull.link(&jit);
+ }
JSTests/wasm/function-references/nullability.js
+ let instance = new WebAssembly.Instance(module("..."), {
+ env: {
+ getFunc: () => null
+ }
+ });
+ assert.throws(
+ () => {
+ for (let i = 0; i < 1000; i++) {
+ instance.exports.callGetFunc();
+ }
+ },
+ TypeError,
+ "Host function incorrectly returned null for a nonnullable reference type"
+ )
Patch Details
Four code paths are patched. In operationWasmToJSExitMarshalReturnValues (both single-return and multi-return branches) and operationIterateResults, a value.isNull() && !returnType.isNullable() check is added before type-specific conversion, throwing TypeError on violation. In the JIT-compiled wasmToJS stub, a branchIfNotNull guard is emitted that throws ExceptionType::TypeErrorUnexpectedNullReference when the return type is nonnullable. A new exception type is added to WasmExceptionType.h.
Background
The WebAssembly type system distinguishes nullable reference types (ref null T) from nonnullable ones (ref T). Nullable references permit null; nonnullable references guarantee the value is always a valid reference. The engine may omit null checks on operations applied to nonnullable references because the type system guarantees non-nullness — this is a correctness and performance optimization.
When a Wasm module imports a JS function, calls to that function cross the Wasm-JS boundary. The return value must be converted from a JS value back into the declared Wasm return type. This marshalling occurs in JIT-compiled stubs (wasmToJS) for the fast path and in C++ slow-path operations for complex cases. The (ref func) type is a nonnullable function reference introduced by the function-references proposal, distinct from funcref which is (ref null func) — nullable.
Analysis
Missing nullability enforcement at the JS-to-Wasm return boundary for nonnullable reference types.
Before the fix, no null check was performed when a JS host function returned a value to Wasm code expecting a nonnullable reference type. The return value was decoded and passed through type-specific conversion (funcref validation, anyref internalization, externref passthrough) but the nonnullability constraint was never enforced. This allowed a JS function to return null for a Wasm import declared with a nonnullable return type. The null value then propagated into Wasm code that, per the type system, assumes the reference is guaranteed non-null. Downstream Wasm instructions operating on nonnullable references (e.g., call_ref, struct.get, ref.test, ref.cast) may omit their own null guards because the type system already guarantees non-nullness.
Aaa Aaaa Aaaa Aaaaaaaaaa a Aaaa Aaaaaa Aaaaaaaaa a Aa Aaaaaaaa Aaaaaaaa Aa Aaaaaa Aaaaa Aaaaaaa Aaaaa Aaa Aa Aaaaaaaa Aaaaaaa Aaaaaaa Aaaaaa Aaa Aaaa Aaa Aaaa Aaaaaa Aaaaaaa Aaaaaaaaaa Aaaaa Aaa Aaaa Aa Aaaaaa Aaaaaaaaaaaa
Aaa Aaaaaa Aaaaaaaaaa Aaaaaaaaaaaa Aaaaaaaaaaaa a Aaaaaa Aaaa Aaaaa Aaaaaa Aaaaaa Aaa Aaaaaaaaaaaa Aaaaa Aaaaaa Aa a Aaaaaaaaaaa Aaaaaa Aaaa Aaaaa Aaaa Aaaaa Aaaa Aaaa a Aaa Aaa Aaaaaaa Aaaaaaaa Aa Aaa Aaaa Aaaaaaaa Aaaaaa Aaaa Aaaaaa Aaaaaa Aaaaaaaaaaaa Aa Aaaaa Aaaaa Aaaaaaa Aa Aaaaaaaaaa Aaaaaaaaa Aaaa Aaaaaaa Aaaaaa Aaa Aaaaaaaaaaaa Aaaaaaaa Aaaaaa a Aaaaaa Aaaaa a Aaaaaa Aaaa Aaaaaaa Aa Aaaaaaaa Aaaaaaaaa Aaaaa Aaaaaa Aaaa Aaa Aa Aaaaaaa Aaa Aaaa Aaaaaaaa Aaaaaaaa Aaaaaaaa Aaaaa Aaaaaaaa Aaaaaaaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaa Aaaa Aaaa Aaaaaaaa Aaaaaa Aaaaaa Aaaaaaaaaaa Aaa Aaaa Aaaa Aaaaaaaa Aaaa Aaaaaaaaaaa Aaaaaaaaa Aaaaa Aaa Aaaaa Aaaa Aaaaa Aaa Aaa Aaaaaa Aaaaaa Aa Aaaa Aaaaaaaaa Aaa Aaaaaaaaaaaaa Aa Aaaaaaaaa Aaaa Aaaa Aaa Aaaaaaaaaa Aaaaaaaaa Aa Aaaaaaaa Aaaaaaaa Aaaa Aaaaaaaaaa Aaa Aaa Aaaaaaa Aaaa Aaaaaaaa Aaaa Aaaaa Aaaa Aaa Aaa Aaa Aaaa Aaaaaaaa a Aaaaaaaaaa Aaa Aaaaaaaaaaa Aaaaa Aaa Aaaaa Aaaa Aa Aaa Aaaaaaaa Aaaaaaaaaaa Aaaaaa Aaa Aaa Aaaaaaaaaaaaaaaaaaa Aaaaaaaaa
Aa Aaaa Aa Aaaaa Aaaaaaaaa Aaaa Aaaaaaaaaaa Aaaaaaaaa Aaaaa Aaaaaaaaa Aaaaaaaa Aaaa Aaaaa Aa Aaaaaaaa Aaaaaaaaa Aaaaaaa Aaaaaaaaaaaa Aaaaaaaaa Aaaaaaa Aaa Aaaaaa Aaa Aaaa Aaaaaaaaaaaa Aaaaaaaa Aaaaa Aa Aaaaaaaaaaaaaa Aaaaaaaaaaa
🔒Explores whether this type system violation can be escalated beyond a simple crash under realistic conditions
Subscribe to read more
Audit directions
a Aaaaaaaa Aaaa Aaaa Aaaaaaaaaa Aaaaaaa Aaa Aaa Aaaaaaaa Aa Aaaaaaa Aaaaaaaaaaa Aaa Aaaa Aaaa Aaaaaa Aaaaaaaaaa Aaaaaaaaaa Aaaaaaaaaaaaa Aaaaaaaaaa Aaaaa Aaaaa Aaaa Aa Aaaaaa Aaaaaaa Aaaaa Aaa Aaaaaaaaaa Aaaaaaaaaaa Aaaaa a Aaaaaaaaa Aaaaaaa Aaa Aaaaaaa Aaaaaaaa Aaaaaa Aaaaaa Aaaaa Aaaaaaaaaa Aaaaa Aaaaaaa Aaaaaaaa Aaaa Aaa Aaaaaaaaaaaa Aaaaaaaaa Aaaa Aa a Aaa Aaaaaaa Aaaa Aaaaaaaaaa Aaaaaaaaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaa Aaa Aaaaaaa Aaaaaaaaaa Aaaaaaaaaa Aaaa Aaa Aaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaa Aa Aaaa Aaa Aaaaaaaaaaaaaa Aaaaaaaaaaa Aaaaaa
a Aaaaaaaa Aaaaaaaaaaaaa Aaaaaaaaaa Aa Aaaaaaaaaaa Aaaaa Aaaa Aaaaaa Aaaaaaa Aa Aaaa Aaaaa Aaa Aaaaa Aaa Aaaa Aaaaaaaa Aaaaa Aaaaa Aaaaaaa Aaa Aaaaaaaaaa Aaaaaaaaaaa Aaaaa Aa Aaaaaaaaaaa Aaaaaaaaa Aaaaaa Aa Aaaa Aa Aaaaaaaaaaaaa Aaaaaa Aaa Aaaaaaaa Aa Aaaa Aaaa Aaaaaa Aaaaaa Aaaaaaaaaaaaa Aaaaaaaaaaaa Aa Aaaaaaaaaaa Aaaa Aaa Aaaa Aaaa Aaaaaaaa Aaaa Aaaa Aaa Aaaa Aaaaaaaa Aaaaaaaa a Aaaa Aaaaa Aaaa a Aaaaaaaaaaaa Aaaaaaaaaaa Aa Aa Aaaaaaaaaaaaaaa Aaaaaaa Aaaa Aaa Aaaaaaaaaaaa Aa Aaa Aaaa Aaaaaaaa Aaaaaaaa Aaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaa Aa Aaaa Aaaaa Aaaaa Aaaaaaaaaaaaaa Aaaaaa Aaaa Aaaaaaaaaa Aaaaaaaaaa
a Aaaaaaaa Aaaaaaaa Aaaa Aaaaa Aaaaaaaaaaaa Aaa Aaaa Aaaaaaaaaaa Aaaaa Aaaa Aaaaaaaaaaaa Aaaaaaa Aaaa Aaaaaa Aaaaaaa Aaaa Aaaaaaaa Aaaaaaaaa Aaaa Aaa Aaaaaa Aaa Aaaa Aaaa Aaaaaa Aaaaa Aaaaaaa Aaaaa Aaaa Aaaa Aaaaaaaaaaa Aaaaaaaa Aaaaaaa Aaa Aaaaa Aaaaaaa Aaaaa Aaaaaaa Aaa Aaaaaaaaaaaa Aaaaaaaa Aaaaaa Aaa Aaaaaaaaaaa Aaaaaa Aaaaaaa Aaa Aaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaa Aaa Aaa Aaaaa Aaaaaaaaaaaa
🔒Multiple reusable audit patterns identified, with concrete starting points for variant discovery across JS-Wasm boundary code
Subscribe to read more