CVE-2026-65338
Overview
Advisory: Apple Security Advisory
Impact:
Description: Processing maliciously crafted web content may lead to an unexpected Safari crash
Researchers: OpenAI Codex Security - Amy Burnett
| Attribute | Value |
|---|---|
| CVE | CVE-2026-65338 |
| Bugzilla | 318348 |
| Component | JSC |
| Bug Class | LogicError |
| Severity | medium |
| Commit | 26aa84fcd527016d… |
| Advisory | Apple Advisory |
Root Cause Analysis
The issue was addressed with improved memory handling.
Files Changed
Source Files
Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cppSource/JavaScriptCore/dfg/DFGCombinedLiveness.cppSource/JavaScriptCore/dfg/DFGCombinedLiveness.hSource/JavaScriptCore/dfg/DFGForAllKills.hSource/JavaScriptCore/dfg/DFGOSRAvailabilityAnalysisPhase.cpp
Test Files
JSTests/stress/arguments-elimination-inlined-load-varargs-preserves-recoveries.jsJSTests/stress/arguments-elimination-load-varargs-kills-promoted-recoveries.js
Patch Preview
diff --git a/JSTests/stress/arguments-elimination-inlined-load-varargs-preserves-recoveries.js b/JSTests/stress/arguments-elimination-inlined-load-varargs-preserves-recoveries.js
new file mode 100644
index 000000000000..6a9e21ffb290
--- /dev/null
+++ b/JSTests/stress/arguments-elimination-inlined-load-varargs-preserves-recoveries.js
@@ -0,0 +1,84 @@
+//@ runDefault("--thresholdForJITAfterWarmUp=10", "--thresholdForFTLOptimizeAfterWarmUp=1000", "--useConcurrentJIT=false", "--validateFTLOSRExitLiveness=true")
+
+"use strict";
+
+function shouldBe(actual, expected)
+{
+ if (actual !== expected)
+ throw new Error("bad value: " + actual + ", expected: " + expected);
+}
+
+function five(values1, values2)
+{
+ let result = null;
+ for (let i = 0; i < 5; ++i) {
+ function arg() { "use strict"; return arguments; }
+ const a = arg.apply(undefined, values1);
+ const b = arg.apply(undefined, values2);
+ try {
+ (3881)(b);
+ } catch (error) {
+ a.toString();
+ result = a;
+ }
+ }
+ return result;
+}
+
+function eight(values1, values2)
+{
+ let result = null;
+ for (let i = 0; i < 5; ++i) {
+ function arg() { "use strict"; return arguments; }
+ const a = arg.apply(undefined, values1);
+ const b = arg.apply(undefined, values2);
+ try {
+ (3881)(b);
+ } catch (error) {
+ a.toString();
+ result = a;
+ }
+ }
+ return result;
+}
+
+function filled(length, value)
+{
+ const result = [];
+ for (let i = 0; i < length; ++i)
+ result.push(value);
+ return result;
+}
+
+const fiveMarker = { marker: "five" };
+const eightMarker = { marker: "eight" };
+const seedArray = [{ marker: "seed" }, 1, 2, 3, 4, 5];
+
+const firstFive = filled(5, fiveMarker);
+const overwriteFive = filled(30, fiveMarker);
+overwriteFive[22] = 9;
+
+const firstEight = filled(8, eightMarker);
+const overwriteEight = filled(30, eightMarker);
+overwriteEight[20] = 9;
+
+for (let i = 0; i < testLoopCount; ++i) {
+ five(firstFive, overwriteFive);
+ eight(firstEight, overwriteEight);
+}
+
+const seedValues = filled(30, seedArray);
+seedValues[20] = 9;
+for (let i = 0; i < testLoopCount; ++i)
+ eight(firstEight, seedValues);
+
+const recoveredEight = eight(firstEight, seedValues);
+shouldBe(recoveredEight.length, firstEight.length);
+for (let i = 0; i < firstEight.length; ++i)
+ shouldBe(recoveredEight[i], eightMarker);
+
+const recoveredFive = five(firstFive, overwriteFive);
+shouldBe(recoveredFive.length, firstFive.length);
+for (let i = 0; i < firstFive.length; ++i)
+ shouldBe(recoveredFive[i], fiveMarker);
+shouldBe(recoveredFive[5], undefined);
diff --git a/JSTests/stress/arguments-elimination-load-varargs-kills-promoted-recoveries.js b/JSTests/stress/arguments-elimination-load-varargs-kills-promoted-recoveries.js
new file mode 100644
index 000000000000..aa818cb6d313
--- /dev/null
+++ b/JSTests/stress/arguments-elimination-load-varargs-kills-promoted-recoveries.js
@@ -0,0 +1,88 @@
+//@ runDefault("--thresholdForJITAfterWarmUp=10", "--thresholdForFTLOptimizeAfterWarmUp=1000", "--useConcurrentJIT=false")
+
+"use strict";
+
+function shouldBe(actual, expected)
+{
+ if (actual !== expected)
+ throw new Error("bad value: " + actual + ", expected: " + expected);
+}
+noInline(shouldBe);
+
+function five(values1, values2)
+{
+ let result = null;
+ for (let i = 0; i < 5; ++i) {
+ function arg() { "use strict"; return arguments; }
+ const a = arg.apply(undefined, values1);
+ const b = arg.apply(undefined, values2);
+ try {
+ (3881)(b);
+ } catch (error) {
+ a.toString();
+ result = a;
+ }
+ }
+ return result;
+}
+noInline(five);
+
+function eight(values1, values2)
+{
+ let result = null;
+ for (let i = 0; i < 5; ++i) {
+ function arg() { "use strict"; return arguments; }
+ const a = arg.apply(undefined, values1);
+ const b = arg.apply(undefined, values2);
+ try {
+ (3881)(b);
+ } catch (error) {
+ a.toString();
+ result = a;
+ }
+ }
+ return result;
+}
+noInline(eight);
+
+function filled(length, value)
+{
+ const result = [];
+ for (let i = 0; i < length; ++i)
+ result.push(value);
+ return result;
+}
+noInline(filled);
+
+const fiveMarker = { marker: "five" };
+const eightMarker = { marker: "eight" };
+const seedArray = [{ marker: "seed" }, 1, 2, 3, 4, 5];
+
+const firstFive = filled(5, fiveMarker);
+const overwriteFive = filled(30, fiveMarker);
+overwriteFive[22] = 9;
+
+const firstEight = filled(8, eightMarker);
+const overwriteEight = filled(30, eightMarker);
+overwriteEight[20] = 9;
+
+for (let i = 0; i < testLoopCount; ++i) {
+ five(firstFive, overwriteFive);
+ eight(firstEight, overwriteEight);
+}
+
+const seedValues = filled(30, seedArray);
+seedValues[20] = 9;
+for (let i = 0; i < testLoopCount; ++i)
+ eight(firstEight, seedValues);
+
+const recoveredEight = eight(firstEight, seedValues);
+shouldBe(recoveredEight.length, firstEight.length);
+for (let i = 0; i < firstEight.length; ++i)
+ shouldBe(recoveredEight[i], eightMarker);
+
+const recoveredFive = five(firstFive, overwriteFive);
+shouldBe(recoveredFive.length, firstFive.length);
+for (let i = 0; i < firstFive.length; ++i)
+ shouldBe(recoveredFive[i], fiveMarker);
+shouldBe(recoveredFive[5], undefined);
diff --git a/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp b/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp
index b90a0b9649cf..52567cea948e 100644
--- a/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp
@@ -33,6 +33,7 @@
#include "DFGArgumentsUtilities.h"
#include <wtf/IndexMap.h>
#include "DFGClobberize.h"
+#include "DFGCombinedLiveness.h"
#include "DFGForAllKills.h"
#include "DFGGraph.h"
#include "DFGInsertionSet.h"
@@ -722,7 +723,16 @@ class ArgumentsEliminationPhase : public Phase {
}
if (clobberStack) {