Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in ANGLE
DescriptionUse after free in ANGLE
ComponentANGLE
Sub-componentGL
Bug ClassUAF
Tracker550379413
Fix commit0a35d208b1a4 (angle/angle) +111/-8
CISA KEVNot listed
CreditedShaked Reiner (Palo Alto Networks)
Disclosed2026-10-06

Files Changed

  • src/libANGLE/renderer/gl/ContextGL.h
  • src/tests/egl_tests/EGLContextASANTest.cpp
  • src/libANGLE/renderer/gl/ContextGL.cpp
From 0a35d208b1a4234571e689b56fb52bebcaaf38e2 Mon Sep 17 00:00:00 2001
From: Tzarial <zork@google.com>
Date: Thu, 27 Aug 2026 17:41:46 +0000
Subject: [PATCH] GL: Fix UAF in ContextGL with depth init PBOs

ContextGL declared mDepthInitPBOs before mRenderer in the class
definition, causing mRenderer to be destroyed before mDepthInitPBOs
during ~ContextGL(). If mRenderer was the last shared_ptr, RendererGL
and its FunctionsGL were freed before ~PixelBufferGL() attempted to
call functions->deleteBuffers(), resulting in a heap-use-after-free.

This CL fixes the issue by:
1. Moving mRenderer before mDepthInitPBOs so member destruction order
   guarantees mDepthInitPBOs is destroyed while mRenderer is still
   valid.
2. Implementing ContextGL::onDestroy to clear mDepthInitPBOs on
   context destruction.

Bug: b/550379413
Test: EGLContextASANTest.DestroyContextWithRobustDepthInitPBO/*
Change-Id: Iefab278ad75b7ef6a45a64db7dbd6876498bdff0
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8308574
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Commit-Queue: Tzarial <zork@chromium.org>
---
diff --git a/src/libANGLE/renderer/gl/ContextGL.cpp b/src/libANGLE/renderer/gl/ContextGL.cpp
index 7943806..911d5ab 100644
--- a/src/libANGLE/renderer/gl/ContextGL.cpp
+++ b/src/libANGLE/renderer/gl/ContextGL.cpp
@@ -96,15 +96,20 @@
                      const std::shared_ptr<RendererGL> &renderer,
                      RobustnessVideoMemoryPurgeStatus robustnessVideoMemoryPurgeStatus)
     : ContextImpl(state, errorSet),
+      mRenderer(renderer),
+      mRobustnessVideoMemoryPurgeStatus(robustnessVideoMemoryPurgeStatus),
       // Maximum 3 cached depth initialization PBOs stored for now; can be changed in the future if
       // needed.
-      mDepthInitPBOs(/*max=*/3),
-      mRenderer(renderer),
-      mRobustnessVideoMemoryPurgeStatus(robustnessVideoMemoryPurgeStatus)
+      mDepthInitPBOs(/*max=*/3)
 {}
 
 ContextGL::~ContextGL() {}
 
+void ContextGL::onDestroy(const gl::Context *context)
+{
+    mDepthInitPBOs.Clear();
+}
+
 angle::Result ContextGL::initialize(const angle::ImageLoadContext &imageLoadContext)
 {
     return angle::Result::Continue;
diff --git a/src/libANGLE/renderer/gl/ContextGL.h b/src/libANGLE/renderer/gl/ContextGL.h
index 770da35..99dd6d5 100644
--- a/src/libANGLE/renderer/gl/ContextGL.h
+++ b/src/libANGLE/renderer/gl/ContextGL.h
@@ -48,6 +48,8 @@
               RobustnessVideoMemoryPurgeStatus robustnessVideoMemoryPurgeStatus);
     ~ContextGL() override;
 
+    void onDestroy(const gl::Context *context) override;
+
     angle::Result initialize(const angle::ImageLoadContext &imageLoadContext) override;
 
     // Shader creation
@@ -326,6 +328,12 @@
     gl::AttributesMask updateAttributesForBaseInstance(GLuint baseInstance);
     void resetUpdatedAttributes(gl::AttributesMask attribMask);
 
