High chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized Use in Skia
DescriptionUninitialized Use in Skia
ComponentSkia
Bug ClassUninitialized Memory
Tracker536165038
Fix commit30d6603d43a5 (skia) +130/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-06

Changed Functions

FunctionChangeNotes
if
src/gpu/ganesh/GrDrawingManager.cpp
modified
for
src/gpu/ganesh/GrDrawingManager.cpp
modified
if
tests/GrSurfaceResolveTest.cpp
modified

Files Changed

  • src/gpu/ganesh/GrDrawingManager.cpp
  • tests/GrSurfaceResolveTest.cpp
From 30d6603d43a591eadedfff08c467fe76c7110779 Mon Sep 17 00:00:00 2001
From: Robert Phillips <[email protected]>
Date: Wed, 22 Jul 2026 09:29:24 -0400
Subject: [PATCH] [ganesh] Skip resolve/mipmap step on flush failure

This AI generated patch seems reasonable and harmless enough.

I do think that it is only a small part of a larger problem around Ganesh's handling of flush failures.

In practice, Chrome will have to have handled the flush failure via the callback system in order to respond to the failure. That handling should discard the texture the bug is worried about. This CL adds a bit of defense in depth (and seems harmless).

Bug: b/536165038
Change-Id: I55adb244deb8656a2895771491456a3770c8a2fa
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1299577
Reviewed-by: Michael Ludwig <[email protected]>
Commit-Queue: Robert Phillips <[email protected]>
---

diff --git a/src/gpu/ganesh/GrDrawingManager.cpp b/src/gpu/ganesh/GrDrawingManager.cpp
index 4fa514b..0b986eb 100644
--- a/src/gpu/ganesh/GrDrawingManager.cpp
+++ b/src/gpu/ganesh/GrDrawingManager.cpp
@@ -126,7 +126,9 @@
             if (info.fSubmittedProc) {
                 info.fSubmittedProc(info.fSubmittedContext, true);
             }
-            return false;
+            // Nothing to flush is a success (fSubmittedProc is already called with `true`
+            // above).
+            return true;
         }
     }
 
@@ -542,8 +544,11 @@
     // portion of the DAG required by 'proxies' in order to restore some of the
     // semantics of this method.
     bool didFlush = this->flush(proxies, access, info, newState);
-    for (GrSurfaceProxy* proxy : proxies) {
-        resolve_and_mipmap(gpu, proxy);
+    if (didFlush) {
+        // Only resolve/regen mips if the flush actually executed the render tasks.
+        for (GrSurfaceProxy* proxy : proxies) {
+            resolve_and_mipmap(gpu, proxy);
+        }
     }
 
     SkDEBUGCODE(this->validate());
diff --git a/tests/GrSurfaceResolveTest.cpp b/tests/GrSurfaceResolveTest.cpp
index fbe52a1..74b5bf2 100644
--- a/tests/GrSurfaceResolveTest.cpp
+++ b/tests/GrSurfaceResolveTest.cpp
@@ -512,3 +512,125 @@
         }
     }
 }
