Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Skia
DescriptionInappropriate implementation in Skia
ComponentSkia
Bug ClassLogic Error
Tracker514017326
Fix commit43f969a9bce8 (skia) +15/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Changed Functions

FunctionChangeNotes
if
src/gpu/ganesh/ops/AtlasTextOp.cpp
modified

Files Changed

  • src/gpu/ganesh/ops/AtlasTextOp.cpp
From 43f969a9bce8019354d392f384fe10328655f924 Mon Sep 17 00:00:00 2001
From: Michael Ludwig <michaelludwig@google.com>
Date: Tue, 19 May 2026 14:53:32 -0400
Subject: [PATCH] [ganesh] Require glyph padding to use linear sampling

Bug: b/514017326
Change-Id: I9425c1f86aa8762333ba72b4ba30f5a056cdbfa9
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1239738
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
---

diff --git a/src/gpu/ganesh/ops/AtlasTextOp.cpp b/src/gpu/ganesh/ops/AtlasTextOp.cpp
index e1005ce..d335480 100644
--- a/src/gpu/ganesh/ops/AtlasTextOp.cpp
+++ b/src/gpu/ganesh/ops/AtlasTextOp.cpp
@@ -491,8 +491,12 @@
     } else
 #endif
     {
-        auto filter = fNeedsGlyphTransform ? GrSamplerState::Filter::kLinear
-                                           : GrSamplerState::Filter::kNearest;
+        // Only use linear padding if the glyphs were also padded for it. If we somehow get a direct
+        // subrun with a corrupted transform, we should still use nearest neighbor since it was
+        // packed tightly.
+        const bool hasGlyphPadding = fHead->fSubRun.glyphSrcPadding() > 0;
+        auto filter = fNeedsGlyphTransform && hasGlyphPadding ? GrSamplerState::Filter::kLinear
+                                                              : GrSamplerState::Filter::kNearest;
         // Bitmap text uses a single color, combineIfPossible ensures all geometries have the same
         // color, so we can use the first's without worry.
         flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
@@ -647,8 +651,9 @@
         } else
 #endif
         {
-            auto filter = fNeedsGlyphTransform ? GrSamplerState::Filter::kLinear
-                                               : GrSamplerState::Filter::kNearest;
+            const bool hasGlyphPadding = fHead->fSubRun.glyphSrcPadding() > 0;
+            auto filter = fNeedsGlyphTransform && hasGlyphPadding
+                    ? GrSamplerState::Filter::kLinear : GrSamplerState::Filter::kNearest;
             reinterpret_cast<GrBitmapTextGeoProc*>(gp)->addNewViews(views, numActiveViews, filter);
         }
     }
@@ -677,6 +682,12 @@
         return CombineResult::kCannotCombine;
     }
 
+    // We use the same filter for every Geometry that is combined, but the filter choice only looks
+    // at the head's src padding, so we can only combine if we are consistent with that.
+    if (fHead->fSubRun.glyphSrcPadding() != that->fHead->fSubRun.glyphSrcPadding()) {
+        return CombineResult::kCannotCombine;
+    }
+
     if (fProcessors != that->fProcessors) {
         return CombineResult::kCannotCombine;
     }
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential cross-origin information disclosure via bilinear bleed in Skia Ganesh glyph atlas

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 logic flaw in Skia’s Ganesh backend allows a compromised renderer to force bilinear sampling on a shared GPU glyph atlas that lacks texel padding. By providing forged matrix metadata in serialized Slug objects, an attacker can bypass nearest-neighbor sampling constraints. The resulting sampling bleed enables the potential disclosure of cross-origin pixel data from the shared atlas.

