High watch CVSS 9.6 chrome UAF 🔧 Commit mapped

Overview

High
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebGL
DescriptionUse after free in WebGL
ComponentWebGL
Sub-componentGL
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker534468209
Fix commite33e55e0801c (angle/angle) +142/-2
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-07-13 (per vendor advisory)
Fix landed2026-07-23 (public git)
Disclosed2026-08-25
Patch gap33 days — fix public in source before the advisory

Background

ANGLE `StateManagerGL`
ANGLE’s GL-backend state tracker that mediates framebuffer (FBO) binding/deletion and query begin/end calls to the underlying native GL driver.
GPU query / pending job
an asynchronous operation (e.g. GL_ANY_SAMPLES_PASSED occlusion query) whose result and referenced resources may still be in-flight on the GPU after the API call returns.
FBO unbind/delete
bindFramebuffer/deleteFramebuffers, which on ARM Mali (Valhall/Avalon) can be processed by the driver while a query/job that references that FBO is still pending.
`forcefullyFlush()` / `glFenceSync`
a new synchronization primitive that pushes and waits on pending command buffers (falling back to glFinish()), ensuring outstanding queries complete before the FBO is torn down.

Root Cause Analysis

On ARM Mali Valhall/Avalon drivers, unbinding or deleting a user framebuffer while a GPU query/job that references it is still pending leaves the driver holding a dangling reference to the now-torn-down framebuffer, which it later dereferences when the query result is retrieved — a driver-side use-after-free.

Pre-patch, StateManagerGL::deleteFramebuffer() and bindFramebuffer() performed the delete/unbind without any guarantee that outstanding queries had been flushed, so the reordering of teardown before query completion violated the invariant that a framebuffer outlives all GPU work referencing it.

The fix adds mHasUnflushedQueries tracking (set in the query begin/end paths, cleared in onSyncedFlushOrFinish()), and, under the new flushQueriesBeforeDeletingOrUnbindingFbo feature (enabled on Mali), calls forcefullyFlush() before deleting an unbound FBO and after an actual framebuffer change, forcing pending queries/jobs to complete first. Because the flush drains the referencing work before the framebuffer memory is released or rebound, the driver no longer dereferences a freed object. The added DeleteFBOWithPendingQuery / UnbindFBOWithPendingQuery tests exercise exactly this delete/unbind-then-glGetQueryObjectuiv sequence.

Key insight
The core mistake was tearing down (deleting/unbinding) a framebuffer while GPU queries/jobs referencing it were still pending on Mali drivers; the fix forces a synchronizing flush (forcefullyFlush()) whenever there are unflushed queries and an FBO is deleted or the framebuffer binding changes.

Attack Path

  1. Set up an FBO with a pending query WebGL/WebGL2 content binds a user framebuffer, begins a query (glBeginQuery(GL_ANY_SAMPLES_PASSED, ...)), issues a draw, and ends the query, leaving the result and referenced FBO pending on the GPU.
  2. Delete or unbind the FBO before completion The content deletes the FBO or rebinds to another/default framebuffer while the query is still pending, so pre-patch deleteFramebuffer()/bindFramebuffer() tears it down with no flush.
  3. Force the driver to touch the stale reference The content retrieves the query result (glGetQueryObjectuiv), causing the Mali driver to process the pending job that still references the freed framebuffer, triggering a use-after-free in the driver.
  4. Crash / memory corruption The dangling dereference crashes the GPU process, with potential for further memory corruption in the driver (inferred; the patch demonstrates only the driver crash it works around).

Impact Assessment

An attacker with the ability to run WebGL content can trigger a use-after-free in the ARM Mali (Valhall/Avalon) GPU driver from the GPU process by deleting/unbinding a framebuffer while a query referencing it is pending and then reading the query result. The patch is a workaround that prevents the driver crash; escalation beyond a GPU-process crash (memory corruption exploitation) is (inferred) and not demonstrated by the diff. Preconditions are an affected Mali device where the flushQueriesBeforeDeletingOrUnbindingFbo feature applies; no exploited-in-the-wild status is stated in the advisory.

