High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the Layout: Positioned component
ComponentLayout
Bug ClassLogic Error
Tracker2082450
Fix commite70bde084494 (firefox) +106/-4
CISA KEVNot listed
CreditedOpenAI Codex Security (kylebot)
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
if
testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html
modified
for
testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html
modified

Files Changed

  • layout/generic/nsFlexContainerFrame.cpp
  • layout/generic/nsGridContainerFrame.cpp
  • testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html
diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp
index 253ba3adadc..2ff3648ed39 100644
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -4675,7 +4675,8 @@ void nsFlexContainerFrame::Reflow(nsPresContext* aPresContext,
                                   nsReflowStatus& aStatus) {
   NormalizeChildLists();
 
-  if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
+  if (IsHiddenByContentVisibilityOfInFlowParentForLayout() &&
+      !GetNextContinuation()) {
     return;
   }
 
@@ -5625,7 +5626,7 @@ std::tuple<nscoord, nsReflowStatus> nsFlexContainerFrame::ReflowChildren(
     const LogicalSize& aAvailableSizeForItems,
     const LogicalMargin& aBorderPadding, const FlexboxAxisTracker& aAxisTracker,
     FlexLayoutResult& aFlr, PerFragmentFlexData& aFragmentData) {
-  if (HidesContentForLayout()) {
+  if (HidesContentForLayout() && !GetNextContinuation()) {
     return {0, nsReflowStatus()};
   }
 
diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp
index 5189f3f6f31..1699a429103 100644
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -9275,7 +9275,7 @@ nscoord nsGridContainerFrame::ReflowChildren(GridReflowInput& aGridRI,
   nscoord bSize = aContentArea.BSize(wm);
   MOZ_ASSERT(aGridRI.mReflowInput);
   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
-  if (HidesContentForLayout()) {
+  if (HidesContentForLayout() && !GetNextContinuation()) {
     return bSize;
   }
 
@@ -9444,7 +9444,8 @@ void nsGridContainerFrame::Reflow(nsPresContext* aPresContext,
                                   nsReflowStatus& aStatus) {
   NormalizeChildLists();
 
-  if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
+  if (IsHiddenByContentVisibilityOfInFlowParentForLayout() &&
+      !GetNextContinuation()) {
     return;
   }
 
diff --git a/testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html b/testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html
new file mode 100644
index 00000000000..adb42689f6c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html class="test-wait">
+<meta charset="utf-8">
+<title>Hiding fragmented absolute children with escaped fixed descendants</title>
+<link rel="help" href="https://drafts.csswg.org/css-contain/#content-visibility">
+<link rel="help" href="https://drafts.csswg.org/css-position-3/#abspos-breaking">
+<style>
+html, body { margin: 0 }
+.cols {
+  width: 620px;
+  height: 120px;
+  columns: 3;
+  column-gap: 10px;
+  column-fill: auto;
+}
+.outer { position: relative; contain: layout style paint }
+.normal { height: 700px }
+.victim {
+  position: absolute;
+  inset: 0 auto auto 0;
+  width: 170px;
+  display: flex;
+  flex-flow: column nowrap;
+}
+.item { flex: 0 0 auto; margin: 0; line-height: 17px; font-size: 12px }
+.row { height: 21px }
+.fx { position: fixed; inset: 2px auto auto 2px; width: 13px; height: 13px }
+.grid .victim { display: grid; grid-template-rows: repeat(7, 120px) }
+.grid .item { grid-row: 1 / -1; min-height: 0 }
+.hide-self .victim { contain: layout style paint }
+.hide-self.flex .item { flex-basis: 756px; min-height: 0; height: 756px }
+.case[popover] {
+  position: fixed;
+  inset: 0 auto auto 0;
+  margin: 0;
+  border: 0;
+  padding: 0;
+}
+.case::backdrop { display: none }
+.fx[popover] { margin: 0; border: 0; padding: 0 }
+</style>
+<body>
+<template id="fixture">
+  <div class="cols"><div class="outer">
+    <div class="normal">normal</div>
+    <div class="victim"><div class="item"></div></div>
+  </div></div>
+</template>
+<script>
+function runTest(layout, topLayer, hideSelf) {
+  const test = document.createElement("div");
+  test.classList.add("case", layout);
+  if (hideSelf) {
+    test.classList.add("hide-self");
+  }
+  test.append(document.getElementById("fixture").content.cloneNode(true));
+  const cols = test.querySelector(".cols");
+  const outer = test.querySelector(".outer");
+  const victim = test.querySelector(".victim");
+  test.querySelector(".item").innerHTML = Array.from({length: 36}, (_, i) =>
+    '<div class="row">row ' + i +
+    (i === 20 ? '<span class="fx">x</span>' : '') + '</div>').join('');
+  const fixed = test.querySelector(".fx");
+  if (topLayer) {
+    test.setAttribute("popover", "manual");
+    fixed.setAttribute("popover", "manual");
+  }
+  document.body.append(test);
+  if (topLayer) {
+    test.showPopover();
+    fixed.showPopover();
+  }
+
+  // Create the flex/grid and item continuations, with the fixed descendant's
+  // placeholder in a later fragment. In the top-layer case, the fixed frame
+  // escapes even a paint-containing victim to the viewport's absolute list.
+  victim.getClientRects();
+  const hidden = hideSelf ? victim : outer;
+  hidden.style.contentVisibility = "hidden";
+  cols.offsetHeight;
+  hidden.style.contentVisibility = "visible";
+  cols.offsetHeight;
+  fixed.getClientRects();
+
+  if (topLayer) {
+    fixed.hidePopover();
+    test.hidePopover();
+  }
+  test.remove();
+}
+
+addEventListener("load", () => {
+  for (const layout of ["flex", "grid"]) {
+    runTest(layout, false, false);
+    runTest(layout, true, false);
+    runTest(layout, true, true);
+  }
+  document.documentElement.classList.remove("test-wait");
+});
+</script>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html b/testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html
new file mode 100644
index 00000000000..adb42689f6c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-position/crashtests/content-visibility-abspos-continuations-crash.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html class="test-wait">
+<meta charset="utf-8">
+<title>Hiding fragmented absolute children with escaped fixed descendants</title>
+<link rel="help" href="https://drafts.csswg.org/css-contain/#content-visibility">
+<link rel="help" href="https://drafts.csswg.org/css-position-3/#abspos-breaking">
+<style>
+html, body { margin: 0 }
+.cols {
+  width: 620px;
+  height: 120px;
+  columns: 3;
+  column-gap: 10px;
+  column-fill: auto;
+}
+.outer { position: relative; contain: layout style paint }
+.normal { height: 700px }
+.victim {
+  position: absolute;
+  inset: 0 auto auto 0;
+  width: 170px;
+  display: flex;
+  flex-flow: column nowrap;
+}
+.item { flex: 0 0 auto; margin: 0; line-height: 17px; font-size: 12px }
+.row { height: 21px }
+.fx { position: fixed; inset: 2px auto auto 2px; width: 13px; height: 13px }
+.grid .victim { display: grid; grid-template-rows: repeat(7, 120px) }
+.grid .item { grid-row: 1 / -1; min-height: 0 }
+.hide-self .victim { contain: layout style paint }
+.hide-self.flex .item { flex-basis: 756px; min-height: 0; height: 756px }
+.case[popover] {
+  position: fixed;
+  inset: 0 auto auto 0;
+  margin: 0;
+  border: 0;
+  padding: 0;
+}
+.case::backdrop { display: none }
+.fx[popover] { margin: 0; border: 0; padding: 0 }
+</style>
+<body>
+<template id="fixture">
+  <div class="cols"><div class="outer">
+    <div class="normal">normal</div>
+    <div class="victim"><div class="item"></div></div>
+  </div></div>
+</template>
+<script>
+function runTest(layout, topLayer, hideSelf) {
+  const test = document.createElement("div");
+  test.classList.add("case", layout);
+  if (hideSelf) {
+    test.classList.add("hide-self");
+  }
+  test.append(document.getElementById("fixture").content.cloneNode(true));
+  const cols = test.querySelector(".cols");
+  const outer = test.querySelector(".outer");
+  const victim = test.querySelector(".victim");
+  test.querySelector(".item").innerHTML = Array.from({length: 36}, (_, i) =>
+    '<div class="row">row ' + i +
+    (i === 20 ? '<span class="fx">x</span>' : '') + '</div>').join('');
+  const fixed = test.querySelector(".fx");
+  if (topLayer) {
+    test.setAttribute("popover", "manual");
+    fixed.setAttribute("popover", "manual");
+  }
+  document.body.append(test);
+  if (topLayer) {
+    test.showPopover();
+    fixed.showPopover();
+  }
+
+  // Create the flex/grid and item continuations, with the fixed descendant's
+  // placeholder in a later fragment. In the top-layer case, the fixed frame
+  // escapes even a paint-containing victim to the viewport's absolute list.
+  victim.getClientRects();
+  const hidden = hideSelf ? victim : outer;
+  hidden.style.contentVisibility = "hidden";
+  cols.offsetHeight;
+  hidden.style.contentVisibility = "visible";
+  cols.offsetHeight;
+  fixed.getClientRects();
+
+  if (topLayer) {
+    fixed.hidePopover();
+    test.hidePopover();
+  }
+  test.remove();
+}
+
+addEventListener("load", () => {
+  for (const layout of ["flex", "grid"]) {
+    runTest(layout, false, false);
+    runTest(layout, true, false);
+    runTest(layout, true, true);
+  }
+  document.documentElement.classList.remove("test-wait");
+});
+</script>
Loading diff…