[2] YARR JIT ParenContext Use-After-Free via Incomplete Stale-Pointer Clearing
Severity: High | Component: JSC YARR JIT | 2d16551
Rated High because the observable effect is a use-after-free on free-list-recycled ParenContext objects in JIT-compiled regex code reachable from web content, and the bug title explicitly claims instruction pointer control — escalation to code execution within the renderer sandbox is projected with confidence 0.92 from the incomplete-clearing mechanism shown in the diff.
Patch Details
The patch replaces clearInnerParenContextHeadSlots(term->parentheses.disjunction) with clearParenContextHeadSlotsInRange(m_pattern.m_body, parenthesesFrameLocation + YarrStackSpaceForBackTrackInfoParentheses, m_parenContextSizes.frameSlots()) at two call sites in YarrJIT.cpp — the FixedCount backtrack path and the Greedy/NonGreedy backtrack path. The new function walks the entire pattern tree (m_pattern.m_body) instead of only the current group's inner disjunction, and applies a frame-slot range filter (headSlot >= minFrameLocation && headSlot < maxFrameLocation) to null out every parenContextHead pointer whose frame slot falls within the restored range. The old function only cleared inner groups of the current term, missing sibling and ancestor-sibling groups.
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');
+ // +? expands to {1,1} + *?(isCopy=true). The *? copy uses ParenContext
+ // and is a sibling of (c|b)*?. Both bugs trigger: isCopy skip + sibling
+ // scope mismatch.
Incomplete invalidation of stale pointers after a global state restore — clearing scope narrower than restore scope leaves dangling references to freed free-list objects.
Background
YARR is WebKit's regex engine; when a pattern is hot, it JIT-compiles to native code. ParenContext is a per-group backtracking state object managed via a singly-linked free list — freed contexts are recycled for new allocations within the same match. restoreParenContext is a JIT-emitted operation that bulk-restores a range of frame slots (the JIT's stack-like storage for backtracking state) from a saved ParenContext, reinstating pointer values that were valid at save time. parenContextHead is the frame slot storing the head of a group's ParenContext chain — it points to the most recently allocated ParenContext for that group.
Non-greedy quantifiers (*?, +?) use ParenContext for backtracking state. Critically, +? internally expands to {n,n} (a FixedCount group) plus *?(isCopy=true), creating sibling groups in the pattern tree that share the parent's frame slot range. clearInnerParenContextHeadSlots (the incomplete previous fix) walked only the current term's inner disjunction subtree to null restored pointers — it was introduced by 310115@main to address the same class of bug but scoped too narrowly.
Analysis
The previous fix introduced clearInnerParenContextHeadSlots to null stale parenContextHead pointers after restoreParenContext. The critical mismatch: restoreParenContext restores ALL frame slots in a global range ([parenthesesFrameLocation+4, m_parenContextSizes.frameSlots())), which includes frame slots for sibling and ancestor-sibling groups — not just the current group's inner children. Because the clearing function's tree walk was rooted at the current term's inner disjunction rather than the full pattern body, sibling and ancestor-sibling groups with parenContextHead pointers in the restored range were never nulled.
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)
After restoration, stale parenContextHead pointers reference ParenContext objects that were freed and recycled via the free list during a later iteration. When the JIT-compiled code dereferences these stale pointers — for instance, to traverse the ParenContext chain during subsequent backtracking — it accesses memory that now belongs to a different, recycled ParenContext.
This was discovered through variant analysis of the previous incomplete fix. The researcher identified that restoreParenContext operates on a global frame range while the clearing was scoped to a local subtree, then crafted the PoC pattern ((c|b)*?(y|x)+?.){3}mp to specifically target the isCopy expansion of +? that creates sibling groups triggering the incomplete clearing.
Aaa Aaaa Aaaa Aaaaaaaaa Aaa Aaaaa Aaaaaaaa Aaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aaaaaaa Aaaaaaaaaa Aaaaaa Aaaaa Aaaaaaaaaaaaaaaaaa Aaaaa Aaaaa Aaaa Aa Aaa Aaaaaa Aaaaaaa Aaaaaa Aaaaaaa Aaa Aaaaa Aa a Aaa Aaaa Aaaaaaaa Aaa Aaaaaaaaaaaa Aaaaa Aaaa Aaaaaa Aaaaaaaaaaaa Aaaaaaa Aaa Aaaaaaaaaa Aa Aaaaaaaaaaaaaaaa Aaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaa Aaaaaaaaa Aaa Aaaaa Aaaaaaaaaaaaaa Aaaaaa Aaa Aaaa Aaaaaaaa Aaa Aaa Aaaa Aaaa a a Aaaaaaaaaa Aaaaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaa Aaaa Aaaa Aaa Aaaaaaaa Aaaaa
Aaa Aaaaaaaaa Aaaaaaaaa Aa Aaaaaaaaaaaaaa Aaaaaaa Aaaaaaaa Aaaaaaaa Aaaaaaaaa Aaaa Aaa Aaaaa Aaaa Aaaaaaaaa Aa Aaaaaaaa Aaa Aaaaaaaa Aaa Aaaaa Aaaaaaa Aaa Aaaaa Aaaaaa Aaa Aaaaaaaaa Aaa Aaaa Aaaaaaaa Aaa Aaaaaaaaa Aaa Aaaaa Aaaaaaa Aaa Aaaa Aaaaa Aaa Aaa Aaaaa Aaaaaaaaaaaa Aaaaaaaaaaa Aaaaa Aaaaaaaa Aaaaaaa Aaaaaaaa Aaa Aaaaa Aaaaa Aaa Aaa Aaaaa Aaaaaaaaaa Aaaaaa Aaaaaaaa Aa Aaa Aaaaaaaaaaa Aaaaaaaa a Aaaaaa Aaa Aaaaaaaaaaaaaa Aaaaaaaaa Aaaaaaaa Aa Aaaaa Aa Aaaaaa Aa Aaaaaaa a Aaaaa Aaaa Aaaaaaaaaaaaa Aaaa Aaaaaaaaaa Aa a Aaaaaa Aaaaaa Aaa Aaa Aaa Aaaaa Aaaaaa Aaa Aaaaa Aa Aaa Aaaaaaaa Aaaaaaaaaa Aaaa Aaa Aaaaaa Aaaa Aaaaa Aaaa Aaaa Aaaaaaaaa Aaaaaa Aaa Aaaaaaaa Aaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaa Aa Aaa Aaaaaaaa Aaaaaaaa Aaa Aaaa Aaa Aaaaa Aaaaaa Aa Aaaaaaaaa Aaaa Aaa Aaaaaaaaaa Aaaaaaaa Aaaaaaaaaa Aaaaaa Aaa Aaaa a Aaaaaaa Aaaaa Aaaaaaa Aaaa Aaaaaaaaaa Aaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaaa Aaaaa Aaaaa Aaaaaaaaaaaa Aaaaa Aaaa Aa Aaaaaaaaaaa a Aaaaa Aaa Aaaaaaaaaaa Aaaaaaaa Aaaaaaaaaaaaaa Aaaaaaa
Aaaa Aa a Aaaaaaa Aa Aaa Aaaaaa Aaaaaaaaaa Aaa Aa Aaaaaaaaaaa a Aaa Aaaaaaaa Aaa Aaaaaaaaa Aaa Aaaaa Aaaaa Aa Aaa Aaaaaa Aaaaaaaa Aaaaa Aaaaaaa Aaaaaaaa Aaa Aaaaaa Aaa Aaaaaaaaaa Aaa Aaaaaaaaa Aaa Aaaaaaaaaaa Aaaaa Aa Aaaa Aaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aa a Aaaaaa Aaaaa Aaaaa Aaaaa Aaaaaaaaaaaa Aaa Aaaaaa Aa a Aaaaa Aaaaaaaa Aaaa Aaaaaaaa Aaaaaaa Aaaaaaa Aaaaa Aaa Aaaaaaaa Aaaaa Aa a Aaaaaaa Aaaaa Aaaaaaaaa Aaa Aaaaaa Aaaaaa Aa Aaaaa Aaaa Aaaaaa Aa Aaaaaaaaaaaa Aaaaaaaaa Aaaa Aaaaaa Aaaaa Aaaaaa Aaaaaa Aaaaaaaa
Aaaaaaaaa Aaaa Aaaaaaa Aaa Aaaaaaaa Aaaa Aaa Aaaa Aaaa Aaa Aaaaaaaaaaa Aaaa Aaaaaaaa a Aaaaaaaaaaaaa Aaaaaaa Aaa Aaaaaaaaaaaaaa Aaaaaaaaa Aaaaaaaa Aaaaaa Aaaaaaaaaaaaa Aa Aaaaaa Aaaaaaa Aaa Aaa Aaa Aaaaa Aaaaaaaa Aaa Aaaa Aaaaaaaaa Aaaa Aaa Aaa Aaaaaaaaaaaaaaaaaaa Aaaaaaaaa Aaa Aaaaaaaaaaaa Aaaaaaaaa Aa Aaa Aaaaa Aaa Aaaa
🔒The ownership and lifetime implications of this free-list recycling bug are analyzed in depth, including escalation potential beyond the immediate crash
Subscribe to read more
Audit directions
a Aaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaa Aaaaaaaaaaa Aaa Aaaaaaaaa Aaaa Aaaaaaaaaaaaa Aaaaa Aaaa a Aaaaa Aaaa Aaaaaaaaaa Aaa Aaaaaaaa Aa Aaaa Aaaa Aaaaaa Aaa a Aaaaaaa Aaaaa Aaaaa Aaaa Aaa Aaaaaaaaa Aaaaa Aaaa Aaaa Aaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaa Aaaaaaaaaaaa Aaaaaaaaaaa Aaaaa Aaaaaaa Aaa Aaa Aaaaaaaaaa Aaaaa Aa Aaaaaaa Aaaaaaaaaa Aaaaa Aaaaaaaaa Aaaaaaaaaaaaaaaaaa Aaaaaa Aaaaa Aaaaa Aaaaa Aaaa Aaaaaaa Aaa Aaaaaaa Aaaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaa Aa Aaaaaaaaaaaaaa
a Aaaaaaaaaaa Aaa Aaa Aaaaaaaaaa Aaaaaaaa Aaaaa Aaaaa Aaaaaaaaaaa Aaa Aaaaaa Aaaa Aaa Aaaaa Aaaaaaaa Aa Aaaaaaaaaaaaaaaaa Aaaaaaaa Aaa Aaaaaaaaaaaaaa Aaaaa Aaaaaaa Aaaaaa Aaaaaaaaaa Aaa Aaa Aaaaaaaa Aaa Aaaaa Aaaaaaaa Aaaaaa Aaaaaaaaaaaa Aa Aaaaaaaaaaaa Aaaaa Aaaaaa Aaaaaaaaaaaaaa Aaaaaaaaa Aaaaaaaaaa a Aaaaaaaaaaaa Aaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaa a Aa Aaaaaa Aa Aaaaa Aaaaa Aaaaa Aaaaa Aaaaaaaa Aaaaa Aaaaaaaaa Aaaaaaaaaa Aaaa Aaaaa Aaaaaaa Aaa Aaaaaaaa Aaaaaaaaa Aa Aaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaa Aaaaa Aaa Aaaaaaa Aaaaaa Aaaaaa Aaaaaaaa Aaa Aaaaa Aaaaa Aa Aaa Aaaaa Aaaaaa Aaa Aaaaaa Aaaa Aaaaaaaaa Aaaaa Aaaaaa Aaa Aaaaaaaaa Aaaaaa
a Aaaaaaaaaaaaaa Aaaaa Aa Aaaaaa Aaaaaaa Aaa Aaaa Aaaaaaaaaaaaa Aaaa Aaa Aa Aaaaaaaaa Aaaa Aaa Aaa Aaaa Aaa Aaaaaaa Aaaaa Aaaaa Aaaa Aaa Aaaaaaaaaaaa Aaaaa Aaa Aaaaaaa Aaaaaaaaaaaaaaaa Aaaaaa a Aaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaa Aaaaa Aa Aaaaa Aaaaa Aaaaaaaaaaaa Aaaaaa Aaa Aaaaaaaaaaa Aaaaaa Aaaaaaaaaaaa Aaa Aaaaaa Aaaa Aaa Aaaaa Aaaaaaaa Aaaaaa Aaaaaa
🔒Multiple reusable audit patterns identified, with concrete starting points for variant discovery across YARR JIT backtracking paths
Subscribe to read more