CVE-2026-28859
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