CVE-2025-31277 Inside the JavaScriptCore JIT Vulnerability That Opened iPhones to DarkSword
Author: Security Research
Date: March 21, 2026
Severity: High (CVSS 8.8)
Tags:
Executive Summary
CVE-2025-31277 is a JIT type confusion / use-after-free memory corruption vulnerability in JavaScriptCore — the JavaScript engine that powers Apple’s Safari browser and WebKit runtime. It was the Stage 1 entry point of the DarkSword exploit chain, providing attackers with an arbitrary memory read/write primitive inside Safari’s WebContent renderer process — the critical foothold from which all subsequent stages of full iPhone kernel compromise were built.
Exploited in the wild by multiple state-sponsored groups and commercial surveillance vendors since November 2025 as part of the DarkSword campaign, CVE-2025-31277 exemplifies why JIT compilers remain one of the most lucrative and dangerous attack surfaces in modern software. The vulnerability was patched in iOS 18.6 (August 2025) — but DarkSword’s operators continued using it against the hundreds of millions of devices that hadn’t yet updated.
This post provides a technical deep dive into:
- How JavaScriptCore’s DFG JIT works
- What type confusion means at the machine code level
- How CVE-2025-31277 was likely structured and exploited
- How it served as the gateway to full kernel compromise in DarkSword
- What it tells us about the ongoing war between JIT performance and security
CVE Summary
| Field | Detail |
|---|---|
| CVE ID | CVE-2025-31277 |
| Product | Apple WebKit / JavaScriptCore |
| Affected Platforms | iOS/iPadOS < 18.6, Safari < 18.6, macOS Sequoia < 15.6, watchOS < 11.6, visionOS < 2.6, tvOS < 18.6 |
| Vulnerability Type | JIT type confusion / use-after-free (memory corruption) |
| CVSS v3.1 Score | 8.8 (High) |
| Attack Vector | Network (via malicious web content in Safari) |
| Attack Complexity | Low |
| Privileges Required | None |
| User Interaction | Required (visit malicious page) |
| Impact | Arbitrary memory R/W → RCE in WebContent process |
| Role in DarkSword | Stage 1 (for iOS 18.4 – 18.5) — initial RCE entry point |
| Patched In | iOS 18.6, Safari 18.6, macOS Sequoia 15.6 |
| Fix Description | “Improved memory handling” (Apple) |
| Active Exploitation | Yes — DarkSword campaign, Nov 2025+ |
Understanding JavaScriptCore and the JIT Pipeline
Before diving into the vulnerability, we need to understand the architecture that makes it possible.
JavaScriptCore: The Engine Under Safari
JavaScriptCore (JSC) is Apple’s open-source JavaScript engine, embedded in WebKit. Every webpage you load in Safari executes its JavaScript through JSC. It must balance two competing demands:
- Security — untrusted JavaScript from arbitrary websites must be sandboxed
- Performance — modern web apps are JavaScript-heavy, speed is critical
To achieve performance, JSC uses tiered Just-In-Time compilation — progressively more aggressive optimization as code runs more frequently:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
JavaScript Source Code
↓
LLInt (Low-Level Interpreter)
First execution — pure interpretation
Gathers profiling data (what types are actually used?)
↓
Baseline JIT
Generates simple native code
Adds type feedback collection
↓
DFG JIT (Data Flow Graph JIT) ← CVE-2025-31277 lives here
Speculative optimization based on profiling
Generates highly optimized machine code
Makes type assumptions → speed gains
↓
FTL JIT (Faster Than Light JIT)
LLVM-based maximum optimization
Inlines functions, eliminates branches
How DFG JIT Works: Speculative Optimization
The DFG JIT is where CVE-2025-31277 originates. Understanding it requires grasping type speculation:
1
2
3
4
5
6
function add(a, b) {
return a + b;
}
// Called 1000 times with integers:
add(1, 2); add(5, 3); add(10, 7); ...
DFG observes that a and b are always integers. It compiles a specialized native version:
; DFG-compiled add() — assumes integer arguments
mov eax, [a_value] ; load first arg
add eax, [b_value] ; integer add (fast!)
ret ; return
This is much faster than a generic version that handles any JavaScript type. But there’s a contract: if the assumption is ever violated (someone calls add("hello", 42)), DFG must “bail out” — deoptimize back to a slower tier that handles all types.
The security question: what happens if that bail-out fails or is incorrectly triggered?
JSValue: How JavaScript Values Are Represented in Memory
All JavaScript values in JSC are represented as 64-bit JSValue structures using NaN-boxing encoding:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
JSValue: 64-bit value with type embedded in high bits
Double (floating point number):
Any valid IEEE 754 double (< 0xFFFE000000000000)
Integer (32-bit int packed):
0xFFFF000000000000 | int32_value
Pointer to JSObject (heap-allocated object):
0x0000XXXXXXXXXXXX (pointer with upper bits zero/small)
Special values:
0xFFFFFFFFFFFFFFFF = undefined
0xFFFF000000000002 = null
0xFFFF000000000003 = true
0xFFFF000000000004 = false
Critical insight: The same 64-bit pattern is interpreted differently depending on its type tag. If the DFG JIT incorrectly identifies the type, it will treat an object pointer as a floating point number — or vice versa — causing catastrophic memory misinterpretation.
CVE-2025-31277: The Type Confusion Bug
Nature of the Vulnerability
CVE-2025-31277 is described as a JIT optimization/type confusion vulnerability in the DFG layer of JavaScriptCore, with a use-after-free component. Apple’s fix description was simply “improved memory handling.”
Based on the vulnerability class and how similar JSC DFG bugs have historically worked, the likely root cause involves one of two patterns:
Pattern A: Incorrect Speculative Type
The DFG speculatively marks an object as a specific type (e.g., Int32 or Double). Under certain operation sequences, an object can be transformed (its “structure” changed — JSC’s internal class descriptor) after the DFG’s type snapshot is taken, but before the generated code executes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Conceptual — illustrative of type confusion class
function confuse(arr) {
// DFG profiles: arr is always an array of doubles
// Generates optimized code treating arr[0] as Double
return arr[0] + 1.0;
}
// Warm up DFG with doubles:
for (let i = 0; i < 10000; i++) confuse([1.1, 2.2]);
// Now trigger: replace arr[0] with an object DURING execution
// via a side effect in a property getter or Proxy trap
// The DFG-compiled code treats the object pointer as a double
// Reading the raw bits of an object pointer as a float → type confusion
Pattern B: Use-After-Free via GC Race
An object is enqueued for garbage collection. The DFG JIT holds a reference to the freed memory through a type-profiling structure. When the GC reuses that memory for a new object, the JIT’s reference now points to the new object — but with the old type information:
1
2
3
4
5
6
7
Timeline:
T1: DFG records: slot[0] = type:Double, ptr=0xABCD0000
T2: GC frees object at 0xABCD0000
T3: New JSObject allocated at 0xABCD0000 (same address)
T4: DFG-compiled code accesses slot[0]
→ Reads new JSObject's header bits as a Double
→ type confusion achieved
Achieving Arbitrary Memory R/W: addrof and fakeobj
Once type confusion is achieved — the ability to read an object’s pointer bits as a float, or write a float’s bits as a pointer — attackers construct two fundamental exploit primitives:
1. addrof primitive — get the memory address of any JavaScript object:
1
2
3
4
5
6
7
8
9
10
11
// CONCEPTUAL — illustrative only
function addrof(target) {
// Put target at a position where type confusion reads it as float
confusedArray[0] = target; // Store object
triggerConfusion(); // Trigger type confusion
return confusedArray[0]; // Read as float — raw pointer bits!
// The float's bit pattern = the object's memory address
}
let secretAddr = addrof(sensitiveObject);
// secretAddr is now a float whose bits = pointer to sensitiveObject
2. fakeobj primitive — create a JavaScript object at an arbitrary memory address:
1
2
3
4
5
6
7
8
9
10
11
12
// CONCEPTUAL — illustrative only
function fakeobj(addr) {
// Write the address bits as a float at a position
// where type confusion will interpret float as object pointer
confusedArray[0] = addr; // Write raw address as float
triggerConfusion(); // Trigger type confusion
return confusedArray[0]; // Read as JSObject pointer!
// Returns a "fake" JS object backed by memory at addr
}
let fakeObject = fakeobj(targetMemoryAddress);
// fakeObject is a JS object whose backing storage starts at targetMemoryAddress
3. Constructing arbitrary R/W from addrof + fakeobj:
The standard technique is to craft a fake JSArray object pointing to attacker-controlled memory as its “butterfly” (element storage):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// CONCEPTUAL — illustrative of exploit primitive construction
// Full arbitrary read/write achieved by crafting a fake JSArray
// Step 1: Create a real container object and get its address
let container = {
butterfly_ptr: null, // Will hold controlled butterfly
structure_id: realArrayStructureId
};
// Step 2: Get the address of container
let containerAddr = addrof(container);
// Step 3: Craft a fake array object with container as its butterfly
// Container's butterfly_ptr field = our target address
let fakeArr = fakeobj(containerAddr - BUTTERFLY_OFFSET);
// Now:
// fakeArr[0] = arbitrary read from target_addr
// fakeArr[0] = value → arbitrary write to target_addr
function read64(addr) {
container.butterfly_ptr = itof(addr); // set butterfly to target
return fakeArr[0];
}
function write64(addr, value) {
container.butterfly_ptr = itof(addr);
fakeArr[0] = value;
}
With read64 and write64, the attacker has arbitrary read/write within the WebContent process — the full RCE primitive needed to proceed to DarkSword Stage 2.
From Memory R/W to Code Execution: Bypassing JIT Cage
Even with arbitrary R/W in the WebContent process, there’s one more obstacle: JIT Cage — Apple’s mitigation that prevents JIT-compiled code from writing to executable (JIT) memory regions.
JIT Cage works by mapping JIT memory regions in a way that only a special hardware-gated path can write to them. Bypassing this requires:
- Reading the WebContent process’s memory map using the arbitrary R/W primitive
- Finding the base address of WebKit’s binary via ASLR slide calculation
- Locating function pointers in writable memory that are called by JavaScript execution paths
- Overwriting a function pointer to point to attacker-controlled ROP (Return Oriented Programming) gadget chain
For DarkSword, this step feeds directly into Stage 2 (CVE-2026-20700’s PAC bypass), which handles the pointer authentication hurdle that would otherwise prevent function pointer overwriting.
CVE-2025-31277 in the DarkSword Kill Chain
Here’s exactly how CVE-2025-31277 fits into DarkSword’s full attack flow:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[Victim loads malicious webpage in Safari]
│
│ Hidden iframe loads DarkSword JavaScript
▼
[Fingerprinting: check iOS version]
├── iOS 18.4 or 18.5? → Use CVE-2025-31277
└── iOS 18.6 or 18.7? → Use CVE-2025-43529 (fallback)
│
▼ [CVE-2025-31277 path]
[Stage 1: JIT warmup phase]
JavaScript repeatedly calls target functions
DFG profiles type usage, JIT-compiles optimized native code
│
▼
[Stage 1: Trigger type confusion]
Crafted JS violates DFG's type assumption
Object pointer misread as float (or vice versa)
Type confusion achieved in DFG JIT compiled code
│
▼
[Stage 1: Build addrof + fakeobj primitives]
Exploit the confusion to get object addresses (addrof)
Craft fake objects at controlled memory (fakeobj)
│
▼
[Stage 1: Construct arbitrary R/W]
Fake JSArray with controlled butterfly pointer
read64() / write64() functions now operational
FULL ARBITRARY READ/WRITE IN WEBCONTENTPROCESS
│
▼
[Stage 1: Pre-stage 2 prep]
Read WebKit ASLR slide
Locate PAC-related structures in memory
Prepare data needed for CVE-2026-20700 PAC bypass
│
▼ → Proceeds to Stage 2, 3, 4, 5, 6...
[Eventually: Root kernel access + GHOSTBLADE/GHOSTKNIFE/GHOSTSABER deployed]
Historical Context: JIT Type Confusion in JavaScriptCore
CVE-2025-31277 is far from unique — it belongs to a well-documented class of JSC DFG vulnerabilities:
| CVE | Year | Type | Notes |
|---|---|---|---|
| CVE-2018-4233 | 2018 | DFG type confusion via Proxy/CreateThis | First major exploited DFG bug |
| CVE-2022-42856 | 2022 | FTL JIT type confusion | Zero-day, exploited in the wild |
| CVE-2023-37450 | 2023 | WebKit type confusion | Exploited same day as disclosure |
| CVE-2024-23222 | 2024 | Type confusion | Zero-day, arbitrary code execution |
| CVE-2025-24201 | 2025 | WebKit OOB write (JIT context) | Zero-day, patched March 2025 |
| CVE-2025-31277 | 2025 | DFG JIT type confusion / UAF | DarkSword Stage 1 |
| CVE-2025-43529 | 2025 | DFG JIT GC bug (DarkSword fallback) | Patched iOS 18.7.3 |
The pattern is consistent: the DFG JIT’s speculative optimization engine is attacked repeatedly because it is one of the few places in iOS where performance demands require accepting a fundamental tension with security.
Why JIT Compilers Are Perpetually Vulnerable
The Fundamental Tension
JIT optimization relies on trusted assumptions about types to generate fast code. Security requires never trusting input. These two principles are in direct conflict.
Every performance optimization the DFG JIT makes — “this variable is always an integer,” “this array always holds doubles,” “this function never changes its structure” — is a potential type confusion attack vector if an attacker can violate that assumption.
The Complexity Problem
The DFG JIT is one of the most complex subsystems in any software stack. Its optimization passes, speculative compilation, bailout handling, and interaction with the garbage collector involve millions of lines of code. Each interaction between subsystems is a potential vulnerability surface.
The GC interaction alone is notoriously difficult to reason about:
1
2
3
4
5
6
7
Garbage Collector runs concurrently
↓
Object freed while DFG code holds reference
↓
Same memory reused for different object
↓
DFG's stale reference → wrong type → confusion
Apple’s Mitigations and Their Limits
Apple has implemented several mitigations specifically targeting JIT exploitation:
JIT Cage: Prevents writing to JIT-compiled code regions. Effective but bypassable with sufficient arbitrary R/W (as DarkSword demonstrated via CVE-2026-20700).
PAC (Pointer Authentication Codes): Signs function pointers with hardware cryptographic tags. Prevents naive function pointer overwriting. Bypassable via CVE-2026-20700 in dyld.
Gigacage: Restricts where certain sensitive memory regions can be allocated. Raises the bar for butterfly pointer manipulation.
Lockdown Mode’s JIT disable: The strongest mitigation — disabling JIT compilation entirely in Safari under Lockdown Mode. Eliminates the entire attack class at the cost of performance. DarkSword Stage 1 cannot function without JIT.
Patch Analysis: What Apple Fixed
Apple’s advisory stated: “Improved memory handling” — characteristically minimal. Based on the vulnerability class and patch timing (iOS 18.6, August 2025), the fix likely involved one or more of:
Type check hardening:
1
2
3
4
5
6
7
8
9
10
11
12
// Before (vulnerable — pseudocode):
if (node->hasResult() && speculativeType == SpecInt32) {
// Generate optimized integer code — no re-check at runtime
generateFastIntegerOperation(node);
}
// After (patched — pseudocode):
if (node->hasResult() && speculativeType == SpecInt32) {
// Add runtime type guard — verify assumption before use
addTypeCheck(node, SpecInt32, ExitKind::BadType);
generateFastIntegerOperation(node);
}
GC write barrier strengthening: Ensuring that when the GC marks an object for collection, any DFG type speculation data referencing that object is immediately invalidated — preventing use-after-free through stale type profiles.
Bailout condition coverage: Adding additional deoptimization triggers for edge cases where the type assumption could be violated in previously unchecked ways.
Detection
For Security Researchers / iOS Forensics
CVE-2025-31277 exploitation occurs entirely in-memory within the JIT-compiled execution context. Classic detection approaches:
Crash logs (partial):
1
2
3
4
5
Settings → Privacy → Analytics → Analytics Data
Look for:
- WebContent crashes near suspicious JavaScript execution
- jsc.crash logs with DFG-related stack frames
- JSC::DFG::* function names in crash traces
Memory analysis:
- iVerify and similar tools can detect kernel-level anomalies resulting from DarkSword exploitation
- Direct memory forensics on jailbroken/research devices: look for unusually large WebContent process memory maps
- Butterfly pointer anomalies in JS heap (requires specialized tooling)
Network behavioral detection:
- After Stage 1 succeeds, DarkSword proceeds rapidly — look for burst HTTPS traffic from Safari
- Connections to attacker C2 from WebContent process immediately after page load
For Blue Teams
1
2
3
4
5
6
7
8
9
Network monitoring:
- Alert on HTTPS connections from mobile devices to newly registered domains
- Detect unusual POST requests with large encrypted bodies immediately after page loads
- Flag DNS queries to DarkSword-associated infrastructure (use threat intel feeds)
MDM / Endpoint:
- Enforce minimum iOS version policy
- Alert on devices running iOS < 18.7.6 accessing corporate resources
- Deploy mobile threat detection (Lookout, Zimperium, iVerify)
Mitigation
Patch Status by Platform
| Platform | Vulnerable Range | Patched Version |
|---|---|---|
| iOS / iPadOS | < 18.6 | 18.6 (CVE-2025-31277 fixed) |
| Safari | < 18.6 | 18.6 |
| macOS Sequoia | < 15.6 | 15.6 |
| watchOS | < 11.6 | 11.6 |
| visionOS | < 2.6 | 2.6 |
| tvOS | < 18.6 | 18.6 |
Note: CVE-2025-31277 was patched in iOS 18.6. DarkSword’s operators switched to CVE-2025-43529 (patched in iOS 18.7.3) for newer devices. Full DarkSword protection requires iOS 18.7.6 or iOS 26.3.1+ which patch all six chain CVEs.
Recommendations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. Update ALL Apple devices to latest available iOS
Priority: iOS 18.7.6 or iOS 26.3.1
2. Enable Lockdown Mode for high-risk individuals
Disables JIT in Safari → blocks CVE-2025-31277 class entirely
3. Enterprise MDM:
Enforce minimum OS version, block non-compliant device access
4. For security researchers:
Study this class of vulnerability via:
- WebKit source: https://github.com/WebKit/WebKit
- JSC DFG documentation: https://docs.webkit.org/Deep%20Dive/JSC/
- Prior DFG bug writeups (links in references)
Conclusion
CVE-2025-31277 is a textbook example of the JIT compiler security problem: an engine that must make aggressive type assumptions to deliver performance will, eventually, have those assumptions exploited by attackers willing to study its internals.
What elevates CVE-2025-31277 from an interesting browser bug to a critical threat is its weaponization within DarkSword. As Stage 1 of a six-stage full-chain iOS exploit, it provided the initial arbitrary memory access needed to defeat every subsequent security layer — PAC, sandboxes, kernel isolation — ultimately delivering GHOSTBLADE spyware to hundreds of millions of iPhones without any visible indication of compromise.
The lesson for the security community: browser JIT engines are permanent attack surfaces. Apple’s Lockdown Mode — which disables JIT entirely — is the only reliable protection against this class of attack for high-risk individuals. For everyone else: patch aggressively, enable automatic updates, and understand that every unpatched iOS device running Safari is a potential entry point for nation-state-grade exploitation.
References
- Google Threat Intelligence — DarkSword iOS Exploit Chain
- NVD — CVE-2025-31277
- Apple Security Updates — iOS 18.6
- WebKit Blog — Speculation in JavaScriptCore
- WebKit Docs — JSC Type Inference
- Exodus Intel — Arbitrary R/W in WebKit
- LiveOverflow — Arbitrary R/W in WebKit
- CyberArk — The Mysterious Realm of JavaScriptCore
- GitHub Blog — Getting RCE in Chrome with JIT Side Effects
- Theori — Patch Gapping a Safari Type Confusion
- iVerify — DarkSword Explained
- The Hacker News — DarkSword iOS Exploit Kit
- DarkSword The Six-Stage iOS Exploit Kit Used by States, Spies, and Surveillance Vendors
This post is intended for security researchers, browser security engineers, and mobile threat analysts. All code samples are conceptual and based on publicly documented vulnerability classes. No functional exploit code is provided or implied.