Patch to Root CausePart 1 of 2
  1. Part 1CVE-2025-24201: How Web Content Reached an Unneeded GPU State
  2. Part 2CVE-2025-32463: How a User-Controlled Chroot Loaded Code as Root

CVE-2025-24201 in 60 seconds

Safari runs website code inside a restricted Web Content process. CVE-2025-24201 was an out-of-bounds write that Apple says could let malicious web content break out of that sandbox. The public WebKit patch removes one unnecessary capability from that process: a page-controlled WebGL context could ask the graphics layer to enable or disable PRIMITIVE_RESTART_FIXED_INDEX and WebKit forwarded the request to ANGLE.

The public path is:

malicious web content
  -> issue a WebGL state-change request
  -> GraphicsContextGLANGLE forwards primitive-restart enable/disable
  -> ANGLE / graphics state is changed from the Web Content process
  -> Apple associates the bug with an out-of-bounds write and sandbox escape

The final arrow is an important evidence boundary. Apple’s advisory confirms the vulnerability class and impact, while the public patch confirms the capability that was removed. Neither public source reveals the complete memory-corruption primitive or the original exploit chain. This article does not pretend that twelve changed lines alone reconstruct those private details.

What are Web Content, WebGL, ANGLE, and primitive restart?

The Web Content process parses and executes untrusted page content under sandbox restrictions. WebGL gives that content a browser-mediated graphics API. WebKit’s GraphicsContextGLANGLE is part of the bridge to ANGLE, a graphics translation layer that ultimately manages lower-level GPU state.

Primitive restart is a rendering feature that lets a special index value end one geometry primitive and begin another without a separate draw call. WebGL 2 enables fixed-index primitive restart by default; WebGL 1 leaves it disabled. The WebKit commit says there was no use case for the Web Content process to toggle that state. A page could request a transition that the browser already knew should remain fixed for the context type.

What happened?

Apple’s Safari 18.3.1 advisory describes an out-of-bounds write fixed with improved checks and says malicious web content could break out of the Web Content sandbox. It calls the release a supplementary fix for an attack blocked in iOS 17.2 and notes a report of exploitation in an extremely sophisticated attack against specific targeted individuals on earlier iOS versions.

WebKit commit 7d784721e4 changes GraphicsContextGLANGLE::enable() and disable(). Both now intercept PRIMITIVE_RESTART_FIXED_INDEX and return before calling ANGLE; invalid state changes also record InvalidOperation. The patch removes the unnecessary state-changing route rather than trying to sanitize a collection of known inputs after they reach the graphics layer.

Who was actually affected?

Safari 18.3.1 fixed the issue for Safari on macOS Ventura and Sonoma. Apple’s exploitation note refers specifically to targeted individuals on iOS versions before 17.2 and describes Safari 18.3.1 as a supplementary fix. Those statements should not be collapsed into “every vulnerable Safari user was attacked.” They establish a serious web-content entry point, a sandbox-escape impact, and evidence of targeted use; device-specific exposure still depends on the platform release and update history.

What I verified

  • The commit changes one file and the two methods GraphicsContextGLANGLE::enable() and GraphicsContextGLANGLE::disable().
  • Both methods intercept PRIMITIVE_RESTART_FIXED_INDEX before forwarding the request to ANGLE.
  • The patched branch returns immediately. On an invalid WebGL context it records InvalidOperation instead of changing graphics state.
  • I matched the WebKit commit and Bugzilla identifier to Apple’s Safari 18.3.1 advisory.
  • This is a static patch reproduction. I verified the changed control flow, not the out-of-bounds write, the original exploit, or the reported targeted attacks.

Deep dive: the public trail is unusually complete

Apple’s advisory names WebKit Bugzilla issue 285858. The corresponding WebKit commit is 292004@main, represented in Git as 7d784721e4. Its subject is direct: a WebGL context could toggle primitive-restart state from the WebContent process.

The change touches one file and two methods:

  • GraphicsContextGLANGLE::enable
  • GraphicsContextGLANGLE::disable

Ten lines are added and two blank lines are removed. No allocator is replaced. No large parser is rewritten. The fix stops one capability from being passed to the graphics layer.

BEFORE WebContent page-controlled WebGL GraphicsContextGLANGLE forwards the state change ANGLE / GPU graphics state changes AFTER WebContent same public API call WebKit check reject or return here ANGLE / GPU call never arrives
The patch does not add a complicated mitigation. It removes an unnecessary state-changing message before that message reaches ANGLE.

Primitive restart is not supposed to be a page-controlled switch

Primitive restart is a graphics feature used while drawing indexed geometry. A special index value ends the current primitive and starts another one without a separate draw call.

The important part of the commit message is not the rendering detail. It is the ownership of the state:

  • WebGL 2 expects primitive restart to be enabled.
  • WebGL 1 expects it to be disabled.
  • Web content has no legitimate reason to flip that setting.

Before the patch, the general enable and disable paths could still forward this capability to ANGLE. After the patch, WebKit handles the capability locally and returns before calling the underlying graphics function.

In simplified pseudocode, the new behavior is:

