High chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in Skia
DescriptionInteger overflow in Skia
ComponentSkia
Bug ClassInteger Overflow
Tracker499152771
Fix commit8ac48d9b53de (skia) +3/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Files Changed

  • src/text/gpu/GlyphVector.cpp
From 8ac48d9b53de0287c171093d751e8db180c641cf Mon Sep 17 00:00:00 2001
From: Kaylee Lubick <[email protected]>
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 <[email protected]>
Reviewed-by: Michael Ludwig <[email protected]>
---

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;
     }
Loading diff…

Original Bug Report

reported by [email protected]

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.h
  • third_party/skia/src/text/gpu/GlyphVector.cpp
  • third_party/skia/src/text/gpu/SubRunContainer.cpp
  • components/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).

  1. The check SkASSERT_RELEASE(size <= kMaxByteSize) passes because a negative number is mathematically less than the positive limit.
  2. The method calls allocateBytes(-2147483648, 8).
  3. Inside allocateBytes, the condition if (fCapacity < size) (e.g., 4096 < -2147483648) evaluates to false, bypassing the need to allocate more memory.
  4. 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:

  1. Compromise Renderer: An attacker compromises a renderer process.
  2. Craft Payload: The attacker crafts a malicious SKP containing a DrawSlugOp with a glyphCount of 0x10000000. The SKP is padded to exactly 1 GB to satisfy availability checks.
  3. Deliver Payload: The SKP is sent via Mojo using mojo_base.mojom.BigBuffer to the PaintPreviewCompositor utility process. (Note: This is unreachable via OOP-R because OOP-R restricts serialized PaintOps to 16 MB, but Paint Preview allows gigabyte-scale payloads).
  4. Trigger Overflow: GlyphVector::MakeFromBuffer (third_party/skia/src/text/gpu/GlyphVector.cpp:61) reads the glyphCount. The value passes the glyphCount <= INT_MAX / sizeof(uint32_t) check and the buffer.available() check. It then requests an allocation for the glyphs, triggering the integer overflow in SubRunAllocator as described above.
  5. Heap Corruption: GlyphVector enters its deserialization loop:
    for (int i = 0; i < glyphCount; i++) {
        *reinterpret_cast<SkPackedGlyphID*>(glyphs[i].data()) = SkPackedGlyphID(buffer.readUInt());
    }
    
    This loop writes 4 bytes and skips 4 bytes over the course of 0x10000000 iterations, writing attacker-controlled 20-bit values linearly across the heap.
  6. Exploitation: The linear write traverses PartitionAlloc. While it will inevitably hit a PROT_NONE guard 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.

View on issue tracker