Moderate firefox Logic Error 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionIncorrect boundary conditions in the Layout: Text and Fonts component
ComponentLayout
Bug ClassLogic Error
Tracker2079686
Fix commit7ae94165b77c (firefox) +7/-3
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Background

`nsFlexContainerFrame`
The layout frame class implementing CSS flexbox. ReflowChildren reflows 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.

Key insight
Forced breaks were honored whenever the reflow was “in a fragmented context,” even when the available block-size was unconstrained — so a measuring/unconstrained reflow pushed flex items to a next-in-flow that should never have been created, corrupting continuation state.

Attack Path

  1. 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)
  2. Unconstrained reflow During an intrinsic/measuring reflow, aAvailableSizeForItems.BSize is NS_UNCONSTRAINEDSIZE.
  3. Wrongful push Pre-patch, IsInFragmentedContext() is true, so ReflowChildren honors the forced breaks and pushes remaining flex items to a next-in-flow.
  4. Corrupt continuation state The flex container ends up with continuation/frame-tree state inconsistent with a non-fragmenting reflow.
  5. 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

This is layout code in the content process; the trigger is ordinary (attacker-authored) CSS/HTML with forced breaks in a flex container, requiring no special privilege. The diff concretely fixes a logic/boundary error about when forced breaks may be honored, preventing flex items from being pushed to a next-in-flow during an unconstrained reflow. The advisory rates it moderate “incorrect boundary conditions”; the memory-safety impact (crash or potential use-after-free from inconsistent continuation state) is the inferred sink and is not directly demonstrated by the diff.

Changed Functions

FunctionChangeNotes
if
layout/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 effects
    Review 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 pushes
    Check how later passes handle a flex container whose items were pushed to a next-in-flow that a subsequent constrained reflow does not expect.
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) {
Loading diff…