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.
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.
The victim navigates to an attacker-controlled web page in Safari on iOS or macOS.
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.
The compiled bytecode strips the NaN-box metadata tag without emitting a type assertion guard.
The attacker supplies a disguised object structure, forging a signed pointer to execute shellcode and begin the secondary kernel privilege escalation chain.
Delivered in clean, readable high-level source code (no raw assembly or binary diffs).
// 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!
}
// 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())));
}