Closed Bug 2043031 Opened 5 months ago Closed 5 months ago

Differential behavior: memory.init skips destination bounds check for dropped/active data segments

Categories

(Core :: JavaScript: WebAssembly, defect)

x86_64
Linux
defect

Tracking

()

RESOLVED DUPLICATE of bug 2042287

People

(Reporter: decoder, Unassigned)

Details

(Keywords: ai-involved, sec-audit, testcase)

Attachments

(1 file)

Summary

memory.init on a dropped or active (implicitly dropped at instantiation) data segment silently accepts an out-of-bounds destination offset instead of trapping, diverging from the WebAssembly specification and V8.

Root Cause

js/src/wasm/WasmInstance.cpp, function MemoryInit. When the referenced data segment has been dropped (maybeSeg == nullptr), the function takes a fast path:

if (!maybeSeg) {
  if (len == 0 && srcOffset == 0) {
    return 0;   // BUG: returns success without checking dstOffset against memLen
  }
  ReportTrapError(cx, JSMSG_WASM_OUT_OF_BOUNDS);
  return -1;
}

A dropped segment behaves as a zero-length segment. Per the WebAssembly spec, memory.init must still trap when dstOffset + len > memLen. With len == 0 this reduces to dstOffset > memLen. The fast path only validates the source side (srcOffset == 0 && len == 0) but never checks the destination offset against the memory length.

For a non-null (live) segment, control reaches BoundsCheckInit which correctly validates both source and destination bounds. This creates an internal inconsistency: a live zero-length segment traps on out-of-bounds dstOffset, but a dropped segment with the same offsets does not.

Reproduction

Tested with mozilla-central revision 20260521-b754e06486cf (ASan debug build).

Run poc.js with the SpiderMonkey JS shell:

$ m-c-asan-debug/dist/bin/js --fuzzing-safe poc.js
NO TRAP: memory.init accepted out-of-bounds destination on a dropped segment

Compare with V8:

$ d8 poc.js
RuntimeError: memory access out of bounds

poc.js constructs a binary wasm module with:

  • 1 page of memory (65536 bytes)
  • An active data segment (dropped at instantiation)
  • An exported function that calls memory.init 0 with the three parameters

The test invokes init(65537, 0, 0) — destination offset 65537 exceeds the 65536-byte memory. SpiderMonkey returns normally; V8 correctly traps.

Suggested Fix

In MemoryInit, route the null/dropped case through the destination bounds check instead of unconditionally returning success:

if (!maybeSeg) {
  WasmMemoryObject* mem = instance->memory(memoryIndex);
  const size_t memLen = mem->volatileMemoryLength();
  if (BoundsCheckInit(dstOffset, srcOffset, len, memLen, /*segLen=*/0)) {
    ReportTrapError(cx, JSMSG_WASM_OUT_OF_BOUNDS);
    return -1;
  }
  return 0;
}
Group: javascript-core-security
Status: NEW → RESOLVED
Closed: 5 months ago
Duplicate of bug: 2042287
Resolution: --- → DUPLICATE
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: