High CVSS 6.5 firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionWhen manipulating the selection node cache, an attacker may have been able to cause unexpected behavior, potentially leading to an exploitable crash.
ComponentDOM
Bug ClassLogic Error
Tracker1920381
Fix commit148ccccc2009 (firefox) +7/-0
CWECWE-362 (Race condition)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
CISA KEVNot listed
CreditedAtte Kettunen
Disclosed2024-10-14

Background

`SelectionNodeCache`
A per-selection cache that records which nsINode objects are fully selected so repeated selection queries avoid re-walking every range.
`AbstractRange`
The base type for a selection range, subclassed by dynamic nsRange and StaticRange, exposing boundary accessors like MayCrossShadowBoundaryStartRef.
`StaticRange`
An AbstractRange variant that stores fixed boundary points and, unlike nsRange, is not automatically kept valid as the DOM mutates, so it can reference stale or mismatched nodes.
Range boundary
A RangeBoundary pairing a container node with an offset; a range is well-formed only when its start and end boundaries are consistent within the same tree.

Root Cause Analysis

The vulnerable path is SelectionNodeCache::MaybeCollect, which iterates over every range of a Selection via GetAbstractRangeAt and reads each range’s start and end boundaries through MayCrossShadowBoundaryStartRef and MayCrossShadowBoundaryEndRef to compute the set of fully selected nodes. The code assumed every range was non-collapsed and internally valid, but a StaticRange can become invalid after DOM mutation while still being present in the selection, and a collapsed range yields degenerate boundaries. When such a range was processed, the boundary comparison operated on inconsistent or stale endpoints, violating the invariant that boundaries used for node collection belong to a coherent, live range.

The fix restores that invariant by asserting the range is non-null and then skipping any range that Collapsed() reports as empty or that is a StaticRange failing IsValid(), so only well-formed ranges contribute to the cache. This prevents the cache from being populated from garbage boundaries that could drive later logic into an inconsistent, exploitable state.

Key insight
The single core mistake was treating every range in the selection as valid and traversable without checking for collapsed or invalid StaticRange objects; the fix addresses it by filtering those degenerate ranges out before their boundaries are ever dereferenced.

Attack Path

  1. Create a selection with a static range Script adds a range to a Selection that is (or can become) a StaticRange, whose boundaries are not auto-corrected on mutation.
  2. Mutate the DOM to invalidate it The attacker moves or removes nodes so the StaticRange becomes invalid or collapses, while it remains attached to the selection.
  3. Trigger cache collection An operation that queries fully selected nodes invokes SelectionNodeCache::MaybeCollect, which iterates the now-inconsistent range.
  4. Dereference stale boundaries MayCrossShadowBoundaryStartRef and MayCrossShadowBoundaryEndRef return mismatched or stale endpoints, and the cache is built from unexpected node relationships.
  5. Reach an exploitable crash The resulting inconsistent state leads to unexpected behavior and a potentially exploitable memory-safety crash.

Impact Assessment

An attacker running script in a content page can drive the selection node cache into an inconsistent state, producing unexpected behavior and a potentially exploitable crash in the content process. Exploitation requires the ability to script DOM selection and mutate the tree so a range becomes collapsed or an invalid StaticRange; no special privileges beyond ordinary web content are needed, matching the high-severity rating.

Files Changed

  • dom/base/Selection.cpp

Audit Directions

  • Unvalidated StaticRange traversal
    Search for other consumers of GetAbstractRangeAt or range iteration that read boundaries without checking IsStaticRange/IsValid or Collapsed.
  • Boundary access without invariants
    Review call sites of MayCrossShadowBoundaryStartRef and MayCrossShadowBoundaryEndRef that assume live, consistent endpoints across DOM mutations.
  • Cache population from selection state
    Audit any cache or precomputed set built from Selection ranges to confirm degenerate or stale ranges are filtered before contributing entries.
diff --git a/dom/base/Selection.cpp b/dom/base/Selection.cpp
index b080f98ebbc..0ad66b0882b 100644
--- a/dom/base/Selection.cpp
+++ b/dom/base/Selection.cpp
@@ -331,6 +331,13 @@ const nsTHashSet<const nsINode*>& SelectionNodeCache::MaybeCollect(
     nsTHashSet<const nsINode*> fullySelectedNodes;
     for (size_t rangeIndex = 0; rangeIndex < sel->RangeCount(); ++rangeIndex) {
       AbstractRange* range = sel->GetAbstractRangeAt(rangeIndex);
+      MOZ_ASSERT(range);
+      if (range->Collapsed()) {
+        continue;
+      }
+      if (range->IsStaticRange() && !range->AsStaticRange()->IsValid()) {
+        continue;
+      }
       const RangeBoundary& startRef = range->MayCrossShadowBoundaryStartRef();
       const RangeBoundary& endRef = range->MayCrossShadowBoundaryEndRef();
 
Loading diff…