← All issues

[2] YARR JIT ParenContext Use-After-Free via Incomplete Stale-Pointer Clearing

Severity: High | Component: JSC YARR JIT | 2d16551

이 항목을 High로 평가한 근거는 다음과 같습니다. Web content에서 도달 가능한 JIT 컴파일된 regex 코드 내에서, free list로 재활용된 ParenContext 객체에 대한 use-after-free가 실제로 관찰됩니다. 또한 bug 제목에서 instruction pointer 제어 가능성을 명시적으로 언급합니다. diff에 드러난 incomplete-clearing 메커니즘을 근거로, renderer sandbox 내 code execution으로의 확장 가능성은 confidence 0.92로 전망됩니다.

패치는 YarrJIT.cpp의 두 호출 지점에서 함수를 교체했습니다. FixedCount backtrack 경로와 Greedy/NonGreedy backtrack 경로가 대상입니다. 기존의 clearInnerParenContextHeadSlots(term->parentheses.disjunction) 대신 clearParenContextHeadSlotsInRange(m_pattern.m_body, parenthesesFrameLocation + YarrStackSpaceForBackTrackInfoParentheses, m_parenContextSizes.frameSlots())를 호출하도록 변경되었습니다. 새 함수는 현재 group의 inner disjunction만 순회하는 대신 전체 pattern 트리(m_pattern.m_body)를 순회합니다. 그리고 frame slot 범위 필터(headSlot >= minFrameLocation && headSlot < maxFrameLocation)를 적용해 복원 범위 안에 속하는 모든 parenContextHead 포인터를 null로 처리합니다. 기존 함수는 현재 term의 inner group만 처리해 sibling 및 ancestor-sibling group을 놓쳤습니다.

Source/JavaScriptCore/yarr/YarrJIT.cpp

- clearInnerParenContextHeadSlots(term->parentheses.disjunction);
+ clearParenContextHeadSlotsInRange(m_pattern.m_body, parenthesesFrameLocation + YarrStackSpaceForBackTrackInfoParentheses, m_parenContextSizes.frameSlots());

Source/JavaScriptCore/yarr/YarrJIT.cpp

