Medium CVSS 8.8 webkit OOB 🔧 Commit mapped

Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to an unexpected process crash
ComponentJSC
Sub-componentJSC YARR
Bug ClassOOB
Tracker308707
Fix commite5368156542a (WebKit/WebKit) +89/-1
CWECWE-119, CWE-120 (Buffer bounds error, Buffer overflow)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedIdan Masas, DARKNAVY (@DarkNavyOrg), Anonymous working with TrendAI Zero Day Initiative, Daniel Rhea
Disclosed2026-05-11

Background

Offset vector (ovector)
The array Yarr fills with pairs of start/end string offsets, one pair for the whole match and one per capturing subpattern, that the caller reads back after a match.
m_numSubpatterns
The count of capturing subpatterns recorded for a compiled regular expression, historically used to size the offset vector as (count+1)*2.
m_offsetsSize
The authoritative number of offset slots the compiled Yarr bytecode expects, recorded on the bytecode object at compile time.
Duplicate named capture groups
An ECMAScript feature (with UnicodeSets/v mode) allowing the same group name to appear in mutually exclusive alternatives, which can change the offsets layout the compiler emits.
UnicodeSets flag
The Yarr Flags::UnicodeSets (v) mode enabling set-notation and, relevant here, duplicate named capture group semantics.

Root Cause Analysis

RegularExpression::match in Source/JavaScriptCore/yarr/RegularExpression.cpp allocates an offset vector to receive the start/end offsets of every capturing subpattern produced by a Yarr match. Before the patch it sized that vector as (d->m_numSubpatterns + 1) * 2, i.e. it assumed the number of offset slots the compiled bytecode writes is derived purely from the count of subpatterns plus one for the whole match, times two (start and end per group).

The patch replaces this with d->m_regExpByteCode->m_offsetsSize, the authoritative offsets count the bytecode compiler itself recorded. This means the old computation could under-size the offset vector relative to what the Yarr interpreter actually writes.

The added tests all use duplicate named capture groups under UnicodeSets/v flag (e.g. (?<a>x)|(?<a>y)), which strongly indicates the mismatch arises with duplicate named capture groups: when the same name is reused across alternatives, the compiler produces an offsets layout whose size is not simply (m_numSubpatterns + 1) * 2. The violated invariant is that the buffer handed to the Yarr interpreter must be at least as large as the offsets area the compiled bytecode assumes; when it is not, the interpreter writes match offsets past the end of nonReturnedOvector (the 32-element inline Vector<unsigned, 32>) or the returned vector, corrupting adjacent memory or reading/writing out of bounds.

The fix restores the invariant by sizing the offset vector from the single source of truth, m_offsetsSize, so the allocation always matches what the bytecode was compiled to use. (Inference: the exact divergence between (m_numSubpatterns + 1) * 2 and m_offsetsSize for duplicate named groups lives in the Yarr compiler, which is not part of this diff; the patch itself only shows the sizing correction in the consumer.)

Key insight
The offset vector was sized from a heuristic ((subpatterns+1)*2) that no longer matches how the Yarr compiler lays out offsets for duplicate named capture groups; the fix makes the allocation defer to the bytecode’s own recorded m_offsetsSize, the only value guaranteed to match the writer.

Attack Path

  1. Craft a duplicate-named-group regex From attacker-controlled web content (JavaScript), construct a regular expression that reuses the same capture-group name across alternatives under the UnicodeSets/v semantics, e.g. (?<a>x)|(?<a>y), or many such groups to enlarge the offsets area.
  2. Trigger the C++ RegularExpression path Cause WebKit to route the pattern through JSC::Yarr::RegularExpression::match (the WTF/WebCore-facing wrapper used internally), reaching the offset-vector sizing at line 112.
  3. Force offset-vector under-sizing Because the code computes (d->m_numSubpatterns + 1) * 2 rather than the bytecode’s real m_offsetsSize, the allocated vector is smaller than the number of offset slots the compiled bytecode writes for the duplicate-named layout.
  4. Drive a match that populates all offsets Supply an input string that matches, so the Yarr interpreter writes the full set of offset entries, spilling past the end of the under-sized inline/heap offset vector.
  5. Observe memory corruption / crash The out-of-bounds write of match offsets corrupts adjacent stack/heap memory (the inline Vector<unsigned, 32> nonReturnedOvector or its heap backing), producing an unexpected WebContent process crash; escalation beyond a crash is not demonstrated by the commit.

Impact Assessment

The primitive is an out-of-bounds write of Yarr match offsets into an under-allocated unsigned buffer; the number and values written are constrained by the compiled pattern and the matched input, so it is a comparatively weak, structured overflow rather than a freely controlled write. The commit and its Description establish only an unexpected process crash (LogicError, medium severity); no UAF or arbitrary-write primitive toward RCE is demonstrated by the diff. It is confined to the process that runs the JS/Yarr engine, i.e. the sandboxed WebContent process. Realistic escalation to code execution would require a separate, undemonstrated shaping of the adjacent memory and control over the overflowing values.

Files Changed

  • Source/JavaScriptCore/yarr/RegularExpression.cpp
  • Tools/TestWebKitAPI/CMakeLists.txt
  • Tools/TestWebKitAPI/Tests/JavaScriptCore/RegularExpression.cpp

Audit Directions

  • Other ovector sizing in the same file/callers
    Grep RegularExpression.cpp and Yarr callers for m_numSubpatterns, (... + 1) * 2, and offsetVectorSize to find any remaining site that derives offset-vector size heuristically instead of from m_offsetsSize.
  • All consumers of Yarr::interpret / bytecode offsets
    Search for m_offsetsSize, nonReturnedOvector, and Vector<unsigned, 32>-style inline ovectors across Source/JavaScriptCore/yarr and runtime RegExp code to confirm every match entrypoint sizes buffers to the bytecode’s expectation.
  • Duplicate-named-group layout assumptions
    In the Yarr compiler (YarrPattern/YarrInterpreter/YarrJIT), audit how duplicate named capture groups affect subpattern-to-offset mapping; grep for hasNamedCaptures, duplicateNamed, m_numSubpatterns, and offset-slot allocation to ensure count-derived sizing is never reused elsewhere.
diff --git a/Source/JavaScriptCore/yarr/RegularExpression.cpp b/Source/JavaScriptCore/yarr/RegularExpression.cpp
index d3b46b6629b2..b91ec283b272 100644
--- a/Source/JavaScriptCore/yarr/RegularExpression.cpp
+++ b/Source/JavaScriptCore/yarr/RegularExpression.cpp
@@ -109,7 +109,7 @@ int RegularExpression::match(StringView str, unsigned startFrom, int* matchLengt
     if (str.isNull())
         return -1;
 
-    int offsetVectorSize = (d->m_numSubpatterns + 1) * 2;
+    int offsetVectorSize = d->m_regExpByteCode->m_offsetsSize;
     unsigned* offsetVector;
     Vector<unsigned, 32> nonReturnedOvector;
 
diff --git a/Tools/TestWebKitAPI/CMakeLists.txt b/Tools/TestWebKitAPI/CMakeLists.txt
index 8b3174492bf2..7dc60b7a0ce5 100644
--- a/Tools/TestWebKitAPI/CMakeLists.txt
+++ b/Tools/TestWebKitAPI/CMakeLists.txt
@@ -177,6 +177,7 @@ if (ENABLE_JAVASCRIPTCORE)
         Tests/JavaScriptCore/InspectorConsoleMessage.cpp
         Tests/JavaScriptCore/MarkedVector.cpp
         Tests/JavaScriptCore/PropertySlot.cpp
+        Tests/JavaScriptCore/RegularExpression.cpp
     )
 
     set(TestJavaScriptCore_LIBRARIES
diff --git a/Tools/TestWebKitAPI/Tests/JavaScriptCore/RegularExpression.cpp b/Tools/TestWebKitAPI/Tests/JavaScriptCore/RegularExpression.cpp
new file mode 100644
index 000000000000..0dc35539cd9d
--- /dev/null
+++ b/Tools/TestWebKitAPI/Tests/JavaScriptCore/RegularExpression.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2026 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include <JavaScriptCore/InitializeThreading.h>
+#include <JavaScriptCore/RegularExpression.h>
+#include <JavaScriptCore/YarrFlags.h>
+
+namespace TestWebKitAPI {
+
+using JSC::Yarr::RegularExpression;
+
+TEST(JavaScriptCore_RegularExpression, DuplicateNamedCaptureGroupSimple)
+{
+    JSC::initialize();
+
+    RegularExpression re("(?<a>x)|(?<a>y)"_s, { JSC::Yarr::Flags::UnicodeSets });
+    EXPECT_TRUE(re.isValid());
+
+    int matchLength = 0;
+    EXPECT_EQ(0, re.match("x"_s, 0, &matchLength));
+    EXPECT_EQ(1, matchLength);
+
+    EXPECT_EQ(0, re.match("y"_s, 0, &matchLength));
+    EXPECT_EQ(1, matchLength);
+}
+
+TEST(JavaScriptCore_RegularExpression, DuplicateNamedCaptureGroupMultiple)
+{
+    JSC::initialize();
+
+    RegularExpression re("(?<a>x)|(?<a>y)|(?<b>x)|(?<b>y)|(?<c>x)|(?<c>y)"_s, { JSC::Yarr::Flags::UnicodeSets });
+    EXPECT_TRUE(re.isValid());
+
+    int matchLength = 0;
+    EXPECT_EQ(0, re.match("x"_s, 0, &matchLength));
+    EXPECT_EQ(1, matchLength);
+
+    EXPECT_EQ(0, re.match("y"_s, 0, &matchLength));
+    EXPECT_EQ(1, matchLength);
+}
+
+TEST(JavaScriptCore_RegularExpression, DuplicateNamedCaptureGroupNoMatch)
+{
+    JSC::initialize();
+
+    RegularExpression re("(?<a>x)|(?<a>y)"_s, { JSC::Yarr::Flags::UnicodeSets });
+    EXPECT_TRUE(re.isValid());
+
+    int matchLength = 0;
+    EXPECT_EQ(-1, re.match("z"_s, 0, &matchLength));
+}
+
+TEST(JavaScriptCore_RegularExpression, DuplicateNamedCaptureGroupSearchRev)
+{
+    JSC::initialize();
+
+    RegularExpression re("(?<a>x)|(?<a>y)"_s, { JSC::Yarr::Flags::UnicodeSets });
+    EXPECT_TRUE(re.isValid());
+
+    EXPECT_EQ(3, re.searchRev("abxyz"_s));
+    EXPECT_EQ(1, re.matchedLength());
+}
+
+} // namespace TestWebKitAPI
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/Tools/TestWebKitAPI/CMakeLists.txt b/Tools/TestWebKitAPI/CMakeLists.txt
index 8b3174492bf2..7dc60b7a0ce5 100644
--- a/Tools/TestWebKitAPI/CMakeLists.txt
+++ b/Tools/TestWebKitAPI/CMakeLists.txt
@@ -177,6 +177,7 @@ if (ENABLE_JAVASCRIPTCORE)
         Tests/JavaScriptCore/InspectorConsoleMessage.cpp
         Tests/JavaScriptCore/MarkedVector.cpp
         Tests/JavaScriptCore/PropertySlot.cpp
+        Tests/JavaScriptCore/RegularExpression.cpp
     )
 
     set(TestJavaScriptCore_LIBRARIES
diff --git a/Tools/TestWebKitAPI/Tests/JavaScriptCore/RegularExpression.cpp b/Tools/TestWebKitAPI/Tests/JavaScriptCore/RegularExpression.cpp
new file mode 100644
index 000000000000..0dc35539cd9d
--- /dev/null
+++ b/Tools/TestWebKitAPI/Tests/JavaScriptCore/RegularExpression.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2026 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include <JavaScriptCore/InitializeThreading.h>
+#include <JavaScriptCore/RegularExpression.h>
+#include <JavaScriptCore/YarrFlags.h>
+
+namespace TestWebKitAPI {
+
+using JSC::Yarr::RegularExpression;
+
+TEST(JavaScriptCore_RegularExpression, DuplicateNamedCaptureGroupSimple)
+{
+    JSC::initialize();
+
+    RegularExpression re("(?<a>x)|(?<a>y)"_s, { JSC::Yarr::Flags::UnicodeSets });
+    EXPECT_TRUE(re.isValid());
+
+    int matchLength = 0;
+    EXPECT_EQ(0, re.match("x"_s, 0, &matchLength));
+    EXPECT_EQ(1, matchLength);
+
+    EXPECT_EQ(0, re.match("y"_s, 0, &matchLength));
+    EXPECT_EQ(1, matchLength);
+}
+
+TEST(JavaScriptCore_RegularExpression, DuplicateNamedCaptureGroupMultiple)
+{
+    JSC::initialize();
+
+    RegularExpression re("(?<a>x)|(?<a>y)|(?<b>x)|(?<b>y)|(?<c>x)|(?<c>y)"_s, { JSC::Yarr::Flags::UnicodeSets });
+    EXPECT_TRUE(re.isValid());
+
+    int matchLength = 0;
+    EXPECT_EQ(0, re.match("x"_s, 0, &matchLength));
+    EXPECT_EQ(1, matchLength);
+
+    EXPECT_EQ(0, re.match("y"_s, 0, &matchLength));
+    EXPECT_EQ(1, matchLength);
+}
+
+TEST(JavaScriptCore_RegularExpression, DuplicateNamedCaptureGroupNoMatch)
+{
+    JSC::initialize();
+
+    RegularExpression re("(?<a>x)|(?<a>y)"_s, { JSC::Yarr::Flags::UnicodeSets });
+    EXPECT_TRUE(re.isValid());
+
+    int matchLength = 0;
+    EXPECT_EQ(-1, re.match("z"_s, 0, &matchLength));
+}
+
+TEST(JavaScriptCore_RegularExpression, DuplicateNamedCaptureGroupSearchRev)
+{
+    JSC::initialize();
+
+    RegularExpression re("(?<a>x)|(?<a>y)"_s, { JSC::Yarr::Flags::UnicodeSets });
+    EXPECT_TRUE(re.isValid());
+
+    EXPECT_EQ(3, re.searchRev("abxyz"_s));
+    EXPECT_EQ(1, re.matchedLength());
+}
+
+} // namespace TestWebKitAPI
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker.