[JSC] IPInt slow path for `memory.atomic.notify` truncates the Memory64 pointer and offset to 32 bits
A Memory64 address of 2^32 looked like address 0 to the bounds check.
Component: JSC | afc44e3
Source/JavaScriptCore/wasm/WasmIPIntSlowPaths.cpp
JSTests/wasm/stress/memory64-atomic-notify-out-of-bounds.js
When IPInt cannot handle an instruction inline in assembly, it falls back to a C++ slow path, passing arguments through an IPIntStackEntry union whose .i32 and .i64 members share storage — reading the wrong member silently drops the upper bits with no runtime error. memory.atomic.notify is the Wasm equivalent of a futex wake, and its slow path read both the 64-bit address operand and the immediate offset through .i32. The fix changes both to .i64. Because the assembly fast path always passed full 64-bit values, the bug was invisible to Memory32 modules and only surfaced when a Memory64 address had its upper 32 bits set.
IPInt assembly fast path
└─► pushes args[0]=offset (i64), args[3]=base (i64) onto stack as full 64-bit values
BEFORE (buggy slow path):
offset = args[0].i32 → 0x1_0000_0000 truncates to 0x00000000
base = args[3].i32 → 0x1_0000_0000 truncates to 0x00000000
bounds_check(0x00000000) → IN BOUNDS → no trap ✗
AFTER (fixed slow path):
offset = args[0].i64 → 0x1_0000_0000 preserved
base = args[3].i64 → 0x1_0000_0000 preserved
bounds_check(0x1_0000_0000) → OUT OF BOUNDS → trap ✓
Significance
An out-of-bounds address such as 2^32 truncated to 0 and looked in-bounds, so memory.atomic.notify operated on address 0 of linear memory instead of trapping. This is a confirmed bounds check bypass in the Memory64 slow path, and anyone auditing atomic instruction safety in Memory64 contexts should treat it as a confirmed vulnerability class rather than an isolated slip.
Audit directions
The .i32 read from a union storing .i64 values is a mechanical pattern that copy-paste reproduces easily, and there is no reason to assume it is confined to memory_atomic_notify. Audit WasmIPIntSlowPaths.cpp for every other atomic instruction — memory_atomic_wait32, memory_atomic_wait64, the atomic.rmw.* family, atomic.store, atomic.load — and every other Memory64-capable memory instruction slow path, for the same args[N].i32 versus args[N].i64 mismatch; a single missed instance produces the identical truncation. In review, any unsigned/uint32_t local receiving an address or offset in a Memory64-capable slow path is the tell — the declared C++ type is the giveaway before the union member even matters. Separately, verify that the fix's treatment of memoryIndex and count is right: both are read as .i32 and appear genuinely 32-bit, but that assumption should be confirmed against what the assembly fast path actually pushes.