Medium CVSS 4.3 webkit UAF 🔧 Commit mapped

Overview

Medium
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to an unexpected Safari crash
ComponentWebCore CSS
Bug ClassUAF
Tracker284055
Fix commitb879a659b190 (WebKit/WebKit) +44/-5
CWECWE-119, CWE-508 (Buffer bounds error)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L
CISA KEVNot listed
CreditedPaul Bakker of ParagonERP
Disclosed2025-03-31

Background

CSS nesting resolution
Flattening nested CSS rules into fully-qualified selectors when building a RuleSet.
StyleSheetContents / RuleSet
Parsed stylesheet contents and the built rule set; RuleSets can be rebuilt, re-running resolution.
hasResolvedNesting flag
New per-sheet state ensuring nesting is resolved once, reset when the sheet mutates.
destructorHasBeenCalled
A CSSSelector flag, checked under ASSERT_WITH_SECURITY_IMPLICATION, that detects use of a destroyed selector.

Root Cause Analysis

CSS nesting resolution flattens nested style rules into resolved selectors when building a RuleSet from a stylesheet. RuleSetBuilder drove this with a per-builder flag (m_shouldResolveNesting), but resolution could run more than once against the same StyleSheetContents (e.g. when a RuleSet is rebuilt) and, on a subsequent pass, operate on CSSSelector data whose owning objects had already been destroyed — a use-after-destroy of selector memory (surfacing as a Safari crash).

The fix makes nesting resolution idempotent per sheet and adds a memory-safety assertion: StyleSheetContents gains a hasResolvedNesting()/setHasResolvedNesting() flag, RuleSetBuilder computes m_shouldResolveNestingForSheet as builderShouldResolveNesting && !sheet.hasResolvedNesting() (so a sheet’s nesting is resolved exactly once and marked resolved via setHasResolvedNesting(true)), CSSStyleSheet::didMutateRules resets the flag with setHasResolvedNesting(false) so a genuinely mutated sheet re-resolves, and SelectorChecker/CSSSelector add ASSERT_WITH_SECURITY_IMPLICATION(!selector.destructorHasBeenCalled()) backed by a destructorHasBeenCalled flag to catch use of a destroyed selector.

The restored invariant is that a stylesheet’s nesting is resolved once per (unmutated) content, so resolution never re-runs over selectors that have since been destroyed.

Key insight
CSS nesting could be resolved more than once against the same sheet, re-processing selectors whose owners had been destroyed; making resolution once-per-sheet (with a mutation reset) and asserting selector liveness fixes the use-after-destroy.

Attack Path

  1. Author nested CSS Provide a stylesheet using CSS nesting whose rules get resolved into flattened selectors.
  2. Trigger a rebuild Cause the RuleSet to be rebuilt so nesting resolution runs again against the same sheet contents.
  3. Re-resolve over destroyed selectors Pre-patch, the second resolution pass operates on CSSSelector data whose owning objects were already destroyed.
  4. Use-after-destroy Reading the destroyed selector memory crashes or corrupts memory in the WebContent process.

Impact Assessment

A use-after-destroy of CSS selector data when nesting resolution re-runs over a rebuilt stylesheet, reachable from crafted nested CSS in the WebContent process. The observable is a crash; the added security assertion marks it a memory-safety bug, with corruption potential if the freed selector storage is reclaimed. Confined to WebContent; rated medium.

Changed Functions

FunctionChangeNotes
RuleSetBuilder::addChildRule / addRulesFromSheetContents / addStyleRuleWithSelectorList
Source/WebCore/style/RuleSetBuilder.cpp
modified Gates nesting resolution on m_shouldResolveNestingForSheet = builderShouldResolveNesting && !sheet.hasResolvedNesting(), and marks the sheet resolved, so nesting resolves exactly once per unmutated sheet.
CSSStyleSheet::didMutateRules
Source/WebCore/css/CSSStyleSheet.cpp
modified Calls setHasResolvedNesting(false) so a mutated sheet re-resolves nesting on the next build.
SelectorChecker / CSSSelector::destructorHasBeenCalled
Source/WebCore/css/SelectorChecker.cpp
modified Adds ASSERT_WITH_SECURITY_IMPLICATION(!selector.destructorHasBeenCalled()) and a destructor flag on CSSSelector to catch use of a destroyed selector.

Files Changed

  • LayoutTests/fast/selectors/has-nesting-crash-expected.txt
  • LayoutTests/fast/selectors/has-nesting-crash.html
  • Source/WebCore/css/CSSSelector.h
  • Source/WebCore/css/CSSStyleSheet.cpp
  • Source/WebCore/css/SelectorChecker.cpp
  • Source/WebCore/css/StyleSheetContents.h
  • Source/WebCore/style/RuleSetBuilder.cpp
  • Source/WebCore/style/RuleSetBuilder.h

Audit Directions

  • Repeated resolution passes
    Audit RuleSet rebuild paths for operations that assume single-pass resolution over selector data that may be freed between passes.
  • Selector lifetime
    grep for CSSSelector/CSSSelectorList uses across rule-set rebuilds; extend destructorHasBeenCalled-style checks where selectors outlive expectations.
  • didMutateRules invalidation
    Confirm every cached resolution/state keyed on sheet contents is reset in didMutateRules and similar mutation hooks.

Original Bug Report

The reporter's bug is still restricted on the tracker.