Medium watch CVSS 5.3 chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
5.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized Use in GPU
DescriptionUninitialized Use in GPU
ComponentGPU
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.
Tracker513360781
Fix commit31361adc51ad (chromium/src) +32/-0
CWECWE-457 (Use of uninitialized)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-05-15 (per vendor advisory)
Fix landed2026-05-26 (public git)
Disclosed2026-06-30
Patch gap35 days — fix public in source before the advisory

Background

Shared image clearing
a GPU-service step in the passthrough decoder that binds an uncleared shared-image texture to a framebuffer and issues glClear(GL_COLOR_BUFFER_BIT) to zero-initialize it before first use.
`GL_RASTERIZER_DISCARD`
an OpenGL ES 3.0+ pipeline state that, when enabled, causes clear and draw commands to be ignored (no fragments are produced or written).
`ScopedRasterizerDiscardReset`
a new RAII helper that snapshots the current GL_RASTERIZER_DISCARD value (when available) in its constructor and restores it in its destructor.
`IsAtLeastGLES(3, 0)`
a gl_version_info predicate used to determine whether GL_RASTERIZER_DISCARD exists on the current context.

Root Cause Analysis

The shared-image clearing routine in gles2_cmd_decoder_passthrough.cc backs up and neutralizes several pieces of GL state (color mask, scissor test, etc.) before calling glClear(GL_COLOR_BUFFER_BIT) to initialize an uncleared texture, but it did not manage GL_RASTERIZER_DISCARD. Because clear commands are ignored while GL_RASTERIZER_DISCARD is enabled, if that state was left enabled by prior client activity the glClear was silently dropped and the shared image’s texture remained uninitialized. The invariant that the clear must actually zero the texture before it is treated as cleared was thus violated by inherited rasterizer-discard state.

The fix computes has_rasterizer_discard via gl_version_info().IsAtLeastGLES(3, 0), installs a ScopedRasterizerDiscardReset to save and later restore the state, and explicitly calls glDisableFn(GL_RASTERIZER_DISCARD) before the glClear, guaranteeing the clear executes; the RAII object restores the original value afterward so surrounding client state is preserved.

Key insight
The clear path neutralized other output-suppressing states but not GL_RASTERIZER_DISCARD, so a client-enabled discard silently skipped the clear and left the shared image uninitialized; disabling it for the clear (and restoring it via RAII) ensures the texture is actually zeroed.

Attack Path

  1. Use an ES 3.0+ / WebGL 2 context A page obtains a context where GL_RASTERIZER_DISCARD is available (IsAtLeastGLES(3, 0)).
  2. Enable rasterizer discard The client enables GL_RASTERIZER_DISCARD and leaves it set before an operation that requires a shared image to be lazily cleared.
  3. Trigger shared-image clearing An operation causes the passthrough decoder to clear the uncleared shared image; pre-patch, with rasterizer discard enabled, the glClear(GL_COLOR_BUFFER_BIT) is ignored and the texture is left uninitialized while being marked cleared.
  4. Read uninitialized contents The page consumes or reads back the shared image and obtains residual GPU memory instead of zeroed data, an uninitialized-memory disclosure. (inferred)

Impact Assessment

An attacker running ES3/WebGL2 content can cause a shared image’s lazy clear to be skipped, leaving it backed by uninitialized GPU memory that may subsequently be read, an information-disclosure primitive in the GPU process. The advisory classifies this as uninitialized use in GPU with no exploited-in-the-wild status stated; escalation beyond disclosure of stale GPU memory is not demonstrated by the patch and is (inferred). The precondition is a context that supports GL_RASTERIZER_DISCARD with that state enabled when the shared-image clear occurs.

Changed Functions

FunctionChangeNotes
ScopedRasterizerDiscardReset
gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
modified
if
gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
modified

Files Changed

  • gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc

Audit Directions

  • Clear/draw suppression states
    Audit every internal glClear or emulation draw used to initialize a resource to confirm all output-suppressing states (GL_RASTERIZER_DISCARD, scissor, color/depth/stencil masks, sample mask) are neutralized first.
  • Lazy-clear correctness
    Review shared-image and texture lazy-clear paths to verify the resource is actually written before it is marked cleared, regardless of inherited client pipeline state.
  • RAII state save/restore completeness
    Check that scoped state-reset helpers around internal GPU operations cover the full set of relevant states and correctly restore them, matching this new ScopedRasterizerDiscardReset pattern.
From 31361adc51ada04a228b3c9f2693974be8a01fc0 Mon Sep 17 00:00:00 2001
From: Geoff Lang <[email protected]>
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 <[email protected]>
Commit-Queue: Corentin Wallez <[email protected]>
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) {
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • gpu/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.

  1. A compromised renderer enables rasterizer discard using glEnable(GL_RASTERIZER_DISCARD).
  2. The renderer creates a new SharedImage (e.g., via CreateSharedImage) which is initially uncleared.
  3. The renderer consumes this SharedImage (e.g., via CreateAndTexStorage2DSharedImageINTERNAL), triggering the vulnerable EnsureClear call in the GPU process.
  4. EnsureClear issues a glClear that ANGLE ignores due to the GL_RASTERIZER_DISCARD state, but the GPU process still marks the image as cleared.
  5. The renderer disables discard and reads from the texture (e.g., via glReadPixels or 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.

View on issue tracker