CVE-2026-7972
Overview
Background
- RasterDecoderImpl
- the GPU-process command-buffer decoder that services raster/canvas GL commands on behalf of untrusted renderer clients.
- `DoWritePixelsINTERNAL`
- the decoder handler (named in the commit subject) that copies client-supplied pixel data into a destination
SkCanvas/shared image. - Scoped write access
dest_scoped_accessis an RAII handle to the destination image whose contents are committed to the GPU byFlushWriteAccess.- Uninitialized use
- reading or publishing memory whose bytes were never written with valid data.
Root Cause Analysis
In the !written branch, the code recognized that the pixel write into the SkCanvas had failed and raised GL_INVALID_OPERATION via LOCAL_SET_GL_ERROR, but it then fell through and still called shared_context_state_->FlushWriteAccess(dest_scoped_access.get()). Because the destination was never successfully populated, flushing committed a destination image backed by uninitialized (or stale) memory. The invariant violated is that a write-access handle must only be flushed after a successful write; on the failure path that invariant was broken by the missing early exit.
The fix inserts return; immediately after setting the GL error, so the failure path no longer reaches FlushWriteAccess, leaving the uninitialized destination uncommitted. This is a straightforward addition of an early-exit guard before the sink, not a change to any validation condition.
FlushWriteAccess on the unwritten destination; adding return; after the error ensures the uninitialized buffer is never flushed.Attack Path
- Issue a crafted WritePixels command
A compromised or malicious renderer sends a raster
glWritePixelscommand whose parameters cause theSkCanvaswrite to fail (written == false). - Error is set but execution continues
The handler records
GL_INVALID_OPERATIONyet, pre-patch, does not return, so control proceeds to the flush step. - Flush of uninitialized destination
FlushWriteAccess(dest_scoped_access.get())commits the destination image even though no valid pixels were written, publishing uninitialized memory. - Observe leaked contents The attacker reads back the destination (e.g. via subsequent GPU reads/compositing) to recover uninitialized GPU-process memory (inferred: an information-disclosure primitive; the patch demonstrates the uninitialized flush, not a concrete readback).
Impact Assessment
SkCanvas write while reaching the flush path.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgpu/command_buffer/service/raster_decoder.cc |
modified |
Files Changed
gpu/command_buffer/service/raster_decoder.cc
Audit Directions
- Error-set-but-no-returnSearch decoder handlers for paths that call
LOCAL_SET_GL_ERROR(or similar) without an immediatereturn, allowing execution to continue into commit/flush/use sinks. - Flush after failed writeAudit scoped write-access patterns (
FlushWriteAccess,EndWriteAccess) to confirm they are reached only when the preceding write succeeded. - Partial-success GPU operationsReview raster/skia operations whose success is captured in a boolean that must gate every downstream publish of the destination resource.
Patch
From e5d38dd102bdab1617cbf0db17f1c9292cb5c0ec Mon Sep 17 00:00:00 2001
From: Yuki Shiino <yukishiino@chromium.org>
Date: Wed, 01 Apr 2026 22:20:34 -0700
Subject: [PATCH] fortify: Fix a bailout in RasterDecoderImpl::DoWritePixelsINTERNAL
Bug: 497546281
Change-Id: Ibe76a24636c0c46e86cff3cbf622ed432b116b89
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7720035
Auto-Submit: Yuki Shiino <yukishiino@chromium.org>
Reviewed-by: Khushal Sagar <khushalsagar@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1608978}
---
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
index dad0ff5..4424789b 100644
--- a/gpu/command_buffer/service/raster_decoder.cc
+++ b/gpu/command_buffer/service/raster_decoder.cc
@@ -2033,6 +2033,7 @@
if (!written) {
LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glWritePixels",
"Failed to write pixels to SkCanvas");
+ return;
}
shared_context_state_->FlushWriteAccess(dest_scoped_access.get());
Original Bug Report
Uninitialized GPU memory disclosure in RasterDecoderImpl::DoWritePixelsINTERNAL
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A missing return statement in RasterDecoderImpl::DoWritePixelsINTERNAL allows uninitialized GPU texture memory to be incorrectly marked as cleared when a write operation fails. A compromised renderer can exploit this to bypass initialization checks and leak cross-origin GPU memory.
Affected files:
gpu/command_buffer/service/raster_decoder.cc
Estimated timestamp from git blame: 2020-04-15
Summary
There is a potential high-severity information disclosure vulnerability in gpu/command_buffer/service/raster_decoder.cc. In RasterDecoderImpl::DoWritePixelsINTERNAL, if the Skia canvas->writePixels operation fails, the code logs a GL error but fails to return early. This allows execution to fall through and incorrectly mark the destination SharedImage as fully initialized, permitting a compromised renderer to read back uninitialized, potentially cross-origin GPU memory.
Technical Details
When processing a pixel upload to a SharedImage, the function DoWritePixelsINTERNAL executes the following logic on the Skia fallback path:
bool written =
canvas->writePixels(src_info, pixel_data, row_bytes, x_offset, y_offset);
if (!written) {
LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glWritePixels",
"Failed to write pixels to SkCanvas");
// VULNERABILITY: Missing return statement here
}
// ... synchronization code ...
if (!dest_shared_image->IsCleared()) {
dest_shared_image->SetClearedRect(
gfx::Rect(x_offset, y_offset, src_width, src_height));
}
An attacker can intentionally trigger the Skia write failure by providing parameters that pass the decoder’s initial validation but are rejected by Skia. For instance, setting src_sk_alpha_type to 0 (kUnknown_SkAlphaType) passes the enum range check in RasterDecoderImpl (which just checks <= kLastEnum_SkAlphaType) but causes SkColorInfoIsValid to fail inside Skia’s internal checks, resulting in canvas->writePixels returning false without modifying the GPU memory.
Because there is no return statement inside the if (!written) block, execution erroneously continues and dest_shared_image->SetClearedRect is called. If the attacker provided dimensions that cover the entire texture, the SharedImage is marked as fully cleared.
The attacker can then issue a readback command (e.g., ReadbackARGBImagePixelsINTERNALImmediate). Since the IsCleared() security check now returns true, the system will happily copy the uninitialized GPU memory—which may contain sensitive textures from other tabs, WebGL framebuffers, or browser UI tiles—back to a shared memory buffer accessible to the attacker.
Suggested Attacker Steps
Note: These are potential steps, as our tooling agent does not yet have the ability to run code to verify them with a live proof-of-concept.
- Compromise Renderer: Gain code execution in a renderer process.
- Create SharedImage: Create an uninitialized
SharedImagevia the GPU command buffer withSHARED_IMAGE_USAGE_RASTER_WRITE | SHARED_IMAGE_USAGE_RASTER_READusages. - Bypass Fast Path: Send a
WritePixelsINTERNALImmediatecommand. Provide a validSkColorSpacethat deliberately does not match the destination texture’s color space to bypass the direct-upload fast path. - Trigger Failure: In the same command, set
src_sk_alpha_typeto0(kUnknown_SkAlphaType) and providex_offset=0,y_offset=0, and dimensions matching the full texture size. - Exploit Fallthrough: The GPU process will fail to write the pixels in Skia, log a
GL_INVALID_OPERATIONerror, but incorrectly mark the texture as cleared due to the missingreturnstatement. - Readback Memory: Send a
ReadbackARGBImagePixelsINTERNALImmediatecommand for the exact same mailbox. The initialization security gate will pass, and uninitialized GPU memory will be copied into shared memory accessible by the compromised renderer.
Suggested Fix
Add a return; statement immediately after logging the error inside the if (!written) block in RasterDecoderImpl::DoWritePixelsINTERNAL:
if (!written) {
LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, "glWritePixels",
"Failed to write pixels to SkCanvas");
return; // <--- ADD THIS
}
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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.