- void clearInnerParenContextHeadSlots(PatternDisjunction* disjunction)
+ void clearParenContextHeadSlotsInRange(PatternDisjunction* disjunction, unsigned minFrameLocation, unsigned maxFrameLocation)
{
for (auto& alternative : disjunction->m_alternatives) {
for (auto& term : alternative->m_terms) {
- if (term.type == PatternTerm::Type::ParenthesesSubpattern || term.type == PatternTerm::Type::ParentheticalAssertion) {
- if (term.type == PatternTerm::Type::ParenthesesSubpattern
- && term.quantityType != QuantifierType::FixedCount
- && term.quantityMaxCount != 1
- && !term.parentheses.isTerminal
- && !term.parentheses.isCopy)
- storeToFrame(MacroAssembler::TrustedImmPtr(nullptr), term.frameLocation + BackTrackInfoParentheses::parenContextHeadIndex());
-
- clearInnerParenContextHeadSlots(term.parentheses.disjunction);
+ if (term.type != PatternTerm::Type::ParenthesesSubpattern && term.type != PatternTerm::Type::ParentheticalAssertion)
+ continue;
+ if (term.type == PatternTerm::Type::ParenthesesSubpattern
+ && term.quantityType != QuantifierType::FixedCount
+ && term.quantityMaxCount != 1
+ && !term.parentheses.isTerminal
+ && !term.parentheses.isCopy) {
+ unsigned headSlot = term.frameLocation + BackTrackInfoParentheses::parenContextHeadIndex();
+ if (headSlot >= minFrameLocation && headSlot < maxFrameLocation)
+ storeToFrame(MacroAssembler::TrustedImmPtr(nullptr), headSlot);
}
+ clearParenContextHeadSlotsInRange(term.parentheses.disjunction, minFrameLocation, maxFrameLocation);
}
}
}

JSTests/stress/yarr-jit-paren-context-head-uaf.js

+ var re = new RegExp('((c|b)*?(y|x)+?.){3}mp');
+ // +?는 {1,1} + *?(isCopy=true)로 확장됩니다. *? copy는 ParenContext를 사용하며
+ // (c|b)*?의 sibling입니다. 두 가지 버그가 모두 유발됩니다: isCopy skip + sibling
+ // scope 불일치.

전역 상태 복원 이후 stale pointer의 불완전한 무효화 — 복원 범위보다 좁은 clearing 범위로 인해 해제된 free-list 객체를 가리키는 dangling reference 잔존.

YARR는 WebKit의 regex 엔진으로, pattern이 자주 실행되면 native code로 JIT 컴파일됩니다. ParenContext는 group 단위의 backtracking 상태 객체로, singly-linked free list로 관리됩니다. 해제된 context는 같은 match 내 새로운 allocation에 재활용됩니다. restoreParenContext는 JIT가 생성하는 연산으로, 저장된 ParenContext로부터 일정 범위의 frame slot을 일괄 복원해 저장 시점에 유효했던 포인터 값을 되살립니다. parenContextHead는 group의 ParenContext 체인 head를 저장하는 frame slot으로, 해당 group에서 가장 최근에 할당된 ParenContext를 가리킵니다.

Non-greedy quantifier(*?, +?)는 backtracking 상태 저장에 ParenContext를 사용합니다. 특히 중요한 점은 +?가 내부적으로 {n,n}(FixedCount group)과 *?(isCopy=true)로 확장된다는 것입니다. 이 과정에서 pattern 트리에 sibling group이 생성되며, 이들은 부모의 frame slot 범위를 공유합니다. 기존의 불완전한 fix인 clearInnerParenContextHeadSlots는 현재 term의 inner disjunction 서브트리만 순회해 복원된 포인터를 null 처리했습니다. 이 함수는 310115@main에서 동일 계열 버그를 해결하기 위해 도입되었으나, 처리 범위가 너무 좁게 설정된 문제가 있었습니다.

이전 fix는 restoreParenContext 이후 stale parenContextHead 포인터를 null 처리하기 위해 clearInnerParenContextHeadSlots를 도입했습니다. 핵심적인 불일치는 다음과 같습니다. restoreParenContext는 전역 범위([parenthesesFrameLocation+4, m_parenContextSizes.frameSlots()))의 모든 frame slot을 복원합니다. 이 범위에는 현재 group의 inner child뿐 아니라 sibling 및 ancestor-sibling group의 frame slot도 포함됩니다. 그런데 clearing 함수의 트리 순회는 전체 pattern body가 아닌 현재 term의 inner disjunction을 루트로 삼았습니다. 이로 인해 복원 범위 안에 parenContextHead 포인터를 가진 sibling 및 ancestor-sibling group이 null 처리에서 누락되었습니다.

  Pattern tree:  ((c|b)*?(y|x)+?.){3}mp
                       │
                  outer group {3}
                   ├── (c|b)*?        ─ parenContextHead at frameSlot A
                   ├── (y|x)+?        ─ expands to:
                   │    ├── {1,1}       (FixedCount)
                   │    └── *?(isCopy)  ─ parenContextHead at frameSlot B
                   └── .

  restoreParenContext range: [frameSlot_base+4 .. m_parenContextSizes.frameSlots())
                             covers slots A and B (global range)

  Old clearing (scoped to current term's inner disjunction):
    backtrack (c|b)*? → clear inner of (c|b)*? → misses slot B (sibling)
    backtrack {1,1}   → clear inner of {1,1}   → misses slot A (sibling)

  New clearing (scoped to m_pattern.m_body with range filter):
    walks entire tree, nulls every parenContextHead in [min, max)

복원 이후, stale parenContextHead 포인터는 이후 반복 과정에서 free list를 통해 해제되고 재활용된 ParenContext 객체를 가리키는 상태가 됩니다. JIT 컴파일된 코드가 이 stale 포인터를 역참조하면 — 예를 들어 이후 backtracking 과정에서 ParenContext 체인을 순회할 때 — 이미 다른 재활용된 ParenContext에 속한 메모리에 접근하게 됩니다.

이 버그는 기존 불완전한 fix에 대한 variant analysis를 통해 발견되었습니다. 연구자는 restoreParenContext가 전역 frame 범위를 대상으로 동작하는 반면 clearing은 로컬 서브트리에만 적용된다는 불일치를 파악했습니다. 이후 +?isCopy 확장이 sibling group을 생성하고 이것이 불완전한 clearing을 유발한다는 점을 노린 PoC 패턴 ((c|b)*?(y|x)+?.){3}mp를 설계했습니다.

🔒

The ownership and lifetime implications of this free-list recycling bug are analyzed in depth, including escalation potential beyond the immediate crash

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

🔒

Multiple reusable audit patterns identified, with concrete starting points for variant discovery across YARR JIT backtracking paths

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