+
+// Directly read back 'tex' and return the first pixel color.
+static bool read_backing_pixel(GrDirectContext* dContext,
+                               const GrBackendTexture& tex,
+                               const SkImageInfo& info,
+                               SkColor* outPixel) {
+    sk_sp<SkSurface> reader = SkSurfaces::WrapBackendTexture(dContext,
+                                                             tex,
+                                                             kTopLeft_GrSurfaceOrigin,
+                                                             /*sampleCnt=*/1,
+                                                             kRGBA_8888_SkColorType,
+                                                             nullptr,
+                                                             nullptr);
+    if (!reader) {
+        return false;
+    }
+    SkBitmap bm;
+    bm.allocPixels(info);
+    if (!reader->readPixels(bm, 0, 0)) {
+        return false;
+    }
+    *outPixel = bm.getColor(0, 0);
+    return true;
+}
+
+// This test wraps a backend texture as an MSAA render target twice. The first wrap establishes a
+// known baseline color in the single-sample texture via a *successful* flush. The second wrap
+// creates a *fresh, never-written* MSAA color attachment (on GL a new renderbuffer, on Vulkan a
+// scratch/new GrVkImage), records a draw, forces the flush to fail via a failing preFlush
+// callback, and then reads back the single-sample texture. The read-back must not show the
+// uninitialized MSAA attachment contents; if it does, resolve_and_mipmap() ran despite the failed
+// flush and copied uninitialized GPU memory into the client-visible texture.
+DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS(SurfaceResolveAfterFailedFlush,
+                                       reporter,
+                                       ctxInfo,
+                                       CtsEnforcement::kNever) {
+    auto dContext = ctxInfo.directContext();
+    const GrCaps* caps = dContext->priv().caps();
+
+    // Only meaningful on backends that require an explicit resolve of a persistent MSAA attachment.
+    if (caps->msaaResolvesAutomatically() || caps->preferDiscardableMSAAAttachment()) {
+        return;
+    }
+
+    SkImageInfo info = SkImageInfo::Make(8, 8, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+
+    auto managedTex = ManagedBackendTexture::MakeFromInfo(
+            dContext, info, skgpu::Mipmapped::kNo, GrRenderable::kYes);
+    if (!managedTex) {
+        return;
+    }
+    const GrBackendTexture& tex = managedTex->texture();
+
+    constexpr SkColor kBaseline = SK_ColorBLUE;   // known content of the single-sample texture
+    constexpr SkColor kIntended = SK_ColorGREEN;  // what the failed flush *would* have drawn
+
+    // 1. Wrap once and successfully draw kBaseline so the single-sample texture holds known bytes.
+    {
+        sk_sp<SkSurface> surface = SkSurfaces::WrapBackendTexture(dContext,
+                                                                  tex,
+                                                                  kTopLeft_GrSurfaceOrigin,
+                                                                  /*sampleCnt=*/4,
+                                                                  kRGBA_8888_SkColorType,
+                                                                  nullptr,
+                                                                  nullptr);
+        if (!surface) {
+            return;  // MSAA=4 unsupported on this config.
+        }
+        surface->getCanvas()->clear(kBaseline);
+        dContext->flush(surface.get());
+        dContext->submit(GrSyncCpu::kYes);
+    }
+    // Ensure the second wrap below allocates a *fresh* MSAA attachment rather than recycling the
+    // one from the wrap above (whose contents would coincidentally match kBaseline).
+    dContext->purgeUnlockedResources(GrPurgeResourceOptions::kAllResources);
+
+    SkColor pixel = 0;
+    REPORTER_ASSERT(reporter, read_backing_pixel(dContext, tex, info, &pixel));
+    REPORTER_ASSERT(reporter, pixel == kBaseline,
+                    "baseline flush failed to resolve, got 0x%x", pixel);
+
+    // 2. Re-wrap: this creates a *new* persistent MSAA color attachment that has never been
+    //    written by any render pass in this test.
+    sk_sp<SkSurface> surface = SkSurfaces::WrapBackendTexture(dContext,
+                                                              tex,
+                                                              kTopLeft_GrSurfaceOrigin,
+                                                              /*sampleCnt=*/4,
+                                                              kRGBA_8888_SkColorType,
+                                                              nullptr,
+                                                              nullptr);
+    if (!surface) {
+        return;
+    }
+
+    // 3. Record a full-surface draw so OpsTask::onMakeClosed() returns kTargetDirty and
+    //    markMSAADirty() is called during closeAllTasks(). Force the flush to fail.
+    FailingPreFlushCallback failCB(1);
+    dContext->priv().addOnFlushCallbackObject(&failCB);
+
+    surface->getCanvas()->clear(kIntended);
+    GrSemaphoresSubmitted result = dContext->flush(surface.get(), GrFlushInfo{});
+    dContext->submit(GrSyncCpu::kYes);
+
+    GrDrawingManager* drawingManager = dContext->priv().drawingManager();
+    drawingManager->testingOnly_removeOnFlushCallbackObject(&failCB);
+
+    REPORTER_ASSERT(reporter, result == GrSemaphoresSubmitted::kNo,
+                    "expected flush to report semaphores not submitted");
+
+    // 4. Read back the single-sample backing texture.
+    REPORTER_ASSERT(reporter, read_backing_pixel(dContext, tex, info, &pixel));
+
+    // The recorded draw never executed, so kIntended should not appear.
+    REPORTER_ASSERT(reporter, pixel != kIntended,
+                    "flush failed but draw, somehow, occurred (got 0x%x)", pixel);
+
+    // Since the flush failed the MSAA resolve step should not have occurred and the contents
+    // of the backend texture should have remained at 'kBaseline'.
+    REPORTER_ASSERT(reporter, pixel == kBaseline,
+                    "Invalid resolve after a failed flush: expected 0x%x, actual 0x%x",
+                    kBaseline, pixel);
+}
Loading diff…

Original Bug Report

reported by [email protected]

Potential cross-origin GPU data leak due to unconditional resolve on flush failure 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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: In Skia’s Ganesh backend, GrDrawingManager::flushSurfaces unconditionally triggers a resolve on provided surface proxies, regardless of whether the preceding flush() call succeeded. On flush failures (e.g., due to allocator or pre-flush failures), discarded tasks leave the target marked dirty, causing recycled or uninitialized cross-origin MSAA attachment data to be copied into the user-readable single-sample texture.

Affected files:

  • third_party/skia/src/gpu/ganesh/GrDrawingManager.cpp
  • third_party/skia/src/gpu/ganesh/GrRenderTask.cpp
  • third_party/skia/src/gpu/ganesh/ops/OpsTask.cpp

Estimated timestamp from git blame: 2019-05-09

Description

There is a potential cross-origin GPU-process information disclosure vulnerability in the Skia Ganesh rendering backend of Chromium.

Root Cause

In third_party/skia/src/gpu/ganesh/GrDrawingManager.cpp, flushSurfaces() invokes resolve_and_mipmap() on the user-supplied proxies regardless of whether this->flush() succeeded:

// third_party/skia/src/gpu/ganesh/GrDrawingManager.cpp:543-546
bool didFlush = this->flush(proxies, access, info, newState);
for (GrSurfaceProxy* proxy : proxies) {
    resolve_and_mipmap(gpu, proxy);          // Unconditional execution
}

Inside this->flush(), the drawing manager first closes all tasks in the DAG via this->closeAllTasks() (which calls GrRenderTask::makeClosed()). During this closing phase, if any drawing operations were recorded, OpsTask::onMakeClosed() returns kTargetDirty, which triggers rtProxy->markMSAADirty() on the target proxy’s render target [Cite: third_party/skia/src/gpu/ganesh/GrRenderTask.cpp:84-94].

If the flush subsequently fails—for example, due to a resource allocator instantiation failure under memory pressure or a pre-flush callback failure (such as AtlasPathRenderer::preFlush() failing to allocate atlas space)—this->executeRenderTasks() is skipped entirely, and the DAG of render tasks is discarded via this->removeRenderTasks() [Cite: third_party/skia/src/gpu/ganesh/GrDrawingManager.cpp:172-214].

Because the render tasks never ran, nothing was cleared or written to the persistent MSAA color attachment. However, because resolve_and_mipmap() runs unconditionally, it checks rtProxy->isMSAADirty() (which is true from the closed task) and executes a GPU-side resolve (vkCmdResolveImage or glBlitFramebuffer) [Cite: third_party/skia/src/gpu/ganesh/GrDrawingManager.cpp:499-504]. This copies the uninitialized or recycled cross-origin scratch MSAA buffer contents directly into the readable single-sample SharedImage texture.

Since Out-of-Process Rasterization (OOP-R) renderers share a single GrDirectContext (and hence a single GrResourceCache) in the GPU process, the recycled MSAA attachments may contain pixel data belonging to other origins.


Potential Trigger Steps

Note: These are potential steps because our tooling currently lacks the ability to execute code and verify the exploit chain with a running Proof of Concept.

  1. From a compromised renderer, create a SharedImage and issue BeginRasterCHROMIUM with msaa_mode = gpu::raster::kMSAA and msaa_sample_count >= 2 on a platform where automatic MSAA resolve is disabled (e.g., Vulkan or plain GL).
  2. Record draw operations (such as path drawing via AtlasPathRenderer) to ensure the OpsTask is marked as dirty.
  3. Exhaust GPU resources or simulate memory pressure to induce a failure in AtlasPathRenderer::preFlush() or GrResourceAllocator::assign() on the target flush.
  4. Invoke EndRasterCHROMIUM. When the GPU process handles the flush, flush() fails and removes the tasks, but resolve_and_mipmap() executes anyway.
  5. Read back the SharedImage texture to obtain the resolved, uninitialized, or recycled cross-origin GPU memory residue.

Suggested Fix

To prevent resolving dirty surfaces when the drawing operations have been aborted, resolve_and_mipmap should only execute if this->flush() succeeded. Gate the resolve loop on the success of didFlush:

    bool didFlush = this->flush(proxies, access, info, newState);
    if (didFlush) {
        for (GrSurfaceProxy* proxy : proxies) {
            resolve_and_mipmap(gpu, proxy);
        }
    }

Evaluated with Chrome root at commit: bf775e5d75cb9e1767e2cd02cc93efa0077d14a5


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