CVE-2026-10015
Overview
Background
- `WTF::Vector`
- Blink’s vector container whose size/capacity are stored as
wtf_size_t(third_party/blink/renderer/platform/wtf/vector.h). - `wtf_size_t`
- a 32-bit unsigned size type, narrower than the 64-bit
size_t/std::ranges::sizeinputs the constructors accept. - `base::checked_cast`
- a narrowing cast that terminates the process instead of silently truncating when the value does not fit the target type (added by the fix).
Root Cause Analysis
The Vector(base::span<const U> other) constructor delegated the allocation with the untruncated size, : Base(other.size()), and then set size_ = other.size(); (third_party/blink/renderer/platform/wtf/vector.h:1878-1884). Here other.size() is a 64-bit size_t, but Base’s size parameter and size_ are 32-bit wtf_size_t, so a size exceeding 2^32 is silently truncated for the buffer allocation while the subsequent TypeOperations::UninitializedCopy(other, base::span(*this), ...) copies based on the span’s full extent — an undersized buffer with a full-length copy. The invariant that the allocation size equals the number of elements copied is therefore broken only when the source exceeds wtf_size_t range. The sibling range constructor had the same pattern: : Base(std::ranges::size(other)) with size_ = std::ranges::size(other); (vector.h:1890, 1901).
The fix wraps every such size with base::checked_cast<wtf_size_t>(...) in both constructors — Base(base::checked_cast<wtf_size_t>(other.size())) and size_ = base::checked_cast<wtf_size_t>(other.size()) (diff @@ -1876), and the analogous two sites in the range constructor (diff @@ -1887) — so an over-range size now crashes safely before any undersized allocation or oversized copy. The ANNOTATE_NEW_BUFFER calls (vector.h:1880, 1897) are left using the raw size; the security-relevant allocation and size_ are the ones hardened.
base::checked_cast<wtf_size_t> to force a safe crash instead of a silent narrowing.Attack Path
- Allocate oversize data
Produce a source buffer/range larger than 2^32 elements on a 64-bit build. (inferred: the >4 GiB
ArrayBuffervia IndexedDB is the reporter’s claim) - Construct the vector
Reach
Vector(base::span<const U>)with that span, whereBase(other.size())truncates the allocation size (third_party/blink/renderer/platform/wtf/vector.h:1879). (inferred: thev8_binding_for_modules.ccIndexedDB key path is not in the material) - Undersized buffer
The truncated 32-bit value yields a too-small allocation while
size_and the copy reference the full length (vector.h:1880-1882). - Linear overflow
UninitializedCopywrites the full source past the small buffer, a heap buffer overflow in the renderer. (inferred: thememcpy/PartitionAlloc guard-page details are reporter claims)
Impact Assessment
WTF::Vector from a span/range whose size exceeds 32 bits silently allocated a truncated buffer but copied the full source, giving a linear heap buffer overflow in the renderer process. The advisory rates it high severity with no in-the-wild exploitation; JavaScript reachability via IndexedDB and the exact overflow extent are reporter claims not shown in the material (inferred).Files Changed
third_party/blink/renderer/platform/wtf/vector.h
Audit Directions
- 64-bit to `wtf_size_t` narrowingFlag any assignment of
size_t/std::ranges::sizeintowtf_size_tallocation parameters that lacks a checked cast. - Alloc-size vs. copy-length mismatchVerify the value used to size an allocation is the same value (and width) used to bound the subsequent copy/fill.
- Container constructors from external dataAudit container constructors that accept attacker-influenced sizes (spans, ranges, serialized buffers) for silent truncation.
Patch
From ddf427ac6b17eb7fc8ffb41adbac960acbe4d73d Mon Sep 17 00:00:00 2001
From: Kent Tamura <tkent@chromium.org>
Date: Tue, 19 May 2026 20:03:50 -0700
Subject: [PATCH] WTF: Use base::checked_cast for size casting in Vector constructors
Initializing a blink::Vector from a base::span or a range uses size_t
for the input size, but blink::Vector internally uses wtf_size_t
(32-bit) for its size representation.
This change introduces base::checked_cast<wtf_size_t> during
construction to ensure that if the input size exceeds the 32-bit limit,
the process crashes safely. This prevents silent integer truncation,
which could otherwise lead to memory safety issues such as buffer
overflows.
No unit tests are added because it is impractical to allocate a 4GB+
buffer to trigger this condition in a standard test environment.
Bug: 514746176
Change-Id: Iff0edf2a1ce40da294c77f9c5eeb5898ac56611b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7861980
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Commit-Queue: Kent Tamura <tkent@chromium.org>
Auto-Submit: Kent Tamura <tkent@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1633311}
---
diff --git a/third_party/blink/renderer/platform/wtf/vector.h b/third_party/blink/renderer/platform/wtf/vector.h
index e5ae4f4..b1274e3f 100644
--- a/third_party/blink/renderer/platform/wtf/vector.h
+++ b/third_party/blink/renderer/platform/wtf/vector.h
@@ -1876,9 +1876,9 @@
template <typename T, wtf_size_t InlineCapacity, typename Allocator>
template <typename U>
Vector<T, InlineCapacity, Allocator>::Vector(base::span<const U> other)
- : Base(other.size()) {
+ : Base(base::checked_cast<wtf_size_t>(other.size())) {
UNSAFE_TODO(ANNOTATE_NEW_BUFFER(data(), capacity(), other.size()));
- size_ = other.size();
+ size_ = base::checked_cast<wtf_size_t>(other.size());
TypeOperations::UninitializedCopy(other, base::span(*this),
VectorOperationOrigin::kConstruction);
}
@@ -1887,7 +1887,7 @@
template <typename Range, typename Proj>
requires VectorCanAssignFromRange<T, InlineCapacity, Allocator, Range, Proj>
Vector<T, InlineCapacity, Allocator>::Vector(Range&& other, Proj proj)
- : Base(std::ranges::size(other)) {
+ : Base(base::checked_cast<wtf_size_t>(std::ranges::size(other))) {
// Note that `size(other)` may become smaller if `other` is a hash table
// with WeakMember keys and `Base(size(other))` above caused GC which
// removed some entries from `other`, see crbug.com/40448463. This won't
@@ -1898,7 +1898,7 @@
TypeOperations::UninitializedTransform(
std::ranges::begin(other), std::ranges::end(other), data(),
VectorOperationOrigin::kConstruction, std::move(proj));
- size_ = std::ranges::size(other);
+ size_ = base::checked_cast<wtf_size_t>(std::ranges::size(other));
}
template <typename T, wtf_size_t InlineCapacity, typename Allocator>
Original Bug Report
Potential heap overflow in WTF::Vector constructor due to integer truncation
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A 64-to-32-bit integer truncation in the WTF::Vector constructor when initialized from a base::span results in an undersized buffer allocation. This leads to a potential linear heap buffer overflow of attacker-controlled data during the subsequent copy operation. The issue is reachable from JavaScript via the IndexedDB API using large ArrayBuffers.
Affected files:
third_party/blink/renderer/platform/wtf/vector.hthird_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.cc
Estimated timestamp from git blame: 2025-10-24
Description
A potential integer truncation vulnerability exists in the WTF::Vector constructor on 64-bit platforms. When a WTF::Vector is constructed from a base::span, the size of the span (a 64-bit size_t) is implicitly truncated to a 32-bit wtf_size_t (uint32_t) during buffer allocation and when setting the vector’s size_ member. This narrowing occurs without a base::checked_cast or any bounds validation.
The vulnerable code in third_party/blink/renderer/platform/wtf/vector.h is:
template <typename T, wtf_size_t InlineCapacity, typename Allocator>
template <typename U>
Vector<T, InlineCapacity, Allocator>::Vector(base::span<const U> other)
: Base(other.size()) {
UNSAFE_TODO(ANNOTATE_NEW_BUFFER(data(), capacity(), other.size()));
size_ = other.size();
TypeOperations::UninitializedCopy(other, base::span(*this),
VectorOperationOrigin::kConstruction);
}
When other.size() exceeds 2^32 (e.g., 0x100000100 bytes), the Base (VectorBuffer) constructor receives the truncated 32-bit value (e.g., 0x100). This results in an undersized buffer allocation (e.g., 256 bytes).
Subsequently, TypeOperations::UninitializedCopy is called. For types that use memcpy (like char), it calculates the copy length based on the source span’s original 64-bit extent. This results in a memcpy of the full >4 GiB source data into the truncated 256-byte destination buffer, causing a linear heap buffer overflow.
Reachability and Impact
This vulnerability is potentially reachable from JavaScript without user interaction via the IndexedDB API. In third_party/blink/renderer/bindings/modules/v8/v8_binding_for_modules.cc, using an ArrayBuffer as an IndexedDB key triggers the vulnerable WTF::Vector<char> constructor:
return IDBKey::CreateBinary(
base::MakeRefCounted<base::RefCountedData<Vector<char>>>(
Vector<char>(base::as_chars(buffer->ByteSpan()))));
On 64-bit systems, ArrayBuffer objects can be as large as 32 GiB. An attacker can allocate an ArrayBuffer larger than 4 GiB and use it as a key in an operation like IDBObjectStore.put().
While a massive linear overflow will eventually hit a PartitionAlloc guard page (located every 2MB) and cause a crash, it can corrupt adjacent heap objects before the crash occurs. Since the destination vector is allocated on the renderer heap outside the V8 sandbox, this provides a primitive for memory corruption within the renderer process.
Suggested Potential Reproduction Steps
- On a 64-bit system with sufficient RAM, open a website and initialize an IndexedDB database.
- In JavaScript, allocate an
ArrayBufferlarger than 4 GiB (e.g.,new ArrayBuffer(0x100000100)). - Perform an IndexedDB operation using this buffer as a key:
let largeKey = new ArrayBuffer(0x100000100); db.transaction('store', 'readwrite').objectStore('store').put('value', largeKey); - The renderer is expected to crash due to a heap buffer overflow in
memcpywithin theWTF::Vectorconstructor.
Note: These steps are suggested based on code analysis; the behavior hasn’t been verified with a running PoC.
Recommended Fix
Apply base::checked_cast<wtf_size_t> to the span size in the constructor to ensure that any size exceeding the maximum wtf_size_t results in a safe crash/exception rather than a truncation.
template <typename U>
Vector<T, InlineCapacity, Allocator>::Vector(base::span<const U> other)
: Base(base::checked_cast<wtf_size_t>(other.size())) {
...
size_ = base::checked_cast<wtf_size_t>(other.size());
...
}
Evaluated with Chrome root at commit: 29093e11cf509e3593f6229e4b1b075cca356049
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.