← All issues

[10] Wasm nonnullable reference type confusion at JS-to-Wasm boundary

Severity: Medium | Component: JSC WebAssembly | 2d64f53

관측 가능한 효과가 type system invariant 위반(nonnullable reference에 null이 유입됨)이며, regression test를 통해 confidence 0.92로 확인되었기 때문에 Medium으로 평가되었습니다. 즉각적인 결과는 null dereference입니다. 대형 offset의 struct.get을 통한 controlled memory access로의 확장은 예측되지만, Wasm 컴파일러가 nonnullable 타입에 대해 null check를 실제로 생략하는지 여부는 검증되지 않은 전제에 의존합니다.

nonnullable reference 타입에 대한 JS-to-Wasm return boundary에서 null check가 추가되었습니다. 총 네 가지 marshalling path — C++ slow path 세 곳과 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)) {
// 아무 작업 없음.
} 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"
+ )

네 곳의 code path가 수정되었습니다. operationWasmToJSExitMarshalReturnValues(single-return 및 multi-return 분기 모두)와 operationIterateResults에서는 타입별 변환 이전에 value.isNull() && !returnType.isNullable() 검사가 추가되었으며, 위반 시 TypeError가 발생합니다. JIT-compiled wasmToJS stub에서는 return 타입이 nonnullable인 경우 ExceptionType::TypeErrorUnexpectedNullReference를 던지는 branchIfNotNull 가드가 생성되도록 변경되었습니다. 아울러 WasmExceptionType.h에 새로운 exception 타입이 추가되었습니다.

WebAssembly type system은 nullable reference 타입(ref null T)과 nonnullable 타입(ref T)을 구분합니다. Nullable reference는 null 값을 허용하지만, nonnullable reference는 항상 유효한 reference임을 보장합니다. 엔진은 type system이 non-nullness를 보장한다는 전제 하에, nonnullable reference에 적용되는 연산에서 null check를 생략하는 경우가 있습니다. 이는 정확성과 성능을 함께 고려한 최적화입니다.

Wasm 모듈이 JS 함수를 import하면, 해당 함수 호출은 Wasm-JS 경계를 넘나들게 됩니다. 반환 값은 JS 값에서 선언된 Wasm return 타입으로 변환되어야 합니다. 이 marshalling 과정은 fast path에서는 JIT-compiled stub(wasmToJS)에서, 복잡한 경우에는 C++ slow-path operation에서 처리됩니다. (ref func) 타입은 function-references proposal에서 도입된 nonnullable function reference로, nullable 타입인 funcref((ref null func))와는 구별됩니다.

JS-to-Wasm return boundary에서 nonnullable reference 타입에 대한 nullability 검증 누락.

패치 이전에는 JS host 함수가 nonnullable reference 타입을 기대하는 Wasm 코드에 값을 반환할 때 null check가 수행되지 않았습니다. 반환 값은 디코딩된 후 타입별 변환(funcref 검증, anyref 내부화, externref passthrough)을 거쳤지만, nonnullability 제약 조건은 전혀 적용되지 않았습니다. 결과적으로 JS 함수가 nonnullable return 타입으로 선언된 Wasm import에 null을 반환하는 것이 가능했습니다. 이 null 값은 type system 상 reference가 항상 non-null임을 전제하는 Wasm 코드로 전파되었습니다. nonnullable reference를 다루는 하위 Wasm 명령어(예: call_ref, struct.get, ref.test, ref.cast)는 자체 null guard를 생략하는 경우가 있습니다. type system이 이미 non-nullness를 보장한다고 전제하기 때문입니다.

🔒

Explores whether this type system violation can be escalated beyond a simple crash under realistic conditions

더 확인하려면 구독해 주세요

🔒

Multiple reusable audit patterns identified, with concrete starting points for variant discovery across JS-Wasm boundary code

더 확인하려면 구독해 주세요