CVE-2026-13923
Overview
Background
- Luma emulation texture copy
- a GPU-service fallback in
gles2_cmd_copy_tex_image.ccthat emulates luminance/alpha texture formats by drawing into a destination texture, used when the driver lacks native support. - `GL_RASTERIZER_DISCARD`
- an OpenGL ES 3.0 / WebGL 2 pipeline state that, when enabled, discards all primitives before rasterization, so draw calls produce no fragments and write nothing.
- `IsWebGL2OrES3OrHigherContext`
- a
FeatureInfopredicate indicating the context is at least WebGL 2 / ES 3.0, i.e. the level at whichGL_RASTERIZER_DISCARDexists. - Uninitialized texture use
- reading a texture whose backing store was never written, yielding whatever residual GPU memory occupied it.
Root Cause Analysis
The luma-emulation copy routine backs up and neutralizes a set of pipeline states (depth mask, blend, dither, window rectangles, etc.) before performing the emulation draw that writes the destination texture, but it did not account for GL_RASTERIZER_DISCARD. On a WebGL 2 / ES 3.0 context where the client had left GL_RASTERIZER_DISCARD enabled, the emulation draw would be entirely discarded before rasterization, so the copy silently produced no output and the destination texture was left with uninitialized (residual) contents. The invariant that the emulation path must actually populate the destination was violated by inherited client rasterizer-discard state.
The fix adds, guarded by IsWebGL2OrES3OrHigherContext, a glDisable(GL_RASTERIZER_DISCARD) alongside the other state neutralizations so the emulation draw is guaranteed to rasterize and write the texture.
GL_RASTERIZER_DISCARD, letting a client-enabled discard silently skip the copy and leave the texture uninitialized; explicitly disabling it (on contexts where it exists) restores the guarantee that the copy runs.Attack Path
- Use a WebGL 2 / ES 3.0 context
A page (or worker) obtains a WebGL 2 context so that
GL_RASTERIZER_DISCARDis available. - Enable rasterizer discard
The client enables
GL_RASTERIZER_DISCARDviaenable(RASTERIZER_DISCARD)and leaves it set. - Trigger the luma emulation copy The page performs a texture operation that routes into the luma-emulation copy path; pre-patch the emulation draw is discarded before rasterization, so the destination texture is never written.
- Read back uninitialized memory The page samples or reads back the destination texture and obtains residual GPU memory contents rather than the intended copied data, an uninitialized-memory disclosure. (inferred)
Impact Assessment
GL_RASTERIZER_DISCARD enabled at the time the emulation copy runs.Files Changed
gpu/command_buffer/service/gles2_cmd_copy_tex_image.cc
Audit Directions
- Incomplete state backup/resetReview GPU-service helper routines that snapshot and neutralize GL state before an internal draw/copy to confirm they cover all states that can suppress output (rasterizer discard, scissor, color/depth masks, stencil, sample masks).
- Client-inherited pipeline stateAudit internal driver-emulation and blit paths for reliance on default pipeline state that a client could have changed, especially ES3/WebGL2-only states absent on older contexts.
- Silent no-op drawsLook for emulation or clear/copy operations whose result is assumed but not verified, where a discarded or scissored draw would leave a resource uninitialized.
Patch
From 7c0c88c63a9513aecc436d60226ec0e12194d60f Mon Sep 17 00:00:00 2001
From: Ken Russell <kbr@chromium.org>
Date: Mon, 11 May 2026 22:10:17 -0700
Subject: [PATCH] Disable rasterizer discard during luma emulation texture copy.
The copy could otherwise be accidentally skipped.
Fixed: 511772034
Change-Id: I301da32524fb8949b516fb8f5d19765bec202673
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7836292
Reviewed-by: Brandon Jones <bajones@chromium.org>
Commit-Queue: Kenneth Russell <kbr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1629062}
---
diff --git a/gpu/command_buffer/service/gles2_cmd_copy_tex_image.cc b/gpu/command_buffer/service/gles2_cmd_copy_tex_image.cc
index a08bb3ac4..0d8da6e 100644
--- a/gpu/command_buffer/service/gles2_cmd_copy_tex_image.cc
+++ b/gpu/command_buffer/service/gles2_cmd_copy_tex_image.cc
@@ -232,6 +232,9 @@
glDepthMask(GL_FALSE);
glDisable(GL_BLEND);
glDisable(GL_DITHER);
+ if (decoder->GetFeatureInfo()->IsWebGL2OrES3OrHigherContext()) {
+ glDisable(GL_RASTERIZER_DISCARD);
+ }
if (decoder->GetFeatureInfo()->feature_flags().ext_window_rectangles) {
glWindowRectanglesEXT(GL_EXCLUSIVE_EXT, 0, nullptr);
}
Original Bug Report
Potential GPU VRAM leak via GL_RASTERIZER_DISCARD during LUMA emulation blit
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: A malicious WebGL2 page on Android can potentially leak uninitialized GPU memory (VRAM) containing cross-origin pixel data. This occurs because the internal blitter used for LUMA texture emulation fails to disable GL_RASTERIZER_DISCARD, allowing an attacker to discard the internal initialization draw call. The resulting uninitialized memory is then copied to an attacker-accessible texture and incorrectly marked as cleared by Chromium’s safety mechanisms.
Affected files:
gpu/command_buffer/service/gles2_cmd_copy_tex_image.ccgpu/command_buffer/service/gles2_cmd_decoder.cc
Estimated timestamp from git blame: 2017-11-03
Summary
A vulnerability exists in the Chromium GPU process on Android when using the validating command decoder with native OpenGL ES 3 drivers. The CopyTexImageResourceManager uses an internal draw call to emulate legacy LUMINANCE, ALPHA, and LUMINANCE_ALPHA textures during copyTexImage2D and copyTexSubImage2D operations. However, the emulation logic fails to ensure that the GL_RASTERIZER_DISCARD state is disabled. A malicious WebGL application can enable this state to cause the internal blit to be silently discarded, resulting in uninitialized driver memory being copied into a texture and marked as cleared, bypassing Chromium’s lazy-clearing protections.
Technical Details
When a WebGL application requests a copy to a LUMA format texture on hardware that requires emulation, the command is routed to CopyTexImageResourceManager::DoCopyTexSubImageToLUMACompatibilityTexture (gpu/command_buffer/service/gles2_cmd_copy_tex_image.cc).
The emulation process involves the following steps:
- Allocation: A temporary scratch texture (
scratch_textures_[1]) is allocated usingglTexImage2D(..., nullptr)(line 217). Passingnullptrcauses the GPU driver to allocate uninitialized VRAM. - State Configuration: The method configures the GL state for an internal blit, explicitly disabling tests like
GL_SCISSOR_TESTandGL_DEPTH_TEST(lines 227-234). Crucially, it fails to disableGL_RASTERIZER_DISCARD. - Discarded Draw: The method issues
glDrawArrays(GL_TRIANGLES, 0, 6)(line 242) to render swizzled data into the scratch texture. If the attacker previously calledgl.enable(gl.RASTERIZER_DISCARD), the OpenGL driver will silently discard all primitives. The draw call does nothing, and the scratch texture remains completely uninitialized. - VRAM Copy: The method calls
glCopyTexSubImage2D(line 250) to copy from the scratch framebuffer to the destination texture. BecauseglCopyTexSubImage2Dis a pixel transfer operation, it is unaffected byGL_RASTERIZER_DISCARD. It successfully copies the uninitialized VRAM into the attacker’s texture.
Execution returns to GLES2DecoderImpl::DoCopyTexSubImage2D (gpu/command_buffer/service/gles2_cmd_decoder.cc). Because discarding primitives is a valid OpenGL operation, LOCAL_PEEK_GL_ERROR returns GL_NO_ERROR. Finding no errors, Chromium incorrectly assumes the texture was successfully initialized and updates its internal security tracking by calling texture_manager()->SetLevelCleared(texture_ref, target, level, true); (line 13724).
Impact
An attacker can sample the resulting texture to exfiltrate stale GPU memory. This memory may contain sensitive cross-origin data, such as pixels from other web pages (via canvas or video elements) or compositor tiles from other GPU-process clients. This represents a High severity (S1) Site Isolation bypass.
Suggested Reproduction Steps
Note: These steps are based on static analysis and have not been executed in a live environment.
- From a web page, obtain a WebGL2 context.
- Create and explicitly clear a source framebuffer (to bypass Chromium’s
ClearUnclearedAttachmentslogic). - Enable discard:
gl.enable(gl.RASTERIZER_DISCARD). - Perform a copy operation to a LUMA format requiring emulation:
gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, 0, 0, width, height, 0). - Disable discard:
gl.disable(gl.RASTERIZER_DISCARD). - Sample the destination texture in a fragment shader or use
gl.readPixelsto retrieve the uninitialized memory data.
Suggested Fix
In gpu/command_buffer/service/gles2_cmd_copy_tex_image.cc, update DoCopyTexSubImageToLUMACompatibilityTexture to explicitly disable GL_RASTERIZER_DISCARD alongside the other state changes before the draw call:
glDisable(GL_SCISSOR_TEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_BLEND);
glDisable(GL_DITHER);
+ if (decoder->GetFeatureInfo()->IsWebGL2OrES3OrHigherContext()) {
+ glDisable(GL_RASTERIZER_DISCARD);
+ }
Because decoder->RestoreGlobalState() is called at the end of the function (line 261), the GL_RASTERIZER_DISCARD state should be automatically restored to the user’s expected state.
Evaluated with Chrome root at commit: eca8648a4e1cdfdda68c495a6003059fed641955
Results 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.