CVE-2026-9995
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
wrapped_swapchain_third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc |
modified | |
ifthird_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc |
modified | |
XRWebGLCubemapSwapChainthird_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.h |
modified | |
ifthird_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc |
modified | |
XRWebGLStaticSwapChainthird_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h |
modified | |
ifthird_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc |
modified | |
XRWebGLTextureArraySwapChainthird_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.ccthird_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.hthird_party/blink/renderer/modules/xr/xr_webgl_swap_chain.ccthird_party/blink/renderer/modules/xr/xr_webgl_swap_chain.hthird_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.ccthird_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.h
Patch
From 85f3563f9a0fddc077f35e10e88c352f1ca2d599 Mon Sep 17 00:00:00 2001 From: Alexander Cooper <[email protected]> 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 <[email protected]> Commit-Queue: Alexander Cooper <[email protected]> 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;
Original Bug Report
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.ccthird_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.ccthird_party/blink/renderer/modules/xr/xr_webgl_swap_chain.ccthird_party/blink/renderer/modules/xr/xr_webgl_swap_chain.hthird_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)
- Initialize a WebGL context and an immersive WebXR session.
- Create a vulnerable swap chain using
XRWebGLBinding(e.g., by callingcreateCubeLayer). - Release all JavaScript references to the WebGL context and the WebXR objects to make them eligible for garbage collection.
- Trigger a garbage collection cycle while simultaneously spraying the heap with attacker-controlled data designed to overlap with the
WebGLRenderingContextBasememory layout. - 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.
Recommended Fix
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.