High firefox Memory Corruption 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionMemory safety bugs present in Firefox ESR 140.12 and Firefox 152. Some of these bugs showed evidence of memory corruption and we presume that with enough effort some of these could have been exploited to run arbitrary code.
ComponentSpiderMonkey
Bug ClassMemory Corruption
Tracker2043035
Fix commitb8a1987ee374 (firefox) +4/-1
CISA KEVNot listed
CreditedChristian Holler, Frederik Braun, Justin Link, Simon Friedberger, Tom Ritter, Tom Schuster and the Mozilla Fuzzing Team
Disclosed2026-07-21

Background

Realm
A SpiderMonkey security/GC compartment scope that owns a set of JS objects and defines which global they belong to.
`AutoRealmUnchecked`
An RAII helper that temporarily sets cx->realm() to a target realm without the normal security-membrane checks.
WebAssembly table
A wasm growable array of references (here funcref elements) that can hold exported functions callable across module and realm boundaries.
`Instance::getExportedFunction`
The wasm Instance method that lazily creates or returns the JSFunction wrapper for an exported wasm function.

Root Cause Analysis

The vulnerable path is Table::getFuncRef in js/src/wasm/WasmTable.cpp, which reads a funcref element out of a wasm table and calls instance.getExportedFunction to materialize the corresponding JSFunction. Because a table element can reference an Instance living in a different Realm than the currently active cx->realm(), this call could run while the context was entered in the wrong realm. Instance::getExportedFunction allocates and caches the exported-function object and touches per-instance data, all of which implicitly assume the context realm matches the instance’s own realm; that invariant (realm() == cx->realm()) was silently violated.

The fix wraps the call in AutoRealmUnchecked ar(cx, instance.realm()) so the context is entered into the instance’s realm before the function object is created, and hardens Instance::getExportedFunction with a MOZ_RELEASE_ASSERT(realm() == cx->realm()) so any future caller that breaks the invariant crashes safely instead of corrupting memory. This ensures objects are allocated and cached in the correct realm, keeping GC and object-provenance bookkeeping consistent.

Key insight
The single mistake was materializing a cross-realm exported wasm function without first entering that instance’s realm, so allocation and caching happened against a mismatched cx->realm(). The fix enters the instance’s realm via AutoRealmUnchecked at the call site and enforces the realm-match invariant with a release assertion inside getExportedFunction.

Attack Path

  1. Cross-realm table setup Attacker script obtains a wasm Table whose funcref elements point at exported functions belonging to an Instance in a different Realm than the caller’s.
  2. Trigger the read The attacker reads that element (for example via Table.prototype.get or a wasm table.get path) so Table::getFuncRef runs while cx->realm() is the wrong realm.
  3. Wrong-realm materialization Instance::getExportedFunction lazily creates and caches the JSFunction while the context is entered in the mismatched realm, associating the object with the wrong compartment.
  4. Realm/GC inconsistency The mis-attributed object breaks realm and garbage-collection provenance assumptions, producing exploitable memory corruption.

Impact Assessment

An attacker running attacker-controlled JavaScript/WebAssembly in the content process gains a memory-corruption primitive that Mozilla assesses could be leveraged toward arbitrary code execution. The precondition is the ability to construct a wasm module/instance and a Table holding a funcref to an export from an instance in a different realm, then read that element. The corruption occurs in the SpiderMonkey engine within the affected content process context.

Files Changed

  • js/src/wasm/WasmInstance.cpp
  • js/src/wasm/WasmTable.cpp

Audit Directions

  • Cross-realm lazy allocation
    Audit every path that reads funcref/anyref values or wasm exports and lazily creates JS objects, confirming an AutoRealm/AutoRealmUnchecked guard enters the owning instance’s realm first.
  • Realm invariant assertions
    Look for engine methods that assume realm() == cx->realm() but lack a MOZ_RELEASE_ASSERT, and add or verify such guards on object-creating entry points.
  • Table and instance boundaries
    Review WasmTable.cpp and WasmInstance.cpp call sites that pass cx across Instance boundaries, ensuring the context realm is aligned with the target Instance before allocation or caching.
diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp
index 8493d4d994e..a12cf979ea4 100644
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -3895,6 +3895,8 @@ static bool WasmCall(JSContext* cx, unsigned argc, Value* vp) {
 
 bool Instance::getExportedFunction(JSContext* cx, uint32_t funcIndex,
                                    MutableHandleFunction result) {
+  MOZ_RELEASE_ASSERT(realm() == cx->realm());
+
   uint32_t funcExportIndex = codeMeta().findFuncExportIndex(funcIndex);
   FuncExportInstanceData& instanceData =
       funcExportInstanceData(funcExportIndex);
diff --git a/js/src/wasm/WasmTable.cpp b/js/src/wasm/WasmTable.cpp
index 2bcdcf43f23..daf45189206 100644
--- a/js/src/wasm/WasmTable.cpp
+++ b/js/src/wasm/WasmTable.cpp
@@ -19,12 +19,12 @@
 #include "mozilla/CheckedInt.h"
 
 #include "vm/JSContext.h"
-#include "vm/Realm.h"
 #include "wasm/WasmInstance.h"
 #include "wasm/WasmJS.h"
 #include "wasm/WasmValue.h"
 
 #include "gc/StableCellHasher-inl.h"
+#include "vm/Realm-inl.h"
 #include "wasm/WasmInstance-inl.h"
 
 using namespace js;
@@ -171,6 +171,7 @@ bool Table::getFuncRef(JSContext* cx, uint32_t address,
 
   Instance& instance = *elem.instance;
   const CodeRange& codeRange = *instance.code().lookupFuncRange(elem.code);
+  AutoRealmUnchecked ar(cx, instance.realm());
   return instance.getExportedFunction(cx, codeRange.funcIndex(), fun);
 }
 
Loading diff…