Released November 3, 2025

CVE-2025-43438

CVSS 4.3 severity medium CWE-416 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 Safari crash

Researchers: rheza (@ginggilBesel), shandikri working with Trend Micro Zero Day Initiative

Key insight. Map and Set iterators shared the SpecObjectOther speculated type, so the DFG/FTL couldn’t distinguish them and a speculation for one accepted the other – an iterator type confusion; giving each a distinct SpeculatedType makes the checks precise.

Patch Details

diff --git a/JSTests/stress/map-set-iterator-speculated-types.js b/JSTests/stress/map-set-iterator-speculated-types.js
new file mode 100644
index 000000000000..32cdfcc67c97
--- /dev/null
+++ b/JSTests/stress/map-set-iterator-speculated-types.js
@@ -0,0 +1,10 @@
+function test() {
+    const map = new Map();
+    var iterator = map[Symbol.iterator]();
+    const proto = Object.getPrototypeOf(iterator);
+    try{
+        map.get(...map, ...proto);
+    }catch(e){}
+}
+for (var i=0;i<testLoopCount;i++)
+    test();
diff --git a/Source/JavaScriptCore/bytecode/SpeculatedType.cpp b/Source/JavaScriptCore/bytecode/SpeculatedType.cpp
index 33f9ba011188..5c34416860c6 100644
--- a/Source/JavaScriptCore/bytecode/SpeculatedType.cpp
+++ b/Source/JavaScriptCore/bytecode/SpeculatedType.cpp
@@ -731,8 +731,9 @@ std::optional<SpeculatedType> speculationFromJSType(JSType type)
     case DataViewType:
         return SpecDataViewObject;
     case JSMapIteratorType:
+        return SpecMapIteratorObject;
     case JSSetIteratorType:
-        return SpecObjectOther;
+        return SpecSetIteratorObject;
     default:
         return std::nullopt;
     }
diff --git a/Source/JavaScriptCore/bytecode/SpeculatedType.h b/Source/JavaScriptCore/bytecode/SpeculatedType.h
index 0d8e1ce0c25b..86ff82a8c8ac 100644
--- a/Source/JavaScriptCore/bytecode/SpeculatedType.h
+++ b/Source/JavaScriptCore/bytecode/SpeculatedType.h
@@ -63,33 +63,35 @@ static constexpr SpeculatedType SpecDateObject                        = 1ull <<
 static constexpr SpeculatedType SpecPromiseObject                     = 1ull << 21; // It's definitely a Promise object or one of its subclasses.
 static constexpr SpeculatedType SpecMapObject                         = 1ull << 22; // It's definitely a Map object or one of its subclasses.
 static constexpr SpeculatedType SpecSetObject                         = 1ull << 23; // It's definitely a Set object or one of its subclasses.
