Released May 11, 2026

CVE-2026-28955

CVSS 8.8 severity high CWE-119CWE-120 vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Overview

Impact:

Description: Processing maliciously crafted web content may lead to an unexpected process crash

Researchers: wac and Kookhwan Lee working with TrendAI Zero Day Initiative

Key insight. The inspector CSS agent aliased element->styleResolver() (and ancestors) by bare reference across style-resolving calls that can free it, so a style recalc mid-walk left a dangling resolver; retaining them with Ref<> fixes the lifetime.

Patch Details

diff --git a/Source/WebCore/inspector/agents/InspectorCSSAgent.cpp b/Source/WebCore/inspector/agents/InspectorCSSAgent.cpp
index 7447712ac885..d0ca0727b86d 100644
--- a/Source/WebCore/inspector/agents/InspectorCSSAgent.cpp
+++ b/Source/WebCore/inspector/agents/InspectorCSSAgent.cpp
@@ -494,8 +494,8 @@ Inspector::Protocol::ErrorStringOr<std::tuple<RefPtr<JSON::ArrayOf<Inspector::Pr
     }
 
     // Matched rules.
-    auto& styleResolver = element->styleResolver();
-    auto matchedRules = styleResolver.pseudoStyleRulesForElement(element, elementPseudoId, Style::Resolver::AllCSSRules);
+    Ref styleResolver = element->styleResolver();
+    auto matchedRules = styleResolver->pseudoStyleRulesForElement(element, elementPseudoId, Style::Resolver::AllCSSRules);
     auto matchedCSSRules = buildArrayForMatchedRuleList(matchedRules, styleResolver, *element, elementPseudoId);
     RefPtr<JSON::ArrayOf<Inspector::Protocol::CSS::PseudoIdMatches>> pseudoElements;
     RefPtr<JSON::ArrayOf<Inspector::Protocol::CSS::InheritedStyleEntry>> inherited;
@@ -521,7 +521,8 @@ Inspector::Protocol::ErrorStringOr<std::tuple<RefPtr<JSON::ArrayOf<Inspector::Pr
                     continue;
 
                 if (auto protocolPseudoId = protocolValueForPseudoElementType(pseudoElementType)) {
-                    auto matchedRules = styleResolver.pseudoStyleRulesForElement(element, pseudoElementType, Style::Resolver::AllCSSRules);
+                    Ref styleResolver = element->styleResolver();
+                    auto matchedRules = styleResolver->pseudoStyleRulesForElement(element, pseudoElementType, Style::Resolver::AllCSSRules);
                     if (!matchedRules.isEmpty()) {
                         auto matches = Inspector::Protocol::CSS::PseudoIdMatches::create()
                             .setPseudoId(protocolPseudoId.value())
@@ -535,9 +536,10 @@ Inspector::Protocol::ErrorStringOr<std::tuple<RefPtr<JSON::ArrayOf<Inspector::Pr
 
         if (!includeInherited || *includeInherited) {
             inherited = JSON::ArrayOf<Inspector::Protocol::CSS::InheritedStyleEntry>::create();
-            for (auto& ancestor : ancestorsOfType<Element>(*element)) {
-                auto& parentStyleResolver = ancestor.styleResolver();
-                auto parentMatchedRules = parentStyleResolver.styleRulesForElement(&ancestor, Style::Resolver::AllCSSRules);
+            for (Ref ancestor : ancestorsOfType<Element>(*element)) {
+                Ref parentStyleResolver = ancestor->styleResolver();
+                auto parentMatchedRules = parentStyleResolver->styleRulesForElement(ancestor.ptr(), Style::Resolver::AllCSSRules);
+                Ref styleResolver = element->styleResolver();
                 auto entry = Inspector::Protocol::CSS::InheritedStyleEntry::create()
                     .setMatchedCSSRules(buildArrayForMatchedRuleList(parentMatchedRules, styleResolver, ancestor, { }))
                     .release();

Background

Advisory: Apple Security Advisory

Attribute Value
CVE CVE-2026-28955
Bugzilla 310880
Component WebCore
Bug Class UAF
Severity medium
Commit 5233199dcb0cd58c…
Advisory Apple Advisory

Terminology

  • 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

1. Attach the inspector

An active Web Inspector session enables the CSS agent, which computes matched rules for a selected element via InspectorCSSAgent.

2. Trigger rule matching

Invoke the CSS domain operation that calls pseudoStyleRulesForElement and walks ancestors, holding element->styleResolver() by bare reference.

3. 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.

4. Use-after-free

The agent dereferences the freed style resolver (or freed ancestor element), crashing or corrupting memory in WebContent.

Impact Assessment

A use-after-free of a style resolver (or ancestor element) held by bare reference in the inspector CSS agent, in the WebContent process. It is reachable only with an active Web Inspector CSS session, which limits exposure from a plain web page; the realistic effect is a crash, with UAF-to-corruption possible under inspector-driven timing. Rated medium.

Audit Directions

Other bare styleResolver() aliases

grep WebCore for auto& \w+ = .*styleResolver() and other Style::Resolver& locals held across style-resolving or tree-walking calls.

Range-for over tree nodes

Audit for (auto& x : ancestorsOfType/descendantsOfType(...)) loops whose body can run script or style recalc; prefer for (Ref x : ...).

Inspector agents holding engine refs

Review InspectorCSS/DOM agents for bare references to resolvers, scopes, or elements retained across protocol operations.

Changed Functions

Function File Change Note
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 Files

  • Source/WebCore/inspector/agents/InspectorCSSAgent.cpp