High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionIncorrect boundary conditions in the JavaScript: WebAssembly component
ComponentSpiderMonkey
Bug ClassLogic Error
Tracker2051015
Fix commit20715139c2a9 (firefox) +8/-2
CISA KEVNot listed
CreditedNebula Security
Disclosed2026-07-21

Background

`Instance::init`
the SpiderMonkey routine that finalizes a WebAssembly module instance, including allocating GC bookkeeping structures.
`gc::AllocSite`
a GC metadata record; a Wasm instance allocates an array of these, one per allocation site described in the module’s compiled metadata.
`mozilla::CheckedInt`
a Mozilla utility integer type that tracks arithmetic overflow and exposes isValid() to report whether a computed value is representable.
`js_malloc`
SpiderMonkey’s raw heap allocator, which takes a size_t byte count and performs no overflow checking on the caller’s size arithmetic.

Root Cause Analysis

In Instance::init, the array of gc::AllocSite structures was sized with the expression sizeof(gc::AllocSite) * allocSitesCount, where allocSitesCount comes from codeTailMeta().numAllocSites. Both operands are unsigned but the multiplication is performed without any overflow check, so a sufficiently large allocSitesCount (attacker-influenced through the compiled Wasm module’s metadata) causes the byte count to wrap around modulo size_t, yielding a small allocation. The invariant violated is that the number of bytes passed to js_malloc must actually equal sizeof(gc::AllocSite) times the element count; when the product overflows, js_malloc returns an undersized buffer while later code treats it as holding allocSitesCount full elements.

The fix computes the product with mozilla::CheckedInt<size_t>, calls isValid() before use, and bails out with ReportOutOfMemory and return false when the multiplication would overflow, guaranteeing the allocation is either correctly sized or refused.

Key insight
The single core mistake was an unchecked size_t multiplication of an attacker-controlled element count by a fixed element size when computing an allocation length; the fix routes that arithmetic through mozilla::CheckedInt and treats overflow as an out-of-memory failure rather than silently allocating a truncated buffer.

Attack Path

  1. Craft a malicious module An attacker authors a WebAssembly module whose compiled metadata yields a numAllocSites value large enough that sizeof(gc::AllocSite) * allocSitesCount overflows size_t.
  2. Trigger instantiation The victim page instantiates the module, driving Instance::init down the allocSitesCount > 0 branch.
  3. Force the overflow The unchecked multiplication wraps to a small byte count, so js_malloc succeeds but returns a buffer far smaller than allocSitesCount elements require.
  4. Provoke out-of-bounds access Subsequent code that indexes allocSites_ as if it holds allocSitesCount full gc::AllocSite entries reads and writes past the undersized allocation, corrupting adjacent heap memory.

Impact Assessment

An attacker who can get a victim to load a crafted WebAssembly module gains an out-of-bounds heap access inside the content process running SpiderMonkey, which can lead to heap corruption and potential further exploitation. The only precondition is the ability to instantiate attacker-supplied Wasm, which is reachable from ordinary untrusted web content. The severity is rated high, consistent with a memory-safety defect triggerable from the JavaScript/WebAssembly surface.

Changed Functions

FunctionChangeNotes
if
js/src/wasm/WasmInstance.cpp
modified

Files Changed

  • js/src/wasm/WasmInstance.cpp

Audit Directions

  • Unchecked allocation multiplication
    Search for js_malloc, js_calloc, and similar allocators fed sizeof(T) * count where count derives from module or wire data, and require mozilla::CheckedInt or equivalent overflow-safe sizing.
  • Attacker-controlled metadata counts
    Audit other consumers of codeTailMeta() and Wasm metadata counts to confirm each element count is bounds-validated before it drives array sizing or indexing.
  • Truncated buffer, full-count use
    Look for patterns where an allocation size is computed with one expression but the buffer is later indexed with the raw element count, so a wrapped or truncated size produces silent out-of-bounds access.
diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp
index ba90b09b691..bf723f85dff 100644
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -2632,8 +2632,14 @@ bool Instance::init(JSContext* cx, const JSObjectVector& funcImports,
   // Create and initialize alloc sites, they are all the same for Wasm.
   uint32_t allocSitesCount = codeTailMeta().numAllocSites;
   if (allocSitesCount > 0) {
-    allocSites_ =
-        (gc::AllocSite*)js_malloc(sizeof(gc::AllocSite) * allocSitesCount);
+    mozilla::CheckedInt<size_t> numBytesRequired =
+        mozilla::CheckedInt<size_t>(allocSitesCount) *
+        mozilla::CheckedInt<size_t>(sizeof(gc::AllocSite));
+    if (!numBytesRequired.isValid()) {
+      ReportOutOfMemory(cx);
+      return false;
+    }
+    allocSites_ = (gc::AllocSite*)js_malloc(numBytesRequired.value());
     if (!allocSites_) {
       ReportOutOfMemory(cx);
       return false;
Loading diff…