← All issues

[1] WebGPU Shared-Memory TOCTOU Leading to Shader OOB Accesses

Severity: High | Component: WebKit GPU Process — WebGPU IPC (RemoteBuffer, RemoteQueue) | 48fc66a

관찰되는 영향이 cross-process shared memory에서의 TOCTOU race이며, commit이 이것이 shader OOB access로 이어짐을 확인하기 때문에 High로 평가되었습니다. 손상된 WebContent process를 통한 exploit 경로는 confidence 0.88로 추정됩니다. 다만 정확한 threshold 값(16 MB)과 GPU 측 OOB 영향은 diff에서 완전히 드러나지 않는 상수 및 드라이버 동작에 의존합니다.

손상된 web process는 shared memory handle을 전달하고 GPU process 측 CPU validation을 통과한 뒤, 동일한 shmem 영역에 쓰기를 수행하는 race window를 보유합니다. 이를 해결하기 위해, shmem data 크기가 기준값(현재 16 MB)을 초과할 때만 GPU process의 shmem 경로를 허용하도록 변경되었습니다. 새 테스트는 추가되지 않았습니다. 이 문제는 memcpy 완료 전에 한 프로세스가 data 쓰기를 마쳐야 하는 타이밍에 의존하며, race window가 작고 원래 재현 케이스를 안정적으로 재현하기 어렵기 때문입니다.

Source/WebKit/GPUProcess/graphics/WebGPU/RemoteQueue.cpp

