CVE-2026-13923
Overview
Files Changed
gpu/command_buffer/service/gles2_cmd_copy_tex_image.cc
Patch
From 7c0c88c63a9513aecc436d60226ec0e12194d60f Mon Sep 17 00:00:00 2001 From: Ken Russell <[email protected]> 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 <[email protected]> Commit-Queue: Kenneth Russell <[email protected]> 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.