Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebXR
DescriptionUse after free in WebXR
ComponentWebXR
Bug ClassUAF
Tracker513256572
Fix commit85f3563f9a0f (chromium/src) +21/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Changed Functions

FunctionChangeNotes
wrapped_swapchain_
third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc
modified
if
third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc
modified
XRWebGLCubemapSwapChain
third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.h
modified
if
third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc
modified
XRWebGLStaticSwapChain
third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h
modified
if
third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc
modified
XRWebGLTextureArraySwapChain
third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.h
modified

Files Changed

  • third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc
  • third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.h
  • third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc
  • third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h
  • third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc
  • third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.h
From 85f3563f9a0fddc077f35e10e88c352f1ca2d599 Mon Sep 17 00:00:00 2001
From: Alexander Cooper <alcooper@chromium.org>
Date: Fri, 15 May 2026 13:28:18 -0700
Subject: [PATCH] WebXR: Migrate GL cleanup from destructors to pre-finalizers

Migrate GL resource cleanup from destructors to pre-finalizers
(Dispose()) using USING_PRE_FINALIZER in XRWebGLCubemapSwapChain,
XRWebGLStaticSwapChain, and XRWebGLTextureArraySwapChain.

We cannot reference the GL object during the destructor of these objects
because it may have already been deleted during garbage collection.
Accessing it during a pre-finalizer step ensures the referenced context
is still alive.

Fixed: 513256572
Change-Id: Iee77670da2e6714d831356b5262bbbeb2cbf7939
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7853496
Reviewed-by: Brandon Jones <bajones@chromium.org>
Commit-Queue: Alexander Cooper <alcooper@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1631515}
---

diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc b/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc
index ed409409..5fe44a29 100644
--- a/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc
+++ b/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc
@@ -129,7 +129,7 @@
                        wrapped_swapchain->webgl2()),
       wrapped_swapchain_(wrapped_swapchain) {}
 