-static constexpr SpeculatedType SpecWeakMapObject                     = 1ull << 24; // It's definitely a WeakMap object or one of its subclasses.
-static constexpr SpeculatedType SpecWeakSetObject                     = 1ull << 25; // It's definitely a WeakSet object or one of its subclasses.
-static constexpr SpeculatedType SpecProxyObject                       = 1ull << 26; // It's definitely a Proxy object or one of its subclasses.
-static constexpr SpeculatedType SpecGlobalProxy                       = 1ull << 27; // It's definitely a Proxy object or one of its subclasses.
-static constexpr SpeculatedType SpecDerivedArray                      = 1ull << 28; // It's definitely a DerivedArray object.
-static constexpr SpeculatedType SpecObjectOther                       = 1ull << 29; // It's definitely an object but not JSFinalObject, JSArray, or JSFunction.
-static constexpr SpeculatedType SpecStringIdent                       = 1ull << 30; // It's definitely a JSString, and it's an identifier.
-static constexpr SpeculatedType SpecStringResolvedVar                 = 1ull << 31; // It's definitely a JSString, and it's not an identifier. And string is resolved.
-static constexpr SpeculatedType SpecStringUnresolvedVar               = 1ull << 32; // It's definitely a JSString, and it's not an identifier. And string is unresolved.
+static constexpr SpeculatedType SpecMapIteratorObject                 = 1ull << 24; // It's definitely a Map iterator object or one of its subclasses.
+static constexpr SpeculatedType SpecSetIteratorObject                 = 1ull << 25; // It's definitely a Set iterator object or one of its subclasses.
+static constexpr SpeculatedType SpecWeakMapObject                     = 1ull << 26; // It's definitely a WeakMap object or one of its subclasses.
+static constexpr SpeculatedType SpecWeakSetObject                     = 1ull << 27; // It's definitely a WeakSet object or one of its subclasses.
+static constexpr SpeculatedType SpecProxyObject                       = 1ull << 28; // It's definitely a Proxy object or one of its subclasses.
+static constexpr SpeculatedType SpecGlobalProxy                       = 1ull << 29; // It's definitely a Proxy object or one of its subclasses.
+static constexpr SpeculatedType SpecDerivedArray                      = 1ull << 30; // It's definitely a DerivedArray object.
+static constexpr SpeculatedType SpecObjectOther                       = 1ull << 31; // It's definitely an object but not JSFinalObject, JSArray, or JSFunction.
+static constexpr SpeculatedType SpecStringIdent                       = 1ull << 32; // It's definitely a JSString, and it's an identifier.
+static constexpr SpeculatedType SpecStringResolvedVar                 = 1ull << 33; // It's definitely a JSString, and it's not an identifier. And string is resolved.
+static constexpr SpeculatedType SpecStringUnresolvedVar               = 1ull << 34; // It's definitely a JSString, and it's not an identifier. And string is unresolved.
 static constexpr SpeculatedType SpecStringVar                         = SpecStringUnresolvedVar | SpecStringResolvedVar; // It's definitely a JSString, and it's not an identifier.
 static constexpr SpeculatedType SpecStringResolved                    = SpecStringIdent | SpecStringResolvedVar; // It's definitely a JSString, and it's resolved. May be an identifier or not.
 static constexpr SpeculatedType SpecString                            = SpecStringIdent | SpecStringVar; // It's definitely a JSString.
-static constexpr SpeculatedType SpecSymbol                            = 1ull << 33; // It's definitely a Symbol.
-static constexpr SpeculatedType SpecCellOther                         = 1ull << 34; // It's definitely a JSCell but not a subclass of JSObject and definitely not a JSString, BigInt, or Symbol.
-static constexpr SpeculatedType SpecBoolInt32                         = 1ull << 35; // It's definitely an Int32 with value 0 or 1.
-static constexpr SpeculatedType SpecNonBoolInt32                      = 1ull << 36; // It's definitely an Int32 with value other than 0 or 1.
+static constexpr SpeculatedType SpecSymbol                            = 1ull << 35; // It's definitely a Symbol.
+static constexpr SpeculatedType SpecCellOther                         = 1ull << 36; // It's definitely a JSCell but not a subclass of JSObject and definitely not a JSString, BigInt, or Symbol.
+static constexpr SpeculatedType SpecBoolInt32                         = 1ull << 37; // It's definitely an Int32 with value 0 or 1.
+static constexpr SpeculatedType SpecNonBoolInt32                      = 1ull << 38; // It's definitely an Int32 with value other than 0 or 1.
 static constexpr SpeculatedType SpecInt32Only                         = SpecBoolInt32 | SpecNonBoolInt32; // It's definitely an Int32.
 
-static constexpr SpeculatedType SpecInt32AsInt52                      = 1ull << 37; // It's an Int52 and it can fit in an int32.
-static constexpr SpeculatedType SpecNonInt32AsInt52                   = 1ull << 38; // It's an Int52 and it can't fit in an int32.
+static constexpr SpeculatedType SpecInt32AsInt52                      = 1ull << 39; // It's an Int52 and it can fit in an int32.
+static constexpr SpeculatedType SpecNonInt32AsInt52                   = 1ull << 40; // It's an Int52 and it can't fit in an int32.
 static constexpr SpeculatedType SpecInt52Any                          = SpecInt32AsInt52 | SpecNonInt32AsInt52; // It's any kind of Int52.
 