Affected files:

  • third_party/skia/src/gpu/ganesh/ops/AtlasTextOp.cpp
  • third_party/skia/src/text/gpu/VertexFiller.cpp
  • third_party/skia/src/gpu/ganesh/text/GrAtlasManager.cpp
  • third_party/skia/src/text/gpu/SubRunContainer.cpp
  • third_party/skia/src/gpu/ganesh/text/GlyphData.cpp
  • third_party/skia/src/gpu/ganesh/effects/GrBitmapTextGeoProc.cpp
  • third_party/skia/src/gpu/ganesh/Device.cpp

Estimated timestamp from git blame: Unknown (Google3 checkout)

Background

In Chrome’s GPU process, Skia’s Ganesh backend (used for Out-Of-Process Rasterization, or OOP-R) manages a shared glyph atlas. This atlas is stored in GPU textures and is shared across all renderer processes and origins to improve performance and memory efficiency. By default, Chrome disables fSupportBilerpFromGlyphAtlas, causing glyphs to be packed into the atlas with zero border padding (0 texels) to conserve space. This design assumes that glyphs in the atlas will only be sampled using nearest-neighbor (kNearest) filtering.

The Vulnerability

The vulnerability lies in how sktext::gpu::AtlasTextOp determines the sampler filter mode and how the GPU process handles serialized Slug objects (representations of text draws).

  1. Unvalidated Metadata: When a renderer sends a cc::DrawSlugOp, the payload includes a Slug object. This object contains VertexFiller metadata, specifically an fCreationMatrix. The GPU process deserializes this matrix directly from the renderer-supplied buffer in VertexFiller::MakeFromBuffer (third_party/skia/src/text/gpu/VertexFiller.cpp:61) without verifying its validity against the actual drawing context.

  2. Forced Bilinear Filtering: During rasterization, AtlasTextOp decides whether to use kNearest or kLinear (bilinear) filtering based on the fNeedsGlyphTransform flag. This flag is set to true if the draw-time Canvas Transformation Matrix (CTM) does not match the stored fCreationMatrix (e.g., if there is a sub-pixel translation or scale). By forging the fCreationMatrix to mismatch the actual CTM, a compromised renderer can force fNeedsGlyphTransform to be true (third_party/skia/src/gpu/ganesh/ops/AtlasTextOp.cpp:274).

  3. Bilinear Bleed: When fNeedsGlyphTransform is true, the GPU sampler is set to GrSamplerState::Filter::kLinear (third_party/skia/src/gpu/ganesh/ops/AtlasTextOp.cpp:494). Since the atlas was packed with zero padding, bilinear sampling at the glyph boundaries will interpolate between the attacker’s glyph and adjacent glyphs in the shared atlas belonging to other origins.

Impact

An attacker can read back the rasterized output (for example, by drawing to an accelerated 2D canvas and using getImageData()). Because the output contains a blend of the attacker’s pixels and the victim’s cross-origin glyph texels, the attacker can mathematically reconstruct cross-origin text or color-emoji data. This constitutes a high-severity cross-origin information leak in the GPU process.

Potential Exploit Steps

  1. From a compromised renderer, send commands to prime the shared atlas with glyphs from a victim origin (e.g., by navigating an iframe to a target site).
  2. Generate a malicious Slug payload where the fCreationMatrix is intentionally forged to mismatch the target draw CTM by a sub-pixel amount (e.g., 0.5 pixels).
  3. Send a cc::DrawSlugOp containing this Slug to the GPU process.
  4. Execute a draw with the Slug using the CTM that triggers the mismatch.
  5. Read back the resulting pixels from the canvas and solve for the neighboring texel values from the victim’s origin.

Suggested Fix

The GPU process should validate that the initialPositionMatrix (or creationMatrix) within a Slug is consistent with the drawing context. Alternatively, if fSupportBilerpFromGlyphAtlas is false, Skia should strictly enforce kNearest filtering for direct mask subruns, regardless of the transform metadata provided by the renderer. A debug-only check currently exists as a SkASSERT in Device::drawSlug (third_party/skia/src/gpu/ganesh/Device.cpp:1440); this invariant should be enforced at runtime in release builds.

Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a


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.

View on issue tracker