Medium chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized Use in WebXR
DescriptionUninitialized Use in WebXR
ComponentWebXR
Bug ClassUninitialized Memory
Tracker516781007
Fix commitaad96d6b56e0 (chromium/src) +31/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
modified
DrawingBufferClientRestorePixelUnpackBufferBinding
third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
modified
DrawingBufferClientRestorePixelUnpackBufferBinding
third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
modified
if
third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc
modified

Files Changed

  • third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
  • third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.h
  • third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
  • third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
  • third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc
  • third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h
  • third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test_helpers.h
  • third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.cc
From aad96d6b56e0cd7585654bdc5755df1d2c510153 Mon Sep 17 00:00:00 2001
From: Brandon Jones <[email protected]>
Date: Thu, 28 May 2026 17:03:44 -0700
Subject: [PATCH] Disable GL_RASTERIZER_DISCARD when clearing XR targets

Prevents a scenario where uninitialized content may be exposed by
a page that sets GL_RASTERIZER_DISCARD prior to getting new WebXR
textures.

Bug: 516781007
Change-Id: I8d84f165096994365c548ea4e6d4ead5b595d658
Fixed: 516781007
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7884159
Auto-Submit: Brandon Jones <[email protected]>
Commit-Queue: Brandon Jones <[email protected]>
Reviewed-by: Kenneth Russell <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1638070}
---

diff --git a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
index 05fb8fd9..16b2f87 100644
--- a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.cc
@@ -5832,6 +5832,20 @@
   return params;
 }
 
+void WebGL2RenderingContextBase::DrawingBufferClientRestoreRasterizerDiscard() {
+  if (destruction_in_progress_) {
+    return;
+  }
+  if (!ContextGL()) {
+    return;
+  }
+  if (rasterizer_discard_enabled_) {
+    ContextGL()->Enable(GL_RASTERIZER_DISCARD);
+  } else {
+    ContextGL()->Disable(GL_RASTERIZER_DISCARD);
+  }
+}
+
 void WebGL2RenderingContextBase::
     DrawingBufferClientRestorePixelUnpackBufferBinding() {
   if (destruction_in_progress_)
diff --git a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.h
index b50684b6..b47dbbb7 100644
--- a/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl2_rendering_context_base.h
@@ -944,6 +944,7 @@
       Platform::WebGLContextType context_type);
 
   // DrawingBuffer::Client implementation.
+  void DrawingBufferClientRestoreRasterizerDiscard() override;
   void DrawingBufferClientRestorePixelUnpackBufferBinding() override;
   void DrawingBufferClientRestorePixelPackBufferBinding() override;
   void DrawingBufferClientRestorePixelPackParameters() override;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index c46226e..d17e9059 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -7830,6 +7830,7 @@
   RestoreCurrentFramebuffer();
 }
 
+void WebGLRenderingContextBase::DrawingBufferClientRestoreRasterizerDiscard() {}
 void WebGLRenderingContextBase::
     DrawingBufferClientRestorePixelUnpackBufferBinding() {}
 void WebGLRenderingContextBase::
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
index ad987bb..a03e104 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
@@ -777,6 +777,7 @@
   void DrawingBufferClientInterruptPixelLocalStorage() override;
   void DrawingBufferClientRestorePixelLocalStorage() override;
   void DrawingBufferClientRestoreScissorTest() override;
+  void DrawingBufferClientRestoreRasterizerDiscard() override;
   void DrawingBufferClientRestoreMaskAndClearValues() override;
   void DrawingBufferClientRestorePixelPackParameters() override;
   void DrawingBufferClientRestoreTexture2DBinding() override;
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc b/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc
index 7bb3991d..3d4d4ccb 100644
--- a/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc
+++ b/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc
@@ -68,6 +68,9 @@
   }
 
   gl->Disable(GL_SCISSOR_TEST);
+  if (webgl2_) {
+    gl->Disable(GL_RASTERIZER_DISCARD);
+  }
 
   if (descriptor_.is_texture_array) {
     DLOG(ERROR) << __func__ << " Performing texture array clear";
@@ -98,6 +101,7 @@
       static_cast<DrawingBuffer::Client*>(context());
 
   client->DrawingBufferClientRestoreScissorTest();
+  client->DrawingBufferClientRestoreRasterizerDiscard();
   client->DrawingBufferClientRestoreMaskAndClearValues();
   client->DrawingBufferClientRestoreFramebufferBinding();
 }
diff --git a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h
index 83caaca..4aafc1bf 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h
@@ -97,6 +97,7 @@
     // Returns true if the DrawingBuffer is currently bound for draw.
     virtual bool DrawingBufferClientIsBoundForDraw() = 0;
     virtual void DrawingBufferClientRestoreScissorTest() = 0;
+    virtual void DrawingBufferClientRestoreRasterizerDiscard() = 0;
     // Interrupt and restore pixel local storage, if it was active.
     virtual void DrawingBufferClientInterruptPixelLocalStorage() = 0;
     virtual void DrawingBufferClientRestorePixelLocalStorage() = 0;
@@ -317,6 +318,10 @@
 
   scoped_refptr<ExternalCanvasResource> ExportLowLatencyCanvasResource();
 
+  bool IsWebGL2() const {
+    return webgl_version_ == Platform::WebGLContextType::kWebGL2ContextType;
+  }
+
   static const size_t kDefaultColorBufferCacheLimit;
 
  protected:  // For unittests
