CVE-2026-65340
Overview
Advisory: Apple Security Advisory
Impact:
Description: Processing maliciously crafted web content may lead to an unexpected Safari crash
Researchers: Claudio Bozzato and Francesco Benvenuto of Cisco Talos, Josef Korbel (Citadelo)
| Attribute | Value |
|---|---|
| CVE | CVE-2026-65340 |
| Bugzilla | 316996 |
| Component | JSC |
| Bug Class | LogicError |
| Severity | medium |
| Commit | 30b9a27b47e842c4… |
| Advisory | Apple Advisory |
Root Cause Analysis
This issue was addressed through improved state management.
Files Changed
Source Files
Source/JavaScriptCore/runtime/RegExpMatchesArray.hSource/JavaScriptCore/yarr/YarrJIT.cpp
Test Files
JSTests/stress/yarr-jit-non-bmp-backtrack-index-overflow.jsJSTests/stress/yarr-jit-non-bmp-backtrack-index-underflow.js
Patch Preview
diff --git a/JSTests/stress/yarr-jit-non-bmp-backtrack-index-overflow.js b/JSTests/stress/yarr-jit-non-bmp-backtrack-index-overflow.js
new file mode 100644
index 000000000000..8657a02fb82c
--- /dev/null
+++ b/JSTests/stress/yarr-jit-non-bmp-backtrack-index-overflow.js
@@ -0,0 +1,12 @@
+const re = /d\0e?|\u{10000}c/u;
+const subj = "\u{10000}d";
+const m = re.exec(subj);
+
+if (m !== null) {
+ throw new Error(
+ "expected null, got match=" + JSON.stringify(m[0]) +
+ " at index=" + m.index +
+ " (m.index + m[0].length = " + (m.index + m[0].length) +
+ " > subj.length = " + subj.length + ")"
+ );
+}
diff --git a/JSTests/stress/yarr-jit-non-bmp-backtrack-index-underflow.js b/JSTests/stress/yarr-jit-non-bmp-backtrack-index-underflow.js
new file mode 100644
index 000000000000..52fb0bbf4021
--- /dev/null
+++ b/JSTests/stress/yarr-jit-non-bmp-backtrack-index-underflow.js
@@ -0,0 +1,5 @@
+const re = /(?!\u{10000})a*|\u{10000}b/u;
+const subj = "\u{10000}cc";
+const m = re.exec(subj);
+if (m[0].length > subj.length)
+ throw new Error("m[0].length (" + m[0].length + ") > subj.length (" + subj.length + ")");
diff --git a/Source/JavaScriptCore/runtime/RegExpMatchesArray.h b/Source/JavaScriptCore/runtime/RegExpMatchesArray.h
index 4d5d2304e1d1..4d46a7f017f8 100644
--- a/Source/JavaScriptCore/runtime/RegExpMatchesArray.h
+++ b/Source/JavaScriptCore/runtime/RegExpMatchesArray.h
@@ -76,7 +76,8 @@ ALWAYS_INLINE JSArray* createRegExpMatchesArray(
result.start = position;
result.end = subpatternResults[1];
-
+ RELEASE_ASSERT(result.end >= result.start);
+
JSArray* array;
JSArray* indicesArray = nullptr;
diff --git a/Source/JavaScriptCore/yarr/YarrJIT.cpp b/Source/JavaScriptCore/yarr/YarrJIT.cpp
index d5dc7005cc4d..90ead5391420 100644
--- a/Source/JavaScriptCore/yarr/YarrJIT.cpp
+++ b/Source/JavaScriptCore/yarr/YarrJIT.cpp
@@ -3925,9 +3925,19 @@ class YarrGenerator final : public YarrJITInfo {
// already correctly incremented, if more than one then decrement as appropriate.
unsigned delta = alternative->m_minimumSize - beginOp->m_alternative->m_minimumSize;
ASSERT(delta);
+ bool advancedIndexForNonBMP = false;
+#if ENABLE(YARR_JIT_UNICODE_EXPRESSIONS) && ENABLE(YARR_JIT_UNICODE_CAN_INCREMENT_INDEX_FOR_NON_BMP)
+ if (m_useFirstNonBMPCharacterOptimization) {
+ m_jit.add32(m_regs.firstCharacterAdditionalReadSize, m_regs.index);
+ advancedIndexForNonBMP = true;
+ }
+#endif
if (delta != 1)
m_jit.sub32(MacroAssembler::Imm32(delta - 1), m_regs.index);
- m_jit.jump(beginOp->m_reentry);
+ if (advancedIndexForNonBMP)
+ checkInput().linkTo(beginOp->m_reentry, &m_jit);
+ else
+ m_jit.jump(beginOp->m_reentry);
} else {
// If the first alternative has minimum size 0xFFFFFFFFu, then there cannot
// be sufficent input available to handle this, so just fall through.