[4] Remote snapshot recorder hard-codes DisplayList rendering mode
Low. The defect itself is rendering fidelity in PDF output, not memory safety. What keeps this on the security list is a new sender-chosen enum crossing the WebContent-to-UI-to-WebContent relay, narrowed by a release-mode check to the two supported values.
Paint code often switches strategy depending on what kind of surface the output ultimately lands on, so any stand-in drawing context has to report its destination honestly. In WebKit's remote snapshotting, a WebContent process records paint commands into a RemoteSnapshotRecorderProxy, a context that streams drawing operations to the GPU process, which replays them into a bitmap or a PDF. Paint code asks the context for its RenderingMode (display list vs. PDF document) to choose between drawing paths, so the reported mode is expected to match the real sink.
The angle: under site isolation, a compromised renderer can pick between two supported paint modes for another site's frame recording, which shifts output appearance but not memory safety; the lasting value is the relay-validation pattern.
Enabling remote snapshotting causes two PDF drawing API tests to fail. With
RemoteSnapshottingEnabled, content is painted into aRemoteSnapshotRecorderProxyand the GPU process later replays it into a CG PDF context. The recorder always reportedRenderingMode::DisplayList, so checks forRenderingMode::PDFDocumentinPage::preferredFilterRenderingModes,BackgroundPainter::paintFillLayerandLegacyRenderSVGResourceGradientwere skipped, reintroducing the bugs they fixed (filter: invert()not applied,background-clip: textdrawn as a solid rectangle). The patch passes a rendering mode when creating the recorder:PDFDocumentwhen the snapshot is sunk into a PDF,DisplayListotherwise, and carries the mode throughDrawFrameToSnapshotso cross-site iframes are recorded the same way under site isolation.
Source/WebKit/WebProcess/GPU/graphics/RemoteSnapshotRecorderProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.cpp
Patch Details
The change gives the recorder a constructor-supplied rendering mode, picks that mode at each creation site, and carries it across the site-isolation relay with a UI-process range check.
Recorder and creation sites. RemoteSnapshotRecorderProxy's constructor takes a RenderingMode and forwards it to the RemoteGraphicsContextProxy base in place of the hard-coded DisplayList, with a debug ASSERT that the mode is DisplayList or PDFDocument. RemoteRenderingBackendProxy::createSnapshotRecorder gains the argument. WebPage::drawToSnapshot passes PDFDocument and WebPage::takeRemoteSnapshot passes DisplayList. In WebPageCocoa.mm, the site passing rect uses DisplayList and the site passing mediaBox uses PDFDocument. In WebPageIOS.mm, both sites pass mediaBox: the first uses DisplayList, the second PDFDocument.
Relay. Under site isolation, WebPage::paintRemoteFrameContents sends context.renderingMode() in Messages::WebPageProxy::DrawFrameToSnapshot. The UI process relays it in Messages::WebPage::DrawFrameToSnapshot to the process hosting the cross-site frame, which uses it in WebPage::drawFrameToSnapshot to build its own recorder. WebPageProxy::drawFrameToSnapshot now takes the IPC::Connection& and adds the MESSAGE_CHECK_COMPLETION shown above. Both .messages.in files gain the field. The DrawingToPDF.mm changes generalize enableSiteIsolation into enableFeature and add ...WithRemoteSnapshotting variants of the filter-invert and background-clip tests.
A wrapper context hard-codes its self-reported output kind, so downstream code branching on that kind takes the wrong fidelity path.
Background
RenderingMode.
A WebCore enum describing what kind of surface a GraphicsContext or ImageBuffer ultimately draws into. DisplayList means operations are recorded for later replay, and PDFDocument means they are destined for a PDF context. Paint code queries GraphicsContext::renderingMode() to choose a drawing strategy, for example rendering a CSS filter or a text-clipped background differently for vector PDF output than for a raster surface.
Remote snapshotting.
With RemoteSnapshottingEnabled, the WebContent process paints into a RemoteSnapshotRecorderProxy (a RemoteGraphicsContextProxy subclass) instead of painting in-process. The GPU process replays the stream into the final sink, a bitmap or a CG PDF context. m_remoteSnapshotState on WebPage holds the active recorder and a MainRunLoopSuccessCallbackAggregator that completes once every contributing frame has drawn.
Site-isolation relay.
Under site isolation, cross-site iframes live in separate WebContent processes. When the root process meets a remote frame during snapshot painting (WebPage::paintRemoteFrameContents), it sends DrawFrameToSnapshot to the UI process, and WebPageProxy::drawFrameToSnapshot forwards it to the process containing that frame, which records into the same snapshot identifier.
MESSAGE_CHECK_COMPLETION.
The UI-process validator idiom: if the condition fails, the sending process is treated as misbehaving and terminated, and the completion handler runs with a failure value.
Analysis
This is a logic error: a context misreporting its output kind. Before the fix, RemoteSnapshotRecorderProxy always built its RemoteGraphicsContextProxy base with RenderingMode::DisplayList, whatever the recording's destination. Content headed for PDF output was painted into this recorder, so renderingMode() reported DisplayList and any WebCore branch on PDFDocument took its non-PDF path. The commit message names Page::preferredFilterRenderingModes, BackgroundPainter::paintFillLayer and LegacyRenderSVGResourceGradient as such branch points; their sources are outside the supplied context, so that attribution is relayed as-is. The new ...WithRemoteSnapshotting tests target filter: invert() and background-clip: text in PDF output. The commit message describes the failures as the filter not being applied and the text clip drawn as a solid rectangle.
The site-isolation leg had the same flaw one level deeper. WebPage::drawFrameToSnapshot also defaulted to DisplayList when building a cross-site frame's recorder, so the fix has to carry the mode across two IPC hops.
WebContent A (root) UIProcess WebContent B (cross-site frame)
─────────────────── ───────── ──────────────────────────────
paintRemoteFrameContents
DrawFrameToSnapshot(frame, rect, id, mode) ─►
MESSAGE_CHECK: mode in {DisplayList, PDFDocument}
forward(frame, rect, id, mode) ───► new RemoteSnapshotRecorderProxy(mode)
(ASSERT only; relies on relay check)
The attack-surface change is a new field, WebCore::RenderingMode renderingMode, on an existing relay: WebContent to UI (WebPageProxy::DrawFrameToSnapshot) to a different WebContent process (WebPage::DrawFrameToSnapshot). A sending process now chooses the mode another site's process uses to build its recorder, and that mode flows into every WebCore paint decision keyed on renderingMode(). The UI process enforces that the value is DisplayList or PDFDocument; the receiver's ASSERT is debug-only and leans on that relay check. Nothing checks that either allowed mode matches the actual sink, so a compromised sender could pick either one for a cross-site frame. If the relay check were missing or bypassed, other RenderingMode values (such as accelerated or unaccelerated image-buffer modes) could reach a recorder never designed for them, and what that would do in RemoteGraphicsContextProxy and GPU-side replay cannot be determined from the supplied context.
Exploitability as a memory-safety issue is not established. The pre-fix defect was PDF rendering correctness, and the primitive after the fix is a choice between two modes that only affects rendering output. The open audit question is whether anything downstream, in GPU-side replay or the paint paths keyed on renderingMode(), assumes the reported mode matches the real sink in a way that touches memory layout or buffer sizing rather than drawing choices. If it did, a compromised sender picking PDFDocument for a bitmap-bound snapshot, or the reverse, could be more than cosmetic. Nothing in the supplied context points that way. The bug lives in the sandboxed WebContent and GPU processes, and the new field crosses the WebContent-to-UI boundary where it is range-checked, so neither gives a sandbox escape.
The original bug does not weaken a memory-safety or origin boundary. The security-relevant part is the sender-controlled enum on a cross-site relay. The invariant it maintains is that a compromised WebContent process can steer another site's recorder only into a mode the recorder supports.
Insight: a proxy context standing in for a real one has to faithfully report the properties paint code branches on (rendering mode, color space, accelerated-ness), or behavior quietly diverges once a feature flag moves painting out of process. The pre-existing non-remote CSSFilterInvertAppliedOnPDF and BackgroundClipText tests suggest the in-process path already handled these behaviors. Site isolation also turns any per-snapshot parameter into a cross-process relay: values once implied by local state now travel over IPC, and the UI process becomes the place that must bound them. The pattern to watch is a value moving from locally derived to received from another WebContent process as part of a correctness fix, with validation added at the relay but only ASSERTed at the receiver.
Audit directions
- Stand-in contexts with hard-coded properties. Proxy or recorder graphics contexts hard-code a property that downstream paint code branches on. The invariant is that a stand-in reports the same output-kind properties as the sink its commands will reach. Narrow: review the other
RemoteGraphicsContextProxysubclasses and creation sites inSource/WebKit/WebProcess/GPU/graphics/for literalRenderingMode::,ColorSpace::orDrawGlyphsMode::constructor arguments, and compare each against where the recording is replayed. Wider: any WebCore code callingrenderingMode()orrenderingPurpose()on a context it did not create (BackgroundPainter, SVG resource painters, filter-mode selection), confirming every context type in the snapshot, print and display-list paths reports truthfully. Widest: any rendering abstraction where a recording or deferred backend stands in for a final target, such as Skia'sSkPicturerecorders versus PDF/raster canvases or Chromium's paint-record playback. The tell is a constructor passing a constant for a property a consumer later uses to choose a code path, while the real destination is only known at a higher layer. - UI-process relays between peer renderers. A relay forwards a sender-supplied field from one WebContent process to another under site isolation. The invariant is that the relay, not the receiver, bounds every field to the subset the receiver can safely handle, because the receiver trusts the UI process. Narrow: grep
WebPageProxy.messages.infor[EnabledBy=SiteIsolationEnabled]messages whoseWebPageProxyhandler immediately callssendToProcessContainingFrameorsendWithAsyncReplyToProcessContainingFrame, and check that each forwarded enum, rect and identifier gets aMESSAGE_CHECKbefore relay. Wider: any broker-mediated message between peer renderers, such as frame-tree sync, remote frame geometry or accessibility relays. The tell is a handler body that is a single forwarding call with no validation of the payload fields. This class is bound to WebKit's UI-process-as-relay architecture andMESSAGE_CHECKidiom; Chromium routes peer-renderer traffic through browser-side frame hosts with Mojo-typed validation, so the exact shape does not transfer. - Enum parameters narrowed only by a debug
ASSERT. Look for IPC enum parameters whose semantic subset is enforced only by anASSERTin the receiver, as withASSERT(renderingMode == DisplayList || renderingMode == PDFDocument)inRemoteSnapshotRecorderProxy. Narrow: searchSource/WebKit/WebProcessandSource/WebKit/GPUProcessforASSERT(on enum arguments in methods reachable from.messages.inhandlers, and confirm a release-modeMESSAGE_CHECKexists at the trust boundary for each. Wider: the same gap appears wherever an IPC decoder validates enum range but the receiver supports only a subset, including image-buffer purposes, pixel formats and display-list item kinds. Widest: the general "type-valid but semantically-invalid input" class in any RPC framework whose schema validation stops at well-formedness, such as protobuf/Mojo enums or gRPC services accepting every declared member. The tell is a receiver whose only constraint on an externally supplied enum is a debug-only assertion.