[1] CSS @function dashed-substitution use-after-free
Severity: High | Component: WebCore Style — Style::SubstitutionResolver | 4b97014
Rated High because the diff fixes a heap use-after-free where CSSVariableData::create re-captures token data from a freed CustomProperty's string backing; the path is reachable from web content via a one-line @function snippet, and heap reuse before the re-capture would convert the freed-read into a controlled-content read primitive over the recycled buffer.
The commit keeps the resolved CustomProperty alive in m_intermediateCustomProperties until substitute() has constructed the new CSSVariableData, mirroring the existing m_intermediateTokenStrings mechanism used for attr() tokenizer output. The regression test installs @function --f(--a) { result: aaaa var(--a); }, applies --x: --f(bbbb) on a target, and reads getComputedStyle(target).getPropertyValue('--x') to drive the substitution path through the freed-token consumer.
Source/WebCore/style/StyleSubstitutionResolver.cpp
@@ substituteDashedFunction
if (guard.isCyclicContext())
return false;
+ // Tokens reference resolvedResult's string backing; keep it alive until CSSVariableData re-captures.
tokens.appendVector(resolvedResult->tokens());
+ m_intermediateCustomProperties.append(WTF::move(resolvedResult));
return true;
}
@@ substitute
auto substitutedTokens = substituteTokenRange(value.m_data->tokenRange(), context);
if (!substitutedTokens) {
m_intermediateTokenStrings.clear();
+ m_intermediateCustomProperties.clear();
return nullptr;
}
auto data = CSSVariableData::create(*substitutedTokens, m_isAttrTainted ? IsAttrTainted::Yes : IsAttrTainted::No, context);
m_intermediateTokenStrings.clear();
+ m_intermediateCustomProperties.clear();
return data;
Source/WebCore/style/StyleSubstitutionResolver.h
Vector<String> m_intermediateTokenStrings;
+ Vector<RefPtr<const CustomProperty>> m_intermediateCustomProperties;
LayoutTests/fast/css/variables/dashed-function-result-token-lifetime.html
+@function --f(--a) {
+ result: aaaa var(--a);
+}
+#target { --x: --f(bbbb); }
+var v = getComputedStyle(target).getPropertyValue('--x');
Patch Details
The patch introduces a new member Vector<RefPtr<const CustomProperty>> m_intermediateCustomProperties on SubstitutionResolver. In substituteDashedFunction, immediately after tokens.appendVector(resolvedResult->tokens()), the local RefPtr is moved into the new vector. In substitute, the new vector is cleared on both the failure and success paths, paired with the existing m_intermediateTokenStrings.clear(). No existing logic is restructured — the change is purely the addition of a parallel lifetime anchor.
Non-owning token views outliving their backing object due to a missing lifetime anchor across substitution stages.
Background
CSS Mixins introduces @function, a CSS-level function definition (@function --name(--arg) { result: <tokens>; }) that can be invoked as a dashed-function reference inside a custom property value. Style::SubstitutionResolver resolves these references during style computation by recursively expanding nested var() / attr() / dashed-function calls into a single token vector, then constructing a CSSVariableData (via CSSVariableData::create) from that vector. CSSVariableData is the storage object for an unparsed CSS value, and CSSParserToken is a value-type token that stores non-owning views into externally owned backing strings — typically StringImpl payloads owned by an upstream CustomProperty or by parser-allocated buffers. Style::CustomProperty is a reference-counted holder of a parsed/tokenized custom-property value; calling CustomProperty::tokens() returns its tokens, but those tokens reference string storage owned by the CustomProperty itself.
The pre-existing m_intermediateTokenStrings field already exists as a lifetime anchor for transient strings produced during attr() tokenization, exactly to keep token backing alive until the final CSSVariableData::create re-captures the data.
Analysis
Before the fix, substituteDashedFunction called tokens.appendVector(resolvedResult->tokens()) and then returned, dropping the local RefPtr<const CustomProperty> resolvedResult at end-of-scope. Once resolvedResult was destroyed, the backing StringImpls it kept alive could be freed even though the appended token views (now in the resolver's growing tokens buffer) still pointed at that storage. The dangling tokens were subsequently consumed by CSSVariableData::create(*substitutedTokens, ...) in substitute(), which re-captures token data — by that time the original backing was already destroyed, producing the ASAN heap-use-after-free reported in the bug.
This is the classic "tokens view externally owned strings" lifetime hazard, and SubstitutionResolver already had a working solution for it on the attr() path. When CSS Mixins / dashed-function support landed, the same hazard recurred for CustomProperty-owned token backings but the corresponding lifetime anchor was not added — variant analysis from the prior attr() lifetime fix lands the missing anchor mechanically.
Aaa Aaaa Aaaa Aa Aaa Aaaaaaa Aaaaaaaa Aaaaaa Aaaaaaaaaa Aaaaaaaa a Aaaaaaa Aaaa Aaaaaaaaa Aaa Aaaaa Aaaaa Aaaaaaaaaa Aa Aa Aaaaaaaa Aaa Aaaaa Aaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaa Aaaa Aaaa Aaaaaaaa Aaa Aaaaaaaa Aaaa Aa a Aaaaaaaaaaaaaaaaa Aaaaaaa Aaa Aaaaaa Aaaa Aaa Aaaaa Aaaaaaaa Aaaaaaa Aaaaaaaa Aaa Aaaa Aaaaaaaa Aa Aaa Aaaaa Aaaa Aaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaa a Aaaaa Aaaaaaaa Aaa Aaaaaaaaa Aaaaa Aaaaaa
Aa Aaaaaaaaa Aaaaaa Aa Aaaa Aaaaaa Aa Aaaaaaaa Aaaaa Aaaaaaa Aa Aaaaaaa Aaa Aaaaa Aaaaaaaaaaaa Aaaa Aaaaaaa Aaa Aaaa Aa Aaaaaaaaaaaaaaaa Aaa Aaa Aaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aaaaaaaaaa Aaa Aaaaaa Aaaaaaaaaaa Aaaaaa Aaa Aaaa Aaaaaaaaaaaa Aaaaa a Aaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaa Aaaaa a Aaaaa Aaaaa Aaa Aaaaa Aaaaaa Aa Aa Aaaaaa Aaaa Aaaaaaaaaaaaaaaaaaa Aaaaaa Aa Aaaaa Aaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaa Aaaaa Aa Aa Aaaa Aaaa Aaa Aaaaaaaa Aaaaa Aaaaaaaa Aaaaaaaa a Aaaaaaaaaaaaaaaaaa Aaaa Aa Aaa Aaaaaaaaaaa Aaaaaaaaaa Aaaaa Aaaa Aaaaaaaaa Aa Aaa Aaaaaaaaaa Aaaaaaa Aa Aaa Aaaaaaaaa Aaaaaaaaaaaaaaaaa Aaaaaaaaa Aaaa Aaa Aaaaaaaaaa Aaaaaa a Aaaaaaaaaaaaaa Aaaaa Aaaaaaaaa Aaaa Aaa Aaaaaaaa Aaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaa Aaaaaa Aaa Aaaaaaaaaa Aaaaaaaa Aaaaaaaa Aaa Aaaaaaaa Aaaaaaaaa Aaaa Aaaaaaaaaaaaaaaa Aaaaaaaa Aaaaaa Aaaaa Aaa Aaa Aaaaaaaa Aa Aaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaa a Aaaaaa Aaaaaaaaaaa Aaaaaaaa a Aaaaaa Aaaa Aaa Aaaa Aaaaaaaa Aa Aa Aaaaa Aaaaaaaaaaaaa Aa Aaaaaaaa Aaa Aaa Aaaaa Aaa Aaaaaaaaaa a Aaaaaaaaaaa Aaaaaaaaaa Aaa a Aaaaaaaa Aaaaa Aa Aaa Aaaaaaa Aaa Aaaaaaaaaaaa Aaaa Aa Aaaaa Aaaaa Aaaaaaaaaa Aa Aaa Aaaaaaaa Aaaaaaa
🔒The lifetime model behind CSS variable token resolution and what happens when a substitution stage drops its backing object too early.
Subscribe to read more
Audit directions
a Aaaaaaaaaaaa Aaaaaaaaaa Aaaaa Aaaaaaaa a Aaaaaaaaaaa Aaaaaaaaaaaa Aaaaaaaa Aaaaaaaaa Aaaaa Aaaaaaa Aaaaaaaa Aaaaa Aaa Aaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaaa Aaaa Aaa Aaaaaaaaaaaaaa Aaaaaaaa Aaaaaa a Aaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaaa Aaaaaaaaaaaa Aaaaaa a Aaa Aaaaaa Aaaa Aaaa Aaaaa Aaaaa Aaaaaa Aaa Aaaaaaaa Aa Aaaaaaaa Aaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaa Aaaaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaa Aaaaa Aaaaaa Aaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa
a Aaaaaaaaaaaaaaaaaa Aaaaaaaa Aaaaaaa Aaaa a Aaaaaa Aaaa Aaaaaaaa Aaa Aaaaaa Aaaaaaaaa Aaaaaaaa Aaaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaa Aaaa a Aaaaaaaaaaaa Aaaaaa Aa a Aaaaaaaaa Aaaa Aaaa Aaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaa Aaaaaaaaaaaaa Aaaa Aaaa Aaaa a Aaaaaaaaaa Aaaa Aaaaaa Aaaa Aaaa Aaaa Aaaaa Aaa Aaaaa Aaaaa Aaaaaa Aaaaaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaaaaa
a Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aa a Aaaaaaaaaa Aaaaaaaaaaa Aaaaaa Aaa Aaaaaaaa Aa Aa Aaaa Aaaaa Aa Aaa Aaaaaaaa Aaaaaa Aaaaa Aaaaa Aaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaa Aaaa a Aaaaaaaaaaaa Aa Aaaaaaaaaaaaaaa Aaaaaaaaaaa Aaaaaaaaaaa Aaaaaaaaaaaaaa Aaa Aaaaaaaaa Aaaaaaaaa a Aaa Aaa Aaaa Aaaaaaa Aaaaa Aaaa a Aaaaaaaaa Aaaaa Aaaa Aaa Aaaaaaa Aaaa Aaa Aa Aaaaaa Aaaaaa Aaaaaaaa Aaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaa Aaaa Aaaaa Aaa Aaa Aaaaaa Aaaa Aaaaaaaaaa Aaaaaaaaaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aa Aa Aaaaa Aaaaaaaa Aaaaa
a Aaaaaaaaaaaaaaaaa Aaaaaa Aaaaaa Aaaaaaa Aaaaaaaaaaaa Aaaa a Aaaaaaaaa Aaaaaaa Aaaa Aa Aaaaaaaa Aaaaaaaaaaaaaaa Aaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaaaaaaaaaa Aaaaa Aaaa Aaaa Aaaa Aaaaaaaa Aaaaaa Aaaa Aaa a Aaaaaaaa Aaaaaaa Aaaaa Aaaaaa Aaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaa Aaaaaa a Aaaaaaaaaaa Aaaaaaaaaaaaaa Aaa Aaaaa Aaaaa Aaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaa a Aaa Aaaaaaa Aaaa Aaa a Aaaaaaaa Aaaaaaa
a Aaaaaaaaa a Aaa Aaaaaaa Aaaaaaaaaaaaaaaaaaa Aaaa Aaaaaaaaaa Aaa Aaaaaaaaaaa Aaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaaaaaaa Aaa Aaa Aaaaaaaaa Aaaaaaaaaa Aaaaa Aaa Aaaaaaaa Aaaa Aaa Aaaaa Aaaaa Aaa Aaaaaaaaaaa Aaaa Aaaaaa Aaaa Aaaaaaaa Aaaaaaa Aa Aaa Aaaaa Aaa Aaaa Aaa Aaaaaaaaa Aaa Aaa Aaaaa Aaaa Aa a Aaaaaaaa Aaaaaaaa Aaaaaa Aaa Aaaaa Aaaaaaaaa Aa Aaa Aaaaaa
🔒Several reusable lifetime-anchor audit patterns identified across the CSS substitution pipeline, with concrete starting points for variant discovery.
Subscribe to read more