Released October 28, 2024

CVE-2024-44244

CVSS 4.3 severity medium CWE-787 vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L

Overview

Impact:

Description: Processing maliciously crafted web content may lead to an unexpected process crash

Researchers: an anonymous researcher, Q1IQ (@q1iqF) and P1umer (@p1umer)

Key insight. Two set/coalesce routines in YARR character-class construction implicitly required sorted match vectors, but ‘v’-flag string members with case-folding could append code points out of order, so the missing sort broke a merge invariant and led to a crash — an unenforced ordering precondition on data an attacker can shape.

Patch Details

diff --git a/JSTests/stress/regexp-vflag-property-of-strings.js b/JSTests/stress/regexp-vflag-property-of-strings.js
index 8dc09355d962..c263e921a083 100644
--- a/JSTests/stress/regexp-vflag-property-of-strings.js
+++ b/JSTests/stress/regexp-vflag-property-of-strings.js
@@ -573,3 +573,8 @@ testRegExpSyntaxError("\\q{a}", "v", "SyntaxError: Invalid regular expression: i
 testRegExp(/[\&\-\!\#\%\,\:\;\<\=\>\@\`\~]*/v, "&-!#%,:;<=>@`~", ["&-!#%,:;<=>@`~"]);
 testRegExp(/[\q{\&\-\!\#\%\,\:\;\<\=\>\@\`\~}X]*/v, "X&-!#%,:;<=>@`~X", ["X&-!#%,:;<=>@`~X"]);
 testRegExp(/[\q{}]/v, "", [""]);
+
+// Test 294
+testRegExp(/[\q{\u{0095}|k}]/vi, "k", ["k"])
+testRegExp(/[\q{\u{0095}|s}]/vi, "s", ["s"])
+
diff --git a/Source/JavaScriptCore/yarr/YarrPattern.cpp b/Source/JavaScriptCore/yarr/YarrPattern.cpp
index 7f3a0f94502a..9ae4992b9adf 100644
--- a/Source/JavaScriptCore/yarr/YarrPattern.cpp
+++ b/Source/JavaScriptCore/yarr/YarrPattern.cpp
@@ -234,6 +234,8 @@ public:
             }
         }
 
+        std::sort(asciiMatches.begin(), asciiMatches.end());
+        std::sort(unicodeMatches.begin(), unicodeMatches.end());
         performOp();
     }
 
@@ -362,6 +364,9 @@ public:
             utf32Strings.append(string);
         }
 
+        std::sort(matches.begin(), matches.end());
+        std::sort(matchesUnicode.begin(), matchesUnicode.end());
+
         performSetOpWithStrings(utf32Strings);
         performSetOpWithMatches(matches, emptyRanges, matchesUnicode, emptyRanges);
     }

Background

Advisory: Apple Security Advisory

Attribute Value
CVE CVE-2024-44244
Bugzilla 279780
Component WebKit
Bug Class OOB
Severity medium
Commit 85d061be2854ab38…
Advisory Apple Advisory

Terminology

  • 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

1. 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.

2. 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.

3. 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.

4. 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).

5. 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

Per the CVE, the realistic outcome is an unexpected process crash triggered by compiling a malformed ‘v’-flag character class — a denial-of-service-grade primitive rather than a demonstrated read/write. Whether the unsorted-input traversal yields an exploitable out-of-bounds access or merely an assertion/abort is not shown by this diff; the fix (sorting the inputs) is consistent with either a controlled crash or a bounded OOB during range coalescing, so any escalation toward memory disclosure/RCE is speculative and not established by the commit. The bug is confined to the WebContent (or any JSC-hosting) process where the attacker’s script runs; there is no cross-process or sandbox-boundary effect from this flaw itself.

Audit Directions

Other match/range vectors feeding YARR set operations

In 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 handling

Review 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 JSC

Look 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.

Changed Functions

Function File Change Note
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.

Regression Test

The fix ships this proof-of-concept / regression test:

diff --git a/JSTests/stress/regexp-vflag-property-of-strings.js b/JSTests/stress/regexp-vflag-property-of-strings.js
index 8dc09355d962..c263e921a083 100644
--- a/JSTests/stress/regexp-vflag-property-of-strings.js
+++ b/JSTests/stress/regexp-vflag-property-of-strings.js
@@ -573,3 +573,8 @@ testRegExpSyntaxError("\\q{a}", "v", "SyntaxError: Invalid regular expression: i
 testRegExp(/[\&\-\!\#\%\,\:\;\<\=\>\@\`\~]*/v, "&-!#%,:;<=>@`~", ["&-!#%,:;<=>@`~"]);
 testRegExp(/[\q{\&\-\!\#\%\,\:\;\<\=\>\@\`\~}X]*/v, "X&-!#%,:;<=>@`~X", ["X&-!#%,:;<=>@`~X"]);
 testRegExp(/[\q{}]/v, "", [""]);
+
+// Test 294
+testRegExp(/[\q{\u{0095}|k}]/vi, "k", ["k"])
+testRegExp(/[\q{\u{0095}|s}]/vi, "s", ["s"])
+