High patch-now ✔︎ verified CVSS 9.8 chrome Type Confusion CISA KEV ⚠️ Exploited in the wild Exploitability: High (assessed) 🔧 Commit mapped 🧬 Architecture codegen violates zero-extension and ABI invariants

Overview

High
Severity
9.8
CVSS
Yes
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Sub-componentCompiler
Bug ClassType Confusion
ExploitabilityHigh (assessed by PatchGap · confirmed)
Arm64 int32 mul-overflow leaves dirty high bits treated as zero-extended, so JIT consumes a garbage 64-bit value yielding type confusion. Exploited in the wild, 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 AIConfirmed
Reviewer notes: Diff shows an Arm64 codegen miscompile (kSignedMul upper bits not zeroed); memory corruption/type-confusion is inferred from the advisory, not directly provable from the diff. Bug is Arm64-specific, as the analysis notes.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker445380761
Fix commitec6c18478382 (v8/v8) +13/-3
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVListed
CreditedGoogle Threat Analysis Group
Reported2025-09-16 · bounty NA (per vendor advisory)
Fix landed2025-09-16 (public git)
Disclosed2025-09-17
Patch gap1 day — fix public in source before the advisory

Background

`ZeroExtendsWord32ToWord64NoPhis`
an Arm64 instruction-selector helper that tells the compiler whether an operation’s 32-bit result already has its upper 32 bits guaranteed to be zero in the 64-bit register, so a redundant zero-extension can be elided.
`OverflowCheckedBinopOp`
a Turboshaft IR node for arithmetic that also produces an overflow flag, with kinds kSignedAdd, kSignedSub, and kSignedMul and a width given by rep.
`Int32MulWithOverflow` on Arm64
because Arm64 has no flag-setting 32-bit multiply, the 32-bit checked multiply is lowered to a 64-bit multiplication whose overflow is detected by comparing result.X() (full 64-bit) against result.W() (sign-extended low 32-bit).

Root Cause Analysis

Before the patch, ZeroExtendsWord32ToWord64NoPhis returned true for every OverflowCheckedBinopOp whose rep was WordRepresentation::Word32(), regardless of the operation’s kind. This is correct for kSignedAdd and kSignedSub, whose Arm64 lowerings genuinely leave the upper 32 bits zeroed, but it is wrong for kSignedMul. On Arm64 the 32-bit checked multiplication is emitted as a 64-bit multiply, so the destination register can hold nonzero high bits (the full 64-bit product) rather than a clean zero-extended 32-bit value. The compiler therefore skipped a needed zero-extension and treated a dirty 64-bit register as if its top half were zero. Downstream code that consumes the value as a full 64-bit quantity then operates on garbage upper bits, producing an incorrect integer that breaks the engine’s numeric assumptions and enables type confusion.
Key insight
The zero-extension guarantee depends not just on the result width (Word32) but on how the specific operation is lowered on the target; Arm64’s flag-setting-multiply workaround silently violated the zero-extension invariant that kSignedAdd/kSignedSub satisfy.

Attack Path

  1. Trigger the multiply Craft JavaScript that JIT-compiles to an Int32MulWithOverflow (OverflowCheckedBinopOp of kind kSignedMul) on an Arm64 target.
  2. Elide the zero-extension The buggy ZeroExtendsWord32ToWord64NoPhis reports the 64-bit multiply result as already zero-extended, so the compiler omits the extension and leaves nonzero upper bits.
  3. Corrupt the computed value A subsequent 64-bit use of the result reads the polluted high half, yielding an integer that diverges from what the engine’s type/range analysis expects.
  4. Achieve type confusion The mismatched value violates invariants (e.g., length/index or object-shape assumptions), letting an attacker force the engine to treat memory as the wrong type.
  5. Escalate The confusion is leveraged for out-of-bounds access and ultimately memory read/write and code execution.

Impact Assessment

This is a high-severity V8 type-confusion flaw on Arm64 that lets attacker-controlled script corrupt an integer computation and violate the engine’s type invariants, a primitive that typically leads to out-of-bounds access and remote code execution in the renderer. It was exploited in the wild as a 0-day, so working exploit chains against Arm64 Chrome existed before the fix landed.

Changed Functions

FunctionChangeNotes
switch
src/compiler/backend/arm64/instruction-selector-arm64.cc
modified

Files Changed

  • src/compiler/backend/arm64/instruction-selector-arm64.cc

Audit Directions

  • Width-based zero-extension assumptions
    Audit every path where a Word32 result is assumed to be zero-extended to Word64; verify the guarantee holds for the specific opcode kind and its actual target lowering, not just the representation.
  • Target-specific arithmetic lowerings
    Review Arm64 (and other backend) emitters like EmitInt32MulWithOverflow where a 32-bit op is implemented with a wider instruction, and confirm the selector’s assumptions about clean upper bits match the emitted code.
  • Overflow-checked binops in general
    Cross-check kSignedAdd, kSignedSub, and kSignedMul handling across backends for other invariants (flags, sign vs. zero extension) that may hold for some kinds but not others.
From ec6c184783824fa8f974013aae28a45c36c0112f Mon Sep 17 00:00:00 2001
From: Darius Mercadier <[email protected]>
Date: Tue, 16 Sep 2025 16:40:24 +0200
Subject: [PATCH] [compiler] Don't assume that upper 32-bit of Int32MulOvfCheck are 0

Because Arm64 doesn't have a flag-setting 32-bit multiplication,
which means that instead with use a 64-bit multiplication, and compare
result.X() and result.W() to check if an overflow happened. But this
leads to the upper 32-bit not being zeroed.

Fixed: 445380761
Change-Id: I31287faf37dc615695047021324e9d1d802cbec2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6954290
Auto-Submit: Darius Mercadier <[email protected]>
Commit-Queue: Leszek Swirski <[email protected]>
Reviewed-by: Leszek Swirski <[email protected]>
Cr-Commit-Position: refs/heads/main@{#102530}
---

diff --git a/src/compiler/backend/arm64/instruction-selector-arm64.cc b/src/compiler/backend/arm64/instruction-selector-arm64.cc
index 2b6d584..0aad1ef 100644
--- a/src/compiler/backend/arm64/instruction-selector-arm64.cc
+++ b/src/compiler/backend/arm64/instruction-selector-arm64.cc
@@ -2973,9 +2973,19 @@
       return op.Cast<ShiftOp>().rep == WordRepresentation::Word32();
     case Opcode::kComparison:
       return op.Cast<ComparisonOp>().rep == RegisterRepresentation::Word32();
-    case Opcode::kOverflowCheckedBinop:
-      return op.Cast<OverflowCheckedBinopOp>().rep ==
-             WordRepresentation::Word32();
+    case Opcode::kOverflowCheckedBinop: {
+      const OverflowCheckedBinopOp& binop = op.Cast<OverflowCheckedBinopOp>();
+      if (binop.rep != WordRepresentation::Word32()) return false;
+      switch (binop.kind) {
+        case OverflowCheckedBinopOp::Kind::kSignedAdd:
+        case OverflowCheckedBinopOp::Kind::kSignedSub:
+          return true;
+        case OverflowCheckedBinopOp::Kind::kSignedMul:
+          // EmitInt32MulWithOverflow doesn't zero-extend because Arm64 doesn't
+          // have a flag-setting int32 multiplication.
+          return false;
+      }
+    }
     case Opcode::kProjection:
       return ZeroExtendsWord32ToWord64NoPhis(op.Cast<ProjectionOp>().input());
     case Opcode::kLoad: {
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.