High firefox UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the DOM: Core & HTML component
ComponentLayout
Bug ClassUAF
Tracker2057778
Fix commit17062a84ca5d (firefox) +5/-0
CISA KEVNot listed
CreditedAmy Burnett of OpenAI
Disclosed2026-08-18

Background

`PresShell::GoToAnchor`
the layout method that resolves a URL fragment to a target element and scrolls it into view while marking it as the URL target.
`EventStateManager` (`esm`)
the per-presentation object that tracks and mutates element states, here via SetContentState.
`ElementState::URLTARGET`
the element-state flag applied to the anchor target so it can be styled with the :target pseudo-class.
`GetComposedDoc`
the DOM accessor that returns the document a node is currently connected to (composed tree), or nullptr/a different document once the node is detached or moved.

Root Cause Analysis

PresShell::GoToAnchor locates a target element for the fragment and then performs earlier work (captured in rv) that can run script, allowing content to mutate the DOM before the method continues to the aScroll handling. During that reentrancy the target element could be removed from mDocument (or relocated to another document), violating the invariant that target is still connected to the same presentation’s document when it is subsequently scrolled and state-marked. Because the code kept using the now-stale target pointer, it operated on an element whose backing storage may have been freed, producing a use-after-free.

The fix re-checks target->GetComposedDoc() != mDocument after the script-capable step and, if the target is no longer part of this document, clears the ElementState::URLTARGET state via esm->SetContentState(nullptr, ...) and returns NS_OK instead of touching the detached element. This restores the connected-to-mDocument invariant before any further use of target.

Key insight
The core mistake was continuing to use the target element after an intervening, script-reentrant step that could disconnect it from mDocument; the fix revalidates target->GetComposedDoc() against mDocument and bails out safely, eliminating the stale-pointer dereference.

Attack Path

  1. Craft fragment navigation The attacker gets the victim to navigate to a URL whose fragment (or text directive) drives PresShell::GoToAnchor to resolve a specific target element.
  2. Trigger reentrant script The earlier processing in GoToAnchor runs script (for example via events fired during fragment/text-directive handling), yielding control to attacker-controlled JavaScript.
  3. Detach the target That script removes target from the DOM or moves it to another document so its composed document no longer matches mDocument, and the element’s memory can be freed.
  4. Use freed element Control returns to GoToAnchor, which scrolls and applies ElementState::URLTARGET to the stale target, dereferencing freed memory.

Impact Assessment

An attacker gains a use-after-free of a layout/DOM element in the content process that renders the malicious page, reachable purely through fragment or text-directive navigation. Exploitation can corrupt freed heap memory, potentially leading to a crash or memory-corruption primitive usable toward code execution within the content sandbox. The only precondition is inducing the victim to load attacker-controlled content that triggers GoToAnchor with reentrant script that detaches the target.

Changed Functions

FunctionChangeNotes
if
layout/base/PresShell.cpp
modified

Files Changed

  • layout/base/PresShell.cpp

Audit Directions

  • Post-reentrancy pointer reuse
    Audit methods that hold a raw DOM node pointer across any call that can run script (event dispatch, flushes, text-directive/fragment handling) and revalidate the node’s document connectivity before reusing it.
  • `GetComposedDoc` invariant checks
    Look for scroll, focus, and :target/state-setting code paths that assume an element remains in mDocument; confirm they check GetComposedDoc() against the expected document after mutation-capable steps.
  • `EventStateManager::SetContentState` callers
    Review other SetContentState uses on elements resolved earlier in a method to ensure the element is still connected and not freed at the point of the state change.
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
index 71157fae90e..ec1e4881193 100644
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -3210,6 +3210,11 @@ nsresult PresShell::GoToAnchor(const nsAString& aAnchorName,
       return rv.StealNSResult();
     }
 
+    if (MOZ_UNLIKELY(target->GetComposedDoc() != mDocument)) {
+      esm->SetContentState(nullptr, ElementState::URLTARGET);
+      return NS_OK;
+    }
+
     if (aScroll) {
       // https://wicg.github.io/scroll-to-text-fragment/#invoking-text-directives
       // From "Monkeypatching HTML § 7.4.6.3 Scrolling to a fragment:"
Loading diff…