Critical chrome UAF 🔧 Commit mapped

Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebGL
DescriptionUse after free in WebGL
ComponentWebGL
Bug ClassUAF
Tracker537729021
Fix commitb8352abce038 (angle/angle) +225/-4
CISA KEVNot listed
CreditedMuhammad Alifa Ramdhan, Pan ZhenPeng, Billy Jheng Bing Jhong of STAR Labs SG Pte. Ltd.
Disclosed2026-08-06

Files Changed

  • include/platform/autogen/FeaturesGL_autogen.h
  • include/platform/gl_features.json
  • src/libANGLE/Framebuffer.cpp
  • src/libANGLE/Observer.h
  • src/libANGLE/Texture.cpp
  • src/libANGLE/renderer/FramebufferImpl.h
  • src/libANGLE/renderer/gl/ContextGL.cpp
  • src/libANGLE/renderer/gl/FramebufferGL.cpp
From b8352abce0383b26af32415c1d0f4a6912f665ae Mon Sep 17 00:00:00 2001
From: Zhenyao Mo <zmo@chromium.org>
Date: Thu, 30 Jul 2026 19:25:57 -0700
Subject: [PATCH] ANGLE: Reattach texture layer to FBO after layer count increase

On PowerVR Android GPUs, when a layer of a layered texture (e.g.,
GL_TEXTURE_2D_ARRAY) is attached to a framebuffer and later TexImage3D
is called on the texture to increase its layer count, the framebuffer
remains attached to the old memory instead of the newly allocated
texture storage.

This CL adds the reattachTextureToFboAfterLayerIncrease workaround,
enabled on PowerVR Android. When enabled:
- When TextureGL::setImageHelper detects a layer count increase on a
  GL_TEXTURE_2D_ARRAY texture, it sends a TextureLayerCountIncreased
  subject message before reallocating texture storage via TexImage3D.
- When Framebuffer::onSubjectStateChange receives this message, it
  calls FramebufferGL::onAttachmentLayerCountChange to detach the
  attachment point on the GL driver (calling glFramebufferTextureLayer
  with texture id 0) and sets the attachment dirty bit.
- Re-attachment happens lazily: when the Framebuffer is next bound and
  used, ANGLE's normal state synchronization processes the dirty bit
  and automatically re-attaches the texture layer to the GL framebuffer.
- Adds unit tests verifying correct layer re-attachment behavior across
  single-context and shared multi-context scenarios, instantiated with
  the workaround enabled and disabled.

Bug: chromium:499602793,chromium:537729021
Change-Id: I84899b963d8e8683b5998d281c12263415c709a8
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8162364
Auto-Submit: Zhenyao Mo <zmo@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Commit-Queue: Geoff Lang <geofflang@chromium.org>
---

diff --git a/include/platform/autogen/FeaturesGL_autogen.h b/include/platform/autogen/FeaturesGL_autogen.h
index 42c3e4a..5eed5e6 100644
--- a/include/platform/autogen/FeaturesGL_autogen.h
+++ b/include/platform/autogen/FeaturesGL_autogen.h
@@ -752,6 +752,12 @@
         &members,
     };
 
+    FeatureInfo reattachTextureToFboAfterLayerIncrease = {
+        "reattachTextureToFboAfterLayerIncrease",
+        FeatureCategory::OpenGLWorkarounds,
+        &members,
+    };
+
 };
 
 inline FeaturesGL::FeaturesGL()  = default;
diff --git a/include/platform/gl_features.json b/include/platform/gl_features.json
index 32dffc1..f6cd33d 100644
--- a/include/platform/gl_features.json
+++ b/include/platform/gl_features.json
@@ -985,6 +985,14 @@
                 "object on ARM Mali Valhall/Avalon GPUs to avoid driver crash."
             ],
             "issue": "http://crbug.com/534468209"
+        },
+        {
+            "name": "reattach_texture_to_fbo_after_layer_increase",
+            "category": "Workarounds",
+            "description": [
+                "Reattach texture layer to framebuffer after calling TexImage3D to increase layer count on PowerVR Android."
+            ],
+            "issue": "http://crbug.com/499602793"
         }
     ]
 }
diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp
index afa7711..5261ffd 100644
--- a/src/libANGLE/Framebuffer.cpp
+++ b/src/libANGLE/Framebuffer.cpp
@@ -2327,6 +2327,18 @@
             return;
         }
 
+        if (message == angle::SubjectMessage::TextureLayerCountIncreased)
+        {
+            FramebufferAttachment *attachment = getAttachmentFromSubjectIndex(index);
+            if (attachment)
+            {
+                (void)mImpl->onAttachmentLayerCountChange(attachment);
+            }
+            mDirtyBits.set(index);
+            onStateChange(angle::SubjectMessage::DirtyBitsFlagged);
+            return;
+        }
+
         // This can be triggered by the GL back-end TextureGL class.
         ASSERT(message == angle::SubjectMessage::DirtyBitsFlagged ||
                message == angle::SubjectMessage::TextureIDDeleted);
diff --git a/src/libANGLE/Observer.h b/src/libANGLE/Observer.h
index 526f82b..2e0ddea 100644
--- a/src/libANGLE/Observer.h
+++ b/src/libANGLE/Observer.h
@@ -88,6 +88,9 @@
     // Indicates the underlying object storage has been reallocated.
     ObjectReallocated,
 
+    // Indicates a layered texture's layer count has increased.
+    TextureLayerCountIncreased,
+
     // Indicates a change in foveated rendering state in the subject.
     FoveatedRenderingStateChanged,
 };
diff --git a/src/libANGLE/Texture.cpp b/src/libANGLE/Texture.cpp
index 3611262..6b05a48 100644
--- a/src/libANGLE/Texture.cpp
+++ b/src/libANGLE/Texture.cpp
@@ -2749,6 +2749,9 @@
         case angle::SubjectMessage::ObjectReallocated:
             onStateChange(angle::SubjectMessage::ObjectReallocated);
             break;
+        case angle::SubjectMessage::TextureLayerCountIncreased:
+            onStateChange(angle::SubjectMessage::TextureLayerCountIncreased);
+            break;
         case angle::SubjectMessage::DirtyBitsFlagged:
             signalDirtyState(DIRTY_BIT_IMPLEMENTATION);
 
diff --git a/src/libANGLE/renderer/FramebufferImpl.h b/src/libANGLE/renderer/FramebufferImpl.h
index dbace39..d7ecc89 100644
--- a/src/libANGLE/renderer/FramebufferImpl.h
+++ b/src/libANGLE/renderer/FramebufferImpl.h
@@ -105,6 +105,8 @@
 
     virtual angle::Result onLabelUpdate(const gl::Context *context);
 
+    virtual angle::Result onAttachmentLayerCountChange(gl::FramebufferAttachment *attachment);
+
     const gl::FramebufferState &getState() const { return mState; }
 
   protected:
@@ -116,6 +118,12 @@
     return false;
 }
 
+inline angle::Result FramebufferImpl::onAttachmentLayerCountChange(
+    gl::FramebufferAttachment *attachment)
+{
+    return angle::Result::Continue;
+}
+
 // Default implementation returns the format specified in the attachment.
 inline const gl::InternalFormat &FramebufferImpl::getImplementationColorReadFormat(
     const gl::Context *context) const
diff --git a/src/libANGLE/renderer/gl/ContextGL.cpp b/src/libANGLE/renderer/gl/ContextGL.cpp
index c78f216..fd2c023 100644
--- a/src/libANGLE/renderer/gl/ContextGL.cpp
+++ b/src/libANGLE/renderer/gl/ContextGL.cpp
@@ -143,7 +143,7 @@
         funcs->genFramebuffers(1, &fbo);
     }
 
-    return new FramebufferGL(data, fbo, false);
+    return new FramebufferGL(data, fbo, false, funcs, getStateManager());
 }
 
 TextureImpl *ContextGL::createTexture(const gl::TextureState &state)
diff --git a/src/libANGLE/renderer/gl/FramebufferGL.cpp b/src/libANGLE/renderer/gl/FramebufferGL.cpp
index e24641c..625eefc 100644
--- a/src/libANGLE/renderer/gl/FramebufferGL.cpp
+++ b/src/libANGLE/renderer/gl/FramebufferGL.cpp
@@ -464,11 +464,17 @@
     return textureGL->hasEmulatedAlphaChannel(attachment->getTextureImageIndex());
 }
 
-FramebufferGL::FramebufferGL(const gl::FramebufferState &data, GLuint id, bool emulatedAlpha)
+FramebufferGL::FramebufferGL(const gl::FramebufferState &data,
+                             GLuint id,
+                             bool emulatedAlpha,
+                             const FunctionsGL *functions,
+                             StateManagerGL *stateManager)
     : FramebufferImpl(data),
       mFramebufferID(id),
       mHasEmulatedAlphaAttachment(emulatedAlpha),
-      mAppliedEnabledDrawBuffers(1)
+      mAppliedEnabledDrawBuffers(1),
+      mFunctions(functions),
+      mStateManager(stateManager)
 {
     ASSERT((isDefault() && id == 0) || !isDefault());
 }
@@ -1377,6 +1383,15 @@
     return blitter->clearFramebuffer(context, colorAttachments, depth, stencil, this);
 }
 
+angle::Result FramebufferGL::onAttachmentLayerCountChange(gl::FramebufferAttachment *attachment)
+{
+    ASSERT(!isDefault() && attachment && attachment->isAttached() &&
+           attachment->type() == GL_TEXTURE && mFunctions->framebufferTextureLayer);
+    mStateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
+    mFunctions->framebufferTextureLayer(GL_FRAMEBUFFER, attachment->getBinding(), 0, 0, 0);
+    return angle::Result::Continue;
+}
+
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/tests/capture_replay_tests/capture_replay_expectations.txt b/src/tests/capture_replay_tests/capture_replay_expectations.txt
index 3a6a919..e140de3 100644
--- a/src/tests/capture_replay_tests/capture_replay_expectations.txt
+++ b/src/tests/capture_replay_tests/capture_replay_expectations.txt
@@ -286,3 +286,6 @@
 
 # Crashes on win and linux trace bots
 372059358 : MultisampleTestES3.CopyTexImage2DFromMsaaDefaultFbo/* = SKIP_FOR_CAPTURE
+
+# Fails on win and linux trace bots
+499602793 : Texture2DArrayTestES3_ReattachTextureToFbo.IncreaseLayersWithFramebufferAttachedMultiContext/* = SKIP_FOR_CAPTURE
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index acc3375..8ebcd4d 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -9298,6 +9298,135 @@
     EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
 }
 
+class Texture2DArrayTestES3_ReattachTextureToFbo : public Texture2DArrayTestES3
+{};
+
+// Test increasing layer count of a 2D array texture when one of its layers is attached to a
+// framebuffer. http://crbug.com/499602793
+TEST_P(Texture2DArrayTestES3_ReattachTextureToFbo, IncreaseLayersWithFramebufferAttached)
+{
+    // http://crbug.com/499602793 - Metal backend does not support redefining 2D array texture
+    // layer count without releasing storage.
+    ANGLE_SKIP_TEST_IF(IsMetal());
+
+    glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
+    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+    std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red);
+    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 pixelsRed.data());
+    ASSERT_GL_NO_ERROR();
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0);
+    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red);
+
+    // Increase layer count to 2.
+    std::vector<GLColor> pixelsGreen(4 * 4 * 2, GLColor::green);
+    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 pixelsGreen.data());
+    ASSERT_GL_NO_ERROR();
+
+    // Verify layer 0 points to the new memory (green).
+    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+
+    // Verify layer 1 is also green.
+    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 1);
+    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+
+    // Clear layer 1 to blue and verify.
+    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+    glClear(GL_COLOR_BUFFER_BIT);
+    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::blue);
+
+    // Now sample from layer 0 and layer 1 using a shader to ensure texture memory matches FBO
+    // memory.
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0);
+    glUseProgram(mProgram);
+    glUniform1i(mTextureArrayLocation, 0);
+
+    // Verify layer 0 is green.
+    glUniform1i(mTextureArraySliceUniformLocation, 0);
+    drawQuad(mProgram, "position", 0.5f);
+    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
+
+    // Verify layer 1 is blue.
+    glUniform1i(mTextureArraySliceUniformLocation, 1);
+    drawQuad(mProgram, "position", 0.5f);
+    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::blue);
+}
+
+// Test increasing layer count of a 2D array texture when one of its layers is attached to a
+// framebuffer in one context, and the texture is redefined in another shared context.
+// http://crbug.com/499602793
+TEST_P(Texture2DArrayTestES3_ReattachTextureToFbo,
+       IncreaseLayersWithFramebufferAttachedMultiContext)
+{
+    // http://crbug.com/499602793 - Metal backend does not support redefining 2D array texture
+    // layer count without releasing storage.
+    ANGLE_SKIP_TEST_IF(IsMetal());
+
+    glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
+    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+    std::vector<GLColor> pixelsRed(4 * 4 * 1, GLColor::red);
+    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 pixelsRed.data());
+    ASSERT_GL_NO_ERROR();
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 0);
+    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::red);
+
+    // Set up and switch to a secondary context sharing resources with the current context.
+    EGLWindow *window          = getEGLWindow();
+    EGLDisplay display         = window->getDisplay();
+    EGLConfig config           = window->getConfig();
+    EGLSurface surface         = window->getSurface();
+    EGLint contextAttributes[] = {
+        EGL_CONTEXT_MAJOR_VERSION_KHR,
+        GetParam().majorVersion,
+        EGL_CONTEXT_MINOR_VERSION_KHR,
+        GetParam().minorVersion,
+        EGL_NONE,
+    };
+    EGLContext context1 = eglGetCurrentContext();
+    EGLContext context2 = eglCreateContext(display, config, context1, contextAttributes);
+    ASSERT_NE(context2, EGL_NO_CONTEXT);
+    eglMakeCurrent(display, surface, surface, context2);
+
+    // In the secondary context, bind the texture and increase layer count to 2.
+    glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
+    std::vector<GLColor> pixelsGreen(4 * 4 * 2, GLColor::green);
+    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 4, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+                 pixelsGreen.data());
+    ASSERT_GL_NO_ERROR();
+
+    // Switch back to the primary context.
+    eglMakeCurrent(display, surface, surface, context1);
+
+    // Verify layer 0 points to the new memory (green).
+    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+
+    // Attach layer 1 to the FBO in context1 and verify. Explicitly calling
+    // glFramebufferTextureLayer attaches to the newly allocated texture storage.
+    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m2DArrayTexture, 0, 1);
+    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::green);
+
+    // Clear layer 1 to blue and verify.
+    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
+    glClear(GL_COLOR_BUFFER_BIT);
+    EXPECT_PIXEL_RECT_EQ(0, 0, 4, 4, GLColor::blue);
+
+    // Clean up secondary context.
+    eglDestroyContext(display, context2);
+}
+
 // Create a 3D texture, use it, then redefine one level without changing dimensions.
 TEST_P(Texture3DTestES3, RedefineLevelData)
 {
@@ -23208,6 +23337,16 @@
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DArrayTestES3);
 ANGLE_INSTANTIATE_TEST_ES3(Texture2DArrayTestES3);
 
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(Texture2DArrayTestES3_ReattachTextureToFbo);
+ANGLE_INSTANTIATE_TEST_ES3_AND(
+    Texture2DArrayTestES3_ReattachTextureToFbo,
+    ES3_OPENGL().enable(Feature::ReattachTextureToFboAfterLayerIncrease),
+    ES3_OPENGL().disable(Feature::ReattachTextureToFboAfterLayerIncrease),
+    ES3_OPENGLES().enable(Feature::ReattachTextureToFboAfterLayerIncrease),
+    ES3_OPENGLES().disable(Feature::ReattachTextureToFboAfterLayerIncrease),
+    ES3_VULKAN().enable(Feature::ReattachTextureToFboAfterLayerIncrease),
+    ES3_VULKAN().disable(Feature::ReattachTextureToFboAfterLayerIncrease));
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TextureSizeTextureArrayTest);
 ANGLE_INSTANTIATE_TEST_ES3(TextureSizeTextureArrayTest);
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.