← All issues

[1] CSS @function dashed-substitution use-after-free

Severity: High | Component: WebCore Style — Style::SubstitutionResolver | 4b97014

CSSVariableData::create가 이미 해제된 CustomProperty의 string backing에서 token 데이터를 재참조하는 heap use-after-free를 수정합니다. 단 한 줄의 @function snippet만으로 web content에서 이 경로에 도달할 수 있습니다. 재참조 전에 heap 재사용이 발생하면, freed-read가 recycled buffer에 대한 controlled-content read primitive로 전환됩니다. 이러한 이유로 Severity를 High로 평가합니다.

substitute()가 새로운 CSSVariableData를 생성할 때까지, resolve된 CustomPropertym_intermediateCustomProperties에 유지하도록 수정되었습니다. attr() tokenizer 출력에 사용되는 기존의 m_intermediateTokenStrings 방식과 동일한 패턴입니다. regression test는 @function --f(--a) { result: aaaa var(--a); }를 정의하고, target에 --x: --f(bbbb)를 적용한 뒤 getComputedStyle(target).getPropertyValue('--x')를 호출하여, substitution 경로가 해제된 token consumer를 통과하도록 유도합니다.

Source/WebCore/style/StyleSubstitutionResolver.cpp

@@ substituteDashedFunction
if (guard.isCyclicContext())
return false;
 
+ // Tokens가 resolvedResult의 string backing을 참조합니다. CSSVariableData가 재참조할 때까지 살아있어야 합니다.
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는 SubstitutionResolver에 새로운 멤버 Vector<RefPtr<const CustomProperty>> m_intermediateCustomProperties를 추가합니다. substituteDashedFunction에서는 tokens.appendVector(resolvedResult->tokens()) 직후, local RefPtr를 새로운 vector로 이동시킵니다. substitute에서는 기존의 m_intermediateTokenStrings.clear()와 쌍을 이루어, 실패 경로와 성공 경로 모두에서 새로운 vector를 비웁니다. 기존 로직의 구조 변경은 없으며, parallel lifetime anchor가 추가된 것이 변경의 전부입니다.

substitution 단계를 가로지르는 lifetime anchor가 누락되어, backing object보다 오래 살아남는 non-owning token view.

CSS Mixins는 @function을 도입하여 CSS 수준의 함수 정의(@function --name(--arg) { result: <tokens>; })를 가능하게 합니다. 이 함수는 custom property 값 안에서 dashed-function reference로 호출됩니다. Style::SubstitutionResolver는 style computation 도중 이러한 참조를 재귀적으로 확장합니다. 중첩된 var() / attr() / dashed-function 호출을 단일 token vector로 펼친 뒤, CSSVariableData::create를 통해 CSSVariableData를 생성합니다.

CSSVariableData는 파싱되지 않은 CSS 값의 storage 객체입니다. CSSParserToken은 외부 소유의 backing string에 대한 non-owning view를 저장하는 value-type token으로, backing string은 보통 upstream CustomProperty나 parser-allocated buffer가 소유하는 StringImpl payload입니다. Style::CustomProperty는 파싱/토크나이즈된 custom-property 값을 담는 reference-counted holder입니다. CustomProperty::tokens()를 호출하면 token이 반환되지만, 해당 token은 CustomProperty 자체가 소유하는 string storage를 참조합니다.

기존에 이미 m_intermediateTokenStrings 필드가 lifetime anchor로 존재했습니다. attr() tokenization 도중 생성되는 일시적인 string의 backing을 유지하여, 최종 CSSVariableData::create가 데이터를 재참조할 때까지 살아있게 하는 역할을 합니다.

패치 이전에는, substituteDashedFunctiontokens.appendVector(resolvedResult->tokens())를 호출한 뒤 반환될 때 local RefPtr<const CustomProperty> resolvedResult가 scope 종료 시점에 drop되었습니다. resolvedResult가 소멸되면, 해당 객체가 유지하던 StringImpl이 해제될 수 있었습니다. 이때 resolver의 tokens buffer에 이미 추가된 token view들은 여전히 그 storage를 가리키는 상태였습니다. 이후 substitute()CSSVariableData::create(*substitutedTokens, ...)가 dangling token을 참조하게 되고, 이 시점에는 이미 original backing이 소멸된 상태이므로 ASAN이 heap-use-after-free를 보고합니다.

이것은 "tokens가 외부 소유의 string을 view한다"는 전형적인 lifetime 문제입니다. SubstitutionResolver에는 attr() 경로에서 이미 동일한 문제의 해법이 적용되어 있었습니다. CSS Mixins / dashed-function 지원이 추가될 때 CustomProperty 소유의 token backing에 대해 동일한 문제가 재발했지만, 대응하는 lifetime anchor가 함께 추가되지 않았습니다. 기존 attr() lifetime fix에서 variant analysis를 수행하면 누락된 anchor를 기계적으로 도출할 수 있습니다.

🔒

The lifetime model behind CSS variable token resolution and what happens when a substitution stage drops its backing object too early.

더 확인하려면 구독해 주세요

🔒

Several reusable lifetime-anchor audit patterns identified across the CSS substitution pipeline, with concrete starting points for variant discovery.

더 확인하려면 구독해 주세요