CVE-2026-9909
Overview
Files Changed
src/text/gpu/GlyphVector.cpp
Patch
From 8ac48d9b53de0287c171093d751e8db180c641cf Mon Sep 17 00:00:00 2001
From: Kaylee Lubick <kjlubick@google.com>
Date: Mon, 27 Apr 2026 19:58:02 +0000
Subject: [PATCH] Identify overflow in SkGlyph allocation earlier
Follow-up to https://review.skia.org/1209996
Bug: b/499152771
Change-Id: I487f64d92aa7177a84c1754e275852373a3cc40e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1219717
Commit-Queue: Kaylee Lubick <kjlubick@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
---
diff --git a/src/text/gpu/GlyphVector.cpp b/src/text/gpu/GlyphVector.cpp
index d1a41e0..7040436 100644
--- a/src/text/gpu/GlyphVector.cpp
+++ b/src/text/gpu/GlyphVector.cpp
@@ -73,14 +73,14 @@
return std::nullopt;
}
- // Make sure we can multiply without overflow in the check below.
- static constexpr int kMaxCount = (int)(INT_MAX / sizeof(uint32_t));
- if (!buffer.validate(glyphCount <= kMaxCount)) {
+ // Make sure we won't overflow the glyphCount math or allocation below.
+ if (!buffer.validate(BagOfBytes::WillCountFit<GlyphBytes>(glyphCount))) {
return std::nullopt;
}
// Check for enough bytes to populate the packedGlyphID array. If not enough something has
// gone wrong.
+ static_assert(sizeof(GlyphBytes) >= sizeof(uint32_t));
if (!buffer.validate(glyphCount * sizeof(uint32_t) <= buffer.available())) {
return std::nullopt;
}
Original Bug Report
Integer Overflow in Skia SubRunAllocator Leads to Heap OOB Write
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 without the security team.
Overview: An integer overflow in Skia’s SubRunAllocator during SKP deserialization can result in a negative allocation size, bypassing capacity checks. This returns an undersized arena pointer, allowing an attacker to perform a massive linear heap out-of-bounds write. While PartitionAlloc guard pages ensure an eventual crash, a concurrent thread could access corrupted data in the 2 MB window before the crash, potentially leading to RCE in the Paint Preview utility process.
Affected files:
third_party/skia/src/text/gpu/SubRunAllocator.hthird_party/skia/src/text/gpu/GlyphVector.cppthird_party/skia/src/text/gpu/SubRunContainer.cppcomponents/paint_preview/common/serial_utils.cc
Estimated timestamp from git blame: 2026-02-24
Summary
There is a potential integer overflow vulnerability in Skia’s SubRunAllocator within BagOfBytes::allocateBytesFor. When deserializing SkPicture (SKP) payloads in the PaintPreviewCompositor utility process, an attacker can supply a large glyphCount that causes an allocation size calculation to overflow a 32-bit signed integer to a negative value. This bypasses internal capacity checks and returns an undersized memory chunk. The subsequent initialization loop performs a massive out-of-bounds (OOB) heap write.
While PartitionAlloc’s 2 MB Super Page guard pages make a crash deterministic, an attacker can overwrite up to 2 MB of adjacent heap objects prior to the crash, which may be exploitable via a race condition.
Root Cause Analysis
The vulnerability lies in third_party/skia/src/text/gpu/SubRunAllocator.h:
template <size_t Size, size_t Alignment>
char* allocateBytesFor(int n = 1)
requires (Alignment <= kMaxAlignment && Size < kMaxByteSize && Size % Alignment == 0) {
int size = n ? n * Size : 1;
SkASSERT_RELEASE(size <= kMaxByteSize);
return this->allocateBytes(size, Alignment);
}
When n is large (e.g., 0x10000000 or 268,435,456) and Size is 8 (the size of GlyphBytes on 64-bit systems), the multiplication n * Size is performed as a 64-bit size_t due to C++ promotion rules, resulting in 0x0000000080000000.
This value is then assigned to the 32-bit signed integer size, where it overflows and is interpreted as INT_MIN (-2,147,483,648).
- The check
SkASSERT_RELEASE(size <= kMaxByteSize)passes because a negative number is mathematically less than the positive limit. - The method calls
allocateBytes(-2147483648, 8). - Inside
allocateBytes, the conditionif (fCapacity < size)(e.g.,4096 < -2147483648) evaluates to false, bypassing the need to allocate more memory. - The method updates the capacity (
fCapacity -= size), causing a massive positive overflow, and returns a pointer to the current, undersized arena block.
Potential Attack Path
These are suggested steps based on static analysis, as our tooling cannot currently run code to provide a working Proof of Concept:
- Compromise Renderer: An attacker compromises a renderer process.
- Craft Payload: The attacker crafts a malicious SKP containing a
DrawSlugOpwith aglyphCountof0x10000000. The SKP is padded to exactly 1 GB to satisfy availability checks. - Deliver Payload: The SKP is sent via Mojo using
mojo_base.mojom.BigBufferto thePaintPreviewCompositorutility process. (Note: This is unreachable via OOP-R because OOP-R restricts serializedPaintOps to 16 MB, but Paint Preview allows gigabyte-scale payloads). - Trigger Overflow:
GlyphVector::MakeFromBuffer(third_party/skia/src/text/gpu/GlyphVector.cpp:61) reads theglyphCount. The value passes theglyphCount <= INT_MAX / sizeof(uint32_t)check and thebuffer.available()check. It then requests an allocation for the glyphs, triggering the integer overflow inSubRunAllocatoras described above. - Heap Corruption:
GlyphVectorenters its deserialization loop:This loop writes 4 bytes and skips 4 bytes over the course offor (int i = 0; i < glyphCount; i++) { *reinterpret_cast<SkPackedGlyphID*>(glyphs[i].data()) = SkPackedGlyphID(buffer.readUInt()); }0x10000000iterations, writing attacker-controlled 20-bit values linearly across the heap. - Exploitation: The linear write traverses PartitionAlloc. While it will inevitably hit a
PROT_NONEguard page at a 2 MB Super Page boundary and crash, it will overwrite up to 2 MB of adjacent heap objects in the current Super Page before doing so. If the attacker grooms the heap and triggers a concurrent thread to interact with the corrupted objects (e.g., vtable pointers) before the crash, they may achieve Remote Code Execution (RCE) in the sandboxed utility process.
Suggested Fix
Refactor allocateBytesFor in SubRunAllocator.h to use Skia’s safe math utilities (or base::CheckedNumeric if permitted in this context) to prevent integer overflows during size calculations. Alternatively, explicitly cast the size to size_t and validate it against kMaxByteSize before converting it to an int for the arena logic:
size_t safe_size = (size_t)n * Size;
SkASSERT_RELEASE(safe_size <= kMaxByteSize);
int size = static_cast<int>(safe_size);
Furthermore, adding strict bounds checking on fCapacity and size in BagOfBytes::allocateBytes to reject negative sizes would serve as an effective defense-in-depth measure.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.