Overview

Medium
Severity
10
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionMaliciously crafted web content may be able to break out of Web Content sandbox. This is a supplementary fix for an attack that was blocked in iOS 17.2. (Apple is aware of a report that this issue may have been exploited in an extremely sophisticated attack against specific targeted individuals on versions of iOS before iOS 17.2.)
ComponentWebKit
Sub-componentWebCore Platform/Graphics
Bug ClassOOB
Tracker285858
Fix commit7d784721e440 (WebKit/WebKit)
CWECWE-787 (Out-of-bounds write)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
CISA KEVListed
CreditedApple
Disclosed2025-03-11

Background

ANGLE
WebKit’s translation layer running WebGL on native GPU APIs; its Metal backend executes in the GPU/WebContent graphics path.
PRIMITIVE_RESTART_FIXED_INDEX
A GL capability controlling primitive-restart in indexed draws; in WebGL2 it is implicitly always on and not app-toggleable, and absent in WebGL1.
WebGL enable/disable
APIs that set GL capabilities; forwarding an implementation-managed capability from untrusted content desynchronizes GL state.

Root Cause Analysis

This is a supplementary fix (exploited in the wild before iOS 17.2) for an out-of-bounds sandbox escape via WebGL primitive-restart state in the ANGLE Metal backend. WebGL exposes enable(cap)/disable(cap); GraphicsContextGLANGLE::enable/disable forwarded the capability straight to GL_Enable/GL_Disable. PRIMITIVE_RESTART_FIXED_INDEX must not be toggled by web content: in WebGL2 it is always enabled and managed by the implementation, and in WebGL1 it does not exist. Allowing content to enable or disable it drove ANGLE/GL into an inconsistent primitive-restart configuration that, combined with index buffers during geometry processing, produced out-of-bounds memory access in the GPU pipeline — a corruption primitive used to break out of the WebContent sandbox.

The fix special-cases the capability: disable(PRIMITIVE_RESTART_FIXED_INDEX) raises InvalidOperation in WebGL2 and returns without forwarding to GL; enable(PRIMITIVE_RESTART_FIXED_INDEX) raises InvalidOperation when NOT in WebGL2 and returns.

The restored invariant is that primitive-restart state is controlled by the implementation per WebGL version, never by untrusted enable/disable calls.

Key insight
WebGL enable/disable forwarded PRIMITIVE_RESTART_FIXED_INDEX — a capability the implementation must manage per WebGL version — straight to GL, letting content desynchronize primitive-restart state into an out-of-bounds condition; rejecting the toggle restores control to the implementation.

Attack Path

  1. Create a WebGL context Open a WebGL1 or WebGL2 context from web content.
  2. Toggle the forbidden capability Call gl.enable/gl.disable(PRIMITIVE_RESTART_FIXED_INDEX), which pre-patch forwarded straight to ANGLE/GL.
  3. Desynchronize primitive-restart state Combined with crafted index buffers, the inconsistent state drives out-of-bounds access in the GPU geometry pipeline.
  4. Escape the sandbox Leverage the OOB corruption to break out of the WebContent sandbox (observed in a sophisticated in-the-wild attack).

Impact Assessment

An out-of-bounds memory-corruption primitive reachable from ordinary WebGL content that was used to escape the WebContent sandbox — the advisory notes exploitation in an extremely sophisticated targeted attack and frames it as a supplementary fix. Despite the ‘medium’ label the real-world impact is sandbox escape, among the most serious outcomes for renderer-reachable bugs.

Changed Functions

FunctionChangeNotes
if
Source/WebCore/platform/graphics/angle/GraphicsContextGLANGLE.cpp
modified

Files Changed

  • Source/WebCore/platform/graphics/angle/GraphicsContextGLANGLE.cpp

Audit Directions

  • Other implementation-managed capabilities
    Audit GraphicsContextGLANGLE::enable/disable and cap-forwarding for other GL capabilities that WebGL1/WebGL2 must manage internally rather than accept from content.
  • WebGL version gating
    Grep for m_isForWebGL2 checks around GL state to find capabilities/queries valid in only one WebGL version that are not gated.
diff --git a/Source/WebCore/platform/graphics/angle/GraphicsContextGLANGLE.cpp b/Source/WebCore/platform/graphics/angle/GraphicsContextGLANGLE.cpp
index 55f23b3eb337..bbe382cf413d 100644
--- a/Source/WebCore/platform/graphics/angle/GraphicsContextGLANGLE.cpp
+++ b/Source/WebCore/platform/graphics/angle/GraphicsContextGLANGLE.cpp
@@ -1187,7 +1187,11 @@ void GraphicsContextGLANGLE::disable(GCGLenum cap)
 {
     if (!makeContextCurrent())
         return;
-
+    if (cap == PRIMITIVE_RESTART_FIXED_INDEX) {
+        if (m_isForWebGL2)
+            addError(GCGLErrorCode::InvalidOperation);
+        return;
+    }
     GL_Disable(cap);
 }
 
@@ -1221,7 +1225,11 @@ void GraphicsContextGLANGLE::enable(GCGLenum cap)
 {
     if (!makeContextCurrent())
         return;
-
+    if (cap == PRIMITIVE_RESTART_FIXED_INDEX) {
+        if (!m_isForWebGL2)
+            addError(GCGLErrorCode::InvalidOperation);
+        return;
+    }
     GL_Enable(cap);
 }
 
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker.