High chrome UAF 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebGL
DescriptionUse after free in WebGL
ComponentWebGL
Bug ClassUAF
Tracker534468209
Fix commite33e55e0801c (angle/angle) +142/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Files Changed

  • include/platform/autogen/FeaturesGL_autogen.h
  • include/platform/gl_features.json
  • src/libANGLE/renderer/gl/RendererGL.cpp
  • src/libANGLE/renderer/gl/StateManagerGL.cpp
  • src/libANGLE/renderer/gl/StateManagerGL.h
From e33e55e0801c4ba770e39a8b52679c0205475184 Mon Sep 17 00:00:00 2001
From: Thomas Smith <[email protected]>
Date: Tue, 21 Jul 2026 14:44:35 +0000
Subject: [PATCH] GL: workaround: Flush queries before deleting or unbinding FBOs on ARM

Implement a workaround to prevent a driver crash on ARM Mali
(Avalon/Valhall) devices. The crash is triggered when a framebuffer
object is unbound or deleted while GPU queries or jobs are still
pending.

This change introduces the `flushQueriesBeforeDeletingOrUnbindingFbo`
feature. When active, ANGLE forces a synchronization of pending queries
prior to FBO deletion or unbinding. The synchronization utilizes a
`glFenceSync` flush to push pending command buffers, gracefully falling
back to `glFinish()` if sync extensions are unavailable.

TAG: agy
CONV: c1a3bcfb-0d1f-4b4e-86b4-c6731bfeebfd
Bug: chromium:534468209
Change-Id: I39a26436ce8937a7682ce61355c164a6b306ee7a
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8110977
Reviewed-by: Shahbaz Youssefi <[email protected]>
Reviewed-by: Kenneth Russell <[email protected]>
Commit-Queue: Kenneth Russell <[email protected]>
---

diff --git a/include/platform/autogen/FeaturesGL_autogen.h b/include/platform/autogen/FeaturesGL_autogen.h
index c4174cd..6c4f57d 100644
--- a/include/platform/autogen/FeaturesGL_autogen.h
+++ b/include/platform/autogen/FeaturesGL_autogen.h
@@ -740,6 +740,12 @@
         &members,
     };
 
+    FeatureInfo flushQueriesBeforeDeletingOrUnbindingFbo = {
+        "flushQueriesBeforeDeletingOrUnbindingFbo",
+        FeatureCategory::OpenGLWorkarounds,
+        &members,
+    };
+
 };
 
 inline FeaturesGL::FeaturesGL()  = default;
diff --git a/include/platform/gl_features.json b/include/platform/gl_features.json
index d83d124..3d77686 100644
--- a/include/platform/gl_features.json
+++ b/include/platform/gl_features.json
@@ -967,6 +967,15 @@
                 "Remove complex expressions from struct constructors."
             ],
             "issue": "http://crbug.com/529509587"
+        },
+        {
+            "name": "flush_queries_before_deleting_or_unbinding_fbo",
+            "category": "Workarounds",
+            "description": [
+                "Flush pending GPU jobs/queries before unbinding or deleting a user framebuffer ",
+                "object on ARM Mali Valhall/Avalon GPUs to avoid driver crash."
+            ],
+            "issue": "http://crbug.com/534468209"
         }
     ]
 }
diff --git a/src/libANGLE/renderer/gl/RendererGL.cpp b/src/libANGLE/renderer/gl/RendererGL.cpp
index 0dbf240..d611c9f 100644
--- a/src/libANGLE/renderer/gl/RendererGL.cpp
+++ b/src/libANGLE/renderer/gl/RendererGL.cpp
@@ -238,6 +238,7 @@
     mFunctions->finish();
     mNeedsFlushBeforeDeleteTextures = false;
     mWorkDoneSinceLastFlush         = false;
