CVE-2026-17802
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Pgpu/command_buffer/tests/gl_virtual_contexts_unittest.cc |
modified | |
ifgpu/command_buffer/tests/gl_virtual_contexts_unittest.cc |
modified |
Files Changed
gpu/command_buffer/tests/gl_virtual_contexts_unittest.ccui/gl/gl_context.cc
Patch
From 3d5b3cd7445872d40b9b63e31a41b666ac3e7556 Mon Sep 17 00:00:00 2001
From: Ken Russell <kbr@chromium.org>
Date: Thu, 04 Jun 2026 18:19:34 -0700
Subject: [PATCH] Pause queries during virtual context switch.
Prevents query state from leaking across contexts.
Added minimized test case, ported from the bug report, which catches
the bug.
Co-authored with jetski-cli.
Fixed: 514512198
Change-Id: I080df453f235df0785a02d063f50ac1a68327412
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7904480
Auto-Submit: Kenneth Russell <kbr@chromium.org>
Reviewed-by: Zhenyao Mo <zmo@chromium.org>
Commit-Queue: Zhenyao Mo <zmo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1642066}
---
diff --git a/gpu/command_buffer/tests/gl_virtual_contexts_unittest.cc b/gpu/command_buffer/tests/gl_virtual_contexts_unittest.cc
index adfbf39..4ac6ea2b 100644
--- a/gpu/command_buffer/tests/gl_virtual_contexts_unittest.cc
+++ b/gpu/command_buffer/tests/gl_virtual_contexts_unittest.cc
@@ -370,6 +370,71 @@
}
}
+TEST_P(GLVirtualContextsTest, VirtualQueriesOcclusionLeak) {
+ // This test requires occlusion query support.
+ // We use GL_ANY_SAMPLES_PASSED_EXT.
+ const GLenum query_target = GL_ANY_SAMPLES_PASSED_EXT;
+
+ gl1_.MakeCurrent();
+ GLuint query1 = 0;
+ glGenQueriesEXT(1, &query1);
+ glBeginQueryEXT(query_target, query1);
+ const GLenum begin_error = glGetError();
+ if (GL_INVALID_OPERATION == begin_error) {
+ // Not supported, simply skip.
+ glDeleteQueriesEXT(1, &query1);
+ return;
+ }
+ ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), begin_error);
+ glFlush();
+
+ // Context B (gl2_) warmup.
+ gl2_.MakeCurrent();
+ SetUpColoredUnitQuad(kFloatGreen);
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ glFinish();
+
+ // Switch back to Context A (gl1_) to ensure it is the active context
+ // when the new context is created.
+ gl1_.MakeCurrent();
+
+ // Context C (gl3) - brand new context.
+ // Initializing it should trigger MakeVirtuallyCurrent on a new context,
+ // which should trigger the bug (skipping PauseQueries on gl1_).
+ GLManager gl3;
+ GLManager::Options options;
+ options.context_type = gl1_.GetContextType();
+ options.virtual_manager = &gl_real_shared_;
+ options.size = gfx::Size(kSize0, kSize0);
+ gl3.InitializeWithWorkarounds(options, GetParam());
+
+ // Context B (gl2_) renders.
+ // If gl1_'s query was not paused, gl2_'s rendering will leak into it.
+ gl2_.MakeCurrent();
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ glFlush();
+
+ // Context A (gl1_) ends query and checks result.
+ gl1_.MakeCurrent();
+ glEndQueryEXT(query_target);
+ glFinish();
+
+ GLuint query1_available = 0;
+ glGetQueryObjectuivEXT(query1, GL_QUERY_RESULT_AVAILABLE_EXT,
+ &query1_available);
+ EXPECT_TRUE(query1_available);
+
+ GLuint query_result = 0;
+ glGetQueryObjectuivEXT(query1, GL_QUERY_RESULT_EXT, &query_result);
+ glDeleteQueriesEXT(1, &query1);
+
+ // Since we drew nothing in gl1_ while the query was active,
+ // the result should be 0. If it's > 0, it leaked from gl2_.
+ EXPECT_EQ(0u, query_result);
+
+ gl3.Destroy();
+}
+
// http://crbug.com/930327
TEST_P(GLVirtualContextsTest, Texture2DArrayAnd3DRestore) {
// This test should only be run for ES3 or higher context
diff --git a/ui/gl/gl_context.cc b/ui/gl/gl_context.cc
index e4655ff3e..0e1cba8 100644
--- a/ui/gl/gl_context.cc
+++ b/ui/gl/gl_context.cc
@@ -456,6 +456,7 @@
if (current_state &&
!virtual_context->GetGLStateRestorer()->IsInitialized()) {
current_state->PauseTransformFeedback();
+ current_state->PauseQueries();
}
if (virtual_context->GetGLStateRestorer()->IsInitialized()) {
Regression Test / PoC
diff --git a/gpu/command_buffer/tests/gl_virtual_contexts_unittest.cc b/gpu/command_buffer/tests/gl_virtual_contexts_unittest.cc
index adfbf39..4ac6ea2b 100644
--- a/gpu/command_buffer/tests/gl_virtual_contexts_unittest.cc
+++ b/gpu/command_buffer/tests/gl_virtual_contexts_unittest.cc
@@ -370,6 +370,71 @@
}
}
+TEST_P(GLVirtualContextsTest, VirtualQueriesOcclusionLeak) {
+ // This test requires occlusion query support.
+ // We use GL_ANY_SAMPLES_PASSED_EXT.
+ const GLenum query_target = GL_ANY_SAMPLES_PASSED_EXT;
+
+ gl1_.MakeCurrent();
+ GLuint query1 = 0;
+ glGenQueriesEXT(1, &query1);
+ glBeginQueryEXT(query_target, query1);
+ const GLenum begin_error = glGetError();
+ if (GL_INVALID_OPERATION == begin_error) {
+ // Not supported, simply skip.
+ glDeleteQueriesEXT(1, &query1);
+ return;
+ }
+ ASSERT_EQ(static_cast<GLenum>(GL_NO_ERROR), begin_error);
+ glFlush();
+
+ // Context B (gl2_) warmup.
+ gl2_.MakeCurrent();
+ SetUpColoredUnitQuad(kFloatGreen);
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ glFinish();
+
+ // Switch back to Context A (gl1_) to ensure it is the active context
+ // when the new context is created.
+ gl1_.MakeCurrent();
+
+ // Context C (gl3) - brand new context.
+ // Initializing it should trigger MakeVirtuallyCurrent on a new context,
+ // which should trigger the bug (skipping PauseQueries on gl1_).
+ GLManager gl3;
+ GLManager::Options options;
+ options.context_type = gl1_.GetContextType();
+ options.virtual_manager = &gl_real_shared_;
+ options.size = gfx::Size(kSize0, kSize0);
+ gl3.InitializeWithWorkarounds(options, GetParam());
+
+ // Context B (gl2_) renders.
+ // If gl1_'s query was not paused, gl2_'s rendering will leak into it.
+ gl2_.MakeCurrent();
+ glDrawArrays(GL_TRIANGLES, 0, 6);
+ glFlush();
+
+ // Context A (gl1_) ends query and checks result.
+ gl1_.MakeCurrent();
+ glEndQueryEXT(query_target);
+ glFinish();
+
+ GLuint query1_available = 0;
+ glGetQueryObjectuivEXT(query1, GL_QUERY_RESULT_AVAILABLE_EXT,
+ &query1_available);
+ EXPECT_TRUE(query1_available);
+
+ GLuint query_result = 0;
+ glGetQueryObjectuivEXT(query1, GL_QUERY_RESULT_EXT, &query_result);
+ glDeleteQueriesEXT(1, &query1);
+
+ // Since we drew nothing in gl1_ while the query was active,
+ // the result should be 0. If it's > 0, it leaked from gl2_.
+ EXPECT_EQ(0u, query_result);
+
+ gl3.Destroy();
+}
+
// http://crbug.com/930327
TEST_P(GLVirtualContextsTest, Texture2DArrayAnd3DRestore) {
// This test should only be run for ES3 or higher context
Original Bug Report
Potential cross-origin info leak via unpaused GL queries during virtual context switch
Flapjack, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A logic flaw in GL context virtualization fails to pause active hardware queries when switching to an uninitialized context. An attacker can use this to orphan a WebGL query, keeping it active while cross-origin or browser contexts render on the GPU. This allows precise cross-origin side-channel leaks of pixel counts or render times.
Affected files:
ui/gl/gl_context.cc
Estimated timestamp from git blame: Unknown (Google3 checkout)
Description
A potential logic error exists in GLContext::MakeVirtuallyCurrent (ui/gl/gl_context.cc) regarding the handling of virtual GL context switches. When switching from an initialized context to a newly created, uninitialized virtual context, the code explicitly pauses Transform Feedback but neglects to pause active hardware queries.
In GLContext::MakeVirtuallyCurrent, the following block handles uninitialized target contexts:
if (current_state &&
!virtual_context->GetGLStateRestorer()->IsInitialized()) {
current_state->PauseTransformFeedback();
// MISSING: current_state->PauseQueries();
}
Because the subsequent block that performs standard state restoration (which normally pauses and resumes queries) is gated behind if (virtual_context->GetGLStateRestorer()->IsInitialized()), standard query pausing is bypassed entirely.
As a result, a hardware query (such as an occlusion or timer query) started in an attacker’s WebGL context can remain actively running on the GPU while a victim cross-origin context executes. This allows the attacker to accurately measure cross-origin rendering activity.
Potential Attack Scenario
Note: These are suggested steps based on static code analysis; a working proof-of-concept has not yet been executed by the tooling agent.
- An attacker page creates a WebGL context (Context A) and begins a hardware query (e.g.,
glBeginQuery(GL_ANY_SAMPLES_PASSED)). - The attacker triggers the creation of a second, new WebGL context (Context C) on the same GPU channel.
- The GPU process calls
MakeVirtuallyCurrentto switch from Context A to Context C for initialization. Because Context C is uninitialized,PauseQueries()is skipped. Context A’s query remains active on the GPU hardware, and its internal state manager still considers it “Active” (not paused). - The browser schedules a cross-origin iframe or the browser compositor (Context B) to render. The GPU process switches to Context B. It correctly pauses Context C’s queries (which are empty), completely ignoring Context A’s orphaned hardware query.
- Context B renders. Its drawn pixels or execution time are accumulated into Context A’s running hardware query.
- The GPU process eventually switches back to Context A.
ResumeQueries()is called, but it acts as a no-op because Context A’s query was never internally marked as paused in Step 3. - The attacker calls
glEndQueryin Context A, reads the result, and accurately extracts the cross-origin rendering data.
Suggested Fix
Update GLContext::MakeVirtuallyCurrent in ui/gl/gl_context.cc to ensure queries are also paused when switching to an uninitialized context:
if (current_state &&
!virtual_context->GetGLStateRestorer()->IsInitialized()) {
current_state->PauseTransformFeedback();
current_state->PauseQueries(); // Add this line to prevent orphaned queries
}
Evaluated with Chrome root at commit: b7d0c4d810da1b31400f198c70d9720fc8f0e5a0
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.