CVE-2026-117991
Overview
Background
- `nsFlexContainerFrame`
- The layout frame class implementing CSS flexbox.
ReflowChildrenreflows the flex items of each flex line and decides whether any must be pushed to the container’s next-in-flow (a continuation frame used in fragmentation/pagination/multicol). - Fragmented context
- A reflow that splits content across fragmentainers (print pages, multicol columns, etc.).
aReflowInput.IsInFragmentedContext()reports whether the current reflow is fragmented. - Forced breaks
break-before/break-after(and inline break status) that request a mandatory break; honoring them can push subsequent flex items to a next-in-flow.- `NS_UNCONSTRAINEDSIZE`
- Sentinel block-size meaning “no constraint”; used during measuring/intrinsic reflows where the available block-size is effectively infinite.
- Next-in-flow
- A continuation frame holding content that did not fit; pushing items to it mutates the frame tree.
Root Cause Analysis
ReflowChildren in layout/generic/nsFlexContainerFrame.cpp gated its forced-break handling (pushing items/lines to a next-in-flow on break-before/break-after/inline break status) on aReflowInput.IsInFragmentedContext(). That condition is too broad: a reflow can be “in a fragmented context” yet still run with an unconstrained available block-size (aAvailableSizeForItems.BSize(flexWM) == NS_UNCONSTRAINEDSIZE), as happens during measuring/intrinsic-size reflows. In that state there is no real fragmentainer boundary to break at, and — as the added comment states — “an unconstrained reflow must not push items to a next-in-flow.” Honoring forced breaks under an unconstrained block-size caused flex items to be pushed to / split into a next-in-flow when none should exist, corrupting the flex container’s frame-tree/continuation state.
The patch introduces const bool shouldHonorForcedBreaks = aAvailableSizeForItems.BSize(flexWM) != NS_UNCONSTRAINEDSIZE; and replaces the three aReflowInput.IsInFragmentedContext() guards (the break-before handling, the break-after handling, and the row-oriented line break-value propagation) with shouldHonorForcedBreaks. Forced breaks are now honored only when the block-size is genuinely constrained, so an unconstrained reflow no longer pushes items to a next-in-flow. The advisory classifies this as “incorrect boundary conditions” in Layout; the diff substantiates a layout-invariant (break/continuation) logic fix. The concrete memory-safety consequence of the inconsistent continuation state (e.g. a dangling/duplicated frame reference leading to a crash or use-after-free) is the inferred security sink and is not shown by the diff.
Attack Path
- Craft layout
Web content builds a flex container with items carrying forced breaks (
break-before/break-after) inside a context (e.g. multicol/print-related) that both marks the reflow fragmented and triggers an unconstrained-block-size (measuring) reflow pass. (reachability inferred) - Unconstrained reflow
During an intrinsic/measuring reflow,
aAvailableSizeForItems.BSizeisNS_UNCONSTRAINEDSIZE. - Wrongful push
Pre-patch,
IsInFragmentedContext()is true, soReflowChildrenhonors the forced breaks and pushes remaining flex items to a next-in-flow. - Corrupt continuation state The flex container ends up with continuation/frame-tree state inconsistent with a non-fragmenting reflow.
- Primitive The inconsistent frame/continuation state is the lever toward a crash or memory-safety fault during subsequent layout; the diff proves the invariant violation, not the faulting access. (inferred)
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
iflayout/generic/nsFlexContainerFrame.cpp |
modified |
Files Changed
layout/generic/nsFlexContainerFrame.cpp
Audit Directions
- `IsInFragmentedContext()` as a proxy for "may break"Audit other block/flex/grid/table reflow code that gates pushing-to-next-in-flow or honoring forced breaks on
IsInFragmentedContext()without also checking for an unconstrained available block-size. - Unconstrained/measuring reflow side effectsReview measuring/intrinsic reflow passes for any code that mutates continuation or frame-tree state (pushing items, creating next-in-flows) that should only occur during a real fragmenting reflow.
- Continuation consistency after wrongful pushesCheck how later passes handle a flex container whose items were pushed to a next-in-flow that a subsequent constrained reflow does not expect.
Patch
diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp
index 849389f87d6..253ba3adadc 100644
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -5654,6 +5654,10 @@ std::tuple<nscoord, nsReflowStatus> nsFlexContainerFrame::ReflowChildren(
const FlexItem* endmostItem =
endmostLine.IsEmpty() ? nullptr : &endmostLine.EndmostItem(aAxisTracker);
+ // Forced breaks are honored only if the available block-size is constrained,
+ // since an unconstrained reflow must not push items to a next-in-flow.
+ const bool shouldHonorForcedBreaks =
+ aAvailableSizeForItems.BSize(flexWM) != NS_UNCONSTRAINEDSIZE;
bool endmostItemOrLineHasBreakAfter = false;
// If true, push all remaining flex items to the container's next-in-flow.
bool shouldPushRemainingItems = false;
@@ -5816,7 +5820,7 @@ std::tuple<nscoord, nsReflowStatus> nsFlexContainerFrame::ReflowChildren(
ReflowFlexItem(aAxisTracker, aReflowInput, item, framePos,
isAdjacentWithBStart, availableSize, aContainerSize);
- if (aReflowInput.IsInFragmentedContext()) {
+ if (shouldHonorForcedBreaks) {
const bool itemHasBreakBefore =
item.Frame()->ShouldBreakBefore(aReflowInput.mBreakType) ||
childStatus.IsInlineBreakBefore();
@@ -5882,7 +5886,7 @@ std::tuple<nscoord, nsReflowStatus> nsFlexContainerFrame::ReflowChildren(
overflowIncompleteItems.Insert(item.Frame());
}
- if (aReflowInput.IsInFragmentedContext()) {
+ if (shouldHonorForcedBreaks) {
const bool itemHasBreakAfter =
item.Frame()->ShouldBreakAfter(aReflowInput.mBreakType) ||
childStatus.IsInlineBreakAfter();
@@ -5991,7 +5995,7 @@ std::tuple<nscoord, nsReflowStatus> nsFlexContainerFrame::ReflowChildren(
}
}
- if (aReflowInput.IsInFragmentedContext() && aAxisTracker.IsRowOriented()) {
+ if (shouldHonorForcedBreaks && aAxisTracker.IsRowOriented()) {
// Propagate forced break values from the flex items to its flex line.
if (lineHasBreakBefore) {
if (&line == &startmostLine) {