Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in ANGLE
DescriptionOut of bounds read in ANGLE
ComponentANGLE
Bug ClassOOB
Tracker517972648
Fix commitbf459445a71f (angle/angle) +48/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • src/libANGLE/renderer/metal/DisplayMtl.mm
  • src/tests/angle_end2end_tests_expectations.txt
  • src/tests/deqp_support/deqp_egl_test_expectations.txt
  • src/tests/egl_tests/EGLRobustnessTest.cpp
From bf459445a71f5a0a33f5b20289169cda3ef4333e Mon Sep 17 00:00:00 2001
From: Le Hoang Quyen <[email protected]>
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 <[email protected]>
Reviewed-by: Geoff Lang <[email protected]>
Commit-Queue: Quyen Le <[email protected]>
---

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()),
Loading diff…

Regression Test / PoC

shipped with the fix
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()),
Loading diff…

Original Bug Report

reported by [email protected]

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.mm
  • ui/gl/gl_context_egl.cc
  • third_party/angle/src/libANGLE/Context.cpp
  • third_party/angle/src/libANGLE/validationES.h
  • gpu/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

  1. Context Creation & Mojo Deserialization: A compromised renderer can request a GLES2 command buffer by calling mojom::GpuChannel::CreateCommandBuffer with attribs.context_type set to ContextType::kOpenGLES2 (which is permitted and successfully deserialized by context_type_mojom_traits.h). Because the requested context type is not WebGL, webgl_compatibility_context evaluates to false in GenerateGLContextAttribsForDecoder (gpu/command_buffer/service/service_utils.cc).

  2. Omission of Robust Buffer Access: Inside GLContextEGL::InitializeImpl (ui/gl/gl_context_egl.cc), context attributes are only populated with EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT if the display advertises b_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 define createContextRobustness. As a result, the robust buffer access attribute is silently dropped during context creation.

  3. Bypassing Validation in ANGLE: The created ANGLE context has both mState.isWebGL() and mState.hasRobustAccess() set to false. Inside ANGLE’s Context::init (third_party/angle/src/libANGLE/Context.cpp), this causes both mRequiresRobustBehavior and mBufferAccessValidationEnabled to resolve to false. Consequently, validation entry points (such as ValidateDrawArraysAttribs in third_party/angle/src/libANGLE/validationES.h) immediately return true without verifying vertex array and index bounds against bound buffer sizes.

  4. 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.

  1. From a compromised renderer on macOS, open a GpuChannel and request a command buffer with CreateCommandBufferParams.attribs set to ContextType::kOpenGLES2 (with WebGL compatibility context set to false).
  2. Create and bind a small GL_ARRAY_BUFFER (e.g., 16 bytes) and configure a vertex attribute pointing to it.
  3. Attach a vertex shader that reads from this attribute and writes the value to a varying or render target.
  4. Call glDrawArrays with a count parameter exceeding the buffer boundary (e.g., count = 1 << 16).
  5. Retrieve the read data by rendering to a framebuffer and executing glReadPixels to 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.

View on issue tracker