Released November 19, 2024

CVE-2024-44308

CVSS 8.8 severity high ⚠ CISA KEV vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Overview

Impact:

Description: Processing maliciously crafted web content may lead to arbitrary code execution. Apple is aware of a report that this issue may have been actively exploited on Intel-based Mac systems.

Researchers: Clément Lecigne and Benoît Sevens of Google’s Threat Analysis Group

Key insight. All scratch registers a JIT path needs must be reserved before operand setup; reserving scratch2 after getIntTypedArrayStoreOperand let it alias a live register and mis-compile the store.

Patch Details

diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
index 32f08d29e1c1..80b9a3b50f0a 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
@@ -3578,6 +3578,14 @@ void SpeculativeJIT::compilePutByValForIntTypedArray(Node* node, TypedArrayType
         }
     }
 
+    GPRReg scratch2GPR = InvalidGPRReg;
+#if USE(JSVALUE64)
+    if (node->arrayMode().mayBeResizableOrGrowableSharedTypedArray()) {
+        scratch2.emplace(this);
+        scratch2GPR = scratch2->gpr();
+    }
+#endif
+
     bool result = getIntTypedArrayStoreOperand(
         value, propertyReg,
 #if USE(JSVALUE32_64)
@@ -3589,14 +3597,6 @@ void SpeculativeJIT::compilePutByValForIntTypedArray(Node* node, TypedArrayType
         return;
     }
 
-    GPRReg scratch2GPR = InvalidGPRReg;
-#if USE(JSVALUE64)
-    if (node->arrayMode().mayBeResizableOrGrowableSharedTypedArray()) {
-        scratch2.emplace(this);
-        scratch2GPR = scratch2->gpr();
-    }
-#endif
-
     GPRReg valueGPR = value.gpr();
     GPRReg scratchGPR = scratch.gpr();
 #if USE(JSVALUE32_64)

Background

Advisory: Apple Security Advisory

Attribute Value
CVE CVE-2024-44308
Bugzilla 283063
Component WebKit
Bug Class OOB
Severity critical
Commit 82abacffb221fb67…
Advisory Apple Advisory

Terminology

  • DFG SpeculativeJIT — JavaScriptCore’s speculative optimizing JIT that emits machine code for typed-array stores.
  • Scratch register (scratch2) — A temporary GPR the emitted code needs; it must be reserved before other operands claim registers so it cannot alias.
  • Resizable/growable typed array — Typed arrays over resizable ArrayBuffers / growable SharedArrayBuffers needing extra length handling.

Root Cause Analysis

This fixes a DFG JIT register-allocation ordering bug in JavaScriptCore’s typed-array store fast path. In SpeculativeJIT::compilePutByValForIntTypedArray, on JSVALUE64 a scratch2 GPR is reserved only when the array mode may be a resizable-or-growable-shared typed array (mayBeResizableOrGrowableSharedTypedArray()), where it is needed for the extra length/bounds handling those arrays require. Before the fix, scratch2 was allocated AFTER getIntTypedArrayStoreOperand(…) had already assigned value/property/scratch registers; the fix moves the scratch2 reservation to BEFORE that call. The violated invariant is that all scratch registers a code path needs must be reserved before other operands claim registers, so they cannot alias. Allocating scratch2 late means it could collide with a register already in use for the store, so for resizable/growable typed arrays the generated code used a clobbered register in the bounds check or store-address computation, producing an out-of-bounds write in the JIT-compiled store. That is a powerful memory-corruption primitive, consistent with the critical ‘arbitrary code execution’ impact and Apple’s note of active exploitation on Intel Macs. Reordering the allocation guarantees scratch2 is a distinct register. INFERENCE: the exact clobbered register/instruction is not printed by the diff; the commit establishes the allocation-order change on the resizable-typed-array path.

Attack Path

1. Create a resizable/growable typed array

JS allocates a typed array backed by a resizable ArrayBuffer or growable SharedArrayBuffer so arrayMode().mayBeResizableOrGrowableSharedTypedArray() holds.

2. Trigger DFG compilation of a store

Hot code repeatedly writes into the typed array so the DFG compiles compilePutByValForIntTypedArray for it.

3. Miscompiled store uses a clobbered register

With scratch2 reserved too late, the emitted bounds/address computation aliases a live register on the resizable path.

4. Out-of-bounds write

The store writes outside the typed array’s backing store, corrupting adjacent heap memory.

5. Escalate to code execution

With the heap groomed, the OOB write corrupts an adjacent object to build an exploitation primitive and achieve arbitrary code execution (standard escalation; actively exploited on Intel Macs per the advisory).

Impact Assessment

An out-of-bounds write from a JIT register-allocation ordering bug on the resizable-typed-array path; critical and actively exploited on Intel Macs. A classic strong primitive that groom-and-corrupt escalates to arbitrary code execution in WebContent.

Audit Directions

Scratch allocation ordering in the JITs

Audit DFG/FTL emitters where scratch/tmp registers are reserved after operand materialization; look for early-return paths between reservation and use.

Resizable/growable typed-array code paths

Review mayBeResizableOrGrowableSharedTypedArray branches for correct bounds/length register usage.

Changed Functions

Function File Change Note
SpeculativeJIT::compilePutByValForIntTypedArray Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp modified Moves the JSVALUE64 scratch2 GPR reservation for resizable/growable-shared typed arrays to before getIntTypedArrayStoreOperand(), preventing the scratch register from aliasing operands and mis-computing the store on that path.