diff --git a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test_helpers.h b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test_helpers.h
index 844a03c..e8cf9f40 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test_helpers.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test_helpers.h
@@ -255,6 +255,7 @@
   void DrawingBufferClientRestoreScissorTest() override {
     state_.scissor_enabled = saved_state_.scissor_enabled;
   }
+  void DrawingBufferClientRestoreRasterizerDiscard() override {}
   void DrawingBufferClientRestoreMaskAndClearValues() override {
     UNSAFE_TODO(memcpy(state_.color_mask, saved_state_.color_mask,
                        sizeof(state_.color_mask)));
diff --git a/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.cc b/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.cc
index 11d49c2..0fb55e81 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.cc
@@ -386,6 +386,9 @@
   }
 
   gl->Disable(GL_SCISSOR_TEST);
+  if (drawing_buffer_->IsWebGL2()) {
+    gl->Disable(GL_RASTERIZER_DISCARD);
+  }
 
   gl->Clear(clear_bits);
 
@@ -394,6 +397,7 @@
     return;
 
   client->DrawingBufferClientRestoreScissorTest();
+  client->DrawingBufferClientRestoreRasterizerDiscard();
   client->DrawingBufferClientRestoreMaskAndClearValues();
 }
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential WebXR Layers security clear bypass via GL_RASTERIZER_DISCARD leaking uninitialized VRAM

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: A potential security clear bypass exists in XRWebGLSwapChain::ClearCurrentTexture when WebXR Layers is used on Android. Because the function does not temporarily disable the GL_RASTERIZER_DISCARD capability before executing clear operations, a page can enable this state to make ANGLE early-return and silently discard the clear. This could allow the page to read uninitialized GPU memory/VRAM from freshly-allocated gralloc swap chain textures.

Affected files:

  • third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc

Estimated timestamp from git blame: 2025-01-17

Summary

A potential security clear bypass vulnerability has been identified in XRWebGLSwapChain::ClearCurrentTexture (located in third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc). When utilizing WebXR Layers on Android, swap chain textures are backed by newly allocated AHardwareBuffer SharedImages. If a page enables the GL_RASTERIZER_DISCARD capability prior to accessing a WebXR sub-image, the mandatory security clear of the swap chain texture is bypassed. This can potentially leak uninitialized GPU VRAM memory (such as recycled system-wide gralloc allocations containing cross-origin imagery or system UI) back to the renderer process.

Technical Details & Root Cause

When WebXR Layers is active on Android, AHardwareBuffer SharedImages are imported via AHardwareBufferImageBackingFactory::CreateSharedImage in gpu/command_buffer/service/shared_image/ahardwarebuffer_image_backing_factory.cc. Upon import, the backing is unconditionally marked as cleared via backing->SetCleared() without performing raw gralloc zero-initialization on the newly allocated buffer.

This bypasses the GPU command decoder’s safe clear fallback, transferring the sole responsibility of clearing the texture to Blink’s WebXR implementation.

During frame rendering, XRWebGLSwapChain::ClearCurrentTexture is executed to clear the texture before exposing it to the renderer:

void XRWebGLSwapChain::ClearCurrentTexture() {
  ...
  gpu::gles2::GLES2Interface* gl = context()->ContextGL();
  ...
  gl->Disable(GL_SCISSOR_TEST);
  ...
  gl->FramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D,
                           texture->Object(), 0);
  gl->Clear(clear_bits);
  ...
}

However, ClearCurrentTexture does not temporarily disable the GL_RASTERIZER_DISCARD capability (which is a page-settable WebGL2 state). If the page has enabled this state, the clear command is forwarded to ANGLE, which early-returns inside Context::clear (in third_party/angle/src/libANGLE/Context.cpp) without executing the clear:

void Context::clear(GLbitfield mask) {
    if (mState.isRasterizerDiscardEnabled()) {
        return;
    }
    ...
}

Consequently, the security clear is silently discarded, leaving the uninitialized gralloc / GPU VRAM contents intact.

Suggested Steps to Reproduce

Note: The following are potential steps that require testing on a physical device, as our automated analysis tools do not have the ability to execute the code.

  1. Serve a page over HTTPS to a compatible Chrome for Android browser.
  2. Create a WebGL2 context and request an immersive WebXR session with layers enabled:
    const gl = canvas.getContext('webgl2', {xrCompatible: true});
    const session = await navigator.xr.requestSession('immersive-ar', {requiredFeatures: ['layers']});
    const binding = new XRWebGLBinding(session, gl);
    
  3. Instantiate a projection layer:
    const layer = binding.createProjectionLayer({textureType: 'texture'});
    session.updateRenderState({layers: [layer]});
    
  4. Within the animation frame loop, enable rasterizer discard before querying the sub-image:
    gl.enable(gl.RASTERIZER_DISCARD);
    const sub = binding.getViewSubImage(layer, frame.getViewerPose(refSpace).views[0]);
    gl.disable(gl.RASTERIZER_DISCARD);
    
  5. Bind the returned sub.colorTexture to a custom framebuffer, and read the pixels using gl.readPixels(...) or sample it in a custom shader.
  6. Observe if the read pixels contain uninitialized graphics/VRAM data rather than transparent black.

Proposed Fix

Apply a mechanism to temporarily disable and then restore GL_RASTERIZER_DISCARD inside XRWebGLSwapChain::ClearCurrentTexture. This can be achieved by checking the current state of rasterizer discard on the context, disabling it before gl->Clear(), and restoring the original value afterwards, similar to how it is handled in webgl_rendering_context_base.cc via ScopedDisableRasterizerDiscard or by querying via gl->IsEnabled(GL_RASTERIZER_DISCARD).

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


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