High patch-now CVSS 8.8 chrome Type Confusion 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in Runtime
DescriptionType Confusion in Runtime
ComponentRuntime
Sub-componentBuiltins
Bug ClassType Confusion
Action tierpatch-now — corruption primitive, web-reachable in default config (unverified)
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.
Tracker502830119
Fix commit615d074ab277 (v8/v8) +15/-2
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-04-15 (per vendor advisory)
Fix landed2026-04-16 (public git)
Disclosed2026-05-05
Patch gap19 days — fix public in source before the advisory

Background

`Array.prototype.slice` fast path
V8 has Torque (.tq) builtins that slice arguments objects without the general (slow) runtime path when the result is small enough to fit in new space.
New-space allocation folding
V8 can “fold” the JSArray object and its backing FixedArray elements into a single contiguous young-generation allocation, which requires the combined size to fit within the new-space fixed-array limit.
`kMaxNewSpaceFixedArrayElements`
the maximum element count a FixedArray may have while still being allocatable in new space.
`kJSArrayHeaderSizeSlots`
a constant added by this patch equal to JSArray::kHeaderSize / kTaggedSize, i.e. the number of tagged slots the JSArray header occupies.

Root Cause Analysis

The arguments-slice fast-path macros bailed to the slow path only when count >= kMaxNewSpaceFixedArrayElements, i.e. they accepted any count strictly below the bare FixedArray element limit. But the fast path then folds the JSArray header and the FixedArray of count elements into one new-space allocation, so the true space consumed is count elements plus the header slots. For a count just under kMaxNewSpaceFixedArrayElements, the folded allocation count + kJSArrayHeaderSizeSlots exceeds the new-space fixed-array capacity, so the threshold check was off by the header size and permitted an allocation larger than the invariant allows — producing a malformed/overlapping object layout that is observable as type confusion rather than a clean bailout.

The fix tightens the bailout condition to count >= kMaxNewSpaceFixedArrayElements - kJSArrayHeaderSizeSlots, reserving room for the header so the folded JSArray + elements allocation is always valid; oversized slices now take the Bailout slow path instead.

Key insight
The bailout threshold compared only the element count against the new-space limit while the allocation actually folds in the JSArray header too, so subtracting kJSArrayHeaderSizeSlots from the limit is what restores the invariant that the folded object fits in new space.

Attack Path

  1. Craft a large arguments object Script creates a function call whose arguments (mapped/sloppy or simple) has a length chosen so a slice yields a count just below kMaxNewSpaceFixedArrayElements.
  2. Invoke the fast slice Calling Array.prototype.slice on that arguments object reaches HandleSimpleArgumentsSlice (or the sloppy-arguments variant) with that count, which passes the old, too-loose threshold instead of bailing out.
  3. Fold an oversized allocation The fast path folds the JSArray header plus count elements into a single new-space allocation whose total exceeds the fixed-array limit, violating the folding invariant.
  4. Trigger type confusion The resulting mis-sized/overlapping object layout is interpreted inconsistently by the engine, the type confusion the advisory describes (any further exploitation primitive is inferred and not demonstrated by the diff).

Impact Assessment

A web page running JavaScript in the V8/renderer sandbox can reach the vulnerable fast path with ordinary Array.prototype.slice on an arguments object, requiring no special permissions. The advisory rates this high severity and classifies it as type confusion in Runtime (V8); it does not state in-the-wild exploitation. Any escalation from the type confusion to a memory read/write primitive or code execution is (inferred) and not shown by the patch.

Changed Functions

FunctionChangeNotes
if
src/builtins/array-slice.tq
modified

Files Changed

  • src/builtins/array-slice.tq

Audit Directions

  • Folded-allocation size math
    Audit every fast path that folds a header object with a variable-length backing store into one allocation; confirm the capacity check accounts for header slots, not just element count.
  • Off-by-header thresholds
    Review Torque builtins that compare a user-influenced count against kMaxNewSpace*/capacity limits for strict-vs-inclusive and header-inclusive correctness before bailing.
  • Arguments-object fast paths
    Check other arguments/array builtins (concat, splice, spread) that special-case JSArgumentsObjectWithLength/SloppyArgumentsElements for the same unbounded fast-path entry.
