Closed Bug 2008884 Opened 10 months ago Closed 9 months ago

WASM Bounds Check Elimination (BCE) out-of-bounds read/write (when custom page sizes is enabled)

Categories

(Core :: JavaScript: WebAssembly, defect, P1)

defect

Tracking

()

RESOLVED FIXED
149 Branch
Tracking Status
firefox-esr115 --- unaffected
firefox-esr140 --- unaffected
firefox147 --- disabled
firefox148 --- disabled
firefox149 --- fixed

People

(Reporter: pwning.me, Assigned: bvisness)

References

(Blocks 2 open bugs)

Details

(4 keywords)

Attachments

(1 file, 1 external file)

3.40 KB, text/javascript
Details
Attached file poc —

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36

Steps to reproduce:

Details

The popConstMemoryAccess function in the WebAssembly Baseline compiler incorrectly determines when to eliminate bounds checks. The vulnerable code compares only the effective address against the memory limit, without accounting for the access size.

Vulnerable Code

// js/src/wasm/WasmBCMemory.cpp:164
uint64_t ea = uint64_t(addr) + uint64_t(access->offset32());
uint64_t limit = codeMeta_.memories[access->memoryIndex()].initialLength() +
                 offsetGuardLimit;

check->omitBoundsCheck = ea < limit;  // BUG: ignores access->byteSize()

Correct Fix

check->omitBoundsCheck = (ea + access->byteSize()) <= limit;

Root Cause

The Bounds Check Elimination (BCE) optimization skips runtime bounds verification when the compiler can statically prove an access is safe. The bug is a classic off-by-size error:

  1. Bug Logic: Skip bounds check if ea < limit
  2. Correct Logic: Skip bounds check if ea + byteSize <= limit

When ea < limit but ea + byteSize > limit, the optimization incorrectly eliminates the bounds check, allowing OOB access.

Triggering Conditions

  1. Custom page sizes enabled (wasm_custom_page_sizes=true)
    • Custom page sizes set offsetGuardLimit = 0 (no guard pages)
    • This is required for the bug window to exist
  2. Constant address in WASM (triggers popConstMemoryAccess)
  3. Access near memory boundary where ea < limit < ea + byteSize

Trigger Path

  1. Enable custom page sizes pref
  2. Create WASM memory with custom page size (e.g., 1-byte pages)
  3. Use i32.const to push a constant address near memory limit
  4. Execute i64.load/i64.store (8-byte access) that crosses boundary
  5. BCE incorrectly skips bounds check -> OOB access occurs

Exploitation

Primitive Gained

  • OOB Read: Information leak from memory beyond WASM buffer
  • OOB Write: Memory corruption beyond WASM buffer

Exploitation Window

  • Maximum OOB: byteSize - 1 bytes (up to 15 bytes for v128)
  • The OOB access occurs within the system page-aligned allocation

Actual results:

anya@subagent ~/w/f/o/j/d/bin> ./js --setpref wasm_custom_page_sizes=true /tmp/poc.js                                                                     main?
Valid read at offset 4088: 0x4847464544434241
Expected:                  0x4847464544434241
Status: PASS

OOB read at offset 4095:
Result: 0x5806c455d24048
Status: BOUNDS CHECK BYPASSED - Read succeeded!

Byte Analysis:
  Byte 0 (offset 4095): 0x48 [valid] = 'H' (our data)
  Byte 1 (offset 4096): 0x40 [OOB LEAK]
  Byte 2 (offset 4097): 0xd2 [OOB LEAK]
  Byte 3 (offset 4098): 0x55 [OOB LEAK]
  Byte 4 (offset 4099): 0xc4 [OOB LEAK]
  Byte 5 (offset 4100): 0x06 [OOB LEAK]
  Byte 6 (offset 4101): 0x58 [OOB LEAK]
  Byte 7 (offset 4102): 0x00 [OOB LEAK]

TEST 2: Information Leak Statistical Analysis
----------------------------------------------------------------------
Samples collected: 100
Non-zero leaks: 5 (5.0%)
Pointer-like values: 0
Unique values: 5

leaked ptrs:
  0x0070870ba8b280
  0xda001020010324
  0x0070870a91a000
  0x006c870a638000
  0x006c87091f5000
Group: firefox-core-security → core-security
Component: Untriaged → JavaScript: WebAssembly
Product: Firefox → Core
Version: other → unspecified
Group: core-security → javascript-core-security
Summary: WASM BCE OOB READ/WRITE → WASM Bounds Check Elimination (BCE) out-of-bounds read/write (when custom page sizes is enabled)

This check has been around a long time, much longer than custom page sizes. Is it just easier to abuse with small pages, or are standard pages actually safe for some other reason?

Status: UNCONFIRMED → NEW
Ever confirmed: true
Flags: needinfo?(rhunt)

Preliminary thought: before custom page sizes, we could rely on access beyond the end to trap, at least within a page or so (https://searchfox.org/firefox-main/source/js/src/wasm/WasmMemory.cpp#289-292). But with custom page sizes, this is no longer the case. It does seem like a real bug, at first glance.

Yes, we generally rely on guard pages to catch accesses beyond the end of memory, including the byte size of the access. But indeed that is different with custom page sizes, since with one-byte “pages” the only thing to do is a proper bounds check. I will audit things this morning, since it’s possible that there are some other cases where this could trigger a problem, but I think this will likely be unique to custom page sizes.

Assignee: nobody → bvisness
Flags: needinfo?(rhunt)

This is legit but low-impact due to several mitigating factors:

  1. It requires custom page sizes, which are disabled by default.
  2. You can only read or write at most 15 bytes past the end of memory, since 16 bytes is the largest access size in wasm.
  3. The memory size must be a multiple of the system page size (or close to it) in order to access any meaningful data. This is because we round the actual allocation for the wasm memory up to the system page size so it is compatible with mmap/VirtualAlloc/etc.

If for example the memory size was 4000 bytes instead of 4096 (as in the poc), we would still allocate 4096 bytes for the memory. You could then do a load from address 3999, incorrectly accessing bytes 4000-4014, but yielding no interesting data because it is still part of the wasm memory allocation.

Additionally, it does not seem that any other aspects of bounds checking are affected. All other bounds checking paths in Baseline seem correct, and custom page sizes are not enabled yet in Ion.

Because this seems low-severity we will just put up a patch to fix this.

Attached file (secure) —

Access size was not accounted for, allowing for small reads off the end
of memory under specific circumstances, and only with custom page sizes
enabled.

Blocks: sm-security
Severity: -- → S2
Priority: -- → P1
Group: javascript-core-security → core-security-release
Status: NEW → RESOLVED
Closed: 9 months ago
Flags: in-testsuite+
Resolution: --- → FIXED
Target Milestone: --- → 149 Branch
QA Whiteboard: [sec] [qa-triage-done-c150/b149]
Flags: sec-bounty?

Unfortunately this bug is not eligible for our bounty program because it requires changing hidden prefs. Features that are enabled by default in "Nightly" and not "Release" can be eligible for the bounty, but if they are not even enabled on nightly then they are unfinished and untested and off-limits for the bounty program.

Flags: sec-bounty?
Flags: sec-bounty-
Flags: behind-pref+
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: