CVE-2026-17701
Overview
Files Changed
src/libANGLE/renderer/metal/DisplayMtl.mmsrc/tests/angle_end2end_tests_expectations.txtsrc/tests/deqp_support/deqp_egl_test_expectations.txtsrc/tests/egl_tests/EGLRobustnessTest.cpp
Patch
From bf459445a71f5a0a33f5b20289169cda3ef4333e Mon Sep 17 00:00:00 2001
From: Le Hoang Quyen <lehoangquyen@chromium.org>
Date: Thu, 04 Jun 2026 19:33:11 +0800
Subject: [PATCH] Metal: enable EGL_EXT_create_context_robustness
Element array's OOB indices are already prevented by the front-end
as long as a context is created with
EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT attrib.
The only incomplete implementation of this extension is that the
extension expects OOB access in buffers to execute silently or trigger
context loss. However, because the Metal backend doesn't implement
GL_KHR_robust_buffer_access_behavior, the front-end will simply throw
GL_INVALID_OPERATION in those cases. This is reflected in these dEQP
tests:
- EGL.functional.robustness.reset_context.fixed_function_pipeline.\
reset_status.index_buffer_out_of_bounds
- EGL.functional.robustness.reset_context.fixed_function_pipeline.\
reset_status.vertex_buffer_out_of_bounds
Bug: chromium:517972648
Change-Id: Ia7b6e07d3f8b34f7a1669c5e576f168ad6deb159
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7901455
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Commit-Queue: Quyen Le <lehoangquyen@google.com>
---
diff --git a/src/libANGLE/renderer/metal/DisplayMtl.mm b/src/libANGLE/renderer/metal/DisplayMtl.mm
index 066733a..da5beda 100644
--- a/src/libANGLE/renderer/metal/DisplayMtl.mm
+++ b/src/libANGLE/renderer/metal/DisplayMtl.mm
@@ -468,6 +468,7 @@
void DisplayMtl::generateExtensions(egl::DisplayExtensions *outExtensions) const
{
+ outExtensions->createContextRobustness = true;
outExtensions->iosurfaceClientBuffer = true;
outExtensions->surfacelessContext = true;
outExtensions->noConfigContext = true;
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index aeb808a..3454da1 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -2820,6 +2820,9 @@
// Crashing on Win and Linux Vulkan
519817006 VULKAN : VulkanPerformanceCounterTest_TileMemory.RedefineSharedDepthTextureWithOpenRenderPasses/* = SKIP
+// This test fails because of incorrect GLX MakeCurrent calls in other tests
+520248049 LINUX OPENGL : EGLSurfacelessContextTest.CheckFramebufferStatus/* = SKIP
+
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Slow tests, should appear last in this file
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
diff --git a/src/tests/deqp_support/deqp_egl_test_expectations.txt b/src/tests/deqp_support/deqp_egl_test_expectations.txt
index 1a520f4..4605aa7 100644
--- a/src/tests/deqp_support/deqp_egl_test_expectations.txt
+++ b/src/tests/deqp_support/deqp_egl_test_expectations.txt
@@ -293,6 +293,10 @@
// New crash since VK-GL-CTS roller on 2023-05-03
42266606 MAC METAL : dEQP-EGL.functional.fence_sync.valid.egl_fence_persistent_buffer = SKIP
+// On Metal, these OOB draws will simply throw GL_INVALID_OPERATION instead of causing context loss or being ignored silently
+519942256 METAL : dEQP-EGL.functional.robustness.reset_context.fixed_function_pipeline.reset_status.index_buffer_out_of_bounds = SKIP
+519942256 METAL : dEQP-EGL.functional.robustness.reset_context.fixed_function_pipeline.reset_status.vertex_buffer_out_of_bounds = SKIP
+
42265027 PIXEL4ORXL GLES : dEQP-EGL.functional.get_frame_timestamps.* = FAIL
42265027 PIXEL4ORXL GLES : dEQP-EGL.functional.wide_color.* = FAIL
42265027 PIXEL4ORXL GLES : dEQP-EGL.functional.choose_config.simple.selection_only.transparent_red_value = FAIL
diff --git a/src/tests/egl_tests/EGLRobustnessTest.cpp b/src/tests/egl_tests/EGLRobustnessTest.cpp
index 6c7a789..3645eb5 100644
--- a/src/tests/egl_tests/EGLRobustnessTest.cpp
+++ b/src/tests/egl_tests/EGLRobustnessTest.cpp
@@ -865,6 +865,44 @@
}
}
+// Test drawing with an out-of-bounds index in an index buffer.
+// Under robust access, we only expect that the draw call doesn't crash.
+TEST_P(EGLRobustnessTestES3, DrawElementsWithOutOfBoundsIndex)
+{
+ ANGLE_SKIP_TEST_IF(!mInitialized);
+
+ ANGLE_SKIP_TEST_IF(
+ !IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_create_context") ||
+ !IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_create_context_robustness"));
+
+ createRobustContext(EGL_NO_RESET_NOTIFICATION_EXT, EGL_NO_CONTEXT);
+
+ ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
+ glUseProgram(program);
+
+ // Provide only 3 vertices (indices 0, 1, 2)
+ const GLfloat vertices[] = {
+ -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
+ };
+ GLBuffer vertexBuffer;
+ glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
+
+ GLint posLocation = glGetAttribLocation(program, essl3_shaders::PositionAttrib());
+ ASSERT_NE(-1, posLocation);
+ glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
+ glEnableVertexAttribArray(posLocation);
+
+ // Index buffer containing an OOB index (e.g. 100)
+ const GLushort indices[] = {0, 1, 100};
+ GLBuffer indexBuffer;
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
+
+ // Draw. Under robustness, this must not crash the GPU/driver.
+ glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, nullptr);
+}
+
// Test context destruction after recovering from a long running task.
TEST_P(EGLRobustnessTest, DISABLED_LongRunningTaskVulkanShutdown)
{
@@ -880,6 +918,7 @@
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLRobustnessTestES31);
ANGLE_INSTANTIATE_TEST(EGLRobustnessTest,
WithNoFixture(ES2_VULKAN()),
+ WithNoFixture(ES2_METAL()),
WithNoFixture(ES2_D3D9()),
WithNoFixture(ES2_D3D11()),
WithNoFixture(ES2_OPENGL()),
@@ -887,6 +926,7 @@
WithNoFixture(ES2_VULKAN_SWIFTSHADER()));
ANGLE_INSTANTIATE_TEST(EGLRobustnessTestES3,
WithNoFixture(ES3_VULKAN()),
+ WithNoFixture(ES3_METAL()),
WithNoFixture(ES3_D3D11()),
WithNoFixture(ES3_OPENGL()),
WithNoFixture(ES3_OPENGLES()),
Regression Test / PoC
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index aeb808a..3454da1 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -2820,6 +2820,9 @@
// Crashing on Win and Linux Vulkan
519817006 VULKAN : VulkanPerformanceCounterTest_TileMemory.RedefineSharedDepthTextureWithOpenRenderPasses/* = SKIP
+// This test fails because of incorrect GLX MakeCurrent calls in other tests
+520248049 LINUX OPENGL : EGLSurfacelessContextTest.CheckFramebufferStatus/* = SKIP
+
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Slow tests, should appear last in this file
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
diff --git a/src/tests/deqp_support/deqp_egl_test_expectations.txt b/src/tests/deqp_support/deqp_egl_test_expectations.txt
index 1a520f4..4605aa7 100644
--- a/src/tests/deqp_support/deqp_egl_test_expectations.txt
+++ b/src/tests/deqp_support/deqp_egl_test_expectations.txt
@@ -293,6 +293,10 @@
// New crash since VK-GL-CTS roller on 2023-05-03
42266606 MAC METAL : dEQP-EGL.functional.fence_sync.valid.egl_fence_persistent_buffer = SKIP
+// On Metal, these OOB draws will simply throw GL_INVALID_OPERATION instead of causing context loss or being ignored silently
+519942256 METAL : dEQP-EGL.functional.robustness.reset_context.fixed_function_pipeline.reset_status.index_buffer_out_of_bounds = SKIP
+519942256 METAL : dEQP-EGL.functional.robustness.reset_context.fixed_function_pipeline.reset_status.vertex_buffer_out_of_bounds = SKIP
+
42265027 PIXEL4ORXL GLES : dEQP-EGL.functional.get_frame_timestamps.* = FAIL
42265027 PIXEL4ORXL GLES : dEQP-EGL.functional.wide_color.* = FAIL
42265027 PIXEL4ORXL GLES : dEQP-EGL.functional.choose_config.simple.selection_only.transparent_red_value = FAIL
diff --git a/src/tests/egl_tests/EGLRobustnessTest.cpp b/src/tests/egl_tests/EGLRobustnessTest.cpp
index 6c7a789..3645eb5 100644
--- a/src/tests/egl_tests/EGLRobustnessTest.cpp
+++ b/src/tests/egl_tests/EGLRobustnessTest.cpp
@@ -865,6 +865,44 @@
}
}
+// Test drawing with an out-of-bounds index in an index buffer.
+// Under robust access, we only expect that the draw call doesn't crash.
+TEST_P(EGLRobustnessTestES3, DrawElementsWithOutOfBoundsIndex)
+{
+ ANGLE_SKIP_TEST_IF(!mInitialized);
+
+ ANGLE_SKIP_TEST_IF(
+ !IsEGLDisplayExtensionEnabled(mDisplay, "EGL_KHR_create_context") ||
+ !IsEGLDisplayExtensionEnabled(mDisplay, "EGL_EXT_create_context_robustness"));
+
+ createRobustContext(EGL_NO_RESET_NOTIFICATION_EXT, EGL_NO_CONTEXT);
+
+ ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
+ glUseProgram(program);
+
+ // Provide only 3 vertices (indices 0, 1, 2)
+ const GLfloat vertices[] = {
+ -1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
+ };
+ GLBuffer vertexBuffer;
+ glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
+
+ GLint posLocation = glGetAttribLocation(program, essl3_shaders::PositionAttrib());
+ ASSERT_NE(-1, posLocation);
+ glVertexAttribPointer(posLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
+ glEnableVertexAttribArray(posLocation);
+
+ // Index buffer containing an OOB index (e.g. 100)
+ const GLushort indices[] = {0, 1, 100};
+ GLBuffer indexBuffer;
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
+
+ // Draw. Under robustness, this must not crash the GPU/driver.
+ glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, nullptr);
+}
+
// Test context destruction after recovering from a long running task.
TEST_P(EGLRobustnessTest, DISABLED_LongRunningTaskVulkanShutdown)
{
@@ -880,6 +918,7 @@
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLRobustnessTestES31);
ANGLE_INSTANTIATE_TEST(EGLRobustnessTest,
WithNoFixture(ES2_VULKAN()),
+ WithNoFixture(ES2_METAL()),
WithNoFixture(ES2_D3D9()),
WithNoFixture(ES2_D3D11()),
WithNoFixture(ES2_OPENGL()),
@@ -887,6 +926,7 @@
WithNoFixture(ES2_VULKAN_SWIFTSHADER()));
ANGLE_INSTANTIATE_TEST(EGLRobustnessTestES3,
WithNoFixture(ES3_VULKAN()),
+ WithNoFixture(ES3_METAL()),
WithNoFixture(ES3_D3D11()),
WithNoFixture(ES3_OPENGL()),
WithNoFixture(ES3_OPENGLES()),
Original Bug Report
Potential GPU process OOB buffer read in ANGLE/Metal due to missing context robustness on macOS
Project Fortify, 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 potential out-of-bounds GPU buffer read vulnerability exists in the ANGLE Metal backend on macOS. This issue is caused by the silent omission of the EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT attribute when Chrome creates GL contexts because the Metal backend does not advertise EGL_EXT_create_context_robustness. A compromised renderer process could potentially bypass all draw-call bounds validation under the passthrough command decoder, leading to cross-origin GPU data exposure.
Affected files:
third_party/angle/src/libANGLE/renderer/metal/DisplayMtl.mmui/gl/gl_context_egl.ccthird_party/angle/src/libANGLE/Context.cppthird_party/angle/src/libANGLE/validationES.hgpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
Estimated timestamp from git blame: 2024-01-25
Description
A compromised renderer process can potentially bypass draw-call bounds validation under the OpenGL ES 2.0 passthrough decoder on macOS. This behavior arises from a silent fallback during EGL context initialization where robust buffer access is omitted because ANGLE’s Metal display backend does not advertise EGL_EXT_create_context_robustness support.
Vulnerability Mechanics & Root Cause
-
Context Creation & Mojo Deserialization: A compromised renderer can request a GLES2 command buffer by calling
mojom::GpuChannel::CreateCommandBufferwithattribs.context_typeset toContextType::kOpenGLES2(which is permitted and successfully deserialized bycontext_type_mojom_traits.h). Because the requested context type is not WebGL,webgl_compatibility_contextevaluates tofalseinGenerateGLContextAttribsForDecoder(gpu/command_buffer/service/service_utils.cc). -
Omission of Robust Buffer Access: Inside
GLContextEGL::InitializeImpl(ui/gl/gl_context_egl.cc), context attributes are only populated withEGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXTif the display advertisesb_EGL_EXT_create_context_robustness. On macOS with the default ANGLE Metal backend,DisplayMtl::generateExtensions(third_party/angle/src/libANGLE/renderer/metal/DisplayMtl.mm) does not definecreateContextRobustness. As a result, the robust buffer access attribute is silently dropped during context creation. -
Bypassing Validation in ANGLE: The created ANGLE context has both
mState.isWebGL()andmState.hasRobustAccess()set tofalse. Inside ANGLE’sContext::init(third_party/angle/src/libANGLE/Context.cpp), this causes bothmRequiresRobustBehaviorandmBufferAccessValidationEnabledto resolve tofalse. Consequently, validation entry points (such asValidateDrawArraysAttribsinthird_party/angle/src/libANGLE/validationES.h) immediately returntruewithout verifying vertex array and index bounds against bound buffer sizes. -
Potential GPU Out-Of-Bounds Read: Because the passthrough command decoder forwards drawing operations directly to ANGLE, and ANGLE bypasses CPU-side validation, raw vertex arrays are bound and sent directly to the Metal render command encoder. Since no hardware-level robust access was configured, the GPU vertex fetcher can read out-of-bounds. Given that a global texture and semaphore share group is enabled in the passthrough command decoder, this out-of-bounds read can potentially leak adjacent cross-origin GPU buffer and texture data from other tabs or contexts.
Suggested Potential Reproduction Steps
Note: The following are suggested/potential reproduction steps because our tooling agent does not have the ability to run code or verify a live proof of concept.
- From a compromised renderer on macOS, open a GpuChannel and request a command buffer with
CreateCommandBufferParams.attribsset toContextType::kOpenGLES2(with WebGL compatibility context set to false). - Create and bind a small
GL_ARRAY_BUFFER(e.g., 16 bytes) and configure a vertex attribute pointing to it. - Attach a vertex shader that reads from this attribute and writes the value to a varying or render target.
- Call
glDrawArrayswith a count parameter exceeding the buffer boundary (e.g.,count = 1 << 16). - Retrieve the read data by rendering to a framebuffer and executing
glReadPixelsto inspect potential cross-origin GPU memory content.
Suggested Fix
Support robust context creation in ANGLE’s Metal backend. This can be achieved by implementing robust buffer access behavior or by defining outExtensions->createContextRobustness = true; in DisplayMtl::generateExtensions (third_party/angle/src/libANGLE/renderer/metal/DisplayMtl.mm) and ensuring that appropriate robust emulation or bounds checks are enforced during draw setup if robust access is requested.
Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040
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.