CVE-2024-23222 / iOS Zero-Day

CVE-2024-23222: Apple Safari WebKit Object Unboxing Teardown

How JavaScriptCore's DFG JIT compiler failed to verify object type tags before unboxing, allowing targeted spyware to bypass Pointer Authentication (PAC) on iOS and macOS.

💡 Plain English Explainer (ELI5)

Apple devices have special hardware called Pointer Authentication (PAC) that stamps memory addresses with cryptographic signatures so hackers can't forge them. But in Safari's JavaScript engine, a math helper took an untrusted object and stripped off its label without verifying what it was. This allowed attackers to trick the processor into signing a fake pointer, letting targeted spyware take over Safari.

Core Concepts & Subsystem Terms

JavaScriptCore (JSC)
The open-source JavaScript engine powering Safari and all iOS web browsers.
DFG (Data Flow Graph) JIT
The mid-tier optimizing compiler in JavaScriptCore that optimizes types based on observed execution profiling.
Pointer Authentication Code (PAC)
An ARM64e hardware security feature that uses cryptographic signatures to validate memory pointers before execution.
Speculative Unboxing
Extracting raw integer or pointer values from NaN-boxed JavaScript values under the assumption that the type remains valid.

Step-by-Step Exploit Mechanics

Step 1

1. Malicious Web Page Navigation

The victim navigates to an attacker-controlled web page in Safari on iOS or macOS.

Step 2

2. DFG Speculation Generation

The script executes an object unboxing routine in a tight loop. JavaScriptCore's DFG compiler speculates that the input is always a native JS object.

Step 3

3. Type Tag Strip Without Verification

The compiled bytecode strips the NaN-box metadata tag without emitting a type assertion guard.

Step 4

4. PAC Forgery & Sandbox Escape

The attacker supplies a disguised object structure, forging a signed pointer to execute shellcode and begin the secondary kernel privilege escalation chain.

Source Code: Fatal Flaw vs. High-Level Fix

Delivered in clean, readable high-level source code (no raw assembly or binary diffs).

UNPATCHED FLAW
// VULNERABLE: C++ Logic from WebKit/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
void DFGSpeculativeJIT::compileUnboxObject(Node* node) {
    Edge edge = node->child1();
    GPRReg jsValueGPR = edge.useInfo().gpr();
    GPRReg resultGPR = node->gpr();

    // CRITICAL ROOT CAUSE:
    // Speculatively stripped the TagMask from JSValue without verifying
    // that the value actually satisfied isCell() and was an Object pointer!
    m_jit.move(jsValueGPR, resultGPR);
    m_jit.and64(TrustedImm64(TagMask), resultGPR);
    
    // Resulting pointer assumed authenticated without PAC validation!
}
HARDENED PATCH
// SECURE: Open-Source C++ Patch from WebKit Repository
void DFGSpeculativeJIT::compileUnboxObject(Node* node) {
    Edge edge = node->child1();
    GPRReg jsValueGPR = edge.useInfo().gpr();
    GPRReg resultGPR = node->gpr();

    // 1. Emit explicit type tag assertion guard
    MacroAssembler::Jump notCell = m_jit.branchIfNotCell(jsValueGPR);
    speculationCheck(BadType, JSValueRegs(jsValueGPR), edge.node(), notCell);

    // 2. Verify object structure cell before unmasking pointer
    m_jit.move(jsValueGPR, resultGPR);
    m_jit.and64(TrustedImm64(TagMask), resultGPR);
    
    // 3. Ensure PAC authentication check verifies target pointer integrity
    speculationCheck(BadType, JSValueRegs(jsValueGPR), edge.node(),
                     m_jit.branchTest8(MacroAssembler::Zero, 
                                       MacroAssembler::Address(resultGPR, JSCell::typeInfoTypeOffset())));
}

Engineering & System Hardening Checklist

← Browse Full Security Directory Explore All Source Teardowns →