CVE-2024-44244
Overview
Background
- YARR
- Yet Another Regex Runtime — JavaScriptCore’s regular-expression engine that compiles JS RegExp patterns into an internal representation and bytecode/JIT.
- Character class construction
- The phase that builds the set of code points/ranges a bracket expression […] matches, including coalescing overlapping ranges and applying set operations.
- RegExp 'v' flag (unicodeSets)
- An ES2024 flag enabling set notation and string members (\q{…}, intersection/subtraction) in character classes, which introduces multi-code-point ‘strings’ matches.
- Case-folding under the 'i' flag
- Case-insensitive matching that expands each member to its case-equivalent code points, which can be appended in non-monotonic numeric order.
- Sorted-merge precondition
- The requirement that set/union/intersection routines receive their inputs pre-sorted so their single-pass index-advancing merge produces correct results.
Root Cause Analysis
The patch is in JavaScriptCore’s YARR regex engine, in CharacterClassConstructor, which builds the internal representation of a regular-expression character class. The change adds std::sort calls in two places: after collecting asciiMatches/unicodeMatches, and after collecting matches/matchesUnicode (alongside utf32Strings), immediately before performOp() and performSetOpWithMatches()/performSetOpWithStrings() are invoked. The added stress test exercises character classes using the ES2024 RegExp ‘v’ flag with ‘strings’ syntax (\q{…} with alternations like \q{\u{0095}|k}) under the case-insensitive ‘i’ flag. The violated invariant is that the set-operation and range-coalescing routines (performOp / performSetOpWithMatches) assume their input match/range vectors are in sorted order: these algorithms merge, intersect, subtract, and coalesce ranges by walking the vectors in monotonically increasing order and comparing/advancing indices. When the ‘v’ flag with case-folding (i flag) produces individual match code points, folding a character such as U+0095 can append case-equivalent code points out of numeric order relative to previously appended ASCII characters (e.g. ‘k’/’s’), leaving asciiMatches/unicodeMatches (and the matches/matchesUnicode vectors) unsorted. Feeding an unsorted vector into a routine that assumes sorted order breaks the merge invariant, producing incorrect index advancement and out-of-order comparisons.
The fix restores the invariant by explicitly sorting all four vectors before the set/coalesce operations run, guaranteeing the monotonic-order precondition. Note: the exact failure inside performOp/performSetOpWithMatches is not shown in this diff (those bodies are not included), so the precise mechanism by which unsorted input leads to the observed crash is an inference from the nature of sorted-merge algorithms and the fact that the fix is purely ‘sort the inputs first.’ What the commit establishes concretely is that the inputs were not guaranteed sorted and that the set/coalesce operations require sorted input.
Attack Path
- Deliver a crafted RegExp to the JS engine Attacker-controlled web content constructs and compiles a regular expression using the ‘v’ flag with the ‘strings’ character-class syntax (\q{…}) combined with the ‘i’ (ignore-case) flag, e.g. /[\q{\u{0095}|k}]/vi.
- Trigger case-folding that emits out-of-order matches During compilation, case-insensitive folding of the string/alternation members appends case-equivalent code points (from U+0095 and from ASCII ‘k’/’s’) into the match/unicode vectors in an order that is not numerically sorted.
- Enter the set/coalesce operation with unsorted input CharacterClassConstructor calls performOp()/performSetOpWithMatches() on the unsorted asciiMatches/unicodeMatches (and matches/matchesUnicode) vectors, violating the routine’s sorted-order precondition.
- Corrupt index/range traversal The merge-style algorithm advances indices and compares ranges assuming monotonic order; with unsorted data it mis-tracks positions, leading to an inconsistent internal state during character-class construction (inferred to reach an out-of-bounds access or invalid range).
- Crash the WebContent process The corrupted traversal results in an unexpected process crash (the CVE’s stated impact) when the malformed character class is compiled.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
CharacterClassConstructor (ASCII/unicode match handler, ~line 234)Source/JavaScriptCore/yarr/YarrPattern.cpp |
modified | Added std::sort of asciiMatches and unicodeMatches before performOp(), ensuring the coalesce/set operation receives inputs in the required sorted order. |
CharacterClassConstructor (strings/set-op handler, ~line 362)Source/JavaScriptCore/yarr/YarrPattern.cpp |
modified | Added std::sort of matches and matchesUnicode before performSetOpWithStrings()/performSetOpWithMatches(), restoring the sorted-input precondition for the 'v'-flag string set operations. |
Audit Directions
- Other match/range vectors feeding YARR set operationsIn YarrPattern.cpp, audit every vector consumed by performOp, performSetOpWithMatches, performSetOpWithStrings, and range-coalescing helpers to confirm each is sorted before use; grep ‘performSetOp’, ‘performOp’, ‘matchesUnicode’, ‘asciiMatches’, ‘coalesce’.
- Sorted-order assumptions in unicodeSets/'v'-flag handlingReview all \q{…} string-member and case-folding paths for reliance on numeric ordering; grep ‘utf32Strings’, ‘ignoreCase’, ‘foldCase’, ‘CharacterClassStringSet’, and any binary-search or merge over match arrays.
- Merge/dedup algorithms that assume pre-sorted input across JSCLook for single-pass merge or std::unique-style logic that does not sort first in YARR and adjacent JSC components; grep for ‘std::unique’, ’lower_bound’, ‘binarySearch’, and index-advancing loops (i++/j++) over character-range vectors.