← All issues

[3] Highlight API iterator-invalidation UAF in clearFromSetLike

Severity: High | Component: CSS Custom Highlight API | 13d284a

Rated High because the observable effect is a confirmed use-after-free during document teardown (ASAN crash under Highlight::clearFromSetLike), and the re-entrant modification path through repaintRange() is plausible given WebKit's synchronous layout model — escalation to a controlled read/write primitive would require heap reclamation during the re-entrant window, which is unverified but architecturally feasible.

Follow-up fix after a previous embargoed patch. clearFromSetLike() now drains m_highlightRanges into a stack-local temporary via std::exchange before iterating, so any re-entrant modification to the member during repaintRange() callbacks operates on the now-empty container.

Source/WebCore/Modules/highlight/Highlight.cpp

void Highlight::clearFromSetLike()
{
- for (auto& highlightRange : m_highlightRanges)
+ for (auto& highlightRange : std::exchange(m_highlightRanges, { }))
repaintRange(highlightRange->range());
m_highlightRanges.clear();
}

The single-line change replaces m_highlightRanges as the iteration target with std::exchange(m_highlightRanges, { }), which atomically moves the vector's contents into a temporary and leaves the member empty. The loop body (repaintRange(highlightRange->range())) is unchanged. The trailing m_highlightRanges.clear() is now a no-op (the member is already empty) but remains as defensive code.

The CSS Custom Highlight API allows JavaScript to create Highlight objects containing AbstractRanges, which are registered with the document's HighlightRegistry. When a highlight is cleared (clearFromSetLike), each range must be repainted to remove its visual highlight. repaintRange() walks the DOM nodes intersecting the range and calls renderer->repaint() on each, which can synchronously trigger style recalculation and layout in WebKit's synchronous layout model.

std::exchange(obj, {}) is a C++ idiom that moves the value out of obj (replacing it with a default-constructed empty value) in a single expression and returns the original. In WebKit, this drain-before-iterate pattern is a well-established defensive idiom against re-entrant container modification.

Iterator invalidation from re-entrant modification of a live container during a callback-invoking iteration loop.

Before the fix, clearFromSetLike() iterated directly over the live m_highlightRanges member while calling repaintRange() on each element. repaintRange() walks intersecting nodes and triggers renderer->repaint(), which can synchronously trigger style recalculation, layout, or other repainting side effects. These repaint callbacks could re-entrantly modify m_highlightRanges — for example by removing or adding highlight ranges through observer callbacks or other code paths that mutate the highlight set. This re-entrant modification is a re-entrancy boundary — a point where native code calls into layout/style machinery, which may synchronously re-enter and mutate object state before returning. Such modification would invalidate the iterator mid-loop, producing a use-after-free or out-of-bounds access on the vector's backing store. The ASAN crash signature (Highlight::clearFromSetLike; HighlightRegistry::clear; Document::commonTeardown) confirms this path is reachable during document teardown.

🔒

The ownership model and escalation potential of this iterator-invalidation bug are explored in depth

Subscribe to read more

🔒

Multiple reusable audit patterns identified, with concrete starting points for variant discovery across WebCore

Subscribe to read more