-static constexpr SpeculatedType SpecAnyIntAsDouble                    = 1ull << 39; // It's definitely an Int52 and it's inside a double.
-static constexpr SpeculatedType SpecNonIntAsDouble                    = 1ull << 40; // It's definitely not an Int52 but it's a real number and it's a double.
+static constexpr SpeculatedType SpecAnyIntAsDouble                    = 1ull << 41; // It's definitely an Int52 and it's inside a double.
+static constexpr SpeculatedType SpecNonIntAsDouble                    = 1ull << 42; // It's definitely not an Int52 but it's a real number and it's a double.
 static constexpr SpeculatedType SpecDoubleReal                        = SpecNonIntAsDouble | SpecAnyIntAsDouble; // It's definitely a non-NaN double.
-static constexpr SpeculatedType SpecDoublePureNaN                     = 1ull << 41; // It's definitely a NaN that is safe to tag (i.e. pure).
-static constexpr SpeculatedType SpecDoubleImpureNaN                   = 1ull << 42; // It's definitely a NaN that is unsafe to tag (i.e. impure).
+static constexpr SpeculatedType SpecDoublePureNaN                     = 1ull << 43; // It's definitely a NaN that is safe to tag (i.e. pure).
+static constexpr SpeculatedType SpecDoubleImpureNaN                   = 1ull << 44; // It's definitely a NaN that is unsafe to tag (i.e. impure).
 static constexpr SpeculatedType SpecDoubleNaN                         = SpecDoublePureNaN | SpecDoubleImpureNaN; // It's definitely some kind of NaN.
 static constexpr SpeculatedType SpecBytecodeDouble                    = SpecDoubleReal | SpecDoublePureNaN; // It's either a non-NaN or a NaN double, but it's definitely not impure NaN.
 static constexpr SpeculatedType SpecFullDouble                        = SpecDoubleReal | SpecDoubleNaN; // It's either a non-NaN or a NaN double.
@@ -99,21 +101,21 @@ static constexpr SpeculatedType SpecBytecodeNumber                    = SpecInt3
 static constexpr SpeculatedType SpecIntAnyFormat                      = SpecInt52Any | SpecInt32Only | SpecAnyIntAsDouble;
 
 static constexpr SpeculatedType SpecFullNumber                        = SpecIntAnyFormat | SpecFullDouble; // It's either an Int32, Int52, or a Double, and the Double can be impure NaN.
-static constexpr SpeculatedType SpecBoolean                           = 1ull << 43; // It's definitely a Boolean.
-static constexpr SpeculatedType SpecOther                             = 1ull << 44; // It's definitely either Null or Undefined.
+static constexpr SpeculatedType SpecBoolean                           = 1ull << 45; // It's definitely a Boolean.
+static constexpr SpeculatedType SpecOther                             = 1ull << 46; // It's definitely either Null or Undefined.
 static constexpr SpeculatedType SpecMisc                              = SpecBoolean | SpecOther; // It's definitely either a boolean, Null, or Undefined.
-static constexpr SpeculatedType SpecEmpty                             = 1ull << 45; // It's definitely an empty value marker.
-static constexpr SpeculatedType SpecHeapBigInt                        = 1ull << 46; // It's definitely a BigInt that is allocated on the heap
-static constexpr SpeculatedType SpecBigInt32                          = 1ull << 47; // It's definitely a small BigInt that is inline the JSValue
+static constexpr SpeculatedType SpecEmpty                             = 1ull << 47; // It's definitely an empty value marker.
+static constexpr SpeculatedType SpecHeapBigInt                        = 1ull << 48; // It's definitely a BigInt that is allocated on the heap
+static constexpr SpeculatedType SpecBigInt32                          = 1ull << 49; // It's definitely a small BigInt that is inline the JSValue
 #if USE(BIGINT32)
 static constexpr SpeculatedType SpecBigInt                            = SpecBigInt32 | SpecHeapBigInt;
 #else
 // We should not include SpecBigInt32. We are using SpecBigInt in various places like prediction. If this includes SpecBigInt32, fixup phase is confused if !USE(BIGINT32) since it is not using AnyBigIntUse.
 static constexpr SpeculatedType SpecBigInt                            = SpecHeapBigInt;
 #endif
-static constexpr SpeculatedType SpecDataViewObject                    = 1ull << 48; // It's definitely a JSDataView.
+static constexpr SpeculatedType SpecDataViewObject                    = 1ull << 50; // It's definitely a JSDataView.
 static constexpr SpeculatedType SpecPrimitive                         = SpecString | SpecSymbol | SpecBytecodeNumber | SpecMisc | SpecBigInt; // It's any non-Object JSValue.
-static constexpr SpeculatedType SpecObject                            = SpecFinalObject | SpecArray | SpecFunction | SpecTypedArrayView | SpecDirectArguments | SpecScopedArguments | SpecStringObject | SpecRegExpObject | SpecDateObject | SpecPromiseObject | SpecMapObject | SpecSetObject | SpecWeakMapObject | SpecWeakSetObject | SpecProxyObject | SpecGlobalProxy | SpecDerivedArray | SpecObjectOther | SpecDataViewObject; // Bitmask used for testing for any kind of object prediction.
+static constexpr SpeculatedType SpecObject                            = SpecFinalObject | SpecArray | SpecFunction | SpecTypedArrayView | SpecDirectArguments | SpecScopedArguments | SpecStringObject | SpecRegExpObject | SpecDateObject | SpecPromiseObject | SpecMapObject | SpecSetObject | SpecMapIteratorObject | SpecSetIteratorObject | SpecWeakMapObject | SpecWeakSetObject | SpecProxyObject | SpecGlobalProxy | SpecDerivedArray | SpecObjectOther | SpecDataViewObject; // Bitmask used for testing for any kind of object prediction.
 static constexpr SpeculatedType SpecCell                              = SpecObject | SpecString | SpecSymbol | SpecCellOther | SpecHeapBigInt; // It's definitely a JSCell.
 static constexpr SpeculatedType SpecHeapTop                           = SpecCell | SpecBigInt32 | SpecBytecodeNumber | SpecMisc; // It can be any of the above, except for SpecInt52Only and SpecDoubleImpureNaN.
 static constexpr SpeculatedType SpecBytecodeTop                       = SpecHeapTop | SpecEmpty; // It can be any of the above, except for SpecInt52Only and SpecDoubleImpureNaN. Corresponds to what could be found in a bytecode local.
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
index e830d584a068..30e83c2f2aa2 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
@@ -11887,12 +11887,12 @@ void SpeculativeJIT::speculateSetObject(Edge edge)
 
 void SpeculativeJIT::speculateMapIteratorObject(Edge edge, GPRReg cell)
 {
-    speculateCellType(edge, cell, SpecObjectOther, JSMapIteratorType);
+    speculateCellType(edge, cell, SpecMapIteratorObject, JSMapIteratorType);
 }
 
 void SpeculativeJIT::speculateMapIteratorObject(Edge edge)
 {
-    if (!needsTypeCheck(edge, SpecObjectOther))
+    if (!needsTypeCheck(edge, SpecMapIteratorObject))
         return;
 
     SpeculateCellOperand operand(this, edge);
@@ -11901,12 +11901,12 @@ void SpeculativeJIT::speculateMapIteratorObject(Edge edge)
 
 void SpeculativeJIT::speculateSetIteratorObject(Edge edge, GPRReg cell)
 {
-    speculateCellType(edge, cell, SpecObjectOther, JSSetIteratorType);
+    speculateCellType(edge, cell, SpecSetIteratorObject, JSSetIteratorType);
 }
 
 void SpeculativeJIT::speculateSetIteratorObject(Edge edge)
 {
-    if (!needsTypeCheck(edge, SpecObjectOther))
+    if (!needsTypeCheck(edge, SpecSetIteratorObject))
         return;
 
     SpeculateCellOperand operand(this, edge);
diff --git a/Source/JavaScriptCore/dfg/DFGUseKind.h b/Source/JavaScriptCore/dfg/DFGUseKind.h
index 09c8f8e863a4..edacb1f0641c 100644
--- a/Source/JavaScriptCore/dfg/DFGUseKind.h
+++ b/Source/JavaScriptCore/dfg/DFGUseKind.h
@@ -178,8 +178,9 @@ inline SpeculatedType typeFilterFor(UseKind useKind)
     case SetObjectUse:
         return SpecSetObject;
     case MapIteratorObjectUse:
+        return SpecMapIteratorObject;
     case SetIteratorObjectUse:
-        return SpecObjectOther;
+        return SpecSetIteratorObject;
     case WeakMapObjectUse:
         return SpecWeakMapObject;
     case WeakSetObjectUse:
diff --git a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
index 1561f29e7e15..4c7c38a8ec43 100644
--- a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
+++ b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
@@ -24030,7 +24030,7 @@ IGNORE_CLANG_WARNINGS_END
     void speculateMapIteratorObject(Edge edge, LValue cell)
     {
         FTL_TYPE_CHECK(
-            jsValueValue(cell), edge, SpecObjectOther, isNotType(cell, JSMapIteratorType));
+            jsValueValue(cell), edge, SpecMapIteratorObject, isNotType(cell, JSMapIteratorType));
     }
 
     void speculateMapIteratorObject(Edge edge)
@@ -24052,7 +24052,7 @@ IGNORE_CLANG_WARNINGS_END
     void speculateSetIteratorObject(Edge edge, LValue cell)
     {
         FTL_TYPE_CHECK(
-            jsValueValue(cell), edge, SpecObjectOther, isNotType(cell, JSSetIteratorType));
+            jsValueValue(cell), edge, SpecSetIteratorObject, isNotType(cell, JSSetIteratorType));
     }
 
     void speculateSetIteratorObject(Edge edge)
diff --git a/Source/JavaScriptCore/runtime/JSType.h b/Source/JavaScriptCore/runtime/JSType.h
index a77d5e41a525..5c3f9907df6e 100644
--- a/Source/JavaScriptCore/runtime/JSType.h
+++ b/Source/JavaScriptCore/runtime/JSType.h
@@ -127,8 +127,8 @@ namespace JSC {
     macro(JSArrayIteratorType, SpecObjectOther) \
     macro(JSIteratorType, SpecObjectOther) \
     macro(JSIteratorHelperType, SpecObjectOther) \
-    macro(JSMapIteratorType, SpecObjectOther) \
-    macro(JSSetIteratorType, SpecObjectOther) \

Background

Advisory: Apple Security Advisory

Attribute Value
CVE CVE-2025-43438
Bugzilla 297662
Component JSC
Bug Class TypeConfusion
Severity medium
Commit 045fd8ec92379f84…
Advisory Apple Advisory

Terminology

  • SpeculatedType — A bitset the DFG/FTL use to represent the possible types of a value and to guard specialized code.
  • SpecObjectOther — A generic ‘some other object’ speculated-type bucket that does not distinguish specific object kinds.
  • JSMapIterator / JSSetIterator — Distinct engine objects with different internal layouts backing Map/Set iteration.
  • Speculation check / type confusion — A JIT guard that, if too coarse, lets a value of the wrong concrete type pass and be misinterpreted.

Root Cause Analysis

The DFG/FTL JIT tracks value types with SpeculatedType bitsets and inserts speculation checks that guard type-specialized code. JSMapIterator and JSSetIterator objects were both mapped to the generic bucket SpecObjectOther rather than to distinct speculated types: speculationFromJSType returned SpecObjectOther for both iterator JSTypes, and speculateMapIteratorObject/speculateSetObject (in DFGSpeculativeJIT and FTLLowerDFGToB3) checked against SpecObjectOther. Because both iterators shared the same speculated type, the abstract interpreter and speculation checks could not distinguish a Map iterator from a Set iterator (nor from other SpecObjectOther objects): a node speculated to operate on a Map iterator would accept a Set iterator that passed the same SpecObjectOther check, so type-specialized code then accessed the wrong iterator’s internal layout – a type confusion between JSMapIterator and JSSetIterator. Operating on one iterator’s fields as if it were the other corrupts memory (advisory class UAF/crash). The fix introduces distinct SpeculatedType bits SpecMapIteratorObject and SpecSetIteratorObject, returns them from speculationFromJSType for the respective JSTypes, and updates the speculation checks/UseKinds to use the specific type, so a Map-iterator speculation rejects a Set iterator and vice versa. The restored invariant is that Map and Set iterators are distinct speculated types, so the JIT’s iterator type checks are precise.

Attack Path

1. Warm up iterator code in the JIT

Run JS that uses Map and Set iterators so the DFG/FTL compiles iterator-specialized nodes speculating on iterator type.

2. Confuse the speculation

Pre-patch both iterators are SpecObjectOther, so a Map-iterator-speculated node accepts a Set iterator (or vice versa) that passes the same check.

3. Access wrong iterator layout

The type-specialized code treats one iterator kind as the other, reading/writing the wrong internal fields.

4. Corrupt memory

The iterator type confusion yields memory corruption in the WebContent process.

Impact Assessment

A JIT type confusion between Map and Set iterators (advisory class UAF): both mapped to SpecObjectOther, so speculation checks couldn’t tell them apart and iterator-specialized code accessed the wrong layout. That is a strong memory-corruption primitive reachable from ordinary Map/Set iterator use once JIT-compiled, in the WebContent process; the observable is a crash but the class is type confusion. Rated medium.

Audit Directions

Other objects lumped into SpecObjectOther

grep for speculationFromJSType returning SpecObjectOther for concrete JSTypes whose specialized code assumes a specific layout.

Iterator speculation coverage

Audit DFG/FTL nodes handling Map/Set/Array iterators to ensure each checks the precise iterator SpeculatedType.

UseKind / speculateCellType precision

Review speculateCellType call sites that pass a broad SpeculatedType with a specific JSType, which can admit the wrong concrete type.

Changed Functions

Function File Change Note
speculationFromJSType Source/JavaScriptCore/bytecode/SpeculatedType.cpp modified Returns distinct SpecMapIteratorObject / SpecSetIteratorObject for the Map/Set iterator JSTypes instead of the shared SpecObjectOther.
SpecMapIteratorObject / SpecSetIteratorObject bits Source/JavaScriptCore/bytecode/SpeculatedType.h modified Adds dedicated SpeculatedType bits so Map and Set iterators are distinguishable in speculation.
SpeculativeJIT::speculateMapIteratorObject / speculateSetObject Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp modified Speculation checks now use the specific iterator SpeculatedType, so the wrong iterator kind fails the check.
FTLLowerDFGToB3 iterator checks Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp modified FTL type checks for Map/Set iterators use the distinct speculated types (UseKinds updated in DFGUseKind.h).

Files Changed

Source Files

  • Source/JavaScriptCore/bytecode/SpeculatedType.cpp
  • Source/JavaScriptCore/bytecode/SpeculatedType.h
  • Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
  • Source/JavaScriptCore/dfg/DFGUseKind.h
  • Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
  • Source/JavaScriptCore/runtime/JSType.h

Test Files

  • JSTests/stress/map-set-iterator-speculated-types.js

Regression Test

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

diff --git a/JSTests/stress/map-set-iterator-speculated-types.js b/JSTests/stress/map-set-iterator-speculated-types.js
new file mode 100644
index 000000000000..32cdfcc67c97
--- /dev/null
+++ b/JSTests/stress/map-set-iterator-speculated-types.js
@@ -0,0 +1,10 @@
+function test() {
+    const map = new Map();
+    var iterator = map[Symbol.iterator]();
+    const proto = Object.getPrototypeOf(iterator);
+    try{
+        map.get(...map, ...proto);
+    }catch(e){}
+}
+for (var i=0;i<testLoopCount;i++)
+    test();