CVE-2026-11121
Overview
Files Changed
src/gpu/ganesh/text/GrAtlasManager.cppsrc/gpu/graphite/text/TextAtlasManager.cpp
Patch
From 8317649d9c07cbdbd2cff440404aa7fe7ddd089d Mon Sep 17 00:00:00 2001
From: Michael Ludwig <michaelludwig@google.com>
Date: Wed, 29 Apr 2026 14:16:02 -0400
Subject: [PATCH] Validate glyph size before insetting for atlases
Bug: b/501483855
Change-Id: I27f4201a80238ebbbdf74b45403deda5e5c744ee
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1221677
Reviewed-by: Thomas Smith <thomsmit@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
---
diff --git a/src/gpu/ganesh/text/GrAtlasManager.cpp b/src/gpu/ganesh/text/GrAtlasManager.cpp
index 0414394..f0dbd90 100644
--- a/src/gpu/ganesh/text/GrAtlasManager.cpp
+++ b/src/gpu/ganesh/text/GrAtlasManager.cpp
@@ -211,6 +211,14 @@
const int width = skGlyph.width() + 2*padding;
const int height = skGlyph.height() + 2*padding;
+
+ // Verify that the glyph data (received from potentially untrusted source) actually has room
+ // for the padding. Under normal flow, this should always be the case, but if a glyph was
+ // corrupted or manipulated it has no bearing on the code that *should* have produced the glyph.
+ // It's strict comparison since equality would imply the original glyph was empty, which should
+ // have been dropped.
+ SkASSERT_RELEASE(width > 2*srcPadding && height > 2*srcPadding);
+
int rowBytes = width * bytesPerPixel;
size_t size = height * rowBytes;
diff --git a/src/gpu/graphite/text/TextAtlasManager.cpp b/src/gpu/graphite/text/TextAtlasManager.cpp
index e5dd513..f489e6d 100644
--- a/src/gpu/graphite/text/TextAtlasManager.cpp
+++ b/src/gpu/graphite/text/TextAtlasManager.cpp
@@ -295,6 +295,14 @@
const int width = skGlyph.width() + 2*padding;
const int height = skGlyph.height() + 2*padding;
+
+ // Verify that the glyph data (received from potentially untrusted source) actually has room
+ // for the padding. Under normal flow, this should always be the case, but if a glyph was
+ // corrupted or manipulated it has no bearing on the code that *should* have produced the glyph.
+ // It's strict comparison since equality would imply the original glyph was empty, which should
+ // have been dropped.
+ SkASSERT_RELEASE(width > 2*srcPadding && height > 2*srcPadding);
+
int rowBytes = width * bytesPerPixel;
size_t size = height * rowBytes;
Original Bug Report
Cross-origin glyph atlas disclosure in Skia Graphite via integer underflow
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 Chrome Security team.
Overview: A potential integer underflow in Skia’s Graphite backend could allow a compromised renderer to leak the contents of the shared GPU text atlas. By supplying an artificially small glyph within an SDFT subrun, bounds calculations wrap, resulting in massive texture coordinates. Because the atlas is shared across origins and sampled using clamp-to-edge, this could allow cross-origin text masks to be drawn and read back by an attacker.
Affected files:
third_party/skia/src/gpu/graphite/DrawAtlas.hthird_party/skia/src/gpu/graphite/text/TextAtlasManager.cppthird_party/skia/src/gpu/graphite/text/GlyphData.cppthird_party/skia/src/core/SkGlyph.cppthird_party/skia/src/text/gpu/SubRunContainer.cpp
Estimated timestamp from git blame: 2026-03-06
Summary
In Chromium with the Skia Graphite backend and OOP-raster enabled, a compromised renderer process can potentially leak the contents of the GPU-process-wide kA8 glyph atlas. This atlas caches antialiased and Signed Distance Field (SDF) text masks for all origins. The vulnerability stems from an integer underflow when calculating texture coordinates for exceptionally small glyphs.
Root Cause Analysis
The issue begins when a compromised renderer sends serialized text operations to the GPU process.
- Missing Size Validation: In
SkGlyph::MakeFromBuffer(third_party/skia/src/core/SkGlyph.cpp), glyph dimensions are deserialized directly from attacker-controlled data without enforcing a minimum size, allowing a glyph as small as 2x2 pixels to be specified. - Underflow in
insetSrc: When preparing to draw a Signed Distance Field Text (SDFTSubRun),TextAtlasManager::addGlyphToAtlasplaces the glyph into the atlas and callsglyph->fAtlasLocator.insetSrc(srcPadding)with a padding of 2 (SK_DistanceFieldInset). - Bounds Inversion:
AtlasLocator::insetSrc(third_party/skia/src/gpu/graphite/DrawAtlas.h) subtracts padding from the right and bottom coordinates, and adds it to the left and top. For a 2x2 glyph, the right edge becomes smaller than the left edge. TheSkASSERT(2 * padding <= this->width())check is compiled out in Release builds. - Integer Underflow: In
GlyphData::fillInstanceData(third_party/skia/src/gpu/graphite/text/GlyphData.cpp), the glyph quad size is calculated asAtlasPt{uint16_t(ar-al), uint16_t(ab-at)}. For inverted bounds (e.g., right=0, left=2),0 - 2underflows the unsigned 16-bit integer, resulting in a massive size of65534.
Exploitation Mechanism
In the Graphite vertex shader (sksl_graphite_vert.sksl), this massive size is used to compute texture sampling coordinates. However, an attacker can prevent the physical geometry from being excessively large by specifying a very small strikeToSourceScale matrix scalar (e.g., 1.0/65534.0) in the subrun. This scales the device-space quad down to fit perfectly on the attacker’s canvas, while the texture coordinates remain spanning 65534 pixels.
The fragment shader (sksl_graphite_frag.sksl) samples the kA8 text atlas using SkTileMode::kClamp. As the texture coordinates exceed the glyph’s slot, they sweep across the rest of the 8192x8192 atlas page, interpolating out-of-bounds SDF data (which includes cached text from other origins) into visible shapes on the attacker’s canvas.
Suggested Attacker Steps (Potential)
Note: Our tooling agent cannot run code. These are potential steps an attacker might follow based on code analysis.
- Compromise a renderer process (e.g., via a v8 exploit).
- Construct a custom strike containing a 2x2 glyph.
- Send a
DrawSlugOpcontaining anSDFTSubRunthat references this 2x2 glyph. - Apply an extreme
strikeToSourceScalein the serialized matrix to scale the underflowed quad down to the size of anOffscreenCanvas. - Wait for rasterization, then execute a readback (
getImageData) on the canvas to recover the leaked cross-origin text masks.
Suggested Fix
- Modify
AtlasLocator::insetSrcto clamp the padding so it never exceeds half the width or height of the bounds, or change the return type to safely fail if the glyph is too small. - Add explicit minimum dimension validation in
SkGlyph::MakeFromBufferorSDFTSubRun::MakeFromBufferto reject glyphs that are smaller than the requiredSK_DistanceFieldInset.
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
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.