CVE-2026-13950
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ScopedRasterizerDiscardResetgpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc |
modified | |
ifgpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc |
modified |
Files Changed
gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
Patch
From 31361adc51ada04a228b3c9f2693974be8a01fc0 Mon Sep 17 00:00:00 2001
From: Geoff Lang <geofflang@chromium.org>
Date: Tue, 26 May 2026 01:13:23 -0700
Subject: [PATCH] Disable GL_RASTERIZER_DISCARD during shared image clearing.
Clear commands are ignored while rasterizer discard is enabled.
Fixed: 513360781
Change-Id: I1d13c08b5004d9ff01f5781e87c494958eeb6b17
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7874936
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1635974}
---
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
index b42d917..f9a8cd8 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
@@ -175,6 +175,31 @@
GLboolean scissor_test_;
};
+class ScopedRasterizerDiscardReset {
+ public:
+ explicit ScopedRasterizerDiscardReset(gl::GLApi* api,
+ bool rasterizer_discard_available)
+ : api_(api), rasterizer_discard_available_(rasterizer_discard_available) {
+ if (rasterizer_discard_available_) {
+ api_->glGetBooleanvFn(GL_RASTERIZER_DISCARD, &rasterizer_discard_);
+ }
+ }
+ ~ScopedRasterizerDiscardReset() {
+ if (rasterizer_discard_available_) {
+ if (rasterizer_discard_) {
+ api_->glEnableFn(GL_RASTERIZER_DISCARD);
+ } else {
+ api_->glDisableFn(GL_RASTERIZER_DISCARD);
+ }
+ }
+ }
+
+ private:
+ raw_ptr<gl::GLApi> api_;
+ const bool rasterizer_discard_available_;
+ GLboolean rasterizer_discard_;
+};
+
template <typename ClientType, typename ServiceType, typename DeleteFunction>
void DeleteServiceObjects(ClientServiceMap<ClientType, ServiceType>* id_map,
bool have_context,
@@ -602,6 +627,8 @@
auto texture = representation_->GetTexturePassthrough();
const bool use_oes_draw_buffers_indexed =
impl->features().oes_draw_buffers_indexed;
+ bool has_rasterizer_discard =
+ impl->GetFeatureInfo()->gl_version_info().IsAtLeastGLES(3, 0);
// Back up all state we are about to change.
gl::GLApi* api = impl->api();
@@ -614,6 +641,8 @@
ScopedColorMaskZeroReset color_mask_reset(api,
use_oes_draw_buffers_indexed);
ScopedScissorTestReset scissor_test_reset(api);
+ ScopedRasterizerDiscardReset rasterizer_discard_reset(
+ api, has_rasterizer_discard);
// Generate a new framebuffer and bind the shared image's uncleared texture
// to it.
@@ -632,6 +661,9 @@
api->glColorMaskFn(true, true, true, true);
api->glDisableFn(GL_SCISSOR_TEST);
api->glClearFn(GL_COLOR_BUFFER_BIT);
+ if (has_rasterizer_discard) {
+ api->glDisableFn(GL_RASTERIZER_DISCARD);
+ }
if (api->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER) ==
GL_FRAMEBUFFER_COMPLETE) {
Original Bug Report
VRAM Leak in Passthrough Decoder via GL_RASTERIZER_DISCARD Bypass
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: The GLES2 passthrough decoder fails to disable GL_RASTERIZER_DISCARD during its internal SharedImage initialization clears. A compromised renderer can exploit this by enabling the discard state to silently skip mandatory memory clearing, allowing it to exfiltrate uninitialized GPU memory.
Affected files:
gpu/command_buffer/service/gles2_cmd_decoder_passthrough.ccgpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
Estimated timestamp from git blame: 2019-12-19
Summary
A potential security vulnerability exists in PassthroughResources::SharedImageData::EnsureClear within the GLES2 passthrough decoder. This function is responsible for ensuring that SharedImage textures are cleared (zeroed out) before being accessed by a renderer, preventing the disclosure of uninitialized GPU memory (VRAM residue). However, the implementation does not ensure that the GL_RASTERIZER_DISCARD state is disabled before issuing the glClear command.
In OpenGL ES 3.0 and its implementation in ANGLE, the glClear operation is explicitly discarded or becomes a no-op if GL_RASTERIZER_DISCARD is enabled. Because the passthrough decoder shares the same GL context state as the renderer, a compromised renderer can enable this state to bypass the mandatory initialization clear. The GPU process subsequently marks the SharedImage as cleared, allowing the renderer to read potentially sensitive cross-origin data from the uninitialized backing memory.
Root Cause Analysis
The vulnerability is located in gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc within the EnsureClear function:
void PassthroughResources::SharedImageData::EnsureClear(
const GLES2DecoderPassthroughImpl* impl) {
if (!representation_->IsCleared()) {
// ... state resets ...
ScopedScissorTestReset scissor_test_reset(api);
// Missing: reset for GL_RASTERIZER_DISCARD
// ... FBO setup ...
api->glClearFn(GL_COLOR_BUFFER_BIT); // No-op if RASTERIZER_DISCARD is enabled
if (api->glCheckFramebufferStatusEXTFn(GL_FRAMEBUFFER) ==
GL_FRAMEBUFFER_COMPLETE) {
representation_->SetCleared(); // Mark as cleared even if glClear was skipped
}
// ...
}
}
Unlike the validating decoder (GLES2DecoderImpl), which explicitly handles GL_RASTERIZER_DISCARD during its clear routines (see gpu/command_buffer/service/gles2_cmd_decoder.cc:7096), the passthrough decoder lacks this protection. This is particularly significant because ANGLE’s Context::clear implementation (used by the passthrough decoder) contains an explicit early return when this state is enabled (third_party/angle/src/libANGLE/Context.cpp:4987).
Potential Attack Sequence
Note: These are suggested steps for triggering the vulnerability; our analysis is based on code review without a running proof-of-concept.
- A compromised renderer enables rasterizer discard using
glEnable(GL_RASTERIZER_DISCARD). - The renderer creates a new
SharedImage(e.g., viaCreateSharedImage) which is initially uncleared. - The renderer consumes this
SharedImage(e.g., viaCreateAndTexStorage2DSharedImageINTERNAL), triggering the vulnerableEnsureClearcall in the GPU process. EnsureClearissues aglClearthat ANGLE ignores due to theGL_RASTERIZER_DISCARDstate, but the GPU process still marks the image as cleared.- The renderer disables discard and reads from the texture (e.g., via
glReadPixelsor shader sampling), obtaining uninitialized VRAM residue.
Impact
This is a high-severity information disclosure vulnerability. Successful exploitation allows a compromised renderer to leak stale VRAM contents, which may contain sensitive data such as rendered frames or compositor tiles from other browser tabs or system processes.
Suggested Fix
Implement a ScopedRasterizerDiscardReset helper class in gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc that queries and disables GL_RASTERIZER_DISCARD if supported (GLES 3.0+), and use this helper in EnsureClear to ensure the clear operation is always performed.
Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e
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.