← All issues

[6] [WebCore] Fix UAF in Range::createContextualFragment via Trusted Types policy callback

Severity: High | Component: WebCore DOM Range | 1e11f2a

Rated High because the diff promotes raw Node/Element captures to Ref/RefPtr across the Trusted Types createHTML callback in Range::createContextualFragment; without promotion, attacker-controlled JS in the policy callback can drop all references and force GC, then post-callback dereference operates on freed memory.

Captured Node/Element references in Range::createContextualFragment become Ref/RefPtr, pinning lifetime across the Trusted Types policy invocation.

Source/WebCore/dom/Range.cpp

- auto* node = startContainer.get();
+ Ref node = startContainer;
...
auto sanitized = trustedTypes->createHTML(input);
// attacker JS may mutate range, drop refs, force GC here
- parse(*node, sanitized);
+ parse(node.get(), sanitized);

Re-entrancy UAF: a raw Node pointer is captured before invoking a Trusted Types policy callback that runs attacker JavaScript synchronously, then dereferenced after.

The function's local captures for context node and start container become reference-counted. The policy callback is unchanged.

Range::createContextualFragment parses an HTML fragment in the context of the Range's start container. Trusted Types' createHTML policy sits on the input string path, sanitizing the supplied HTML by running a user-installed JavaScript callback before parsing.

Pre-fix, the function extracted a raw Node*/Element* from the Range's boundary and continued using it across the createHTML call. That callback is attacker-controlled JS. It can mutate the Range (e.g., range.setEnd(document, 0)setEnd collapses start to end on order inversion per the visible implementation), drop all other live references, and force GC.

When control returns from createHTML, the cached raw pointer references a destroyed Node. Subsequent parsing context lookup, fragment construction, or attach operates on freed memory. Promoting the captures to Ref/RefPtr keeps the Node alive for the duration of the stack frame, restoring the invariant.

This weakens the DOM lifetime invariant across JS re-entrancy points. The exploit primitive is UAF on a DOM Node reachable from script.