void RemoteQueue::writeBuffer(
WebGPUIdentifier buffer,
WebCore::WebGPU::Size64 bufferOffset,
std::optional<WebCore::SharedMemoryHandle>&& dataHandle,
CompletionHandler<void(bool)>&& completionHandler)
{
auto data = dataHandle ? WebCore::SharedMemory::map(WTF::move(*dataHandle), WebCore::SharedMemory::Protection::ReadOnly) : nullptr;
auto convertedBuffer = protect(m_objectHeap)->convertBufferFromBacking(buffer);
ASSERT(convertedBuffer);
- if (!convertedBuffer) {
+ if (!convertedBuffer || !data || data->size() <= WebGPU::maxCrossProcessResourceCopySize) {
completionHandler(false);
return;
}

Source/WebKit/GPUProcess/graphics/WebGPU/RemoteBuffer.cpp

- if (!m_isMapped || !m_mapModeFlags.contains(WebCore::WebGPU::MapMode::Write)) {
+ if (!m_isMapped || !m_mapModeFlags.contains(WebCore::WebGPU::MapMode::Write) || data.size() <= WebGPU::maxCrossProcessResourceCopySize) {
completionHandler(false);
return;
}

Source/WebKit/WebProcess/GPU/graphics/WebGPU/RemoteBufferProxy.cpp

- size_t actualCopySize = span.size() - offset;
- if (actualCopySize > maxCrossProcessResourceCopySize) {
+ if (span.size() > maxCrossProcessResourceCopySize) {

이 patch는 GPU process의 IPC handler 세 곳 — RemoteBuffer::copy, RemoteQueue::writeBuffer, RemoteQueue::writeTexture — 에 size-threshold 검사를 추가합니다. data 크기가 maxCrossProcessResourceCopySize(소스 기준 16 MB로 추정) 이하일 때는 shared memory 기반 data 전송을 거부합니다. 패치 이전에는 이 handler들이 크기에 상관없이 모든 shared memory handle을 수락했습니다. 수정 이후에는 충분히 큰 전송만 shared memory 경로를 사용하고, 소규모 전송은 inline IPC copy 경로(CopyWithCopy/writeBufferWithCopy/writeTextureWithCopy)를 거치도록 강제됩니다. 이 경로는 data를 변경 불가능한 snapshot으로 직렬화하기 때문에 TOCTOU에 안전합니다. web process 측에서는 RemoteBufferProxy::copyFrom도 수정되었습니다. 기존에는 span.size() - offset을 threshold와 비교했으나, span.size()를 직접 비교하도록 변경하여 전송 경로 선택 시의 크기 계산 오류가 수정되었습니다.

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

WebKit의 multi-process 구조에서 WebGPU 실행은 전용 GPU process가 담당하며, WebContent process는 IPC를 통해 통신합니다. maxCrossProcessResourceCopySize로 정의된 threshold를 초과하는 대용량 data는 inline IPC copy 대신 shared memory handle을 통해 전달됩니다. 대용량 buffer를 inline으로 복사하는 비용이 지나치게 크기 때문입니다. 한 번 mapping된 shared memory는 양쪽 프로세스 모두에서 접근 가능한 상태를 유지합니다. GPU process가 handle을 read-only로 mapping한 이후에도, 송신 프로세스는 자신의 원래 read-write mapping에 대한 쓰기 권한을 그대로 보유합니다. SharedMemory::Protection::ReadOnly는 수신 측의 쓰기만을 막을 뿐, 송신 측의 접근 권한을 취소하지는 않습니다.

GPU process의 WebGPU validation은 data가 GPU-accessible 메모리에 반영되기 전에 buffer 쓰기가 size 제약과 format 요구사항을 만족하는지 확인합니다. 정상 동작에서 손상되지 않은 web process는 소규모 data를 IPC를 통해 inline으로 전송합니다. shared memory 경로는 copy 비용이 허용 범위를 초과하는 대용량 전송에만 사용합니다.

이 취약점의 근본 원인은 전형적인 TOCTOU race입니다. 패치 이전에는 GPU process 측 handler인 RemoteQueue::writeBuffer, RemoteQueue::writeTexture, RemoteBuffer::copy가 크기에 무관하게 shared memory handle을 통한 data를 수락했습니다. IPC 메시지를 직접 조작할 수 있는 손상된 web process는, 통상적으로 inline으로 전송되어야 할 소규모 data payload를 shared memory handle로 전달하는 것이 가능합니다. shared memory 영역은 송신 측에서 계속 쓰기 가능 상태로 유지됩니다. 따라서 손상된 프로세스는, GPU process가 validation(bounds check, format validation)을 마친 시점부터 memcpy 또는 GPU buffer 쓰기가 완료되는 시점 사이에 data를 변조할 수 있습니다. 즉, GPU process가 T1 시점에 data를 검증하고, 손상된 프로세스가 T2 시점에 shared memory를 변조한 뒤, GPU process가 T3 시점에 변조된 data를 복사합니다. 변조된 data는 validation을 우회하고 GPU shader에 전달되어 GPU 측 out-of-bounds access를 유발합니다.

이 fix는 포괄적인 해결책이 아닌 실용적인 접근입니다. validation 이전에 모든 shared memory를 process-private buffer로 복사하는 방식(대용량 전송에서는 비용이 과도함)을 택하는 대신, shared memory 경로를 대용량 전송에만 허용하도록 제한했습니다. 소규모 전송은 반드시 inline IPC를 사용해야 합니다. inline IPC는 data를 IPC 메시지 안에 변경 불가능한 snapshot으로 직렬화하기 때문에 구조적으로 TOCTOU에 안전합니다. 기술적으로는 대용량(16 MB 초과, 상수가 맞다면) 전송에서도 race window가 여전히 존재합니다. 다만 원래 exploit은 race window를 실질적으로 노릴 수 있는 소규모 payload에 의존했기 때문에, 이 fix로 실용적인 exploit 조건이 제거됩니다.

web process 측에도 별도의 버그가 있었습니다. RemoteBufferProxy::copyFrom이 threshold와 비교할 때 span.size() 대신 span.size() - offset을 사용했습니다. 손상된 프로세스가 offset 파라미터를 조작하면, 대용량 payload를 threshold 이하로 보이게 만들어 shared memory 경로 대신 inline 경로를 강제하는 것이 가능했습니다. 반대의 경우도 마찬가지입니다. 이 수정에서는 span.size()를 직접 비교하도록 변경되었습니다.

🔒

상세 취약점 분석, 공격 가능성 평가, 보안 영향 분석이 포함되어 있습니다

더 확인하려면 구독해 주세요

🔒

이 취약점 패턴의 변종을 찾기 위한 구체적인 탐색 방향이 포함되어 있습니다

더 확인하려면 구독해 주세요