CVE-2026-78984
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgpu/command_buffer/service/shared_context_state.cc |
modified |
Files Changed
gpu/command_buffer/service/shared_context_state.cc
Patch
From 3c83e9618750d2d05ea02e53902547f7978f7177 Mon Sep 17 00:00:00 2001
From: kylechar <kylechar@chromium.org>
Date: Tue, 21 Jul 2026 12:06:31 -0700
Subject: [PATCH] Don't clear image if snap() fails
SharedContextState::FlushGraphiteRecorder() was updated to return a bool
to indicate if flush was successful aka work was submitted to the GPU.
It still returns true if recorder snap() call fails in which case no
work is submitted to the GPU. Have it return false for snap failure as
well.
Bug: 535374213
Change-Id: Ib963b1b0b9d576b3ff6bbbe078ae116c097d301f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8128097
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Commit-Queue: Kyle Charbonneau <kylechar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1665671}
---
diff --git a/gpu/command_buffer/service/shared_context_state.cc b/gpu/command_buffer/service/shared_context_state.cc
index 26c717e..bcbf59bd 100644
--- a/gpu/command_buffer/service/shared_context_state.cc
+++ b/gpu/command_buffer/service/shared_context_state.cc
@@ -822,12 +822,13 @@
bool SharedContextState::FlushGraphiteRecorder() {
auto recording = gpu_main_graphite_recorder()->snap();
- if (recording) {
- skgpu::graphite::InsertRecordingInfo info = {};
- info.fRecording = recording.get();
- return graphite_shared_context()->insertRecording(info);
+ if (!recording) {
+ return false;
}
- return true;
+
+ skgpu::graphite::InsertRecordingInfo info = {};
+ info.fRecording = recording.get();
+ return graphite_shared_context()->insertRecording(info);
}
void SharedContextState::FlushAndSubmit(bool sync_to_cpu) {
Original Bug Report
Potential GPU uninitialized memory leak in RasterDecoder via ignored Graphite recording failures
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: RasterDecoderImpl::DoWritePixelsINTERNALDirectTextureUpload returns record-time success for Graphite texture uploads while ignoring insert-time recording failures in SharedContextState::FlushGraphiteRecorder. This can allow a compromised renderer to prematurely mark a SharedImage as cleared, bypassing the uninitialized texture gate and reading back uninitialized GPU memory. The issue potentially allows cross-origin pixel disclosure from a singleton shared GPU process.
Affected files:
gpu/command_buffer/service/raster_decoder.ccgpu/command_buffer/service/shared_context_state.cc
Estimated timestamp from git blame: 2023-05-12
Root Cause Analysis
In Chromium’s GPU service, RasterDecoderImpl::DoWritePixelsINTERNALDirectTextureUpload handles direct texture uploads when using the Skia Graphite backend. However, it returns a boolean based purely on the record-time result, while the actual command execution/insertion is handled via a void helper that discards the result.
Specifically, in gpu/command_buffer/service/raster_decoder.cc:
} else {
CHECK(graphite_shared_context());
...
written = graphite_recorder()->updateBackendTexture(
graphite_texture_ptr->texture(), &pixmap,
/*numLevels=*/1, release_proc, graphite_texture_ptr);
}
shared_context_state_->FlushWriteAccess(dest_scoped_access.get());
shared_context_state_->SubmitIfNecessary(
std::move(end_semaphores),
dest_scoped_access->NeedGraphiteContextSubmit());
return written;
Here, written receives the return value of updateBackendTexture, which only enqueues an UploadTask into Skia Graphite’s recorder. This returns true at record time before any commands are inserted or executed.
Immediately after, FlushWriteAccess is called, which cascades into SharedContextState::FlushGraphiteRecorder() in gpu/command_buffer/service/shared_context_state.cc:
void SharedContextState::FlushGraphiteRecorder() {
auto recording = gpu_main_graphite_recorder()->snap();
if (recording) {
skgpu::graphite::InsertRecordingInfo info = {};
info.fRecording = recording.get();
graphite_shared_context()->insertRecording(info); // <--- Boolean return value is ignored!
}
}
If the recording fails to insert (for instance, returning skgpu::graphite::InsertStatus::kAddCommandsFailed due to transient setup failures or allocator issues), the failure is silently discarded. No context loss is triggered because kAddCommandsFailed is categorized as a recoverable error in GraphiteSharedContext::InsertRecordingImpl.
Impact on Clear-State Tracking
Because DoWritePixelsINTERNALDirectTextureUpload returns true (the record-time success status), the caller in RasterDecoderImpl::DoWritePixelsINTERNAL prematurely marks the SharedImage as fully cleared:
if (!dest_shared_image->IsCleared()) {
dest_shared_image->SetClearedRect(
gfx::Rect(src_info.width(), src_info.height()));
}
Once marked cleared, subsequent readback requests (such as ReadbackARGBImagePixelsINTERNALImmediate) bypass the uninitialized texture gate in SkiaGraphiteImageRepresentation::BeginScopedReadAccess:
if (!IsCleared()) {
...
return nullptr; // Blocks uninitialized read
}
Furthermore, the IsCleared() status is passed to Dawn/Vulkan during readback as begin_access_desc.initialized = true, which commands Dawn to skip lazy zero-clearing of the texture. As a result, uninitialized physical GPU memory (containing residue from previously allocated/freed textures across different origins) is read directly into the renderer-owned shared memory buffer.
Suggested Potential Trigger Steps
Note: These are suggested potential steps only; our tooling currently lacks the ability to execute code and verify this sequence dynamically.
- A compromised renderer allocates an uncleared single-plane SharedImage (e.g.,
viz::SinglePlaneFormat::kBGRA_8888) withSHARED_IMAGE_USAGE_RASTER_READ | RASTER_WRITE | DISPLAY_READflags and no initial pixels. - The renderer induces transient GPU device pressure (e.g., allocating large buffers or textures via WebGPU) to make subsequent command allocations/command buffer setup fail.
- The renderer calls
WritePixelsINTERNALImmediatetargeting the SharedImage, using dimensions matching the SharedImage’s size to trigger the direct upload fast-path. - Inside the GPU process,
updateBackendTexturereturnstrueat record-time, butinsertRecordingreturnsfalsedue tokAddCommandsFailed. This failure is ignored. - The GPU process calls
SetClearedRect, marking the uninitialized texture as cleared. - The renderer calls
ReadbackARGBImagePixelsINTERNALImmediateon the mailbox. TheIsCleared()check is bypassed, and the GPU copies cross-origin texture residues into the renderer’s shared memory.
Suggested Fix
To prevent uninitialized memory disclosure, the GPU process must ensure that clear-state updates only happen if the command insertion succeeds.
We suggest updating DoWritePixelsINTERNAL and DoWritePixelsINTERNALDirectTextureUpload to propagate the actual insert status, or ensuring FlushWriteAccess can return a status indicating whether the recording was successfully inserted. If the insert fails, the SharedImage must not be marked cleared.
Evaluated with Chrome root at commit: b5b015ea5f690560237d1f0cff1405844cd12b8d
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.