CVE-2026-79144
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/gpu/ganesh/ops/SmallPathAtlasMgr.cpp |
modified | |
SmallPathIDChangeListenersrc/gpu/ganesh/ops/SmallPathShapeData.h |
modified | |
SmallPathShapeDataKeysrc/gpu/ganesh/ops/SmallPathShapeData.h |
modified | |
SmallPathShapeDatasrc/gpu/ganesh/ops/SmallPathShapeData.h |
modified |
Files Changed
src/gpu/ganesh/ops/SmallPathAtlasMgr.cppsrc/gpu/ganesh/ops/SmallPathAtlasMgr.hsrc/gpu/ganesh/ops/SmallPathShapeData.h
Patch
From 013ad6e7097238654847bfa5a5fb2c6c3823ee41 Mon Sep 17 00:00:00 2001 From: Alexis Cruz-Ayala <[email protected]> Date: Fri, 17 Jul 2026 12:51:34 -0400 Subject: [PATCH] [Security] Add a SmallPathIDChangeListener to SmallPathAtlasMgr There was a vulnerability where SkPathID could have duplicate entries if an attacker created and discarded enough Small, Complex Paths to overwhelm the SmallPathAtlasMgr cache. This could lead to the attacker submitting a request to the cache for a path, and receiving a victim's path instead. The solution involved mirroring what is done in the SoftwarePathRenderer.cpp and using an SkIDChangeListener to listen for when a path is modified or discarded and delete the entry in the cache. This way, duplicate entries are avoided. A test was added that shows that an ID collision can be forced amongst SmallPaths with complex geometry; any hash of two small, complex paths would rely only on the genID, making it easier to hit a duplicate entry. Bug: b/501759192 Change-Id: Id80a0167370dfca1b7dac511f1ddcfda16a72fc1 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1294996 Reviewed-by: Florin Malita <[email protected]> Commit-Queue: Alexis Cruz-Ayala <[email protected]> --- diff --git a/src/gpu/ganesh/ops/SmallPathAtlasMgr.cpp b/src/gpu/ganesh/ops/SmallPathAtlasMgr.cpp index 6fa8667..81ca65f 100644 --- a/src/gpu/ganesh/ops/SmallPathAtlasMgr.cpp +++ b/src/gpu/ganesh/ops/SmallPathAtlasMgr.cpp @@ -11,10 +11,12 @@ #include "include/gpu/GpuTypes.h" #include "include/gpu/ganesh/GrBackendSurface.h" #include "include/gpu/ganesh/GrTypes.h" +#include "include/private/SkIDChangeListener.h" #include "include/private/SkTo.h" #include "include/private/gpu/ganesh/GrTypesPriv.h" #include "src/gpu/MaskFormat.h" #include "src/gpu/ganesh/GrCaps.h" +#include "src/gpu/ganesh/geometry/GrStyledShape.h" #include "src/gpu/ganesh/ops/SmallPathShapeData.h" #include <cstddef> @@ -86,11 +88,19 @@ delete shapeData; } -SmallPathShapeData* SmallPathAtlasMgr::findOrCreate(const SmallPathShapeDataKey& key) { +SmallPathShapeData* SmallPathAtlasMgr::findOrCreate(const SmallPathShapeDataKey& key, + const GrStyledShape& shape) { auto shapeData = fShapeCache.find(key); + + if (shapeData && shapeData->fIDChangeListener->hasChanged()) { + this->deleteCacheEntry(shapeData); + shapeData = nullptr; + } + if (!shapeData) { // TODO: move the key into the ctor shapeData = new SmallPathShapeData(key); + shape.addGenIDChangeListener(shapeData->fIDChangeListener); fShapeCache.add(shapeData); fShapeList.addToTail(shapeData); #ifdef DF_PATH_TRACKING @@ -108,7 +118,7 @@ SmallPathShapeDataKey key(shape, desiredDimension); // TODO: move the key into 'findOrCreate' - return this->findOrCreate(key); + return this->findOrCreate(key, shape); } SmallPathShapeData* SmallPathAtlasMgr::findOrCreate(const GrStyledShape& shape, @@ -116,7 +126,7 @@ SmallPathShapeDataKey key(shape, ctm); // TODO: move the key into 'findOrCreate' - return this->findOrCreate(key); + return this->findOrCreate(key, shape); } GrDrawOpAtlas::ErrorCode SmallPathAtlasMgr::addToAtlas(GrResourceProvider* resourceProvider, diff --git a/src/gpu/ganesh/ops/SmallPathAtlasMgr.h b/src/gpu/ganesh/ops/SmallPathAtlasMgr.h index c72fd42..9e17694 100644 --- a/src/gpu/ganesh/ops/SmallPathAtlasMgr.h +++ b/src/gpu/ganesh/ops/SmallPathAtlasMgr.h @@ -93,7 +93,7 @@ void deleteCacheEntry(SmallPathShapeData*); private: - SmallPathShapeData* findOrCreate(const SmallPathShapeDataKey&); + SmallPathShapeData* findOrCreate(const SmallPathShapeDataKey&, const GrStyledShape&); void evict(GrPlotLocator) override; diff --git a/src/gpu/ganesh/ops/SmallPathShapeData.h b/src/gpu/ganesh/ops/SmallPathShapeData.h index 2828b0d..908c5ad 100644 --- a/src/gpu/ganesh/ops/SmallPathShapeData.h +++ b/src/gpu/ganesh/ops/SmallPathShapeData.h @@ -12,6 +12,7 @@ #if !defined(SK_ENABLE_OPTIMIZE_SIZE) #include "include/core/SkRect.h" +#include "include/private/SkIDChangeListener.h" #include "include/private/SkTemplates.h" #include "src/core/SkChecksum.h" #include "src/core/SkTInternalLList.h" @@ -25,6 +26,18 @@ namespace skgpu::ganesh { +class SmallPathIDChangeListener : public SkIDChangeListener { +public: + SmallPathIDChangeListener() : fHasChanged(false) {} + + bool hasChanged() const { return fHasChanged.load(std::memory_order_relaxed); } + + void changed() override { fHasChanged.store(true, std::memory_order_relaxed); } + +private: + std::atomic<bool> fHasChanged; +}; + class SmallPathShapeDataKey { public: // TODO: add a move variant @@ -58,7 +71,9 @@ class SmallPathShapeData { public: - SmallPathShapeData(const SmallPathShapeDataKey& key) : fKey(key) {} + SmallPathShapeData(const SmallPathShapeDataKey& key) + : fKey(key), fIDChangeListener(sk_make_sp<SmallPathIDChangeListener>()) {} + ~SmallPathShapeData() { fIDChangeListener->markShouldDeregister(); } const SmallPathShapeDataKey fKey; SkRect fBounds; @@ -73,6 +88,8 @@ static inline uint32_t Hash(const SmallPathShapeDataKey& key) { return SkChecksum::Hash32(key.data(), sizeof(uint32_t) * key.count32()); } + + sk_sp<SmallPathIDChangeListener> fIDChangeListener; }; } // namespace skgpu::ganesh
Original Bug Report
Potential cross-origin info leak via 30-bit SkPathData ID wrap in Ganesh
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 cross-origin information leak exists in the GPU process when using the Ganesh rendering backend. An attacker could potentially wrap the 30-bit SkPathData generation ID counter to collide with a victim’s cached path in the shared SmallPathAtlasMgr. This collision could allow the attacker to read the alpha mask of the victim’s cross-origin paths.
Affected files:
third_party/skia/src/gpu/ganesh/ops/SmallPathAtlasMgr.cppthird_party/skia/src/core/SkPathData.cppthird_party/skia/src/core/SkPath.cppthird_party/skia/src/gpu/ganesh/geometry/GrStyledShape.cppthird_party/skia/src/gpu/ganesh/ops/SmallPathShapeData.cppthird_party/skia/src/gpu/ganesh/ops/SmallPathRenderer.cpp
Estimated timestamp from git blame: 2025-11-03
A potential cross-origin information leak exists in the Ganesh backend’s SmallPathAtlasMgr. Due to a 30-bit wraparound in path generation IDs combined with cache sharing across origins in the GPU process, an attacker could potentially read the alpha masks (silhouettes) of small, complex paths rendered by other origins.
Technical Details
- 30-bit Generation ID Wraparound: In
third_party/skia/src/core/SkPathData.cpp, thenext_pathdata_unique_id()function generates unique IDs forSkPathDataobjects. It masks the ID to 30 bits (id <<= 2; id >>= 2;) to reserve space for fill type information. This causes the globalgenIDcounter to wrap around after approximately 1.07 billion allocations. - Cache Key Truncation: When rendering small paths,
SmallPathRenderergenerates a cache key viaGrStyledShape::writeUnstyledKey. If a path is complex (has more than 10 verbs, perkMaxKeyFromDataVerbCnt), the function drops the raw geometry (points and verbs) from the key. The key relies entirely on the path’s 30-bitgenIDand state (fill type, winding). - Shared GPU Cache without Eviction: The
SmallPathAtlasMgrcaches these rendered paths into aGrDrawOpAtlas. Because it is owned by aGrDirectContextshared viaSharedContextStatein the GPU process, this cache is globally accessible across all renderer processes. Furthermore, unlike other renderers (e.g.,SoftwarePathRenderer),SmallPathAtlasMgrdoes not register anSkIDChangeListener. The cache entry is not evicted when the original path is destroyed, keeping it alive until the atlas plot is randomly recycled.
Suggested Attacker Steps
Note: These are potential steps based on code analysis. Our tooling agent does not execute code to verify a proof-of-concept.
- A victim origin renders a small, complex path (e.g., >10 verbs, bounds <73px), such as an SVG icon or custom font glyph. This caches the path’s alpha mask in the GPU process’s shared
SmallPathAtlasMgr. - An attacker origin executes a JavaScript loop to repeatedly draw and discard paths to a
<canvas>context. By relying on Blink’sClientPaintCachebudget limits and purges, or using volatile paths, the attacker can deserialize millions of paths in the GPU process without causing an Out-Of-Memory (OOM) crash. - After approximately 1.07 billion path creations, the GPU process’s atomic 30-bit
genIDcounter wraps around. - The attacker times or brute-forces a draw call of an exploratory complex path (>10 verbs) using matrix parameters matching the victim’s, aligning its new
genIDwith the victim’s cachedgenID. - The cache key collides in
SmallPathAtlasMgr::findOrCreate. Instead of rasterizing the attacker’s path,SmallPathOp::onPrepareDrawsbinds the victim’s UV coordinates from the shared atlas. - The victim’s alpha mask is drawn onto the attacker’s canvas. The attacker extracts this cross-origin pixel data using
CanvasRenderingContext2D.getImageData().
Suggested Fix
- Register an
SkIDChangeListenerinSmallPathAtlasMgrorSmallPathShapeData(similar to the pattern used inSoftwarePathRendererandTriangulatingPathRenderer) so that cached atlas entries are explicitly invalidated when the underlyingSkPathis modified or destroyed. - Alternatively, prevent cross-origin cache collisions in the GPU process entirely by scoping the cache keys to the renderer process or using a 64-bit generation ID.
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.