High watch CVSS 4.3 chrome Uninitialized Memory 🔧 Commit mapped

Overview

High
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized resource in Skia
DescriptionUninitialized resource in Skia
ComponentSkia
Sub-componentSkia
Bug ClassUninitialized Memory
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker561994362
Fix commitde4926eaa4d8 (chromium/src) +12/-11
CWECWE-908
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-09-15 (per vendor advisory)
Fix landed2026-09-23 (public git)
Disclosed2026-09-29
Patch gap6 days — fix public in source before the advisory

Background

Shared image
A GPU-backed image resource shared between command-buffer decoders (raster, WebGPU, GL) that carries a “cleared” state so consumers know whether its backing memory has been initialized.
`SetCleared()` / `IsCleared()`
Methods on the shared image that mark, and query, whether the resource’s backing memory has been fully initialized; an uncleared region read before initialization exposes stale GPU memory.
Ganesh flush/submit
Skia’s Ganesh (GL/Vulkan) backend records drawing lazily; GrDirectContext::flush()/submit() (or skgpu::ganesh::Flush()) push the recorded work to the GPU and can fail (e.g. context loss, allocation failure).
`should_clear_shared_image_`
A deferred flag in RasterDecoderImpl recording that the shared image may be marked cleared once the pending GPU work is confirmed flushed successfully.

Root Cause Analysis

In the raster clear path (the enclosing function in raster_decoder.cc, whose name is not shown in the @@ -2972 hunk), when needs_clear was true the decoder drew the clear color into raster_canvas_ and then, for the non-Graphite/Ganesh case, immediately called shared_image_->SetCleared() — marking the resource initialized before the deferred GPU clear had actually been flushed and submitted. The flush helpers themselves discarded their success signal: the function in shared_context_state.cc called gr_context()->flush() then returned gr_context()->submit(...) (dropping the flush result) and ignored the return of skgpu::ganesh::Flush(surface), and webgpu_decoder_impl.cc likewise ignored skgpu::ganesh::Flush(surface). Consequently, if the GPU flush failed the clear draw never landed, yet the shared image was still recorded as cleared, so a later consumer could read uninitialized GPU memory while IsCleared() returned true.

The fix defers the cleared mark by setting should_clear_shared_image_ = true and only marking the image cleared after a successful flush, and propagates flush/submit success up every call site (flushAndSubmit(...).fSuccess, &= ...Flush(surface).fSuccess, and an early return false in the WebGPU clear path). This restores the invariant that a shared image is marked cleared only after its initializing GPU work has demonstrably completed.

Key insight
The core mistake was setting the shared image’s cleared state before confirming that the deferred GPU clear actually flushed, combined with flush helpers that silently swallowed their success/failure result; the fix threads the flush success value through and gates the SetCleared() on it.

Attack Path

  1. Allocate a shared image needing initialization An attacker in the renderer (via WebGL/2D-canvas/WebGPU as appropriate) causes creation of a shared image whose region requires a clear, driving the needs_clear branch in raster_decoder.cc.
  2. Induce a GPU flush failure The attacker arranges conditions under which gr_context()’s flush/submit or skgpu::ganesh::Flush(surface) fails (e.g. resource exhaustion or context loss), so the deferred clear never reaches the GPU (inferred).
  3. Image marked cleared despite failed clear Pre-patch, the code still called shared_image_->SetCleared() (and the DCHECK(... IsCleared()) masked it in debug builds only), so IsCleared() returns true over uninitialized backing memory.
  4. Read back uninitialized memory The attacker samples/copies the shared image (e.g. drawImage, texImage, readback), obtaining the contents of uninitialized GPU memory and leaking data left by other contexts (inferred).

Impact Assessment

An attacker able to trigger a failed GPU flush could cause a shared image to be treated as initialized while its backing memory was never cleared, yielding a read of uninitialized GPU memory in the GPU process — an information-disclosure primitive. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 4.3. Any use of the leaked contents beyond disclosure is (inferred). The precondition is the ability to reach the clear path and to make the underlying Ganesh flush fail.

Changed Functions

FunctionChangeNotes
if
gpu/command_buffer/service/raster_decoder.cc
modified
for
gpu/command_buffer/service/shared_context_state.cc
modified

Files Changed

  • gpu/command_buffer/service/shared_context_state.cc
  • gpu/command_buffer/service/webgpu_decoder_impl.cc
  • gpu/command_buffer/service/raster_decoder.cc

