[1] RemoteBuffer allows to write non-writable buffers, leading to shader's OOB accesses
Severity: High | Component: WebGPU RemoteBuffer (GPU process) | 3aed028
GPU process 측 state validation bypass가 IPC를 통해 침해된 renderer에서 도달 가능한 점에서 High로 평가됩니다. diff에 나타난 m_isMapped의 즉각적인 할당과 일치하는 방식으로, pending-map window 동안 CPU 측에서 GPU buffer 내용에 쓰기가 가능한 primitive가 존재합니다. 다만 downstream GPU memory corruption 경로는 검증되지 않은 getBufferContents()의 동작에 달려 있으며, commit만으로는 확인되지 않습니다.
JS 수준에서는 buffer가 mapped 상태일 때 특정 buffer 작업이 제한되지만, GPU process 측에서는 이를 강제하지 않았습니다. 이로 인해 침해된 web content process가 예상치 못한 상태의 buffer에 쓰기 작업을 수행하는 것이 가능했습니다. 결과적으로 index validation 이후 CPU 측 쓰기가 가능해지고, shader core에서 vertex buffer에 대한 OOB read로 이어질 수 있습니다.
Source/WebKit/GPUProcess/graphics/WebGPU/RemoteBuffer.cpp
Source/WebKit/GPUProcess/graphics/WebGPU/RemoteBuffer.h
Patch Details
이 패치는 RemoteBuffer에 세 가지 변경을 적용합니다.
첫 번째로, mapAsync()의 state 전환 시점이 변경되었습니다. 기존에는 async map이 완료되기 전에 m_isMapped = true와 m_mapModeFlags를 즉각적으로 설정했습니다. 이제는 새로운 m_pendingMap 플래그를 즉시 설정하고, mapped 상태 전환은 success callback 내부에서만 이루어집니다.
두 번째로, copyWithCopy()에 m_pendingMap 거부 검사가 추가되었습니다.
세 번째로, copy()가 재구성되었습니다. 기존 코드는 !m_isMapped || !m_mapModeFlags.contains(Write) 검사와 data.size() 검사를 하나의 if 문에 묶어, SharedMemory::map이 renderer 제공 handle을 처리한 이후에 실행했습니다. 패치는 이를 early guard로 분리하여 m_pendingMap 검사를 추가하고, SharedMemory 처리 이전에 유효하지 않은 상태를 거부하도록 변경되었습니다.
Before: After:
mapAsync() mapAsync()
├─ m_isMapped = true (EAGER) ├─ m_pendingMap = true
├─ m_mapModeFlags = flags (EAGER) └─ backing->mapAsync(callback)
└─ backing->mapAsync(callback) └─ on success:
└─ on success: callback(true) m_isMapped = true
m_mapModeFlags = flags
copy() callback(true)
├─ SharedMemory::map(handle) ← FIRST copy()
└─ if (!m_isMapped || ...) reject ├─ if (m_pendingMap || !m_isMapped || ...) reject ← FIRST
└─ SharedMemory::map(handle)
IPC trust boundary를 넘어 GPU process 측의 buffer state invariant 강제가 누락되어, 침해된 renderer가 async map window 동안 JS 수준의 쓰기 가드를 우회할 수 있었던 패턴.
Background
WebGPU buffer는 CPU 측에서 내용을 읽거나 쓰기 전에 반드시 mapAsync()를 통해 명시적으로 mapped 상태로 전환되어야 합니다. 쓰기 접근을 위해서는 MapMode::Write 플래그가 설정되어 있어야 하며, map이 pending 상태(async 작업이 진행 중)인 동안에는 CPU 측 쓰기가 허용되어서는 안 됩니다. WebKit의 다중 프로세스 아키텍처에서는 GPU 작업이 별도의 GPU process에서 처리되며, WebContent(renderer) process가 IPC를 통해 명령을 전달합니다. GPU process는 renderer를 잠재적으로 침해된 것으로 간주하므로, 모든 작업을 독립적으로 검증해야 합니다. renderer의 JS 수준 가드는 defense-in-depth 조치일 뿐 신뢰 경계로 볼 수 없습니다.
RemoteBuffer는 GPU process 측 IPC 수신자로, WebCore::WebGPU::Buffer를 감싸고 renderer로부터 mapAsync, copy, copyWithCopy, unmap 메시지를 처리합니다. m_isMapped, m_mapModeFlags라는 shadow state를 유지하여, buffer가 쓰기 접근 권한과 함께 유효하게 mapped된 경우에만 쓰기 작업이 진행되도록 강제합니다. 이 shadow state 전환의 정확성이 GPU process에서 buffer 접근 제어의 유일한 강제 지점입니다.
Analysis
Root cause는 "JS 레이어에서는 검증하지만 IPC 레이어에서는 검증하지 않는" 전형적인 anti-pattern입니다. 패치 이전에는 RemoteBuffer::mapAsync()가 async map 작업이 완료되기 전에 m_isMapped = true와 m_mapModeFlags를 즉각적으로 설정했습니다. copyWithCopy()와 copy()의 기존 state 검사가 m_isMapped를 확인하는 구조였기 때문에, pending-map window 동안 이 검사가 잘못 통과되었습니다. backing map이 아직 진행 중임에도 buffer가 유효하게 mapped된 것처럼 보이는 상태가 되었기 때문입니다. 침해된 renderer는 mapAsync를 호출한 직후 IPC를 통해 copy/copyWithCopy를 전송할 수 있었고, write-state 가드(!m_isMapped || !m_mapModeFlags.contains(Write))가 해당 작업을 거부하지 않았습니다.
한편 copy()에서는 state 검증 이전에 renderer 제공 handle에 대해 SharedMemory::map이 호출되었습니다. 검사 이전에 memcpy가 수행되지는 않지만, 작업의 적법성을 확인하기 전에 공격자 제공 데이터를 처리하는 것은 defense-in-depth 관점에서 문제입니다. 패치는 state 검증을 먼저 수행하는 방식으로 이를 해결했습니다.
이 버그는 GPU process IPC handler에서 누락된 state validation을 점검하는 패턴 분석이나, RemoteBuffer 메시지 시퀀스에 순서를 뒤바꾼 map/copy/unmap 작업을 대상으로 한 IPC fuzzing을 통해 발견된 것으로 보입니다.