High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the DOM: CSS Object Model component
ComponentLayout
Bug ClassLogic Error
Tracker2082044
Fix commit4e3929ab47c8 (firefox) +44/-1
CISA KEVNot listed
CreditedOpenAI Codex Security (kylebot)
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
add_task
layout/style/test/test_inline_style_cc.html
modified

Files Changed

  • layout/style/nsDOMCSSAttrDeclaration.cpp
  • layout/style/test/mochitest.toml
  • layout/style/test/test_inline_style_cc.html
diff --git a/layout/style/nsDOMCSSAttrDeclaration.cpp b/layout/style/nsDOMCSSAttrDeclaration.cpp
index dd8b390e3f1..28aa1056bbe 100644
--- a/layout/style/nsDOMCSSAttrDeclaration.cpp
+++ b/layout/style/nsDOMCSSAttrDeclaration.cpp
@@ -47,7 +47,8 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDOMCSSAttributeDeclaration)
   return tmp->HasKnownLiveWrapper() ||
-         (tmp->mElement && Element::CanSkipInCC(tmp->mElement));
+         (tmp->mElement && Element::CanSkipInCC(tmp->mElement) &&
+          tmp->HasNothingToTrace(tmp));
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDOMCSSAttributeDeclaration)
diff --git a/layout/style/test/mochitest.toml b/layout/style/test/mochitest.toml
index 458857fcf66..127d017770b 100644
--- a/layout/style/test/mochitest.toml
+++ b/layout/style/test/mochitest.toml
@@ -506,6 +506,8 @@ skip-if = [
 
 ["test_initial_storage.html"]
 
+["test_inline_style_cc.html"]
+
 ["test_invalidation_basic.html"]
 
 ["test_keyframes_rules.html"]
diff --git a/layout/style/test/test_inline_style_cc.html b/layout/style/test/test_inline_style_cc.html
new file mode 100644
index 00000000000..b48d1c3cc89
--- /dev/null
+++ b/layout/style/test/test_inline_style_cc.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Inline style declarations trace their gray wrappers</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script>
+function makeAnchor() {
+  const doc = new DOMParser().parseFromString(
+    "<!doctype html><body>",
+    "text/html"
+  );
+  const element = doc.createElement("div");
+  element.appendChild(doc.createElement("span"));
+
+  const victim = doc.createRange();
+  victim.selectNodeContents(element);
+  // Preserve the victim's reflector so it is also a gray JS holder.
+  victim.expando = true;
+  element.style.victim = victim;
+
+  // Retain the carrier only through the native edges of a JS-rooted Range.
+  const anchor = doc.createRange();
+  anchor.selectNodeContents(element);
+  return anchor;
+}
+
+add_task(function() {
+  const anchor = makeAnchor();
+  SpecialPowers.forceGC();
+  SpecialPowers.forceCC();
+
+  // Expose the element, but not its style. The first CC has already run the
+  // early forgetSkippable passes, so the style reflector is still gray.
+  const element = anchor.startContainer;
+  SpecialPowers.forceCC();
+
+  // A premature unlink of the still-reachable Range clears its boundaries.
+  is(element.style.victim.collapsed, false, "The style expando must stay alive");
+});
+</script>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/layout/style/test/mochitest.toml b/layout/style/test/mochitest.toml
index 458857fcf66..127d017770b 100644
--- a/layout/style/test/mochitest.toml
+++ b/layout/style/test/mochitest.toml
@@ -506,6 +506,8 @@ skip-if = [
 
 ["test_initial_storage.html"]
 
+["test_inline_style_cc.html"]
+
 ["test_invalidation_basic.html"]
 
 ["test_keyframes_rules.html"]
diff --git a/layout/style/test/test_inline_style_cc.html b/layout/style/test/test_inline_style_cc.html
new file mode 100644
index 00000000000..b48d1c3cc89
--- /dev/null
+++ b/layout/style/test/test_inline_style_cc.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Inline style declarations trace their gray wrappers</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script>
+function makeAnchor() {
+  const doc = new DOMParser().parseFromString(
+    "<!doctype html><body>",
+    "text/html"
+  );
+  const element = doc.createElement("div");
+  element.appendChild(doc.createElement("span"));
+
+  const victim = doc.createRange();
+  victim.selectNodeContents(element);
+  // Preserve the victim's reflector so it is also a gray JS holder.
+  victim.expando = true;
+  element.style.victim = victim;
+
+  // Retain the carrier only through the native edges of a JS-rooted Range.
+  const anchor = doc.createRange();
+  anchor.selectNodeContents(element);
+  return anchor;
+}
+
+add_task(function() {
+  const anchor = makeAnchor();
+  SpecialPowers.forceGC();
+  SpecialPowers.forceCC();
+
+  // Expose the element, but not its style. The first CC has already run the
+  // early forgetSkippable passes, so the style reflector is still gray.
+  const element = anchor.startContainer;
+  SpecialPowers.forceCC();
+
+  // A premature unlink of the still-reachable Range clears its boundaries.
+  is(element.style.victim.collapsed, false, "The style expando must stay alive");
+});
+</script>
Loading diff…