Snapshot completion moves into the GPU process to fix a printing deadlock
Component: WebKit Site Isolation | 0a314be
Source/WebKit/GPUProcess/GPUConnectionToWebProcess.cpp
Source/WebKit/GPUProcess/GPUProcess.cpp
Site isolation이 적용된 페이지를 인쇄하거나 snapshot으로 남기려면, 먼저 root frame의 process가 display list를 기록합니다. 이때 다른 process에 호스팅된 frame은 placeholder로 표시됩니다. UI process는 각 placeholder를 해당 frame을 소유한 process로 라우팅합니다. 그러면 그 process가 GPU process에 있는 공유 RemoteSnapshot에 내용을 기록합니다. 기존 설계에서는 root가 모든 frame의 응답을 UI process를 거쳐 기다렸습니다. 문제는 UIKit이 main thread에서 인쇄를 수행하는 경우입니다. 이때 UI process는 해당 메시지를 전달하지 못했고, 결과적으로 양쪽이 서로를 기다리는 상태에 빠졌습니다.
Before: Root WP ─DrawFrameToSnapshot─► UI (blocked on main thread)
Root WP ◄── waits for reply ─┘ → deadlock
After: Root WP ─createSnapshot──────────────────────────► GPU (root unresolved)
Root WP ─DrawFrameToSnapshot (no reply)─► UI ─► child WP ─sink─► GPU
UI ─WaitForSnapshot (sync)──────────────────────► GPU: complete when
no unresolved frames
이제 GPU process가 아직 해소되지 않은 placeholder를 직접 추적하고, 남은 placeholder가 없어지면 snapshot을 sink합니다. 중단과 실패를 처리하기 위한 새 메시지도 추가되었습니다. AbandonSnapshotFrame, FailSnapshot, WaitForSnapshot, 그리고 reply가 없는 WebProcessProxy::DrawFrameToSnapshot이 여기에 해당합니다. 여기에 10초 deadline도 함께 도입되었습니다. 한편 snapshot identifier는 추측 가능한 counter 대신 random UUID로 변경되었습니다.
Significance
Process 간 snapshot 소유권과 완료 처리가 lock으로 보호되는 GPU process 측 state machine으로 이전되었습니다. 이 state machine은 모든 web process가 전송할 수 있는 새 IPC를 입력으로 받습니다.
Audit directions
- Process별 소유권 검사 부재.
AbandonSnapshotFrame과FailSnapshot은 snapshot이나 frame을 어느 process가 소유하는지 확인하지 않고 그대로GPUProcess로 전달됩니다. 즉 snapshot UUID가 유일한 capability 역할을 합니다. UUID를 알아낸 compromised web process가 자신이 소유하지 않은 snapshot을 실패 처리하거나 중단시킬 수 있는지 점검할 필요가 있습니다. 또한 UI process가 전달하는DrawFrameToSnapshot같은 경로를 통해, snapshot에 참여하지 않는 process로 UUID가 유출되는지도 살펴봐야 합니다. 한편failSnapshot은ensureSnapshot(id, nullopt)를 호출합니다. 그래서 아직 존재하지 않는 ID에 대해 이미 실패 상태인 entry를 생성할 수도 있습니다. - UI process 측 권한 검사.
DrawFrameToSnapshot은 이제 특정 process를 대상으로 전송되며, 송신자가 해당 frame의 parent를 호스팅하는지 검사합니다. 이 검사는 몇 가지 경우를 중심으로 점검할 필요가 있습니다. parent가 process 간에 이동하는 시점의 TOCTOU, parent가 없는 frame, 그리고 stale하거나 누락된 frame 조회 결과가 그 대상입니다. - GPU 측 lifetime. 먼저 completion handler가 lock을 보유한 상태에서 실행되는지, 해제한 뒤 실행되는지 확인이 필요합니다. 이와 함께 completion이나 sink가 두 번 수행되는 경우, 그리고
releaseSnapshot이후에 도착하는 recorder를 살펴봐야 합니다. owner 보고와 connection teardown 사이의 순서, 이미 파괴된 snapshot에서 deadline timer가 동작하는 경우도 점검 대상입니다. - Sync wait 중 재진입.
WaitForSnapshot은 block된 상태에서DrawFrameToSnapshot을 dispatch합니다. 이 메시지 입장에서는 새로운 sync-reply dispatch 경로가 생긴 셈입니다. 따라서WebPage와WebProcess상태로의 reentrancy가 발생하는지 확인할 필요가 있습니다.