Released March 24, 2026

CVE-2026-28859

CVSS 4.3 severity medium CWE-125CWE-416CWE-787CWE-120 vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N

Overview

Impact:

Description: A malicious website may be able to process restricted web content outside the sandbox

Researchers: greenbynox, Arni Hardarson, and an anonymous researcher

Key insight. One of two parallel IPC arrays (the glyphs) was consumed in place as a view into renderer memory while only the advances were copied; drawing then trusted an attacker-controlled span length/lifetime. Copying both into owned Vectors restores validated, consistent buffers.

Patch Details

diff --git a/Source/WebKit/GPUProcess/graphics/RemoteGraphicsContext.cpp b/Source/WebKit/GPUProcess/graphics/RemoteGraphicsContext.cpp
index 9a93fe3ee915..627b8b021edc 100644
--- a/Source/WebKit/GPUProcess/graphics/RemoteGraphicsContext.cpp
+++ b/Source/WebKit/GPUProcess/graphics/RemoteGraphicsContext.cpp
@@ -411,7 +411,9 @@ void RemoteGraphicsContext::drawGlyphs(RenderingResourceIdentifier fontIdentifie
 {
     RefPtr font = resourceCache().cachedFont(fontIdentifier);
     MESSAGE_CHECK(font);
-    context().drawGlyphs(*font, glyphsAdvances.span<0>(), Vector<GlyphBufferAdvance>(glyphsAdvances.span<1>()), localAnchor, fontSmoothingMode);
+    Vector<GlyphBufferGlyph, 128> glyphs { glyphsAdvances.span<0>() };
+    Vector<GlyphBufferAdvance, 128> advances { glyphsAdvances.span<1>() };
+    context().drawGlyphs(*font, glyphs.span(), advances.span(), localAnchor, fontSmoothingMode);
 }
 
 void RemoteGraphicsContext::drawImageBuffer(RenderingResourceIdentifier imageBufferIdentifier, const FloatRect& destinationRect, const FloatRect& srcRect, ImagePaintingOptions options)

Background

Advisory: Apple Security Advisory

Attribute Value
CVE CVE-2026-28859
Bugzilla 308248
Component WebKit
Bug Class OOB
Severity medium
Commit 6aacf62000967a62…
Advisory Apple Advisory

Terminology

  • GPU process — A separate WebKit process that performs graphics/rendering on behalf of WebContent, reached over IPC and meant to be a sandbox boundary.
  • Parallel spans — glyphsAdvances carries glyph IDs and advances as two spans that must be equal length; drawGlyphs iterates them together.
  • In-place span vs owned copy — A span is a non-owning view into a buffer; using a renderer-supplied span in place trusts its length and lifetime, while copying into a Vector captures a consistent snapshot.

Root Cause Analysis

This hardens RemoteGraphicsContext::drawGlyphs in the GPU process against a mismatched/attacker-controlled IPC glyph buffer. drawGlyphs receives glyphsAdvances, an IPC array carrying two parallel spans — the glyph IDs (span<0>) and the per-glyph advances (span<1>) — sent by the WebContent process. The pre-patch code passed the glyph span directly into CoreGraphics text drawing (context().drawGlyphs(*font, glyphsAdvances.span<0>(), Vector<GlyphBufferAdvance>(glyphsAdvances.span<1>()), …)): the advances were copied into an owned Vector, but the glyphs were consumed in place as a view over the IPC-provided buffer. drawGlyphs consumes glyphs and advances as parallel arrays; if the two spans have different lengths (nothing forces span<0>.size()==span<1>.size() across the IPC boundary) or the underlying shared buffer’s lifetime/contents are not pinned for the call, the in-place glyph span can drive an out-of-bounds read (or read racing/attacker-mutated bytes) inside the GPU process while rendering. The fix materializes BOTH arrays into fixed-inline owned Vectors (Vector<GlyphBufferGlyph,128> glyphs and Vector<GlyphBufferAdvance,128> advances) and passes glyphs.span()/advances.span(), so the GPU process works on consistent, owned copies with matching, captured lengths. The restored invariant is that the glyph draw operates on validated, owned buffers rather than a live view into renderer-controlled IPC memory. The precise OOB (length mismatch vs buffer lifetime) is inferred; the diff establishes that the glyph span was previously used in place while only advances were copied.

Attack Path

1. Send a crafted drawGlyphs message

From a compromised WebContent process, issue RemoteGraphicsContext::drawGlyphs with a glyphsAdvances buffer whose glyph and advance spans mismatch or whose backing memory is controlled.

2. Use the glyph span in place

The GPU process consumes the glyph span directly as a view over the renderer-supplied buffer alongside the copied advances.

3. Out-of-bounds read in the GPU process

Parallel-array iteration over mismatched lengths (or racing bytes) reads out of bounds during CoreGraphics glyph drawing.

4. Escape the sandbox

Leverage GPU-process memory disclosure/corruption to process restricted content outside the WebContent sandbox, as the advisory describes.

Impact Assessment

Memory disclosure/corruption in the GPU process driven by a compromised WebContent process — an out-of-bounds read over renderer-controlled glyph/advance buffers. The GPU process is a cross-origin, attacker-influenced target, so the advisory frames this as processing restricted content outside the sandbox; it is a sandbox-boundary weakness rather than a same-process crash.

Audit Directions

Other RemoteGraphicsContext handlers

Grep RemoteGraphicsContext.cpp and GPUProcess/graphics for span<0>()/span<1>() or IPC spans passed directly into drawing calls without copying/length validation.

Parallel-array IPC

Look for IPC handlers that receive multiple parallel arrays and use one in place; verify equal-length checks or owned copies for all of them.

MESSAGE_CHECK coverage

Confirm GPU-process message handlers validate array sizes and cross-array length equality via MESSAGE_CHECK before use.

Changed Functions

Function File Change Note
RemoteGraphicsContext::drawGlyphs Source/WebKit/GPUProcess/graphics/RemoteGraphicsContext.cpp modified Copies both the glyph IDs and advances from the IPC spans into fixed-inline owned Vectors before calling context().drawGlyphs, instead of consuming the glyph span in place.

Files Changed

Source Files

  • Source/WebKit/GPUProcess/graphics/RemoteGraphicsContext.cpp