← All reports

[Site Isolation] Validate the target frame of cross-process postMessage and focus

Under Site Isolation, the UI process relayed postMessage origins it never checked.

Component: WebKit Site Isolation | 1202482

LayoutTests/http/tests/site-isolation/post-message-forged-source-frame.html

+ IPC.addOutgoingMessageListener("UI", (description) => {
+ if (captured || !description || !description.buffer)
+ return;
+ if (description.name !== IPC.messages.WebPageProxy_PostMessageToRemote.name)
+ return;
+ captured = true;
+ // 16바이트 header는 제거합니다. IPC.sendMessage가 자체 header를 기록합니다.
+ frame.contentWindow.postMessage({
+ replay: true,
+ name: description.name,
+ bytes: new Uint8Array(description.buffer).slice(16),
+ }, "*");
+ });
...
+ else if (event.data != "probe count 1")
+ finish("FAIL: the iframe reported \"" + event.data + "\", expected \"probe count 1\".");

Site Isolation 환경에서는 cross-site frame이 각각 다른 WebContent process에서 실행될 수 있습니다. 다른 process에 있는 frame은 로컬에서 remote frame proxy로 표현됩니다. 이때 cross-process window.postMessage나 focus 요청은 먼저 송신 측 WebContent process에서 UI process(WebPageProxy)로 전달됩니다. UI process는 global map에서 FrameIdentifier로 대상 frame을 조회합니다. 그런 다음 해당 frame을 호스팅하는 process로 메시지를 전달합니다.

문제는 이 map이 global이라는 점입니다. 송신 측과 아무 관계가 없는 다른 page의 frame ID라도 정상적으로 조회됩니다. 한편 수신 측은 송신 측이 제공한 source frame ID와 origin 문자열을 바탕으로 event.source와 event.origin을 구성합니다. 그래서 UI process는 이 값을 그대로 전달하지 말고 직접 검증해야 합니다.

이 commit에서는 WebPageProxy::focusRemoteFrame과 postMessageToRemote에 MESSAGE_CHECK가 추가되었습니다. 이제 대상 frame은 반드시 메시지를 받는 page에 속해 있어야 합니다. WebPage::remotePostMessage에서도 같은 대상 검사가 한 번 더 수행됩니다.

postMessageToRemote는 여기에 더해 source frame과 주장된 origin까지 검증합니다. 먼저 송신 측이 source frame을 호스팅하지 않으면 메시지는 drop됩니다. 주장된 origin이 opaque도 아니고 UI process가 도출한 origin과도 다르면, 송신 process가 종료됩니다. 다만 UI process가 opaque origin을 기대하는 경우에는 메시지를 drop하는 데서 그칩니다.

Compromised WebContent (WCP-A)
  │ postMessageToRemote(targetFrameID, sourceFrameID, origin, msg)
  ▼
UI Process: WebPageProxy::postMessageToRemote
  ├─ target frame belongs to this page?  ── no ─► MESSAGE_CHECK (terminate sender)
  ├─ sender hosts source frame?          ── no ─► drop (stale-process race)
  ├─ origin opaque or == UI-derived?     ── no ─► MESSAGE_CHECK (terminate sender)
  │     └─ UI expects opaque (sandbox)?  ── yes, mismatch ─► drop only
  ▼
forward to process hosting target frame
  ▼
WebPage::remotePostMessage ── re-check target frame belongs to page ──► dispatch MessageEvent

패치 이전에는 compromised WebContent process가 cross-process postMessage의 대상 frame, source frame, origin을 위조하거나, 다른 page에 속한 frame에 focus를 줄 수 있었습니다. event.source와 event.origin을 위조할 수 있다면 cross-origin trust boundary가 그대로 무너집니다. 이번 변경이 Site Isolation의 핵심 hardening에 해당하는 이유입니다.

Narrow: WebPageProxy에서 frame을 대상으로 하는 다른 cross-process 메시지 handler들을 점검할 필요가 있습니다. 다른 *Remote* 계열 IPC나 frame 대상 IPC 가운데, global map으로 FrameIdentifier를 조회하면서 송신 process나 page 소속 여부를 확인하지 않는 경우가 대상입니다. 판별 신호는 송신 측이 제공한 frame ID를 곧바로 sendToProcessContainingFrame으로 넘기는 handler입니다.

Wider: 이번 origin 검증은 의도적으로 부분적으로만 적용되었습니다. opaque origin인 경우에는 메시지를 drop하기만 합니다. cross-process parent의 sandbox 속성 변경이 아직 frame 자신의 process까지 전달되지 않기 때문입니다. 결과적으로 UI process와 WebContent process가 바라보는 sandbox 상태가 서로 어긋나는 window가 남습니다. 한편 stale-process race(LoadDidCommitInAnotherProcess를 아직 수신하지 못한 상태)에 대한 "terminate 대신 drop" 경로도 살펴볼 가치가 있습니다. 특히 메시지 처리 도중 frame이 다른 process로 옮겨가는 경우처럼, frame ownership 이전 시점의 timing 문제를 확인해 볼 수 있습니다.