CVE-2026-106419
Overview
Files Changed
src/libANGLE/renderer/gl/ContextGL.hsrc/tests/egl_tests/EGLContextASANTest.cppsrc/libANGLE/renderer/gl/ContextGL.cpp
Patch
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);
Regression Test / PoC
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);
Original Bug Report
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 mDepthInitPBOsdeclared beforemRenderer, so it is destroyed AFTERmRendererin~ContextGL()src/libANGLE/renderer/gl/ContextGL.h:352—std::shared_ptr<RendererGL> mRenderersrc/libANGLE/renderer/gl/ContextGL.cpp:106—~ContextGL() {}(empty destructor)src/libANGLE/renderer/gl/ContextGL.cpp:64-69—~PixelBufferGL()callsfunctions->deleteBuffers()on the cached rawFunctionsGL*src/libANGLE/renderer/gl/RendererGL.cpp:~RendererGL—SafeDelete(mStateManager)and deletesFunctionsGL(owned byRendererGL)
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<RendererGL> mRenderer; // destroyed FIRST
//
// ~ContextGL() = {} (empty)
//
// Destruction sequence:
// 1. ~shared_ptr<RendererGL> -> ~RendererGL -> FunctionsGL freed
// 2. ~DepthInitPBOCache -> ~PixelBufferGL -> functions->deleteBuffers() -> 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 <- FunctionsGL freed here
#2 ~shared_ptr<RendererGL> <- 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<RendererGL> mRenderer;
+ line 349: std::shared_ptr<RendererGL> 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) —
-
Build or download Chrome with ASan and the GL ANGLE backend: chrome –use-angle=gl –no-sandbox
-
Open repro_pixelbuffergl.html in that Chrome build. The page will display “Draw with depth shadow sampler done” when ready.
-
Close the tab or navigate to another URL.
-
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) —
-
Clone ANGLE and sync dependencies: git clone https://chromium.googlesource.com/angle/angle cd angle && gclient sync
-
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” ] }
-
Build with ASan: gn gen out/asan –args=‘is_debug=false is_asan=true is_clang=true’ autoninja -C out/asan pixelbuffergl_uaf_repro
-
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
Is the security issue related to a crash?
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)