Audit Directions

  • State-before-completion
    Look for places that set a “cleared”/“initialized”/“valid” flag (e.g. SetCleared()) before the asynchronous or deferred work that establishes that state has been confirmed to complete.
  • Discarded success results
    Audit call sites of flush(), submit(), flushAndSubmit(), and skgpu::ganesh::Flush() whose returned GrSubmitInfo/fSuccess is ignored, especially where the outcome should gate a security-relevant state transition.
  • DCHECK-masked invariants
    Review invariants enforced only via DCHECK (like the removed IsCleared() assertion) that are silently violated in release builds, particularly around GPU resource initialization.
From de4926eaa4d86714638bb61ee68221a03c64ffbd Mon Sep 17 00:00:00 2001
From: Michael Ludwig <[email protected]>
Date: Wed, 23 Sep 2026 15:05:30 -0700
Subject: [PATCH] [skia] Update Ganesh flush calls to propagate success

Ganesh's flush now reports success, so update call sites to include that
in their `success` results that were already being propagated up.
Additionaly, raster_decoder's SetCleared() logic is unified now between
Ganesh and Graphite to only mark the shared image as cleared after the
flush is successful.

Bug: 561994362
Fixed: 561994362
Change-Id: I8dbe91d8f6481af4542ff8f4023811efa6966f87
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8449772
Commit-Queue: Michael Ludwig <[email protected]>
Reviewed-by: Kyle Charbonneau <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1703817}
---
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
index 02d7ada..e5770e00 100644
--- a/gpu/command_buffer/service/raster_decoder.cc
+++ b/gpu/command_buffer/service/raster_decoder.cc
@@ -2972,13 +2972,10 @@
   // incorrect.
   if (needs_clear) {
     raster_canvas_->drawColor(sk_color_4f, SkBlendMode::kSrc);
-    if (graphite_shared_context()) {
-      should_clear_shared_image_ = true;
-    } else {
-      shared_image_->SetCleared();
-    }
+    // Remember that we can mark the shared image as cleared if the deferred
+    // GPU work is flushed successfully.
+    should_clear_shared_image_ = true;
   }
-  DCHECK(graphite_shared_context() || shared_image_->IsCleared());
 }
 
 scoped_refptr<Buffer> RasterDecoderImpl::GetShmBuffer(uint32_t shm_id) {
diff --git a/gpu/command_buffer/service/shared_context_state.cc b/gpu/command_buffer/service/shared_context_state.cc
index 78dfbec..836f4e9c 100644
--- a/gpu/command_buffer/service/shared_context_state.cc
+++ b/gpu/command_buffer/service/shared_context_state.cc
@@ -880,8 +880,9 @@
                                           : skgpu::graphite::SyncToCpu::kNo);
     return flush_succeeded;
   } else if (gr_context()) {
-    gr_context()->flush();
-    return gr_context()->submit(sync_to_cpu ? GrSyncCpu::kYes : GrSyncCpu::kNo);
+    return gr_context()
+        ->flushAndSubmit(sync_to_cpu ? GrSyncCpu::kYes : GrSyncCpu::kNo)
+        .fSuccess;
   }
   return true;
 }
@@ -907,7 +908,7 @@
       for (int plane_index = 0; plane_index < num_planes; plane_index++) {
         auto* surface = access->surface(plane_index);
         DCHECK(surface);
-        skgpu::ganesh::Flush(surface);
+        success &= skgpu::ganesh::Flush(surface).fSuccess;
       }
     }
   }
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index cc965727..fea1fd4c 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -847,7 +847,7 @@
       // will populate it with semaphores and call GrDirectContext::flush.
       success = true;
       if (shared_context_state_->gr_context()) {
-        skgpu::ganesh::Flush(surface);
+        success = skgpu::ganesh::Flush(surface).fSuccess;
       } else {
         DCHECK(shared_context_state_->graphite_shared_context());
         DCHECK(shared_context_state_->gpu_main_graphite_recorder());
@@ -2479,7 +2479,10 @@
   // It's ok to pass in empty GrFlushInfo here since SignalSemaphores()
   // will populate it with semaphores and call GrDirectContext::flush.
   if (shared_context_state_->gr_context()) {
-    skgpu::ganesh::Flush(surface);
+    if (!skgpu::ganesh::Flush(surface).fSuccess) {
+      DLOG(ERROR) << "ClearSharedImage: skgpu::ganesh::Flush failed";
+      return false;
+    }
   } else {
     DCHECK(shared_context_state_->graphite_shared_context());
     DCHECK(shared_context_state_->gpu_main_graphite_recorder());
Loading diff…