+    mStateManager->onSyncedFlushOrFinish();
 
     if (mFeatures.finishDoesNotCauseQueriesToBeAvailable.enabled && mUseDebugOutput)
     {
diff --git a/src/libANGLE/renderer/gl/StateManagerGL.cpp b/src/libANGLE/renderer/gl/StateManagerGL.cpp
index ba1716a..785d7a6 100644
--- a/src/libANGLE/renderer/gl/StateManagerGL.cpp
+++ b/src/libANGLE/renderer/gl/StateManagerGL.cpp
@@ -127,6 +127,7 @@
       mPlaceholderRbo(0),
       mIndependentBlendStates(extensions.drawBuffersIndexedAny()),
       mSampleCoverageEverChanged(false),
+      mHasUnflushedQueries(false),
       mFramebufferSRGBAvailable(extensions.sRGBWriteControlEXT),
       mHasSeparateFramebufferBindings(nativegl::SupportsSeparateFramebufferBindings(functions)),
       mIsMultiviewEnabled(extensions.multiviewOVR),
@@ -315,6 +316,7 @@
 {
     if (fbo != 0)
     {
+        bool wasBound = false;
         if (mHasSeparateFramebufferBindings)
         {
             for (size_t binding = 0; binding < mState.framebuffers.size(); ++binding)
@@ -324,6 +326,7 @@
                     GLenum enumValue = angle::FramebufferBindingToEnum(
                         static_cast<angle::FramebufferBinding>(binding));
                     bindFramebuffer(enumValue, 0);
+                    wasBound = true;
                 }
             }
         }
@@ -334,8 +337,14 @@
             if (mState.framebuffers[angle::FramebufferBindingRead] == fbo)
             {
                 bindFramebuffer(GL_FRAMEBUFFER, 0);
+                wasBound = true;
             }
         }
+        if (!wasBound && mHasUnflushedQueries &&
+            mFeatures.flushQueriesBeforeDeletingOrUnbindingFbo.enabled)
+        {
+            forcefullyFlush();
+        }
         mFunctions->deleteFramebuffers(1, &fbo);
     }
 }
@@ -681,12 +690,44 @@
             break;
     }
 
-    if (framebufferChanged && mFeatures.flushOnFramebufferChange.enabled)
+    if (framebufferChanged)
     {
-        mFunctions->flush();
+        if (mFeatures.flushOnFramebufferChange.enabled)
+        {
+            mFunctions->flush();
+        }
+        if (mHasUnflushedQueries && mFeatures.flushQueriesBeforeDeletingOrUnbindingFbo.enabled)
+        {
+            forcefullyFlush();
+        }
     }
 }
 
+void StateManagerGL::onSyncedFlushOrFinish()
+{
+    mHasUnflushedQueries = false;
+}
+
+void StateManagerGL::forcefullyFlush()
+{
+    if (mFunctions->fenceSync != nullptr && mFunctions->clientWaitSync != nullptr &&
+        mFunctions->deleteSync != nullptr)
+    {
+        GLsync sync = mFunctions->fenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+        if (sync != nullptr)
+        {
+            mFunctions->clientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, 0);
+            mFunctions->deleteSync(sync);
+            onSyncedFlushOrFinish();
+            return;
+        }
+    }
+
+    // Sync creation not supported or failed; fall back to finish()
+    mFunctions->finish();
+    onSyncedFlushOrFinish();
+}
+
 void StateManagerGL::bindRenderbuffer(GLenum type, GLuint renderbuffer)
 {
     ASSERT(type == GL_RENDERBUFFER);
@@ -758,6 +799,7 @@
 
     mQueries[type] = queryObject;
     mFunctions->beginQuery(ToGLenum(type), queryId);
+    mHasUnflushedQueries = true;
 
     if (oldFramebufferBindingDraw != mPlaceholderFbo)
     {
@@ -771,6 +813,7 @@
     ASSERT(mQueries[type] == queryObject);
     mQueries[type] = nullptr;
     mFunctions->endQuery(ToGLenum(type));
+    mHasUnflushedQueries = true;
 }
 
 void StateManagerGL::updateDrawIndirectBufferBinding(const gl::Context *context)
diff --git a/src/libANGLE/renderer/gl/StateManagerGL.h b/src/libANGLE/renderer/gl/StateManagerGL.h
index 5456796..e6f264e 100644
--- a/src/libANGLE/renderer/gl/StateManagerGL.h
+++ b/src/libANGLE/renderer/gl/StateManagerGL.h
@@ -317,6 +317,8 @@
     void deleteRenderbuffer(GLuint rbo);
     void deleteTransformFeedback(GLuint transformFeedback);
 
+    void onSyncedFlushOrFinish();
+
     void useProgram(GLuint program);
     void forceUseProgram(GLuint program);
     void bindVertexArray(GLuint vao, VertexArrayStateGL *vaoState);
@@ -338,6 +340,7 @@
                           GLenum access,
                           GLenum format);
     void bindFramebuffer(GLenum type, GLuint framebuffer);
