High patch-now ~ plausible CVSS 8.1 chrome Type Confusion CISA KEV ⚠️ Exploited in the wild Exploitability: High (assessed) 📄 Reporter bug report 🔧 Commit mapped 🧬 TDZ hole-check elided across a branch

Overview

High
Severity
8.1
CVSS
Yes
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Sub-componentInterpreter
Bug ClassType Confusion
ExploitabilityHigh (assessed by PatchGap · confirmed)
Chain-wide hole-check elision across an optional chain's short-circuit edges lets a TDZ binding be read as the_hole sentinel, exposing an internal value to JS for type confusion. ITW, so High.
PatchGap's own analysis from the fixing commit, not a vendor rating. Absence of this line means not yet assessed, not "not exploitable".
Action tierpatch-now — exploited in the wild / KEV (itw) — real-world evidence overrides analysis — upheld by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIPlausible
Reviewer notes: Diff+title confirm hole-check elision was wrongly scoped over the whole optional chain (the_hole leak -> type confusion). But analysis states the 'earlier links dominate later, linearly' premise 'is false'; the fix retains that exact comment, so that sub-claim is unsupported.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker427663123
Fix commit22e9d9621de5 (v8/v8) +5/-4
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
CISA KEVListed
CreditedClément Lecigne of Google's Threat Analysis Group
Reported2025-06-25 · bounty NA (per vendor advisory)
Fix landed2025-06-26 (public git)
Disclosed2025-06-30
Patch gap4 days — fix public in source before the advisory

Background

Optional chaining (`?.`)
a JS operator that short-circuits an expression, jumping past the remaining links to yield undefined when a link evaluates to null/undefined.
Hole check
a runtime guard the interpreter emits before reading a let/const binding, verifying it is not the internal the_hole sentinel (i.e., not in its Temporal Dead Zone).
`HoleCheckElisionScope`
a bytecode-generator scope that suppresses redundant hole checks, valid only where an earlier check provably dominates the later access.
Type confusion
treating a value of one representation as another; here the the_hole sentinel leaking into JS-visible values, which downstream code assumes can never occur.

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: Diff+title confirm hole-check elision was wrongly scoped over the whole optional chain (the_hole leak -> type confusion). But analysis states the 'earlier links dominate later, linearly' premise 'is false'; the fix retains that exact comment, so that sub-claim is unsupported.

BytecodeGenerator::BuildOptionalChain created a single HoleCheckElisionScope elider spanning the entire optional-chain expression, on the premise that “links earlier in the chain dominate later links, linearly.” That premise is false: an optional chain short-circuits, so when an early link is nullish, control jumps to the null label (label_scope) and skips the intervening code — meaning a hole check emitted in an earlier link does not actually dominate accesses in later links. Because the elision scope covered the whole chain regardless of these short-circuit edges, a hole check for a TDZ (let/const) binding accessed inside the chain could be wrongly elided. The binding could then be read while still holding the the_hole sentinel without any guard firing, exposing that internal value to JavaScript.

The fix removes the chain-wide elider from BuildOptionalChain and instead makes HoleCheckElisionScope hole_check_scope_ a member of OptionalChainNullLabelScope, so the elision region is entered and torn down in lockstep with the null-label scope. This binds hole-check elision to exactly the control-flow region the null labels model, preventing elision from crossing the short-circuit boundaries of the chain.

Key insight
Optional-chain links do not linearly dominate one another because any link can short-circuit to the shared null label; treating the chain as a single dominated region let the interpreter elide a hole check that a short-circuit path bypasses, surfacing the the_hole sentinel as a real value.

Attack Path

  1. Craft a chain over a TDZ binding Construct an optional chain that references a let/const binding whose hole check the generator will consider elidable across links.
  2. Force short-circuit control flow Arrange the chain so a link is nullish, causing a jump to the null label that bypasses the access the earlier (elided) check was assumed to dominate.
  3. Read the hole The access executes without a hole check and returns the the_hole sentinel instead of throwing a ReferenceError.
  4. Trigger type confusion Feed the leaked the_hole value into operations that assume a well-typed object, corrupting engine invariants.
  5. Escalate Leverage the confusion for out-of-bounds/arbitrary read-write and ultimately code execution in the renderer.

