← All issues

[JSC] Fix memory.init's overflow check

Component: JSC WebAssembly | de45b9b

WebAssembly's memory64 proposal lets linear memory addresses be 64-bit instead of 32-bit. memory.init copies bytes from a data segment into linear memory, and Memory::init guards this with an overflow-safe check on offset + length before comparing against the memory's size. Because destination offsets can now exceed 2^32 under memory64, that check must be computed in 64-bit arithmetic or it misfires on valid calls.

Source/JavaScriptCore/wasm/WasmMemory.cpp

bool Memory::init(uint64_t offset, const uint8_t* data, uint32_t length)
{
- if (sumOverflows<uint32_t>(offset, length))
+ if (sumOverflows<uint64_t>(offset, length))
return false;
 
if (offset + length > m_handle->size())
return false;

JSTests/wasm/wasm/stress/memory64-bulk-memory.js

+async function testInitOperandTypes() {
+ await assert.throwsAsync(compile(`
+ (module (memory i64 1) (data "hello")
+ (func (memory.init 0 (i64.const 0) (i64.const 0) (i32.const 5))))
+ `, { memory64: true }), WebAssembly.CompileError,
+ validationError("src address to type I64 expected I32"));

The commit widens the overflow check to uint64_t and deletes the dead Memory::fill / Memory::copy implementations, which bulk-memory operations no longer reach — those go through Wasm::memoryFill / Wasm::memoryCopy. The new test pins memory.init's operand types: only the destination offset widens to i64 for memory64, while the source offset and length stay i32, since data segment sizes are capped at 32 bits by the module encoding regardless of the memory's addressing mode (unlike memory.fill/memory.copy, whose lengths do widen).

The old 32-bit check was too narrow rather than too permissive: a legitimate memory.init targeting a destination at or above 4 GiB was wrongly rejected. The uint64_t check now matches the memory's actual address type while still catching a genuine 64-bit wraparound of offset + length. No OOB is introduced or fixed — this is a correctness fix, not a security fix. The deleted Memory::fill/Memory::copy weren't buggy either; they already selected the overflow width via addressType().is64Bit() ? sumOverflows<uint64_t> : sumOverflows<uint32_t>, they were just superseded.

The forward-facing question is whether every surviving bulk-memory and bulk-table path picks its overflow width from the address type rather than hardcoding one. Narrow: confirm Wasm::memoryFill and Wasm::memoryCopy — the implementations that outlived the deleted ones — carry the address-type-dependent width through, since Memory::init needed exactly the same fix applied. Wider: extend the same check to table.init and table.copy, where the table64 proposal creates the identical 32-vs-64 width choice on a different index space. Widest: any overflow guard whose width is chosen by a template parameter rather than derived from the operand's declared type is a candidate for drift the moment a wider addressing mode is introduced; the review tell is a sumOverflows<uint32_t> (or equivalent) literal width sitting in a function whose parameters are already uint64_t.