[1] WebGPU Shared-Memory TOCTOU Leading to Shader OOB Accesses
Severity: High | Component: WebKit GPU Process — WebGPU IPC (RemoteBuffer, RemoteQueue) | 48fc66a
Rated High because the observable effect is a TOCTOU race on cross-process shared memory that the commit confirms leads to shader OOB accesses, and the exploitation path from a compromised WebContent process is projected with confidence 0.88 — the precise threshold value (16 MB) and GPU-side OOB impact depend on constants and driver behavior not fully visible in the diff.
There is a race window from a compromised web process where it can pass a shared memory handle, pass GPU process side CPU validation, and then perform writes to the shmem from the compromised WCP. Address this by ensuring GPU process shmem paths are only taken when the shmem data size exceeds the expected amount, which is currently 16 MB. No new test since issue relies on one process completing a data write before a memcpy call completes, race window is small and original repro is not consistently reproducible.
Source/WebKit/GPUProcess/graphics/WebGPU/RemoteQueue.cpp
Source/WebKit/GPUProcess/graphics/WebGPU/RemoteBuffer.cpp
Source/WebKit/WebProcess/GPU/graphics/WebGPU/RemoteBufferProxy.cpp
TOCTOU race on shared memory between cross-process validation and consumption, exploitable by a compromised sender process.
Patch Details
The patch adds size-threshold guards to three GPU-process IPC handlers — RemoteBuffer::copy, RemoteQueue::writeBuffer, and RemoteQueue::writeTexture — that reject shared-memory-based data transfers when the data size is at or below maxCrossProcessResourceCopySize (if 16 MB as indicated in the source). Previously, these handlers accepted shared memory handles of any size. The fix ensures only legitimately large transfers use the shared memory path; small transfers must use the inline IPC copy path (CopyWithCopy/writeBufferWithCopy/writeTextureWithCopy), which transfers an immutable snapshot. On the web process side, RemoteBufferProxy::copyFrom is also corrected to compare span.size() (not span.size() - offset) against the threshold, fixing the size calculation that determines which transfer path to use.
Before: After:
WebProcess GPU Process WebProcess GPU Process
─────────── ─────────── ─────────── ───────────
shmem handle ──► map(ReadOnly) shmem handle ──► map(ReadOnly)
validate(data) size <= 16MB?
mutate shmem ──► memcpy(data) ← UAF └─► reject
GPU shader uses size > 16MB?
corrupted data └─► validate + copy
Background
WebKit's multi-process architecture places WebGPU execution in a dedicated GPU process, with the WebContent process communicating via IPC. For large data transfers (above a threshold defined by maxCrossProcessResourceCopySize), data is passed via shared memory handles rather than inline IPC copies, because copying large buffers inline would be prohibitively expensive. Shared memory, once mapped, remains accessible to both processes — the sending process retains write access to its original read-write mapping even after the GPU process maps the handle as read-only. SharedMemory::Protection::ReadOnly on the receiver side only prevents the receiver from writing; it does not revoke the sender's access.
WebGPU validation in the GPU process checks that buffer writes respect size constraints and format requirements before the data is committed to GPU-accessible memory. Under normal operation, the non-compromised web process sends small data inline via IPC (an immutable copy) and reserves the shared memory path only for large transfers where the copy overhead would be prohibitive.
Analysis
The root cause is a classic TOCTOU race. Before the fix, the GPU-process-side handlers RemoteQueue::writeBuffer, RemoteQueue::writeTexture, and RemoteBuffer::copy accepted data via shared memory handles of any size. A compromised web process — one that can craft IPC messages directly — can send a shared memory handle for an arbitrarily small data payload that would normally be sent inline. Since the shared memory region remains writable by the sender, the compromised process can modify the data after the GPU process has validated it (bounds checks, format validation) but before the memcpy or GPU buffer write completes. The GPU process validates data at time T1, the compromised process mutates the shared memory at time T2, and the GPU process copies the now-mutated data at time T3. The mutated data bypasses validation and, when consumed by GPU shaders, causes out-of-bounds accesses on the GPU side.
The fix is pragmatic rather than comprehensive: rather than copying all shared memory to process-private buffers before validation (expensive for large transfers), it restricts the shared memory path to only large transfers where the copy overhead justifies the risk. Small transfers must use inline IPC, which is inherently TOCTOU-safe because the data is serialized into the IPC message as an immutable copy. The race window still technically exists for large (>16 MB, if the constant is as indicated) transfers, but the original exploit relied on small payloads where the race window was more practical to win.
An additional bug existed on the web-process side: RemoteBufferProxy::copyFrom compared span.size() - offset against the threshold instead of span.size(). A compromised process could manipulate the offset parameter to make a large payload appear below the threshold, forcing the inline path when the shared memory path was intended — or vice versa. The fix corrects this to compare span.size() directly.