CVE-2026-16368
Overview
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_tbyte 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.
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
- Craft a malicious module
An attacker authors a WebAssembly module whose compiled metadata yields a
numAllocSitesvalue large enough thatsizeof(gc::AllocSite) * allocSitesCountoverflowssize_t. - Trigger instantiation
The victim page instantiates the module, driving
Instance::initdown theallocSitesCount > 0branch. - Force the overflow
The unchecked multiplication wraps to a small byte count, so
js_mallocsucceeds but returns a buffer far smaller thanallocSitesCountelements require. - Provoke out-of-bounds access
Subsequent code that indexes
allocSites_as if it holdsallocSitesCountfullgc::AllocSiteentries reads and writes past the undersized allocation, corrupting adjacent heap memory.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifjs/src/wasm/WasmInstance.cpp |
modified |
Files Changed
js/src/wasm/WasmInstance.cpp
Audit Directions
- Unchecked allocation multiplicationSearch for
js_malloc,js_calloc, and similar allocators fedsizeof(T) * countwherecountderives from module or wire data, and requiremozilla::CheckedIntor equivalent overflow-safe sizing. - Attacker-controlled metadata countsAudit 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 useLook 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.
Patch
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;