when enable or disable receives PRIMITIVE_RESTART_FIXED_INDEX:
    report an invalid operation when the requested state conflicts with the WebGL version
    return without forwarding the request to ANGLE
otherwise:
    continue with the normal graphics call

That is a small code change with a large architectural meaning: a component processing untrusted web content loses the ability to send a state transition that the graphics layer never needed to accept from it.

Reading the fix backwards

A patch is strongest when it lets us state a precise before-and-after condition.

Before: enable and disable were generic pass-through methods after making the graphics context current. The primitive-restart capability could travel through the same path as ordinary capabilities.

After: both methods recognize the primitive-restart capability, enforce the WebGL-version rule, and stop processing it locally.

The vulnerability-research lesson is not “one missing if caused a sandbox escape.” The public evidence does not prove that. The stronger and more accurate lesson is:

A state-changing command controlled by web content crossed into a graphics subsystem even though the web-facing API had no valid reason to control that state.

That is the condition worth hunting elsewhere. Generic forwarding functions accumulate exceptions over time. Every exception is a clue that the public API and the lower-level API do not have identical authority.

What the patch proves — and what it does not

The public sources support four conclusions:

  1. Apple associated CVE-2025-24201 with an out-of-bounds write and possible Web Content sandbox escape.
  2. Apple linked the CVE to WebKit Bugzilla issue 285858.
  3. The public fix for that issue blocks WebContent-controlled toggling of primitive restart.
  4. The affected code sits in WebKit’s ANGLE-backed graphics context.

They do not publish the original exploit, the corrupt object, the exact out-of-bounds write primitive, or the rest of the reported attack chain. The patch is a root-cause clue, not a complete exploit narrative.

This distinction matters. It would be easy to turn a twelve-line patch into an impressive but unsupported story about GPU compromise. The responsible analysis stops at the edge of the evidence.

A safe way to reproduce the analysis

This research can be repeated without running exploit code or targeting a device:

  1. Read Apple’s Safari 18.3.1 advisory and record the CVE, impact, affected version, and WebKit Bugzilla number.
  2. Resolve Bugzilla 285858 to WebKit commit 292004@main.
  3. Inspect the commit and confirm that only GraphicsContextGLANGLE.cpp changed.
  4. Compare the old and new enable and disable paths.
  5. Confirm that the patched paths return before the underlying graphics call for PRIMITIVE_RESTART_FIXED_INDEX.
  6. Write down separately what the advisory proves, what the patch proves, and what neither source reveals.

This is patch analysis, not exploit validation. Reproducing the original vulnerability would require an authorized lab, affected builds, reliable crash evidence, and an independently verified impact demonstration.

The next variant-hunting question

The most useful follow-up is not to imitate the unavailable exploit. It is to search WebKit for other generic enable, disable, or state-forwarding paths where:

  • web content can select a low-level capability;
  • the higher-level web standard fixes that capability to one state;
  • the graphics API exposes more authority than the web API requires; and
  • the message crosses into a more privileged or separately isolated component.

The candidate pattern is simple: a public API can ask for more state change than its own contract permits. Candidates found by that search remain hypotheses until tested on an affected build with a safe negative control.

Evidence matrix

SignalWhat it provesWhat it does not proveMy check
Apple advisory links CVE-2025-24201 to Bugzilla 285858The vendor associates the public issue and fix recordThe exact exploit pathI matched the identifiers across Apple’s advisory and WebKit history
Commit changes only the ANGLE graphics contextThe public fix is narrowly scopedThat no private fixes existed elsewhereI inspected the complete public commit and its parent
New branches return before the graphics callPrimitive-restart toggles stop at WebKitThe exact memory corruption mechanismI traced both patched control-flow paths
Apple describes a supplementary fixThe update strengthened an earlier blocked attack pathWhich earlier mitigation blocked itI kept the earlier attack chain explicitly unknown

My conclusion

For users and defenders, the action is straightforward: run a Safari, iOS, iPadOS, macOS, visionOS, or watchOS version containing Apple’s fix. Patch analysis does not replace the vendor update.

My conclusion is that the important change is not its line count but the authority it removes. The patch deletes no capability a WebGL page needs; it prevents untrusted content from toggling a lower-level graphics state that the web-facing contract already fixes to one value.

Public sources

Sources & limits

Evidence used for this analysis

Sources checkedAugust 24, 2026

Version and exploitation status can change; follow the linked vendor records.

Review statusAuthor review complete

The author completed a technical review. This does not, by itself, claim lab reproduction.

◈ Cite This ResearchBibTeX · Markdown

Reference this analysis, root-cause teardown, or vulnerability discovery in your technical reports or academic publications:

@misc{jankesec_apple_webkit_primitive_restart_2026,
  author       = {Sevban D\"{o}nmez},
  title        = {CVE-2025-24201: How Web Content Reached an Unneeded GPU State},
  year         = {2026},
  howpublished = {\url{https://jankesec.com/research/apple-webkit-primitive-restart/}},
  note         = {jankesec technical security research (CVE-2025-24201)}
}