CVE-2026-112669
Overview
Background
- GPU process / `RemoteImageDecoderAVFProxy`
- In WebKit’s multi-process model, image decoding (AVFoundation-backed) runs in the GPU process on behalf of a web content process.
RemoteImageDecoderAVFProxyis the GPU-process endpoint receiving IPC from the web process. - `setData(ImageDecoderIdentifier, ...)`
- IPC handler that feeds encoded image bytes to a decoder identified by
identifier. - `IPC::SharedBufferReference` / `unsafeBuffer()`
- The encoded bytes arrive referencing memory the sender (web process) controls.
unsafeBuffer()returns aSharedBufferbacked by that sender-controlled (shared) memory; its name signals the backing store is not owned/trusted by the receiver. - `SharedBuffer::create(span)`
- Constructs a new
SharedBufferby copying the given bytes into GPU-process-owned memory. - WebKit lifetime model
Ref/RefPtr/WeakPtr; here the hazard is not a local free but concurrent mutation of memory shared across a process boundary.
Root Cause Analysis
RemoteImageDecoderAVFProxy::setData (Source/WebKit/GPUProcess/media/RemoteImageDecoderAVFProxy.cpp) handed the decoder a buffer taken directly from the IPC message: imageDecoder->setData(data.isNull() ? SharedBuffer::create() : data.unsafeBuffer().releaseNonNull(), allDataReceived). data.unsafeBuffer() wraps memory that is shared with and controlled by the web content process. Once setData stores and the decoder reads from that shared buffer, the web process can concurrently mutate or invalidate the underlying memory. The decoder immediately proceeds to use the data — imageDecoder->setData(...) is followed by imageDecoder->frameCount() and further decoding — reading backing store that another process can change out from under it. This is a time-of-check/time-of-use data race across the process boundary (and, if the shared mapping is torn down, a read of invalid memory), which the advisory classifies as a race condition leading to a crash.
The patch copies the bytes into GPU-process-owned memory before use: the non-null branch becomes SharedBuffer::create(data.span()) instead of data.unsafeBuffer().releaseNonNull(). By making an independent copy, the decoder no longer reads from memory the web process can concurrently modify, eliminating the race/TOCTOU on the shared backing store. (The null branch is unchanged: SharedBuffer::create().) The advisory’s “race condition … improved state handling” matches the diff; the fix is the standard “copy untrusted shared memory before consuming it” remedy.
SharedBuffer still backed by web-process-controlled shared memory (unsafeBuffer()), so the sending process could mutate the bytes concurrently; copying them into GPU-owned memory (SharedBuffer::create(data.span())) removes the cross-process race.Attack Path
- Compromise/driver in the web process
A malicious page drives image decoding so the web process sends
setDataIPC with encoded bytes backed by shared memory it still controls. - Reference, don't copy
The GPU process wraps that shared memory via
unsafeBuffer()and passes it straight to the decoder. - Concurrent mutation From the web process, the attacker mutates (or unmaps/shrinks) the shared buffer’s backing store while the GPU-process decoder is reading it.
- TOCTOU / torn read The decoder observes bytes that change mid-decode, or reads memory whose contents/size no longer match what was validated.
- Crash primitive Inconsistent/invalid buffer state leads to an unexpected GPU-process termination (the advisory’s stated impact); any stronger corruption primitive is not shown by the diff (inferred).
Impact Assessment
Files Changed
Source/WebKit/GPUProcess/media/RemoteImageDecoderAVFProxy.cpp
Audit Directions
- `unsafeBuffer()` consumersGrep the GPU/utility processes for other
unsafeBuffer()/SharedBufferReferencecall sites that pass IPC-supplied buffers directly to consumers without copying, especially media/codec paths. - IPC shared-memory TOCTOUAudit GPU-process handlers that validate a shared buffer’s size/contents and then use it, where the web process could mutate it between check and use.
- Other `RemoteImageDecoderAVFProxy` entry pointsReview sibling handlers (frame fetches, metadata) for lingering references to sender-controlled memory.
Patch
diff --git a/Source/WebKit/GPUProcess/media/RemoteImageDecoderAVFProxy.cpp b/Source/WebKit/GPUProcess/media/RemoteImageDecoderAVFProxy.cpp
index c29a0151587d..f2561f5191d0 100644
--- a/Source/WebKit/GPUProcess/media/RemoteImageDecoderAVFProxy.cpp
+++ b/Source/WebKit/GPUProcess/media/RemoteImageDecoderAVFProxy.cpp
@@ -129,7 +129,9 @@ void RemoteImageDecoderAVFProxy::setData(ImageDecoderIdentifier identifier, cons
}
RefPtr imageDecoder = m_imageDecoders.get(identifier);
- imageDecoder->setData(data.isNull() ? SharedBuffer::create() : data.unsafeBuffer().releaseNonNull(), allDataReceived);
+ imageDecoder->setData(
+ data.isNull() ? SharedBuffer::create() : SharedBuffer::create(data.span()),
+ allDataReceived);
auto frameCount = imageDecoder->frameCount();