From 615d074ab27797662bd4c4e1fc5236a1fccce022 Mon Sep 17 00:00:00 2001
From: Marja Hölttä <[email protected]>
Date: Thu, 16 Apr 2026 14:56:51 +0200
Subject: [PATCH] [builtins] Fix Array.p.slice bailout threshold on arguments objects

Fixed: 502830119
Change-Id: Ia5b299cd9c33cabfe2a0647ff6a54e1942d6cd3f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7766691
Commit-Queue: Marja Hölttä <[email protected]>
Reviewed-by: Leszek Swirski <[email protected]>
Cr-Commit-Position: refs/heads/main@{#106550}
---

diff --git a/src/builtins/array-slice.tq b/src/builtins/array-slice.tq
index bb56285..d3f80e5 100644
--- a/src/builtins/array-slice.tq
+++ b/src/builtins/array-slice.tq
@@ -3,12 +3,21 @@
 // found in the LICENSE file.
 
 namespace array {
+
+const kJSArrayHeaderSizeSlots:
+    constexpr int31 generates 'JSArray::kHeaderSize / kTaggedSize';
+
 macro HandleSimpleArgumentsSlice(
     context: NativeContext, args: JSArgumentsObjectWithLength, start: Smi,
     count: Smi): JSArray
     labels Bailout {
   // If the resulting array doesn't fit in new space, use the slow path.
-  if (count >= kMaxNewSpaceFixedArrayElements) goto Bailout;
+  // We subtract the JSArray header size to ensure the JSArray and elements
+  // allocation can be safely folded together into a single object.
+  if (count >= kMaxNewSpaceFixedArrayElements - kJSArrayHeaderSizeSlots) {
+    goto Bailout;
+  }
+
 
   const end: Smi = start + count;
   const sourceElements: FixedArray =
@@ -32,7 +41,11 @@
     count: Smi): JSArray
     labels Bailout {
   // If the resulting array doesn't fit in new space, use the slow path.
-  if (count >= kMaxNewSpaceFixedArrayElements) goto Bailout;
+  // We subtract the JSArray header size to ensure the JSArray and elements
+  // allocation can be safely folded together into a single object.
+  if (count >= kMaxNewSpaceFixedArrayElements - kJSArrayHeaderSizeSlots) {
+    goto Bailout;
+  }
 
   const sloppyElements: SloppyArgumentsElements =
       Cast<SloppyArgumentsElements>(args.elements) otherwise Bailout;
Loading diff…

Original Bug Report

reported by [email protected]

Missing write barrier in Array slice for sloppy arguments due to allocation folding threshold mismatch

Project Fortify has identified a security issue and generated a PoC.

d8 variant: ‘Default’

flags: –allow-natives-syntax –expose-gc –no-inline-new –scavenger-chaos-mode –scavenger-chaos-mode-threshold=90 –verify-heap –omit-quit –fuzzing

Return code: 134

<details>

<summary>stdout</summary>

Starting test...
Attempt 0

</details>

<details>

<summary>stderr</summary>



#
# Fatal error in ../../v8/src/heap/heap-verifier.cc, line 566
# Check failed: untyped_->count(slot.address()) > 0 (0 vs. 0).
#
#
#
#FailureMessage Object: 0x7ffe3ae8a9e0
==== C stack trace ===============================

    /compressed/bin/Default/libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x1e) [0x7fb2375f489e]
    /compressed/bin/Default/libv8_libplatform.so(+0x163cd) [0x7fb23d4d43cd]
    /compressed/bin/Default/libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x194) [0x7fb2375d6b14]
    /compressed/bin/Default/libv8.so(+0x26fb1e5) [0x7fb239cfb1e5]
    /compressed/bin/Default/libv8.so(+0x2729a96) [0x7fb239d29a96]
    /compressed/bin/Default/libv8.so(+0x26f86f4) [0x7fb239cf86f4]
    /compressed/bin/Default/libv8.so(v8::internal::PagedSpaceBase::Verify(v8::internal::Isolate*, v8::internal::SpaceVerificationVisitor*) const+0x156) [0x7fb239ec3176]
    /compressed/bin/Default/libv8.so(+0x26f7412) [0x7fb239cf7412]
    /compressed/bin/Default/libv8.so(v8::internal::HeapVerifier::VerifyHeap(v8::internal::Heap*)+0x87) [0x7fb239cf8e77]
    /compressed/bin/Default/libv8.so(+0x276efbd) [0x7fb239d6efbd]
    /compressed/bin/Default/libv8.so(+0x276df86) [0x7fb239d6df86]
    /compressed/bin/Default/libv8.so(+0x27ad1a2) [0x7fb239dad1a2]
    /compressed/bin/Default/libv8.so(+0x27acd0f) [0x7fb239dacd0f]
    /compressed/bin/Default/libv8.so(+0x3da4d4b) [0x7fb23b3a4d4b]
    /compressed/bin/Default/libv8.so(v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags, v8::internal::PerformHeapLimitCheck, v8::internal::PerformIneffectiveMarkCompactCheck)+0x473) [0x7fb239d69b93]
    /compressed/bin/Default/libv8.so(+0x2579c9e) [0x7fb239b79c9e]
    /compressed/bin/Default/libv8.so(+0x25797f4) [0x7fb239b797f4]
    /compressed/bin/Default/libv8.so(+0xf94930) [0x7fb238594930]
