Moderate firefox Type Confusion 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionIncorrect boundary conditions in the Layout: Scrolling and Overflow component
ComponentLayout
Bug ClassType Confusion
Tracker2078981
Fix commit6272e5372f61 (firefox) +8/-2
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Background

`CanHaveOverflowMarkers`
Decides whether a block frame can show overflow markers; has a special case for the internal editing root of text controls (<input>/<textarea>).
`PseudoStyleType::MozTextControlEditingRoot`
The anonymous pseudo-element that is the editing root inside a text control.
`IsTextInputScrolledContent` / `ScrollContainerFrame`
The editing root is normally wrapped by a scroll container; CanHaveOverflowMarkers wants to static_cast the grandparent frame to ScrollContainerFrame* and read its scroll position.

Root Cause Analysis

The old code keyed solely on the pseudo type: if (aBlockFrame->Style()->GetPseudoType() == PseudoStyleType::MozTextControlEditingRoot). Inside that branch it static_casted aBlockFrame->GetParent()->GetParent() to ScrollContainerFrame* and called GetScrollPosition(), protecting the assumption only with a MOZ_DIAGNOSTIC_ASSERT(IsTextInputScrolledContent(aBlockFrame->GetParent())) — a diagnostic/debug check that does nothing in release builds. The crash test shows the assumption is false in practice: a <textarea> with column-count: 2; writing-mode: vertical-lr and a text-indent produces an editing-root pseudo whose parent is not the expected scrolled content (multicol reframes the subtree). In that case the unchecked static_cast to ScrollContainerFrame* operates on a frame of the wrong type (or a wrong ancestor), and dereferencing it via GetScrollPosition() is an invalid-type access.

The patch makes the real check part of the branch condition: pseudoType == MozTextControlEditingRoot && MOZ_LIKELY(IsTextInputScrolledContent(aBlockFrame->GetParent())), and removes the now-redundant MOZ_DIAGNOSTIC_ASSERT. The branch — and therefore the ScrollContainerFrame* cast — only runs when the parent genuinely is the text-input scrolled content. The advisory’s “incorrect boundary conditions” generically describes this, but the diff substantiates a bad static downcast / type confusion: a frame was cast to ScrollContainerFrame* without verifying its actual type.

Key insight
A static_cast to ScrollContainerFrame* was gated only by a release-build no-op assertion on the frame’s shape; with multicol/vertical writing-mode text controls the assumption fails, so the code cast and dereferenced a frame of the wrong type. The fix promotes the shape check into the runtime branch condition.

Attack Path

  1. Craft Attacker page/XHTML contains a <textarea> styled with column-count: 2; writing-mode: vertical-lr; text-indent: ... (from the WPT crash test).
  2. Layout On load, forcing layout (getBoundingClientRect()) and then setting text-overflow: ellipsis drives CanHaveOverflowMarkers on the editing-root frame.
  3. Mis-cast The pseudo-type matches, but the parent is not text-input scrolled content; the old code static_casts the grandparent to ScrollContainerFrame*.
  4. Invalid access scc->GetScrollPosition() reads a frame of the wrong type (type confusion) → crash / out-of-type memory access.
  5. Primitive Type-confused frame access in the layout engine (demonstrated as a crash; stronger exploitation inferred).

Impact Assessment

This runs in the content process layout engine and is reachable purely from attacker HTML/CSS with no special privilege. The diff plus the crash test prove a reliably reachable invalid static_cast of a layout frame to ScrollContainerFrame* when the structural assumption fails, yielding at least a crash/DoS and an out-of-type pointer dereference. Whether the type confusion is massageable into a controlled read/write is not shown by the diff and is inferred. The advisory rates it moderate.

Files Changed

  • layout/generic/TextOverflow.cpp
  • testing/web-platform/tests/css/css-overflow/text-overflow-root-textarea-crash.xhtml

Audit Directions

  • Diagnostic-asserted casts
    Search layout/ for static_cast<...Frame*> guarded only by MOZ_DIAGNOSTIC_ASSERT/MOZ_ASSERT (no-ops in release); convert the invariant to a runtime check or use do_QueryFrame.
  • Multicol/writing-mode reframes
    Review other special-case frame-ancestor assumptions (GetParent()->GetParent()) that can be broken by column-count or vertical writing modes.
  • Text-control editing-root assumptions
    Audit other code matching MozTextControlEditingRoot that assumes a specific scroll-container ancestry.
diff --git a/layout/generic/TextOverflow.cpp b/layout/generic/TextOverflow.cpp
index c7bb3e6edd5..c33297ca72f 100644
--- a/layout/generic/TextOverflow.cpp
+++ b/layout/generic/TextOverflow.cpp
@@ -860,10 +860,10 @@ bool TextOverflow::CanHaveOverflowMarkers(nsBlockFrame* aBlockFrame,
 
   // Make text-overflow: ellipsis work on the <input> editor.
   if (aBlockFrame->Style()->GetPseudoType() ==
-      PseudoStyleType::MozTextControlEditingRoot) {
+          PseudoStyleType::MozTextControlEditingRoot &&
+      MOZ_LIKELY(IsTextInputScrolledContent(aBlockFrame->GetParent()))) {
     MOZ_ASSERT(IsInlineAxisOverflowVisible(*aBlockFrame),
                "The control editing root must create overflow");
-    MOZ_DIAGNOSTIC_ASSERT(IsTextInputScrolledContent(aBlockFrame->GetParent()));
     auto* scc = static_cast<ScrollContainerFrame*>(
         aBlockFrame->GetParent()->GetParent());
     if (scc->GetScrollPosition() != nsPoint()) {
diff --git a/testing/web-platform/tests/css/css-overflow/text-overflow-root-textarea-crash.xhtml b/testing/web-platform/tests/css/css-overflow/text-overflow-root-textarea-crash.xhtml
new file mode 100644
index 00000000000..3e62fa32ca9
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/text-overflow-root-textarea-crash.xhtml
@@ -0,0 +1,6 @@
+<textarea xmlns="http://www.w3.org/1999/xhtml" style="column-count: 2; writing-mode: vertical-lr; text-indent: 81.98333px"><script>
+onload = () => {
+  document.documentElement.getBoundingClientRect();
+  document.documentElement.style.textOverflow = "ellipsis";
+};
+</script></textarea>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/testing/web-platform/tests/css/css-overflow/text-overflow-root-textarea-crash.xhtml b/testing/web-platform/tests/css/css-overflow/text-overflow-root-textarea-crash.xhtml
new file mode 100644
index 00000000000..3e62fa32ca9
--- /dev/null
+++ b/testing/web-platform/tests/css/css-overflow/text-overflow-root-textarea-crash.xhtml
@@ -0,0 +1,6 @@
+<textarea xmlns="http://www.w3.org/1999/xhtml" style="column-count: 2; writing-mode: vertical-lr; text-indent: 81.98333px"><script>
+onload = () => {
+  document.documentElement.getBoundingClientRect();
+  document.documentElement.style.textOverflow = "ellipsis";
+};
+</script></textarea>
Loading diff…