Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Skia
DescriptionInsufficient validation of untrusted input in Skia
ComponentSkia
Bug ClassLogic Error
Tracker501483855
Fix commit8317649d9c07 (skia) +16/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Files Changed

  • src/gpu/ganesh/text/GrAtlasManager.cpp
  • src/gpu/graphite/text/TextAtlasManager.cpp
From 8317649d9c07cbdbd2cff440404aa7fe7ddd089d Mon Sep 17 00:00:00 2001
From: Michael Ludwig <[email protected]>
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 <[email protected]>
Commit-Queue: Michael Ludwig <[email protected]>
---

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

Original Bug Report

reported by [email protected]

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.h
  • third_party/skia/src/gpu/graphite/text/TextAtlasManager.cpp
  • third_party/skia/src/gpu/graphite/text/GlyphData.cpp
  • third_party/skia/src/core/SkGlyph.cpp
  • third_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.

  1. 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.
  2. Underflow in insetSrc: When preparing to draw a Signed Distance Field Text (SDFTSubRun), TextAtlasManager::addGlyphToAtlas places the glyph into the atlas and calls glyph->fAtlasLocator.insetSrc(srcPadding) with a padding of 2 (SK_DistanceFieldInset).
  3. 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. The SkASSERT(2 * padding <= this->width()) check is compiled out in Release builds.
  4. Integer Underflow: In GlyphData::fillInstanceData (third_party/skia/src/gpu/graphite/text/GlyphData.cpp), the glyph quad size is calculated as AtlasPt{uint16_t(ar-al), uint16_t(ab-at)}. For inverted bounds (e.g., right=0, left=2), 0 - 2 underflows the unsigned 16-bit integer, resulting in a massive size of 65534.

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.

  1. Compromise a renderer process (e.g., via a v8 exploit).
  2. Construct a custom strike containing a 2x2 glyph.
  3. Send a DrawSlugOp containing an SDFTSubRun that references this 2x2 glyph.
  4. Apply an extreme strikeToSourceScale in the serialized matrix to scale the underflowed quad down to the size of an OffscreenCanvas.
  5. Wait for rasterization, then execute a readback (getImageData) on the canvas to recover the leaked cross-origin text masks.

Suggested Fix

  1. Modify AtlasLocator::insetSrc to 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.
  2. Add explicit minimum dimension validation in SkGlyph::MakeFromBuffer or SDFTSubRun::MakeFromBuffer to reject glyphs that are smaller than the required SK_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.

View on issue tracker