← All reports

[Site Isolation] OffscreenCanvas transferred to a cross-site iframe never paints

Component: WebKit Site Isolation / OffscreenCanvas | 28001e7

LayoutTests/http/tests/site-isolation/offscreen-canvas-cross-site-frames-stay-in-order.html

+// cross-site iframe에서 commit된 frame은 UI process에서 layer로 직접 전달되기도 하고,
+// placeholder process의 rendering update를 통해서도 전달됩니다. 마지막으로 commit된 frame이 표시되어야 합니다.
...
+ if (event.data.type == "child-ack") {
+ const colors = [];
+ for (let i = 0; i < 30; ++i)
+ colors.push(i % 2 ? "blue" : "red");
+ colors.push("green");
+ frame.contentWindow.postMessage({ type: "draw-sequence", colors }, "*");
+ return;
+ }
...
+frame.addEventListener("load", () => {
+ const offscreen = canvas.transferControlToOffscreen();
+ frame.contentWindow.postMessage({ type: "offscreen-canvas", canvas: offscreen }, "*", [offscreen]);
+});

OffscreenCanvas를 사용하면 worker나 다른 context에서 transferControlToOffscreen()을 통해 <canvas>에 렌더링할 수 있습니다. 이때 <canvas> element는 placeholder 역할만 맡게 됩니다. Frame은 PlaceholderRenderingContextSource가 공급하는 async contents delegate를 거쳐 해당 element의 CALayer에 전달됩니다. 다만 기존 PlaceholderRenderingContextSource는 producer가 같은 process에 있다고 가정했습니다.

Site Isolation 환경에서는 사정이 다릅니다. Producer와 placeholder, GPU process의 ImageBuffer가 서로 다른 process에 놓일 수 있고, 그 사이를 UI process가 중재합니다. ImageBuffer 전송 과정에서는 먼저 sender가 handle을 기록합니다. 이후 recipient는 이 handle을 바탕으로 buffer에 대한 자체 description을 구성합니다. 결과적으로 recipient는 sender가 제공한 metadata(size, scale, purpose, rendering mode)를 신뢰하게 됩니다. 또한 IOSurface에는 Mach send right가 함께 실리는데, sender가 이 권한을 계속 보유할 수 있습니다.

이번 commit으로 DetachedOffscreenCanvas를 process 간에 serialize할 수 있게 되었습니다. 이전에는 [NotSerialized] 때문에 cross-site iframe으로 postMessage된 OffscreenCanvas가 아무런 오류 없이 누락되었습니다. 이제는 같은 상황에서도 원래의 <canvas> placeholder에 정상적으로 그려집니다.

구조 측면에서는 PlaceholderRenderingContextSource가 세 부분으로 분리되었습니다. Abstract interface, 같은 process용 LocalPlaceholderRenderingContextSource, 그리고 WebKit 쪽에 새로 추가된 RemotePlaceholderRenderingContextSource입니다. Remote source는 GPU process의 buffer ownership을 이전한 뒤 UI process에 통지합니다. 그러면 UI process가 해당 frame을 placeholder layer에 적용합니다. 이와 함께 전달된 ImageBuffer handle에 대한 검증이 추가되었습니다. Claim된 IOSurface가 recipient의 surface pool로 재활용되지 않도록 하는 수정도 포함되었습니다.

Cross-site iframe (producer)            UI Process                    Placeholder (<canvas>) process
        │                                  │                                  │
        │                                  │◄── register(PlaceholderID) ──────┤ (on create; unregister on destroy)
        │                                  │◄── layer-changed(layerID) ───────┤
  GPU: takeTransferredBuffer               │                                  │
        ├── commitFrame(ID, frameID, ──────►│                                  │
        │    handle)                       ├─ known? ─yes─► asyncSetLayerContents(layer)  [fast path]
        │                                  ├─ forward buffer-transfer ───────►│ accepts ownership (readback)
        │                                  │   (throttled; superseded frames released)
        │                                  ├─ unknown? hold newest frame, ping claimed owner
        │                                  │◄── pong (follows any registration)
        │◄── placeholder gone ─────────────┤  deliver or release held frame
        │    (stop sending)                │

새로운 UI process routing, GPU process buffer handoff, capability 방식의 identifier를 통해 cross-site OffscreenCanvas 전송이 동작하게 되었으며, 그만큼 cross-process trust boundary가 여러 곳 새로 생겼습니다. ImageBuffer hardening이 함께 들어간 점도 눈여겨볼 만합니다. 전송 경로가 이미 거짓 정보를 보내는 sender에 노출되어 있었음을 드러내기 때문입니다.

Cross-process attack surface가 새로 추가되었고 security fix도 포함되어 있어, 우선순위가 높은 점검 대상입니다. 범위를 좁혀 시작할 수 있는 지점은 다음과 같습니다.