+    void forcefullyFlush();
     void bindRenderbuffer(GLenum type, GLuint renderbuffer);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/tests/gl_tests/OcclusionQueriesTest.cpp b/src/tests/gl_tests/OcclusionQueriesTest.cpp
index d647151..f45e80e 100644
--- a/src/tests/gl_tests/OcclusionQueriesTest.cpp
+++ b/src/tests/gl_tests/OcclusionQueriesTest.cpp
@@ -1245,6 +1245,78 @@
     EXPECT_GL_TRUE(result);
 }
 
+// Test that deleting an FBO while a query result on that FBO is pending does not crash the driver
+// when calling glGetQueryObjectuiv.
+TEST_P(OcclusionQueriesTestES3, DeleteFBOWithPendingQuery)
+{
+    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_2D, texture);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    glViewport(0, 0, 64, 64);
+    glClear(GL_COLOR_BUFFER_BIT);
+
+    GLQueryEXT query;
+    glBeginQuery(GL_ANY_SAMPLES_PASSED, query);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    glEndQuery(GL_ANY_SAMPLES_PASSED);
+
+    // Delete the FBO on which the query was executed, while result is still pending
+    fbo.reset();
+
+    // Retrieve query result - flushes pending jobs referencing the deleted FBO
+    GLuint result = GL_FALSE;
+    glGetQueryObjectuiv(query, GL_QUERY_RESULT, &result);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_GL_TRUE(result);
+}
+
+// Test that unbinding an FBO while a query result on that FBO is pending does not crash the driver
+// when calling glGetQueryObjectuiv.
+TEST_P(OcclusionQueriesTestES3, UnbindFBOWithPendingQuery)
+{
+    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
+
+    GLTexture texture;
+    glBindTexture(GL_TEXTURE_2D, texture);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+
+    GLFramebuffer fbo;
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
+    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+    glViewport(0, 0, 64, 64);
+    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+    glClear(GL_COLOR_BUFFER_BIT);
+
+    GLQueryEXT query;
+    glBeginQuery(GL_ANY_SAMPLES_PASSED, query);
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+    glEndQuery(GL_ANY_SAMPLES_PASSED);
+
+    // Unbind user FBO by switching back to default framebuffer (0)
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+    // FORCE the driver to commit the unbind and end the previous render pass.
+    // Because drivers often optimize glClear, issuing geometry (drawQuad)
+    // strictly guarantees a new render pass is initiated in the hardware.
+    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+
+    // Retrieve query result
+    GLuint result = GL_FALSE;
+    glGetQueryObjectuiv(query, GL_QUERY_RESULT, &result);
+    EXPECT_GL_NO_ERROR();
+    EXPECT_GL_TRUE(result);
+}
+
 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(OcclusionQueriesTest);
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OcclusionQueriesTestES3);
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.