-XRWebGLCubemapSwapChain::~XRWebGLCubemapSwapChain() {
+void XRWebGLCubemapSwapChain::Dispose() {
   gpu::gles2::GLES2Interface* gl = context()->ContextGL();
   if (!gl) {
     return;
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.h b/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.h
index eac9987..c4edb7f 100644
--- a/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.h
+++ b/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_WEBGL_CUBEMAP_SWAP_CHAIN_H_
 
 #include "third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h"
+#include "third_party/blink/renderer/platform/heap/prefinalizer.h"
 
 namespace blink {
 
@@ -17,10 +18,14 @@
 // swapchains directly, but not all drivers support cubemap buffers. See
 // crbug.com/459811463.
 class XRWebGLCubemapSwapChain final : public XRWebGLSwapChain {
+  USING_PRE_FINALIZER(XRWebGLCubemapSwapChain, Dispose);
+
  public:
   explicit XRWebGLCubemapSwapChain(XRWebGLSwapChain* wrapped_swapchain,
                                    bool clear_on_access);
-  ~XRWebGLCubemapSwapChain() override;
+  ~XRWebGLCubemapSwapChain() override = default;
+
+  void Dispose();
 
   bool IsCube() const override { return true; }
   WebGLUnownedTexture* ProduceTexture() 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 5b5265d7..7bb3991d 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
@@ -121,7 +121,7 @@
     bool webgl2)
     : XRWebGLSwapChain(context, descriptor, webgl2) {}
 
-XRWebGLStaticSwapChain::~XRWebGLStaticSwapChain() {
+void XRWebGLStaticSwapChain::Dispose() {
   if (owned_texture_) {
     gpu::gles2::GLES2Interface* gl = context()->ContextGL();
     if (!gl) {
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h b/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h
index bff7026..2db352e 100644
--- a/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h
+++ b/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
+#include "third_party/blink/renderer/platform/heap/prefinalizer.h"
 
 namespace blink {
 
@@ -65,11 +66,15 @@
 // A texture swap chain that is not communicated back to the compositor, used
 // for things like depth/stencil attachments that don't assist reprojection.
 class XRWebGLStaticSwapChain final : public XRWebGLSwapChain {
+  USING_PRE_FINALIZER(XRWebGLStaticSwapChain, Dispose);
+
  public:
   XRWebGLStaticSwapChain(WebGLRenderingContextBase*,
                          const XRWebGLSwapChain::Descriptor&,
                          bool webgl2);
-  ~XRWebGLStaticSwapChain() override;
+  ~XRWebGLStaticSwapChain() override = default;
+
+  void Dispose();
 
   WebGLUnownedTexture* ProduceTexture() override;
 
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 197ff78..5e2a0daa 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
@@ -43,7 +43,7 @@
   CHECK(webgl2());  // Texture arrays are only available in WebGL 2
 }
 
-XRWebGLTextureArraySwapChain::~XRWebGLTextureArraySwapChain() {
+void XRWebGLTextureArraySwapChain::Dispose() {
   if (owned_texture_) {
     gpu::gles2::GLES2Interface* gl = context()->ContextGL();
     if (!gl) {
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.h b/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.h
index f31a239fb..0e000836 100644
--- a/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.h
+++ b/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_WEBGL_TEXTURE_ARRAY_SWAP_CHAIN_H_
 
 #include "third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h"
+#include "third_party/blink/renderer/platform/heap/prefinalizer.h"
 
 namespace blink {
 
@@ -17,11 +18,15 @@
 // isn't possible until we add texture array support to SharedImages.
 // TODO(crbug.com/359418629): Remove once array SharedImages are available.
 class XRWebGLTextureArraySwapChain final : public XRWebGLSwapChain {
+  USING_PRE_FINALIZER(XRWebGLTextureArraySwapChain, Dispose);
+
  public:
   XRWebGLTextureArraySwapChain(XRWebGLSwapChain* wrapped_swap_chain,
                                uint32_t layers,
                                bool clear_on_access);
-  ~XRWebGLTextureArraySwapChain() override;
+  ~XRWebGLTextureArraySwapChain() override = default;
+
+  void Dispose();
 
   WebGLUnownedTexture* ProduceTexture() override;
 
Loading diff…

Original Bug Report

reported by vm...@google.com

Use-After-Free in WebXR swap chains via unsafe Member access in destructors

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: Multiple WebXR swap chain classes in Blink incorrectly dereference a Member<WebGLRenderingContextBase> during their destruction process. Because Oilpan does not guarantee the destruction order of objects in the same collection cycle, this leads to a potential Use-After-Free if the WebGL context is finalized before the swap chain.

Affected files:

  • third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc
  • third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc
  • third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc
  • third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h
  • third_party/blink/renderer/modules/xr/xr_swap_chain.h

Estimated timestamp from git blame: 2025-01-17

Summary

A potential Use-After-Free (UAF) vulnerability exists in several Oilpan-managed classes within the WebXR module: XRWebGLCubemapSwapChain, XRWebGLTextureArraySwapChain, and XRWebGLStaticSwapChain. These classes violate a core safety contract of Blink’s garbage collector (Oilpan), which specifies that destructors must not access other on-heap objects. This is because the order of finalization during a garbage collection sweep is non-deterministic, meaning an object’s members may be finalized and reclaimed before the object itself is destroyed.

Technical Details

The affected classes inherit from XRWebGLSwapChain, which holds a reference to the WebGL rendering context as a traced member:

// third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h
WebGLRenderingContextBase* context() { return webgl_context_.Get(); }
// ...
Member<WebGLRenderingContextBase> webgl_context_;

In their destructors, these classes call context()->ContextGL() to obtain a gpu::gles2::GLES2Interface* for cleaning up GPU-side resources:

  • XRWebGLCubemapSwapChain: Calls context()->ContextGL() in ~XRWebGLCubemapSwapChain() (third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc:133).
  • XRWebGLTextureArraySwapChain: Calls context()->ContextGL() in ~XRWebGLTextureArraySwapChain() (third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc:48).
  • XRWebGLStaticSwapChain: Calls context()->ContextGL() in ~XRWebGLStaticSwapChain() (third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc:126).

When both the swap chain and the WebGLRenderingContextBase become unreachable in the same GC cycle, the context may be finalized first. The call to context()->ContextGL() then performs a field read from the now-freed or reclaimed memory to retrieve the gles2_interface_ pointer. This pointer is subsequently used for virtual function calls, such as gl->DeleteTextures() or gl->DeleteBuffers().

Potential Impact

By controlling the data at the memory offset of the gles2_interface_ pointer (e.g., through heap spraying), a potential attacker could redirect virtual function calls to arbitrary addresses. This provides a primitive for remote code execution (RCE) within the sandboxed renderer process.

Reachability

This code path is reachable via the WebXR Layers API (e.g., createCubeLayer or createProjectionLayer with depth/stencil attachments), which is stable and enabled by default. Triggering the vulnerability would require an active WebXR session, which involves a one-time user permission prompt.

Suggested Attack Steps (Potential)

  1. Initialize a WebGL context and an immersive WebXR session.
  2. Create a vulnerable swap chain using XRWebGLBinding (e.g., by calling createCubeLayer).
  3. Release all JavaScript references to the WebGL context and the WebXR objects to make them eligible for garbage collection.
  4. Trigger a garbage collection cycle while simultaneously spraying the heap with attacker-controlled data designed to overlap with the WebGLRenderingContextBase memory layout.
  5. If the context is finalized before the swap chain, the swap chain’s destructor will dereference the sprayed memory, potentially leading to hijacked control flow.

Blink objects should not access on-heap members in their destructors. The cleanup logic should be moved to a pre-finalizer or an explicit shutdown method that is guaranteed to run while the referenced objects are still valid. For example, the XRWebGLSwapChain classes could use the USING_PRE_FINALIZER macro to perform GL resource cleanup before finalization begins.

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