CVE-2026-17889
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc |
modified |
Files Changed
third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc
Patch
From cc7f58d1d45bae211877071bfb5bc5ca3bd65c4a Mon Sep 17 00:00:00 2001
From: Brandon Jones <bajones@chromium.org>
Date: Tue, 16 Jun 2026 10:01:42 -0700
Subject: [PATCH] Reset polygon mode when blitting to XR array texture
Ensures that if the WEBGL_polygon_mode extension is used by the
page it doesn't interfere with copies done to the XR texture array
swap chains.
Fixed: 523735357
Change-Id: I46eadb9fd882ede44f5532549d671efaed0c5885
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7947059
Commit-Queue: Brandon Jones <bajones@chromium.org>
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1647673}
---
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc b/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc
index bedbb0a..f2c4ace 100644
--- a/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc
+++ b/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc
@@ -138,6 +138,15 @@
gl->Disable(GL_RASTERIZER_DISCARD);
}
+ // Ensure pages that set WEBGL_polygon_mode don't interfere with rendering.
+ GLenum polygon_mode = GL_FILL_ANGLE;
+ if (context()->ExtensionsUtil()->IsExtensionEnabled("WEBGL_polygon_mode")) {
+ GLint value = 0;
+ gl->GetIntegerv(GL_POLYGON_MODE_ANGLE, &value);
+ polygon_mode = static_cast<GLenum>(value);
+ gl->PolygonModeANGLE(GL_FRONT_AND_BACK, GL_FILL_ANGLE);
+ }
+
gl->ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
gl->DepthMask(GL_FALSE);
gl->BindVertexArrayOES(vao_);
@@ -170,6 +179,9 @@
if (dither_enabled) {
gl->Enable(GL_DITHER);
}
+ if (polygon_mode != GL_FILL_ANGLE) {
+ gl->PolygonModeANGLE(GL_FRONT_AND_BACK, polygon_mode);
+ }
// WebGLRenderingContextBase inherits from DrawingBuffer::Client, but makes
// all the methods private. Downcasting allows us to access them.
Original Bug Report
Visual information disclosure via WEBGL_polygon_mode state leak in WebXR swap chains
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: When using WebXR swap chains with wrapper textures (like texture arrays or cubemaps), the internal copy operation fails to reset the WebGL polygon mode state. An attacker can set the polygon mode to wireframe before the frame ends, causing the copy operation to only draw outlines. Because the destination texture skips clearing for optimization, this leaves the interior pixels uninitialized, potentially exposing stale cross-origin GPU memory to the VR headset.
Affected files:
third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.ccthird_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc
Estimated timestamp from git blame: Unknown (Google3 checkout)
Description
A potential visual information disclosure vulnerability exists in the WebXR implementation when handling internal GPU copy operations for XRWebGLTextureArraySwapChain and XRWebGLCubemapSwapChain.
When a WebXR frame ends, the renderer performs a copy draw operation (DrawArraysInstancedANGLE or DrawElements) to blit content from a source wrapper texture into the inner swap chain texture. To optimize this process, the inner swap chain texture is configured with clear_on_access = false, relying on the assumption that the copy draw will overwrite the entire texture surface.
However, the C++ implementation inside OnFrameEnd() fails to explicitly reset the polygon mode state. The WEBGL_polygon_mode extension (backed by GL_ANGLE_polygon_mode) allows a page to change the polygon fill mode from the default GL_FILL_ANGLE to GL_LINE_ANGLE (wireframe) or GL_POINT_ANGLE. If a malicious page sets the polygon mode to GL_LINE_ANGLE before the frame ends, the internal copy draw operation will render wireframe quads instead of filled triangles.
Because the underlying texture was not cleared, and the copy draw only overwrites the edges of the quads, the interior pixels of the destination texture remain untouched. This leaves the texture containing uninitialized or stale VRAM from previous frames or cross-origin sessions. This partially uninitialized texture is then submitted to the VR compositor and displayed to the user, bypassing WebGL’s security guarantee that all buffers must be initialized before being accessible.
Potential Attacker Steps
(Note: These are potential steps as our tooling does not yet have the ability to run code.)
- A WebGL2 page requests and acquires the
WEBGL_polygon_modeextension. - The page initializes a WebXR session and creates a layer that utilizes a wrapper swap chain (e.g., setting
textureType: "texture-array"). - In a
requestAnimationFramecallback, the page sets the polygon mode to wireframe:ext.polygonModeWEBGL(gl.FRONT_AND_BACK, gl.LINE_ANGLE). - The page renders its frame normally and returns from the callback.
- The browser invokes
XRWebGLTextureArraySwapChain::OnFrameEnd(). - The C++ code prepares the copy operation but does not clear the polygon mode state. It executes
gl->DrawArraysInstancedANGLE(), drawing a wireframe onto the inner swap chain texture. - The uninitialized interior pixels, containing stale VRAM, are passed to the VR compositor and displayed on the headset.
Proposed Fix
Both XRWebGLTextureArraySwapChain::OnFrameEnd() and XRWebGLCubemapSwapChain::OnFrameEnd() must explicitly reset the polygon mode to GL_FILL_ANGLE before performing the internal copy draws. Because the WebGL context state needs to remain consistent for the application, this state should be tracked and restored after the copy draw, similar to the existing handling for states like GL_DEPTH_TEST or GL_RASTERIZER_DISCARD.
Additionally, consider adding a DrawingBufferClientRestorePolygonMode() mechanism to the DrawingBuffer::Client interface to handle the restoration cleanly.
Evaluated with Chrome root at commit: 65b3256311f3ab6fb9870eaa522de7e6dd2663bb
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.