← All issues

[JSC][Wasm] Force BoundsChecking for Memory64 accesses in BBQ

Component: JSC WebAssembly | d6d0926

WebAssembly memory accesses in JSC's BBQ/OMG JIT tiers use one of two strategies. Signaling relies on mapping a huge PROT_NONE guard region after the memory so out-of-range 32-bit accesses trap via a SIGSEGV handler, avoiding explicit bounds-check instructions for speed. BoundsChecking emits explicit compare-and-branch instructions before every access. Signaling assumes addresses are 32-bit — the guard region is sized to catch any 32-bit offset — which breaks down for Memory64, where a 64-bit pointer plus offset can land far outside the guard region without tripping the trap.

Source/JavaScriptCore/wasm/WasmModuleInformation.h

+ MemoryMode memoryModeForAccess(unsigned memoryIndex, MemoryMode memory0Mode) const
+ {
+ if (memoryIndex || memory(memoryIndex).isMemory64())
+ return MemoryMode::BoundsChecking;
+ return memory0Mode;
+ }

Source/JavaScriptCore/wasm/WasmBBQJIT.h

- switch (memoryIndex ? MemoryMode::BoundsChecking : m_mode) {
+ switch (m_info.memoryModeForAccess(memoryIndex, m_mode)) {
case MemoryMode::BoundsChecking: { ... }
case MemoryMode::Signaling: {
- RELEASE_ASSERT(!m_info.memory(memoryIndex).isMemory64());
+ RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!m_info.memory(memoryIndex).isMemory64());

BBQ's pointer-materialization code previously forced bounds-checking only when memoryIndex != 0, otherwise falling through to the current m_mode — so a Memory64 memory at index 0 could still select the Signaling path. The policy is now centralized in ModuleInformation::memoryModeForAccess() and called from WasmBBQJIT.h, its 64-bit-CPU specialization WasmBBQJIT64.h, and OMG's index-0 memory-access path. The OMG call site sits inside an existing if (!memoryIndex) block, so its effect there is confined to a Memory64 memory at index 0.

Wasm memory-mode selection now lives in one policy function, so no JIT call site can independently pick the Signaling fast path for a Memory64 memory. The prior per-call-site check already hard-crashed via RELEASE_ASSERT if a Memory64 memory reached the Signaling path, so this is defense-in-depth against a latent inconsistency between BBQ and OMG call sites rather than a fix for a live exploitable OOB — and the assert is upgraded to RELEASE_ASSERT_WITH_SECURITY_IMPLICATION as a retained backstop.

Centralizing a policy is only as good as its call-site coverage, and the forward-facing hunt is for paths that still make the decision themselves. Narrow: check whether memoryModeForAccess is consulted at every BBQ/OMG site that emits a load or store, including fast paths for constant offsets, SIMD/vector loads, and bulk memory operations that may carry their own hand-rolled mode selection; separately, OMG's non-zero-memoryIndex path is untouched by this diff, so confirm it independently enforces BoundsChecking for Memory64. Wider: ask whether Memory64 combined with shared or growable memories can transiently reach a Signaling-mode compiled function via tiering, and whether the hardened RELEASE_ASSERT_WITH_SECURITY_IMPLICATION is actually reachable and testable rather than dead code — if memoryModeForAccess has any gap, that assert is the only thing between a mismatched mode and an exploitable OOB. Widest: any safety policy that was historically duplicated across call sites and is now consolidated leaves behind stragglers; the review tell is a ternary or switch scrutinee that re-derives a policy value locally when a shared accessor for that policy already exists in the same file.