df9d578b623ffb2e20654af21d5ba3cbff19245c Carry over page name when disabling pagination due to clipping.
Triage note: Renderer crash (DoS) during printing/pagination with CSS named pages; reachable through print rendering but requires the pagination/print path and manifests as a layout crash, not confirmed corruption.
The bug at a glance
The defect is a fragmentation-state inconsistency in Blink’s block-fragmentation (paged layout) engine that the regression test previously recorded as ‘Crash Failure Timeout’. A crash in the layout tree is a renderer-process denial of service and, because it stems from a broken invariant carried across a relayout pass, it is the kind of state confusion that occasionally underlies exploitable corruption rather than a clean CHECK. Severity is held at medium (5.9) because the trigger is confined to printing / paged media with CSS named pages combined with clipped fragmented overflow: it is reachable from web content only through print or paginated rendering, not ordinary painting, and the observed outcome is a crash/timeout rather than a demonstrated memory-corruption primitive.
Reachable from untrusted content via the print path: a page that uses CSS named pages (the ‘page’ property / @page named rules) and puts named content inside a container with clipped overflow can, when printed or laid out for paged media, drive the fragmentation engine into the relayout-without-fragmentation branch while dropping the page name. Because print rendering can be triggered by content (window.print, and print preview on a hostile page) the surface is real, though it requires the user to be in a paginated/print context.
Root cause
During block fragmentation, when content overflows a container whose overflow is clipped, Blink deliberately re-runs layout without fragmentation, because clipped overflow must not spawn extra fragmentainers (pages). Separately, paged layout is speculative about page type: each page is first laid out with the ‘current’ page type inherited from previous pages, and if layout discovers a named page somewhere inside, it relayouts with the new (correct) page type. These two relayout mechanisms intersect badly. When the correct page type is taller than the assumed one, the clipped overflow that previously fragmented may no longer need to fragment. The relayout-without-fragmentation pass rebuilt its container fragment via SetupRelayoutData but did not carry over the page name that the fragmented layout had already resolved. The result is a fragment built with an inconsistent view of the pagination state (a page that should be named but is not), which violates a fragmentation-engine invariant and crashes. The fix teaches SetupRelayoutData that, specifically when relayout_mode_ includes kRelayoutWithoutFragmentation, it must copy the previously computed PageName() from the prior algorithm’s container_builder into the new one via SetPageNameIfNeeded, preserving pagination identity across the non-fragmenting relayout.
Key code
layout_algorithm.h SetupRelayoutData: propagate the resolved page name into the non-fragmenting relayout pass.
void SetupRelayoutData(const LayoutAlgorithm& previous_algorithm,
RelayoutType relayout_type) {
if (relayout_mode_ & kRelayoutWithoutFragmentation) {
// Keep any page name we got from fragmented layout.
container_builder_.SetPageNameIfNeeded(
previous_algorithm.container_builder_.PageName());
}
if (relayout_mode_ & kRelayoutForEarlyBreak) {
// ... existing early-break handling ...
}
}
Patch walkthrough
third_party/blink/renderer/core/layout/layout_algorithm.h— The whole fix. Inside SetupRelayoutData, a new guarded block runs only for kRelayoutWithoutFragmentation. It reads PageName() from the previous algorithm’s container_builder_ (the page name resolved during the fragmented pass) and applies it to the new pass’s container_builder_ via SetPageNameIfNeeded. SetPageNameIfNeeded is a no-op when there is no page name, so unnamed pagination is unaffected; only the clipped-overflow-without-fragmentation branch changes behavior.third_party/blink/web_tests/TestExpectations— Removes the suppression line for external/wpt/css/css-page/page-name-unnamed-trailing-001-print.html, which was marked [ Crash Failure Timeout ] under crbug.com/462233640. Deleting the expectation asserts the test now passes, and the WPT itself is the regression coverage for the fix (no new test file was added because upstream WPT already exercises this named-page + trailing page scenario).
Background
Block fragmentation / fragmentainers — Blink’s mechanism for splitting content across pages or multicol columns. Each page/column is a ‘fragmentainer’; layout runs per-fragmentainer and must keep a consistent notion of where breaks occur.
CSS named pages — The CSS Paged Media ‘page’ property and named @page rules let authors assign specific page boxes (sizes/margins) to elements. A named page can change page dimensions mid-document, which forces Blink to relayout with the corrected page type.
Clipped overflow relayout — When a container clips its overflow, overflowing content must not create additional fragmentainers. Blink detects this during fragmentation and relayouts without fragmentation so the clipped content stays within a single fragment.
SetupRelayoutData / RelayoutType — A helper on LayoutAlgorithm that seeds a fresh layout pass from a previous one when a relayout is required. relayout_mode_ is a bitmask (kRelayoutWithoutFragmentation, kRelayoutForEarlyBreak, …) selecting which state must be carried forward.
PageName() / SetPageNameIfNeeded() — Container-builder accessors for the resolved named-page identity of the fragment being built. If the name is not propagated across a relayout, the two passes disagree about pagination, breaking the fragmentation invariants.
Vulnerability window
- Latent — The relayout-without-fragmentation path never carried the resolved page name forward; the inconsistency only bites when the corrected page type is taller and the clipped overflow stops fragmenting.
- Observed — WPT page-name-unnamed-trailing-001-print.html crashed/timed out and was suppressed under crbug.com/462233640, capturing the fragmentation-state inconsistency.
- Fix + verification — SetupRelayoutData now copies PageName() for the non-fragmenting relayout; the TestExpectations suppression is removed, landing directly on main@{#1576631}.
Proof of concept
No exploit is written or warranted, but the diff points at concrete regression coverage: the Web Platform Test external/wpt/css/css-page/page-name-unnamed-trailing-001-print.html, whose crash suppression this CL removes, is the reproducer. It exercises named-page pagination with an unnamed trailing page under print layout. The ‘code’ field references the test rather than reproducing exploit content, since the trigger is a legitimate CSS paged-media construct (named pages inside clipped-overflow content, rendered for print), not attacker shellcode.
<!-- Regression test: external/wpt/css/css-page/page-name-unnamed-trailing-001-print.html
Named-page content inside a clipped-overflow container, laid out for print.
Previously [ Crash Failure Timeout ] under crbug.com/462233640. -->
Exploitation
- Setup — Author a document combining CSS named pages with content placed in a container that clips overflow, so fragmentation initially splits the content.
- Trigger — Invoke paged/print layout (print preview or window.print) so Blink lays out with an assumed page type, then relayouts with a taller named page type, entering the relayout-without-fragmentation branch.
- Outcome — Pre-fix, the new pass lacked the page name, producing a fragmentation-state inconsistency that crashed/timed out the renderer. No corruption primitive is demonstrated; treat this as a DoS-class layout crash.
Detection & hunting
For defenders and detection engineers:
- Renderer crashes on the print/paged-layout path —
- Test/telemetry regressions on css-page WPTs —
Audit directions
- Other relayout state carried by SetupRelayoutData —
- Named-page + clipped-overflow interactions —
- Fragmentation invariants / CHECKs —