← All reports

Snapshot completion moves into the GPU process to fix a printing deadlock

Component: WebKit Site Isolation | 0a314be

Source/WebKit/GPUProcess/GPUConnectionToWebProcess.cpp

+void GPUConnectionToWebProcess::abandonSnapshotFrame(RemoteSnapshotIdentifier snapshotIdentifier, WebCore::FrameIdentifier frameIdentifier)
+{
+ // FIXME: using global identifiers (frameIdentifier) is not secure. Do not follow this pattern.
+ m_gpuProcess->abandonSnapshotFrame(snapshotIdentifier, frameIdentifier);
+}
+
+void GPUConnectionToWebProcess::failSnapshot(RemoteSnapshotIdentifier snapshotIdentifier)
+{
+ m_gpuProcess->failSnapshot(snapshotIdentifier);
+}

Source/WebKit/GPUProcess/GPUProcess.cpp

+void GPUProcess::failSnapshot(RemoteSnapshotIdentifier snapshotIdentifier)
+{
+ ensureSnapshot(snapshotIdentifier, std::nullopt)->fail();
+}

To print or snapshot a site-isolated page, the root frame's process records its display list, and frames hosted elsewhere appear as placeholders. The UI process routes each placeholder to the owning process, which records into a shared RemoteSnapshot held by the GPU process. In the old design the root waited for every frame's reply through the UI process. When UIKit printed on the main thread, the UI process could not forward those messages, and both sides waited on each other.

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

The GPU process now tracks unresolved placeholders and sinks the snapshot once none remain. New messages handle abandonment and failure (AbandonSnapshotFrame, FailSnapshot, WaitForSnapshot, and a reply-less WebProcessProxy::DrawFrameToSnapshot), plus a 10-second deadline. Snapshot identifiers change from a guessable counter to a random UUID.

Cross-process snapshot ownership and completion become a lock-protected GPU-process state machine fed by new IPC that any web process can send.

  1. No per-process ownership checks. AbandonSnapshotFrame and FailSnapshot forward to GPUProcess without checking which process owns the snapshot or frame, and the snapshot UUID is the only capability. Check whether a compromised web process that learns a UUID can fail or abandon snapshots it does not own, and whether any path, such as the UI-forwarded DrawFrameToSnapshot, leaks the UUID to processes not taking part. failSnapshot calls ensureSnapshot(id, nullopt), so it can also create a pre-failed entry for an ID that does not exist yet.
  2. UI-process authorization. DrawFrameToSnapshot is now addressed to a process and checks that the sender hosts the frame's parent. Review that check for TOCTOU when a parent moves between processes, for frames with no parent, and for stale or missing frame lookups.
  3. GPU-side lifetime. Check whether completion handlers run with the lock held or released, look for double completion or double sink, recorders arriving after releaseSnapshot, ordering of the owner report against connection teardown, and deadline timers firing on a destroyed snapshot.
  4. Sync-wait reentrancy. WaitForSnapshot dispatches DrawFrameToSnapshot while blocked. That is a new sync-reply dispatch path for this message, so look for reentrancy into WebPage and WebProcess state.