+  protected:
+    std::shared_ptr<RendererGL> mRenderer;
+
+    RobustnessVideoMemoryPurgeStatus mRobustnessVideoMemoryPurgeStatus;
+
+  private:
     struct PixelBufferGL
     {
         const FunctionsGL *functions = nullptr;
@@ -347,11 +355,6 @@
     // Keyed by the GLenum type passed to getDepthInitPBO (e.g. GL_UNSIGNED_INT_24_8,
     // GL_FLOAT_32_UNSIGNED_INT_24_8_REV).
     DepthInitPBOCache mDepthInitPBOs;
-
-  protected:
-    std::shared_ptr<RendererGL> mRenderer;
-
-    RobustnessVideoMemoryPurgeStatus mRobustnessVideoMemoryPurgeStatus;
 };
 
 }  // namespace rx
diff --git a/src/tests/egl_tests/EGLContextASANTest.cpp b/src/tests/egl_tests/EGLContextASANTest.cpp
index dbab1ca..ad6f30a 100644
--- a/src/tests/egl_tests/EGLContextASANTest.cpp
+++ b/src/tests/egl_tests/EGLContextASANTest.cpp
@@ -16,6 +16,7 @@
 #include <condition_variable>
 #include <mutex>
 #include <thread>
+#include <vector>
 
 using namespace angle;
 
@@ -201,6 +202,100 @@
     // cleanup
     ASSERT_GL_NO_ERROR();
 }
+
+// Tests that destroying a context with cached robust resource initialization depth PBOs does
+// not cause a use-after-free during destruction.
+// https://crbug.com/550379413
+TEST_P(EGLContextASANTest, DestroyContextWithRobustDepthInitPBO)
+{
+    EGLDisplay display = getEGLWindow()->getDisplay();
+    EGLConfig config   = getEGLWindow()->getConfig();
+    EGLSurface surface = getEGLWindow()->getSurface();
+
+    ANGLE_SKIP_TEST_IF(
+        !IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_robust_resource_initialization"));
+    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
+
+    // Unmake default context
+    EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+    EXPECT_EGL_SUCCESS();
+
+    std::vector<EGLint> contextAttribs = {
+        EGL_CONTEXT_CLIENT_VERSION,
+        3,
+        EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE,
+        EGL_TRUE,
+    };
+
+    if (IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_context_virtualization"))
+    {
+        contextAttribs.push_back(EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE);
+        contextAttribs.push_back(1);
+    }
+    contextAttribs.push_back(EGL_NONE);
+
+    EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs.data());
+    ASSERT_EGL_SUCCESS();
+    ASSERT_NE(context, EGL_NO_CONTEXT);
+
+    EXPECT_EGL_TRUE(eglMakeCurrent(display, surface, surface, context));
+    EXPECT_EGL_SUCCESS();
+
+    // Create an uninitialized depth texture
+    GLTexture depthTex;
+    glBindTexture(GL_TEXTURE_2D, depthTex);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, 64, 64, 0, GL_DEPTH_COMPONENT,
+                 GL_UNSIGNED_INT, nullptr);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS);
+    ASSERT_GL_NO_ERROR();
+
+    constexpr char kVS[] = R"(#version 300 es
+void main() {
+    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
+    gl_PointSize = 1.0;
+})";
+    constexpr char kFS[] = R"(#version 300 es
+uniform highp sampler2DShadow u;
+out highp vec4 o;
+void main() {
+    o = vec4(texture(u, vec3(0.5, 0.5, 0.5)), 0.0, 0.0, 1.0);
+})";
+
+    GLProgram program;
+    program.makeRaster(kVS, kFS);
+    ASSERT_TRUE(program.valid());
+    glUseProgram(program);
+    glUniform1i(glGetUniformLocation(program, "u"), 0);
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    GLTexture colorTex;
+    glBindTexture(GL_TEXTURE_2D, colorTex);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, depthTex);
+
+    glDrawArrays(GL_POINTS, 0, 1);
+    ASSERT_GL_NO_ERROR();
+    glFlush();
+
+    // Unmake current and destroy context.
+    EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+    EXPECT_EGL_SUCCESS();
+
+    EXPECT_EGL_TRUE(SafeDestroyContext(display, context));
+    EXPECT_EGL_SUCCESS();
+
+    // Make default context and surface current again.
+    EXPECT_TRUE(getEGLWindow()->makeCurrent());
+    EXPECT_EGL_SUCCESS();
+}
 }  // anonymous namespace
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLContextASANTest);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/tests/egl_tests/EGLContextASANTest.cpp b/src/tests/egl_tests/EGLContextASANTest.cpp
index dbab1ca..ad6f30a 100644
--- a/src/tests/egl_tests/EGLContextASANTest.cpp
+++ b/src/tests/egl_tests/EGLContextASANTest.cpp
@@ -16,6 +16,7 @@
 #include <condition_variable>
 #include <mutex>
 #include <thread>