Impact Assessment

This is a V8 type-confusion flaw that lets attacker-controlled JavaScript observe the internal the_hole sentinel, undermining type safety and enabling memory corruption leading to remote code execution in the renderer sandbox. It was exploited in the wild as a 0-day (CVE-2025-6554, high severity), so a single malicious web page could compromise a visiting browser. The bug requires no special privileges beyond loading crafted script.

Changed Functions

FunctionChangeNotes
hole_check_scope_
src/interpreter/bytecode-generator.cc
modified

Files Changed

  • src/interpreter/bytecode-generator.cc

Audit Directions

  • Elision-scope vs. control-flow mismatch
    Audit every HoleCheckElisionScope (and analogous elision optimizations) for regions where short-circuit jumps, break/continue, exception edges, or label binds break the assumed domination relationship.
  • Optional-chain and short-circuit constructs
    Review BuildOptionalChain, logical &&/||/??, and conditional expressions that mix jump-to-label semantics with TDZ (let/const) accesses for elided hole checks.
  • Sentinel leakage
    Search for paths where the_hole (or other internal sentinels) can reach JS-visible values because a guard was optimized away, and verify a hole check remains on every non-dominated access.
From 22e9d9621de58ec6fe6581b56215059a48451b9f Mon Sep 17 00:00:00 2001
From: Stephen Roettger <sroettger@google.com>
Date: Thu, 26 Jun 2025 10:33:16 +0200
Subject: [PATCH] [interpreter] don't elide hole checks across optional chain

Bug: 427663123
Change-Id: Iefdb15828d807bf9452b88e918a4b46cc2d422fa
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6678591
Commit-Queue: Stephen Röttger <sroettger@google.com>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/main@{#101050}
---

diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
index 921a8d0..1b8966c9 100644
--- a/src/interpreter/bytecode-generator.cc
+++ b/src/interpreter/bytecode-generator.cc
@@ -1221,7 +1221,8 @@
  public:
   explicit OptionalChainNullLabelScope(BytecodeGenerator* bytecode_generator)
       : bytecode_generator_(bytecode_generator),
-        labels_(bytecode_generator->zone()) {
+        labels_(bytecode_generator->zone()),
+        hole_check_scope_(bytecode_generator) {
     prev_ = bytecode_generator_->optional_chaining_null_labels_;
     bytecode_generator_->optional_chaining_null_labels_ = &labels_;
   }
@@ -1236,6 +1237,9 @@
   BytecodeGenerator* bytecode_generator_;
   BytecodeLabels labels_;
   BytecodeLabels* prev_;
+  // Use the same scope for the entire optional chain, as links earlier in the
+  // chain dominate later links, linearly.
+  HoleCheckElisionScope hole_check_scope_;
 };
 
 // LoopScope delimits the scope of {loop}, from its header to its final jump.
@@ -6483,9 +6487,6 @@
 void BytecodeGenerator::BuildOptionalChain(ExpressionFunc expression_func) {
   BytecodeLabel done;
   OptionalChainNullLabelScope label_scope(this);
-  // Use the same scope for the entire optional chain, as links earlier in the
-  // chain dominate later links, linearly.
-  HoleCheckElisionScope elider(this);
   expression_func();
   builder()->Jump(&done);
   label_scope.labels()->Bind(builder());
Loading diff…

Original Bug Report

reported by cl...@google.com

[0-day] delete construct leaking TheHole

NOTE: We have evidence that the following bug is being used in the wild. Therefore, this bug is subject to a 7 day disclosure deadline

VULNERABILITY DETAILS

Context shared with saelo@ and tsuro@. The following construct is leaking the_hole value which, combined with a typer bug can lead to arbitrary r/w within the v8 heap sandbox.

delete {}.R?.O[{}.a = f];
%DebugPrint(f);
let f = {};

VERSION Chrome Version: HEAD + stable Operating System: all

REPRODUCTION CASE The attached p.js triggers a crash in PushStackTraceAndDie. Samuel and Stephen are working on a reproducer triggering a memory corruption.

CREDIT INFORMATION Reporter credit: Clement Lecigne of Google’s Threat Analysis Group

View on issue tracker
Uploaded PoC files