Received signal 6
Aborted (core dumped)

</details>

Overview: A size threshold mismatch between Torque and the CodeStubAssembler allows certain JSArray allocations to be unfolded despite passing safety checks. This unfolded allocation can trigger a garbage collection that promotes the array elements to old space, while elements are copied into it without a write barrier. This results in stale tagged pointers and V8 heap type confusion.

Affected files:

  • v8/src/builtins/array-slice.tq
  • v8/src/codegen/code-stub-assembler.cc

Estimated timestamp from git blame: 2025-03-31

Root Cause

In v8/src/builtins/array-slice.tq, the HandleFastAliasedSloppyArgumentsSlice macro attempts to optimize Array.prototype.slice on fast-aliased sloppy arguments. It guards the fast path by checking count >= kMaxNewSpaceFixedArrayElements (which resolves to FixedArray::kMaxRegularLength, typically 32766 on pointer-compressed builds). If the check passes, it allocates a new JSArray and copies elements into its backing store using UNSAFE_SKIP_WRITE_BARRIER.

The use of UNSAFE_SKIP_WRITE_BARRIER relies on the assumption that the JSArray header and its FixedArray elements will be allocated as a single contiguous “folded” block in new-space, guaranteeing the elements array cannot be in old-space.

However, in CodeStubAssembler::AllocateUninitializedJSArrayWithElements, the decision to fold the allocation is based on whether the combined size of the JSArray header and the FixedArray elements is within kMaxRegularHeapObjectSize (128 KB). For a count value of 32763, the FixedArray alone fits within kMaxRegularHeapObjectSize (so it passes the Torque guard), but the combined size including the 16-byte JSArray header exceeds 131072 bytes.

Because the combined size is too large, the CSA performs an unfolded allocation: it allocates the FixedArray elements first, and then allocates the JSArray header separately. If the new-space is near capacity, the second allocation will trigger a scavenge garbage collection. During this scavenge, the just-allocated FixedArray can be promoted to the old generation.

When execution returns to Torque, it writes young-generation mapped parameters into this now old-generation FixedArray using UNSAFE_SKIP_WRITE_BARRIER. This old-to-new pointer is never recorded in the remembered set. Upon the next minor GC, the young objects are collected, leaving dangling pointers inside the elements array and leading to highly exploitable V8 heap type confusion.

Suggested Fix

There are two potential fixes for this issue:

  1. Use standard write barriers: The most robust fix is to remove UNSAFE_SKIP_WRITE_BARRIER and use the default StoreFixedArrayElement which emits proper write barriers. The performance overhead in this specific slice path is likely negligible.
  2. Adjust the bailout threshold: If the barrier-skip optimization is strictly required, the bailout threshold in HandleFastAliasedSloppyArgumentsSlice must be tightened to account for the JSArray header size (e.g., subtracting JSArray::kHeaderSize / kTaggedSize from the maximum capacity check) to guarantee the allocation will be safely folded.

Evaluated with Chrome root at commit: c0eb5541aebfa4ea08806eaf6e94bcc69f87ab2f


The description of the vuln is LLM-generated and can contain mistakes. Your feedback is appreciated, and will help us make improvement over time. The PoC was run in a VM and it seemed to be legit - if not, let us know and we can strengthen our checker.

View on issue tracker