CVE-2026-74944
Overview
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
:targetpseudo-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.
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
- Craft fragment navigation
The attacker gets the victim to navigate to a URL whose fragment (or text directive) drives
PresShell::GoToAnchorto resolve a specifictargetelement. - Trigger reentrant script
The earlier processing in
GoToAnchorruns script (for example via events fired during fragment/text-directive handling), yielding control to attacker-controlled JavaScript. - Detach the target
That script removes
targetfrom the DOM or moves it to another document so its composed document no longer matchesmDocument, and the element’s memory can be freed. - Use freed element
Control returns to
GoToAnchor, which scrolls and appliesElementState::URLTARGETto the staletarget, dereferencing freed memory.
Impact Assessment
GoToAnchor with reentrant script that detaches the target.Changed Functions
| Function | Change | Notes |
|---|---|---|
iflayout/base/PresShell.cpp |
modified |
Files Changed
layout/base/PresShell.cpp
Audit Directions
- Post-reentrancy pointer reuseAudit 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 checksLook for scroll, focus, and
:target/state-setting code paths that assume an element remains inmDocument; confirm they checkGetComposedDoc()against the expected document after mutation-capable steps. - `EventStateManager::SetContentState` callersReview other
SetContentStateuses on elements resolved earlier in a method to ensure the element is still connected and not freed at the point of the state change.
Patch
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:"