CVE-2026-28955
Overview
Background
- Style resolver
- WebCore::Style::Resolver computes matched CSS rules for elements; it is owned by a Style::Scope and can be torn down on style recalc.
- Bare reference vs Ref<>
auto& x = obj.styleResolver()aliases without owning, so it dangles if the resolver is destroyed; Ref<> holds a strong reference.- Inspector CSS agent
- The backend that answers Web Inspector CSS-domain requests such as matched-rule queries for an element.
- Ancestor rule walk
- Matched-rule computation iterates an element’s ancestors, each with its own style resolver, any of which can be invalidated during the walk.
Root Cause Analysis
The Web Inspector CSS agent (InspectorCSSAgent) computed matched CSS rules for an element and its ancestors, and it held the element’s style resolver in a bare reference: auto& styleResolver = element->styleResolver();. It then used that reference across operations that can run style resolution and walk/mutate the render tree – pseudoStyleRulesForElement for the element and each pseudo-element, and a loop over ancestorsOfType<Element> calling ancestor.styleResolver() and styleRulesForElement. A style resolver is owned by its Style::Scope and can be destroyed or replaced during style recalc / DOM changes that occur while these calls run, so the retained styleResolver& (and the ancestor& from the range-for) can dangle, and a later use is a use-after-free.
The fix retains everything it dereferences across those steps: Ref styleResolver = element->styleResolver();, Ref ancestor : ancestorsOfType<Element>(*element), and Ref parentStyleResolver = ancestor->styleResolver();, and it re-fetches the element’s style resolver freshly inside the ancestor loop rather than reusing a stale reference.
The restored invariant is that the style resolver (and each ancestor element) is kept alive for the duration of the rule-matching that uses it. This path is reached through the Web Inspector CSS domain.
Attack Path
- Attach the inspector An active Web Inspector session enables the CSS agent, which computes matched rules for a selected element via InspectorCSSAgent.
- Trigger rule matching Invoke the CSS domain operation that calls pseudoStyleRulesForElement and walks ancestors, holding element->styleResolver() by bare reference.
- Destroy the resolver mid-walk Cause a style recalc / DOM mutation (e.g. via script or a pseudo/ancestor traversal side effect) that frees or replaces the style resolver while the agent still references it.
- Use-after-free The agent dereferences the freed style resolver (or freed ancestor element), crashing or corrupting memory in WebContent.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
InspectorCSSAgent::(matched-rules-for-element handler)Source/WebCore/inspector/agents/InspectorCSSAgent.cpp |
modified | Holds the style resolver and ancestors by Ref (Ref styleResolver / Ref ancestor / Ref parentStyleResolver) and re-fetches the element's resolver inside the ancestor loop, so nothing dangles across pseudoStyleRulesForElement/styleRulesForElement. |
Files Changed
Source/WebCore/inspector/agents/InspectorCSSAgent.cpp
Audit Directions
- Other bare styleResolver() aliasesgrep WebCore for
auto& \w+ = .*styleResolver()and otherStyle::Resolver&locals held across style-resolving or tree-walking calls. - Range-for over tree nodesAudit
for (auto& x : ancestorsOfType/descendantsOfType(...))loops whose body can run script or style recalc; preferfor (Ref x : ...). - Inspector agents holding engine refsReview InspectorCSS/DOM agents for bare references to resolvers, scopes, or elements retained across protocol operations.