cfc5c1496292b726ece648763ce166d5609ac238 [layout] Don't compute borders for sections/rows.
The bug at a glance
A web page can drive the layout engine into computing a negative containing-block size for an out-of-flow positioned descendant whose containing block is a 0-sized table row/section fragment inside a fragmented (multicol) context. The accompanying crashtest (oof-in-table-row.html) indicates a reachable renderer crash / invalid layout state. Impact is confined to the renderer process (crash / layout corruption) with no evidence of memory disclosure or controlled corruption, so this is a low-severity renderer DoS.
Internal table elements (rows, sections, header/footer groups) are not supposed to carry borders on their fragments, but the generic border-computation path still applied the element’s declared border to them. When an OOF descendant in a fragmentation context asked its containing block fragment for borders, those borders were subtracted from a possibly zero-height row/section fragment, yielding a negative containing-block size and an invalid/crashing layout.
Root cause
ComputeBorders in length_utils.cc returned ComputeBordersInternal(node.Style()) for table rows and sections, honoring any border declared on them. Out-of-flow positioned descendants inside a fragmented context query the containing block fragment’s borders to contract the available containing-block rect. For a table-row/section fragment that can legitimately be 0-sized (e.g. under columns with a transform and a fixed-position child), subtracting a non-zero border strut produced a negative containing-block size, which the layout code did not expect and which led to a crash (crashtest added). The fix special-cases node.IsTableSection() || node.IsTableRow() to return an empty BoxStrut(), so these internal table fragments report no borders and cannot drive the size negative.
Key code
Table sections/rows now report no fragment borders, preventing a negative containing-block size.
if (node.IsTableSection() || node.IsTableRow()) {
return BoxStrut();
}
return ComputeBordersInternal(node.Style());
Patch walkthrough
third_party/blink/renderer/core/layout/length_utils.cc— In ComputeBorders, adds an early return of an empty BoxStrut() when the node is a table section or table row, before the generic ComputeBordersInternal(node.Style()) path. This stops declared borders on internal table elements from being applied to their fragments and thus from being subtracted off their (possibly zero) size when an OOF descendant queries containing-block borders.third_party/blink/web_tests/external/wpt/css/css-multicol/crashtests/oof-in-table-row.html— New crashtest reproducing the issue: a multicol container (columns:1) holds a display:table-row with transform:scale(1) and border:solid, containing a position:fixed child. This set-up triggered the negative containing-block size before the fix.
Background
Internal table elements — Table rows, row groups, and header/footer groups are internal table box types; per CSS they do not establish usable borders on their fragments the way cells/tables do.
BoxStrut / fragment borders — A BoxStrut holds the four border (or padding/margin) edge lengths; containing-block sizing subtracts the border strut from the fragment rect to get the content box an OOF descendant positions against.
Out-of-flow (OOF) positioned descendant in fragmentation — position:fixed/absolute descendants laid out within a multicol/fragmented context resolve their containing block from an ancestor fragment, querying that fragment’s borders.
Negative containing-block size — Subtracting borders larger than a zero-sized fragment yields a negative size, an invalid layout state the engine did not handle, leading to the crash exercised by the crashtest.
Vulnerability window
- introduction — Generic border computation applied declared borders to table row/section fragments. (inferred)
- discovery — Reported as bug 514535250 with a multicol + table-row + fixed-position descendant reproducer causing a crash. (inferred)
- fix — Change-Id I04ff07e9… returns an empty BoxStrut for sections/rows; merged to main @{#1692857}, Fixed: 514535250, with a crashtest.
Proof of concept
The diff includes a crashtest demonstrating the crash, but no weaponized exploit is provided and none is fabricated. Conceptually: place a display:table-row (with a border and a transform) inside a columns:1 container and give it a position:fixed child; pre-fix the OOF containing-block computation produces a negative size and crashes the renderer.
Exploitation
- setup — Attacker page constructs the multicol + bordered table-row/section + fixed-position descendant layout from the crashtest. (inferred)
- trigger — Layout computes the OOF containing block by subtracting the row/section borders from a 0-sized fragment, producing a negative size. (inferred)
- impact — Invalid negative size leads to a renderer crash / layout failure (DoS). No evidence of controllable corruption. (inferred)
Detection & hunting
For defenders and detection engineers:
- Renderer crashes on table-row/section + multicol + OOF layouts — Watch for crash reports matching the crashtest shape (columns + display:table-row/table-row-group with border + position:fixed/absolute child); correlate with length_utils ComputeBorders / containing-block sizing.
- Negative containing-block sizes in layout — Add DCHECK/telemetry for negative containing-block inline/block sizes derived from fragment border subtraction.
Audit directions
- Fragment border/padding on internal box types — Audit other places that read fragment borders/padding/scrollbars for internal table box types (rows, sections, column/column-group) that may legitimately be zero-sized, to ensure contraction cannot drive sizes negative.
- OOF containing-block sizing under fragmentation — Review containing-block rect computation for OOF descendants across all cases where the containing block fragment can be zero-sized, confirming non-negative clamping or empty border handling.