Files Changed

  • src/libANGLE/renderer/gl/StateManagerGL.cpp
  • util/autogen/angle_features_autogen.h
  • src/libANGLE/renderer/gl/StateManagerGL.h
  • src/libANGLE/renderer/gl/renderergl_utils.cpp
  • include/platform/autogen/FeaturesGL_autogen.h
  • src/tests/gl_tests/OcclusionQueriesTest.cpp
  • util/autogen/angle_features_autogen.cpp
  • src/libANGLE/renderer/gl/RendererGL.cpp
  • include/platform/gl_features.json

Audit Directions

  • Resource teardown vs. pending GPU work
    Audit every path that deletes or unbinds GL resources (FBOs, textures, renderbuffers) for guarantees that in-flight queries/jobs referencing them have been flushed first, especially in driver-specific workaround gates.
  • Async query lifetime
    Look for query begin/end tracking that does not enforce completion before dependent objects are freed; the mHasUnflushedQueries pattern shows where such tracking was previously missing.
  • Driver-specific reordering hazards
    Review other Mali/vendor workarounds (flushOnFramebufferChange, flushBeforeDeleteTextureIfCopiedTo) for analogous cases where a flush is conditionally skipped and a dangling driver reference can result.
From e33e55e0801c4ba770e39a8b52679c0205475184 Mon Sep 17 00:00:00 2001
From: Thomas Smith <thomsmit@google.com>
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 <syoussefi@chromium.org>
Reviewed-by: Kenneth Russell <kbr@chromium.org>
Commit-Queue: Kenneth Russell <kbr@chromium.org>
---
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);
     void bindTransformFeedback(GLenum type, GLuint transformFeedback);
     void onTransformFeedbackStateChange();
@@ -585,6 +588,7 @@
     const bool mIndependentBlendStates;
 
     bool mSampleCoverageEverChanged;
+    bool mHasUnflushedQueries;
 
     const bool mFramebufferSRGBAvailable;
     const bool mHasSeparateFramebufferBindings;
diff --git a/src/libANGLE/renderer/gl/renderergl_utils.cpp b/src/libANGLE/renderer/gl/renderergl_utils.cpp
index e3b2a48..08284c3 100644
--- a/src/libANGLE/renderer/gl/renderergl_utils.cpp
+++ b/src/libANGLE/renderer/gl/renderergl_utils.cpp
@@ -2679,6 +2679,9 @@
     // https://crbug.com/1356053
     ANGLE_FEATURE_CONDITION(features, bindCompleteFramebufferForTimerQueries, isMali);
 
+    // http://crbug.com/534468209
+    ANGLE_FEATURE_CONDITION(features, flushQueriesBeforeDeletingOrUnbindingFbo, isMali);
+
     // https://crbug.com/40264674
     ANGLE_FEATURE_CONDITION(features, disableClipControl, IsMaliG72OrG76OrG51(functions));
 
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);
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/util/autogen/angle_features_autogen.cpp b/util/autogen/angle_features_autogen.cpp
index bbadf28..200856d 100644
--- a/util/autogen/angle_features_autogen.cpp
+++ b/util/autogen/angle_features_autogen.cpp
@@ -188,6 +188,7 @@
     {Feature::FlushAfterStreamVertexData, "flushAfterStreamVertexData"},
     {Feature::FlushBeforeDeleteTextureIfCopiedTo, "flushBeforeDeleteTextureIfCopiedTo"},
     {Feature::FlushOnFramebufferChange, "flushOnFramebufferChange"},
+    {Feature::FlushQueriesBeforeDeletingOrUnbindingFbo, "flushQueriesBeforeDeletingOrUnbindingFbo"},
     {Feature::ForceAtomicValueResolution, "forceAtomicValueResolution"},
     {Feature::ForceBufferGPUStorage, "forceBufferGPUStorage"},
     {Feature::ForceContinuousRefreshOnSharedPresent, "forceContinuousRefreshOnSharedPresent"},
diff --git a/util/autogen/angle_features_autogen.h b/util/autogen/angle_features_autogen.h
index fa23735..56a97bc 100644
--- a/util/autogen/angle_features_autogen.h
+++ b/util/autogen/angle_features_autogen.h
@@ -188,6 +188,7 @@
     FlushAfterStreamVertexData,
     FlushBeforeDeleteTextureIfCopiedTo,
     FlushOnFramebufferChange,
+    FlushQueriesBeforeDeletingOrUnbindingFbo,
     ForceAtomicValueResolution,
     ForceBufferGPUStorage,
     ForceContinuousRefreshOnSharedPresent,
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.