+#include <vector>
 
 using namespace angle;
 
@@ -201,6 +202,100 @@
     // cleanup
     ASSERT_GL_NO_ERROR();
 }
+
+// Tests that destroying a context with cached robust resource initialization depth PBOs does
+// not cause a use-after-free during destruction.
+// https://crbug.com/550379413
+TEST_P(EGLContextASANTest, DestroyContextWithRobustDepthInitPBO)
+{
+    EGLDisplay display = getEGLWindow()->getDisplay();
+    EGLConfig config   = getEGLWindow()->getConfig();
+    EGLSurface surface = getEGLWindow()->getSurface();
+
+    ANGLE_SKIP_TEST_IF(
+        !IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_robust_resource_initialization"));
+    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
+
+    // Unmake default context
+    EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+    EXPECT_EGL_SUCCESS();
+
+    std::vector<EGLint> contextAttribs = {
+        EGL_CONTEXT_CLIENT_VERSION,
+        3,
+        EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE,
+        EGL_TRUE,
+    };
+
+    if (IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_context_virtualization"))
+    {
+        contextAttribs.push_back(EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE);
+        contextAttribs.push_back(1);
+    }
+    contextAttribs.push_back(EGL_NONE);
+
+    EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs.data());
+    ASSERT_EGL_SUCCESS();
+    ASSERT_NE(context, EGL_NO_CONTEXT);
+
+    EXPECT_EGL_TRUE(eglMakeCurrent(display, surface, surface, context));
+    EXPECT_EGL_SUCCESS();
+
+    // Create an uninitialized depth texture
+    GLTexture depthTex;
+    glBindTexture(GL_TEXTURE_2D, depthTex);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, 64, 64, 0, GL_DEPTH_COMPONENT,
+                 GL_UNSIGNED_INT, nullptr);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS);
+    ASSERT_GL_NO_ERROR();
+
+    constexpr char kVS[] = R"(#version 300 es
+void main() {
+    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
+    gl_PointSize = 1.0;
+})";
+    constexpr char kFS[] = R"(#version 300 es
+uniform highp sampler2DShadow u;
+out highp vec4 o;
+void main() {
+    o = vec4(texture(u, vec3(0.5, 0.5, 0.5)), 0.0, 0.0, 1.0);
+})";
+
+    GLProgram program;
+    program.makeRaster(kVS, kFS);
+    ASSERT_TRUE(program.valid());
+    glUseProgram(program);
+    glUniform1i(glGetUniformLocation(program, "u"), 0);
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    GLTexture colorTex;
+    glBindTexture(GL_TEXTURE_2D, colorTex);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, depthTex);
+
+    glDrawArrays(GL_POINTS, 0, 1);
+    ASSERT_GL_NO_ERROR();
+    glFlush();
+
+    // Unmake current and destroy context.
+    EXPECT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+    EXPECT_EGL_SUCCESS();
+
+    EXPECT_EGL_TRUE(SafeDestroyContext(display, context));
+    EXPECT_EGL_SUCCESS();
+
+    // Make default context and surface current again.
+    EXPECT_TRUE(getEGLWindow()->makeCurrent());
+    EXPECT_EGL_SUCCESS();
+}
 }  // anonymous namespace
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLContextASANTest);
Loading diff…

Original Bug Report

reported by sh...@gmail.com

ANGLE GL backend: WebGL2-reachable UAF in ContextGL::PixelBufferGL via C++ member destruction order (GPU process)


Report description

ANGLE GL backend: WebGL2-reachable UAF in ContextGL::PixelBufferGL via C++ member destruction order (GPU process)


Bug location

Where do you want to report your vulnerability?

Chrome VRP – Report security issues affecting the Chrome browser. See program rules

Which URL (or repository) have you found the vulnerability in?

https://chromium.googlesource.com/angle/angle


The problem

Please describe the technical details of the vulnerability

ContextGL in ANGLE’s GL backend has a C++ member destruction order use-after-free. The class declares mDepthInitPBOs (a cache of PixelBufferGL objects, each holding a raw const FunctionsGL*) before mRenderer (std::shared_ptr<RendererGL>) in ContextGL.h. ~ContextGL() is empty (ContextGL.cpp:106), so C++ destroys members in reverse declaration order: mRenderer is freed first (potentially destroying RendererGL and its owned FunctionsGL), then mDepthInitPBOs is destroyed, and each ~PixelBufferGL() calls functions->deleteBuffers() on the now-freed FunctionsGL.

Unlike the recently filed SamplerGL teardown UAF (same class of bug), this does not require EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE. Any single context that uploads a depth/stencil texture and is then torn down is vulnerable.

This is the same object-lifetime bug class as ANGLE commit e9a1fe04 (“GL: Extend RendererGL lifetime to fix SyncGL teardown UAF”) and the SamplerGL sibling. Both have been treated as security issues.

Root cause (key locations):

  • src/libANGLE/renderer/gl/ContextGL.h:349 — DepthInitPBOCache mDepthInitPBOs declared before mRenderer, so it is destroyed AFTER mRenderer in ~ContextGL()
  • src/libANGLE/renderer/gl/ContextGL.h:352 — std::shared_ptr<RendererGL> mRenderer
  • src/libANGLE/renderer/gl/ContextGL.cpp:106 — ~ContextGL() {} (empty destructor)
  • src/libANGLE/renderer/gl/ContextGL.cpp:64-69 — ~PixelBufferGL() calls functions->deleteBuffers() on the cached raw FunctionsGL*
  • src/libANGLE/renderer/gl/RendererGL.cpp:~RendererGL — SafeDelete(mStateManager) and deletes FunctionsGL (owned by RendererGL)

Trigger path (how the cache is populated): ContextGL::getDepthInitPBO() (ContextGL.cpp:1080) is called from TextureGL::initializeContentsImpl() (TextureGL.cpp:2851) during ANGLE’s robust resource init of any depth/stencil texture (internalFormatInfo.depthBits > 0, ES3 PBO path). Chrome enables robust resource init by default for all WebGL contexts (EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE). The PBO cache is never flushed on teardown — tickGC() (ContextGL.cpp:1125) runs only during draw calls.

// The destruction order UAF:
// ContextGL.h:
- line 349:  DepthInitPBOCache mDepthInitPBOs; // destroyed SECOND (reverse order)
- line 352:  std::shared_ptr&lt;RendererGL&gt; mRenderer; // destroyed FIRST
//
// ~ContextGL() = {} (empty)
//
// Destruction sequence:
//  1. ~shared_ptr&lt;RendererGL&gt; -&gt; ~RendererGL -&gt; FunctionsGL freed
//  2. ~DepthInitPBOCache -&gt; ~PixelBufferGL -&gt; functions-&gt;deleteBuffers() -&gt; UAF

Note: Confirmed source-level on ANGLE main (2026-08-21). ASan confirmation requires a non-software-renderer GPU (the standard ANGLE CI hardware). The triager can reproduce with --use-angle=gl on any ANGLE ASan build with a hardware GL driver.

To reproduce (attach: pixelbuffergl_uaf_repro.cpp): Add to ANGLE’s BUILD.gn:

angle_executable("pixelbuffergl_uaf_repro") {
  testonly = true
  sources = [ "pixelbuffergl_uaf_repro.cpp" ]
  deps = [ ":libEGL", ":libGLESv2" ]
}

Then:

gn gen out/asan --args='is_debug=false is_asan=true is_clang=true'
autoninja -C out/asan pixelbuffergl_uaf_repro
Xvfb :99 &
ASAN_OPTIONS=halt_on_error=1:detect_leaks=0 DISPLAY=:99 ./out/asan/pixelbuffergl_uaf_repro

Expected ASan output:

==ERROR: AddressSanitizer: heap-use-after-free
READ/WRITE of size N
  #0 FunctionsGL::deleteBuffers (or gl::deleteBuffers)
  #1 ContextGL::PixelBufferGL::~PixelBufferGL   ContextGL.cpp:68
  #2 ~DepthInitPBOCache (member destruction)
  #3 ContextGL::~ContextGL                      ContextGL.cpp:106

freed by:
  #0 operator delete
  #1 rx::RendererGL::~RendererGL                &lt;- FunctionsGL freed here
  #2 ~shared_ptr&lt;RendererGL&gt;                    &lt;- last ref dropped
  #3 ContextGL::~ContextGL                      ContextGL.cpp:106

Suggested fix (one-line): Swap the declaration order in ContextGL.h so mRenderer is destroyed LAST:

// ContextGL.h
- line 349:  DepthInitPBOCache mDepthInitPBOs;
- line 352:  std::shared_ptr&lt;RendererGL&gt; mRenderer;
+ line 349:  std::shared_ptr&lt;RendererGL&gt; mRenderer;   // destroyed LAST
+ line 352:  DepthInitPBOCache mDepthInitPBOs;        // destroyed FIRST (while mRenderer alive)

Alternative: add ContextGL::onDestroy() override that calls mDepthInitPBOs.clear() before returning, ensuring the PBOs are flushed while mRenderer is still valid.

Related (defense-in-depth): ProgramPipelineGL (ProgramPipelineGL.cpp:25-31) has the same raw mFunctions destructor pattern, currently safe only because mProgramPipelineManager is released before mShaderProgramManager in Context::releaseSharedObjects() — fragile ordering dependency.

REPRODUCTION CASE

Two artifacts attached: repro_pixelbuffergl.html (browser trigger) and pixelbuffergl_uaf_repro.cpp (standalone ANGLE repro).

— Browser trigger (repro_pixelbuffergl.html) —

  1. Build or download Chrome with ASan and the GL ANGLE backend: chrome –use-angle=gl –no-sandbox

  2. Open repro_pixelbuffergl.html in that Chrome build. The page will display “Draw with depth shadow sampler done” when ready.

  3. Close the tab or navigate to another URL.

  4. Expected: ASan reports heap-use-after-free: READ/WRITE of size N #0 ContextGL::PixelBufferGL::~PixelBufferGL ContextGL.cpp:68 freed by: #1 rx::RendererGL::~RendererGL #2 ContextGL::~ContextGL

What the page does: creates a WebGL2 depth texture with null data, draws once using it as a shadow sampler (this triggers ANGLE’s robust resource init path ContextGL::getDepthInitPBO, caching a raw FunctionsGL* in mDepthInitPBOs), then waits. On tab close ~ContextGL() fires – C++ destroys mRenderer before mDepthInitPBOs (reverse declaration order), freeing FunctionsGL, then ~PixelBufferGL dereferences it.

— Standalone ANGLE repro (pixelbuffergl_uaf_repro.cpp) —

  1. Clone ANGLE and sync dependencies: git clone https://chromium.googlesource.com/angle/angle cd angle && gclient sync

  2. Copy pixelbuffergl_uaf_repro.cpp to the ANGLE root, then add to BUILD.gn: angle_executable(“pixelbuffergl_uaf_repro”) { testonly = true sources = [ “pixelbuffergl_uaf_repro.cpp” ] deps = [ “:libEGL”, “:libGLESv2” ] }

  3. Build with ASan: gn gen out/asan –args=‘is_debug=false is_asan=true is_clang=true’ autoninja -C out/asan pixelbuffergl_uaf_repro

  4. Run (requires a hardware GL driver — not Mesa/llvmpipe): ASAN_OPTIONS=halt_on_error=1:detect_leaks=0
    ./out/asan/pixelbuffergl_uaf_repro

Note: the standalone repro requires a non-software GPU driver (Adreno, Mali, NVIDIA, Intel) because Mesa’s llvmpipe takes a different depth-texture init path that bypasses getDepthInitPBO. Chrome’s GPU process always uses a hardware driver, so the browser trigger is the preferred confirmation path.

Impact analysis

Any website can trigger this with no user interaction beyond page load. A WebGL2 page uploads a depth texture (gl.texImage2D with a depth format and null data), draws once using it as a shadow sampler (triggering Chrome’s always-on robust resource init path), then navigates away or closes the tab. Chrome’s GPU process tears down the WebGL context, firing the member-destruction-order UAF in ~ContextGL.

The GPU process holds open handles to GPU driver interfaces (/dev/dri, vendor ioctls). On Android the GPU process runs as privileged_process0 with access to vendor-specific GPU driver ioctls, making it the standard first step in full-chain mobile exploits. Unlike the companion SamplerGL UAF (same session), this bug does not require EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE — it fires on any single WebGL2 context, which is a significantly broader trigger.

This is the same object-lifetime bug class as ANGLE commit e9a1fe04 (“GL: Extend RendererGL lifetime to fix SyncGL teardown UAF”) and the companion SamplerGL report filed this session, both accepted as security issues. Prior accepted fixes for the identical pattern establish that Chrome treats this as a security boundary violation.


The cause

What version of Chrome have you found the security issue in?

151.0.7922.173 (stable) — affects all versions

Yes, it is related to a crash.

Choose the type of vulnerability

Memory Corruption (in a non-sandboxed process)

How would you like to be publicly acknowledged for your report?

Shaked Reiner (Palo Alto Networks)

View on issue tracker