Moderate firefox Logic Error 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionUse-after-free in the JavaScript: WebAssembly component
ComponentSpiderMonkey
Bug ClassLogic Error
Tracker2071764
Fix commit8d04be6aff3d (firefox) +123/-60
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-13

Changed Functions

FunctionChangeNotes
if
js/src/jit/CodeGenerator.cpp
modified
if
js/src/jit/MacroAssembler.cpp
modified
if
js/src/wasm/WasmBaselineCompile.cpp
modified
FaultingCodeRange
js/src/wasm/WasmCodegenTypes.h
modified

Files Changed

  • js/src/jit/CodeGenerator.cpp
  • js/src/jit/MacroAssembler.cpp
  • js/src/jit/MacroAssembler.h
  • js/src/wasm/WasmBaselineCompile.cpp
  • js/src/wasm/WasmCodegenTypes.h
  • js/src/wasm/WasmStacks.cpp
  • js/src/wasm/WasmStubs.cpp
diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp
index c413ebc6724..7c8887508b3 100644
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -10568,13 +10568,17 @@ void CodeGenerator::visitWasmCall(LWasmCall* lir) {
       if (isReturnCall) {
         ReturnCallAdjustmentInfo retCallInfo(
             callBase->stackArgAreaSizeUnaligned(), inboundStackArgBytes_);
-        masm.wasmReturnCallIndirect(desc, callee, nullCheckFailed, retCallInfo);
+        // Discard the FaultingCodeRange returned by the following; we won't
+        // want to generate a stackmap here.
+        (void)masm.wasmReturnCallIndirect(desc, callee, nullCheckFailed,
+                                          retCallInfo);
         // The rest of the method is unnecessary for a return call.
         return;
       }
       MOZ_ASSERT(!isReturnCall);
-      masm.wasmCallIndirect(desc, callee, nullCheckFailed, &retOffset,
-                            &secondRetOffset);
+      // As above, discard the returned FaultingCodeRange.
+      (void)masm.wasmCallIndirect(desc, callee, nullCheckFailed, &retOffset,
+                                  &secondRetOffset);
       // Register reloading and realm switching are handled dynamically inside
       // wasmCallIndirect.  There are two return offsets, one for each call
       // instruction (fast path and slow path).
@@ -10661,7 +10665,7 @@ void CodeGenerator::visitWasmCall(LWasmCall* lir) {
     MOZ_ASSERT(!switchRealm);
   }
   if (reloadPinnedRegs) {
-    masm.loadWasmPinnedRegsFromInstance(mozilla::Nothing());
+    masm.loadWasmPinnedRegsFromInstance();
   }
 
   switch (callee.which()) {
diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp
index 44f057cd3b8..e308fb72491 100644
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -6300,7 +6300,10 @@ void MacroAssembler::appendAndVerify(wasm::Trap trap,
   }
 #endif
 
-  appendNoVerify(trap, insn, fcr, desc);
+  // See block comment at the definition of FaultingCodeRange.
+  if (fcr.isValid()) {
+    appendNoVerify(trap, insn, fcr, desc);
+  }
 }
 
 void MacroAssembler::appendAndVerify(const wasm::MemoryAccessDesc& access,
@@ -6426,7 +6429,7 @@ static ReturnCallTrampolineData MakeReturnCallTrampoline(MacroAssembler& masm) {
   masm.loadPtr(
       Address(masm.getStackPointer(), WasmCallerInstanceOffsetBeforeCall),
       InstanceReg);
-  masm.loadWasmPinnedRegsFromInstance(mozilla::Nothing());
+  masm.loadWasmPinnedRegsFromInstance();
   masm.switchToWasmInstanceRealm(ABINonArgReturnReg0, ABINonArgReturnReg1);
   masm.moveToStackPtr(FramePointer);
 #ifdef JS_CODEGEN_ARM64
@@ -6786,7 +6789,7 @@ CodeOffset MacroAssembler::wasmCallImport(const wasm::CallSiteDesc& desc,
 
   storePtr(InstanceReg,
            Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall));
-  loadWasmPinnedRegsFromInstance(mozilla::Nothing());
+  loadWasmPinnedRegsFromInstance();
 
   return wasmMarkedSlowCall(desc, ABINonArgReg0);
 }
@@ -6827,7 +6830,7 @@ CodeOffset MacroAssembler::wasmReturnCallImport(
 
   storePtr(InstanceReg,
            Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall));
-  loadWasmPinnedRegsFromInstance(mozilla::Nothing());
+  loadWasmPinnedRegsFromInstance();
 
   wasm::CallSiteDesc stubDesc(desc.bytecodeOffset(),
                               wasm::CallSiteKind::ReturnStub);
@@ -6937,11 +6940,11 @@ CodeOffset MacroAssembler::wasmTrapOnFailedInstanceCall(
 // call without the context switch (which additionally avoids a null check) and
 // one for the slow call with the context switch.
 
-void MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc,
-                                      const wasm::CalleeDesc& callee,
-                                      Label* nullCheckFailedLabel,
-                                      CodeOffset* fastCallOffset,
-                                      CodeOffset* slowCallOffset) {
+[[nodiscard]]
+FaultingCodeRange MacroAssembler::wasmCallIndirect(
+    const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee,
+    Label* nullCheckFailedLabel, CodeOffset* fastCallOffset,
+    CodeOffset* slowCallOffset) {
   static_assert(sizeof(wasm::FunctionTableElem) == 2 * sizeof(void*),
                 "Exactly two pointers or index scaling won't work correctly");
   MOZ_ASSERT(callee.which() == wasm::CalleeDesc::WasmTable);
@@ -7001,17 +7004,18 @@ void MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc,
   storePtr(InstanceReg,
            Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall));
 
+  FaultingCodeRange fcr;
 #ifdef WASM_HAS_HEAPREG
   // Use the null pointer exception resulting from loading HeapReg from a null
   // instance to handle a call to a null slot.
   MOZ_ASSERT(nullCheckFailedLabel == nullptr);
-  loadWasmPinnedRegsFromInstance(mozilla::Some(desc.toTrapSiteDesc()));
+  fcr = loadWasmPinnedRegsFromInstance(desc.toTrapSiteDesc());
 #else
   MOZ_ASSERT(nullCheckFailedLabel != nullptr);
   branchTestPtr(Assembler::Zero, InstanceReg, InstanceReg,
                 nullCheckFailedLabel);
 
-  loadWasmPinnedRegsFromInstance(mozilla::Nothing());
+  loadWasmPinnedRegsFromInstance();
 #endif
   switchToWasmInstanceRealm(index, WasmTableCallScratchReg1);
 
@@ -7024,7 +7028,7 @@ void MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc,
 
   loadPtr(Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall),
           InstanceReg);
-  loadWasmPinnedRegsFromInstance(mozilla::Nothing());
+  loadWasmPinnedRegsFromInstance();
   switchToWasmInstanceRealm(ABINonArgReturnReg0, ABINonArgReturnReg1);
   jump(&done);
 
@@ -7048,9 +7052,12 @@ void MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc,
   *fastCallOffset = call(newDesc, calleeScratch);
 
   bind(&done);
+
+  return fcr;
 }
 
-void MacroAssembler::wasmReturnCallIndirect(
+[[nodiscard]]
+FaultingCodeRange MacroAssembler::wasmReturnCallIndirect(
     const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee,
     Label* nullCheckFailedLabel, const ReturnCallAdjustmentInfo& retCallInfo) {
   static_assert(sizeof(wasm::FunctionTableElem) == 2 * sizeof(void*),
@@ -7104,17 +7111,18 @@ void MacroAssembler::wasmReturnCallIndirect(
            Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall));
   movePtr(newInstanceTemp, InstanceReg);
 
+  FaultingCodeRange fcr;
 #ifdef WASM_HAS_HEAPREG
   // Use the null pointer exception resulting from loading HeapReg from a null
   // instance to handle a call to a null slot.
   MOZ_ASSERT(nullCheckFailedLabel == nullptr);
-  loadWasmPinnedRegsFromInstance(mozilla::Some(desc.toTrapSiteDesc()));
+  fcr = loadWasmPinnedRegsFromInstance(desc.toTrapSiteDesc());
 #else
   MOZ_ASSERT(nullCheckFailedLabel != nullptr);
   branchTestPtr(Assembler::Zero, InstanceReg, InstanceReg,
                 nullCheckFailedLabel);
 
-  loadWasmPinnedRegsFromInstance(mozilla::Nothing());
+  loadWasmPinnedRegsFromInstance();
 #endif
   switchToWasmInstanceRealm(index, WasmTableCallScratchReg1);
 
@@ -7137,6 +7145,8 @@ void MacroAssembler::wasmReturnCallIndirect(
   wasmCollapseFrameFast(retCallInfo);
   jump(calleeScratch);
   append(wasm::CodeRangeUnwindInfo::Normal, currentOffset());
+
+  return fcr;
 }
 
 void MacroAssembler::wasmCallRef(const wasm::CallSiteDesc& desc,
@@ -7177,7 +7187,7 @@ void MacroAssembler::wasmCallRef(const wasm::CallSiteDesc& desc,
   storePtr(InstanceReg,
            Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall));
 
-  loadWasmPinnedRegsFromInstance(mozilla::Nothing());
+  loadWasmPinnedRegsFromInstance();
   switchToWasmInstanceRealm(WasmCallRefCallScratchReg0,
                             WasmCallRefCallScratchReg1);
 
@@ -7192,7 +7202,7 @@ void MacroAssembler::wasmCallRef(const wasm::CallSiteDesc& desc,
   // Restore registers and realm and back to this caller's.
   loadPtr(Address(getStackPointer(), WasmCallerInstanceOffsetBeforeCall),
           InstanceReg);
-  loadWasmPinnedRegsFromInstance(mozilla::Nothing());
+  loadWasmPinnedRegsFromInstance();
   switchToWasmInstanceRealm(ABINonArgReturnReg0, ABINonArgReturnReg1);
   jump(&done);
 
@@ -7250,7 +7260,7 @@ void MacroAssembler::wasmReturnCallRef(
   storePtr(InstanceReg,
            Address(getStackPointer(), WasmCalleeInstanceOffsetBeforeCall));
 
-  loadWasmPinnedRegsFromInstance(mozilla::Nothing());
+  loadWasmPinnedRegsFromInstance();
   switchToWasmInstanceRealm(WasmCallRefCallScratchReg0,
                             WasmCallRefCallScratchReg1);
 
@@ -8720,21 +8730,37 @@ void MacroAssembler::boundsCheck32PowerOfTwo(Register index, uint32_t length,
   }
 }
 
-void MacroAssembler::loadWasmPinnedRegsFromInstance(
-    const wasm::MaybeTrapSiteDesc& trapSiteDesc) {
 #ifdef WASM_HAS_HEAPREG
+// The target has a dedicated HeapReg, so load it from the Instance, and note
+// the load as possibly trapping.
+[[nodiscard]]
+FaultingCodeRange MacroAssembler::loadWasmPinnedRegsFromInstance(
+    const wasm::TrapSiteDesc& trapSiteDesc) {
   static_assert(wasm::Instance::offsetOfMemory0Base() < 4096,
                 "We count only on the low page being inaccessible");
   FaultingCodeRange fcr = loadPtr(
       Address(InstanceReg, wasm::Instance::offsetOfMemory0Base()), HeapReg);
-  if (trapSiteDesc) {
+  if (fcr.isValid()) {
     appendAndVerify(wasm::Trap::IndirectCallToNull,
-                    wasm::TrapMachineInsnForLoadWord(), fcr, *trapSiteDesc);
+                    wasm::TrapMachineInsnForLoadWord(), fcr, trapSiteDesc);
   }
+  return fcr;
+}
+
+// The same, but don't bother with the trapping aspect.
+void MacroAssembler::loadWasmPinnedRegsFromInstance() {
+  static_assert(wasm::Instance::offsetOfMemory0Base() < 4096);
+  FaultingCodeRange fcr = loadPtr(
+      Address(InstanceReg, wasm::Instance::offsetOfMemory0Base()), HeapReg);
+  (void)fcr;
+}
+
 #else
-  MOZ_ASSERT(!trapSiteDesc);
+
+// There's no dedicated HeapReg, so nothing to do.
+void MacroAssembler::loadWasmPinnedRegsFromInstance() {}
+
 #endif
-}
 
 //}}} check_macroassembler_style
 
diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h
index 73096c29751..1e1b24f8bb3 100644
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -3791,7 +3791,8 @@ class MacroAssembler : public MacroAssemblerSpecific {
 
   FaultingCodeRange wasmTrapInstruction() PER_SHARED_ARCH;
 
-  // Call here to register a trapping instruction in the metadata.
+  // Call here to register a trapping instruction in the metadata.  Calls
+  // supplying an invalid `fcr` are ignored.
   void appendAndVerify(wasm::Trap trap, wasm::TrapMachineInsn insn,
                        FaultingCodeRange fcr, const wasm::TrapSiteDesc& desc);
 
@@ -3802,11 +3803,17 @@ class MacroAssembler : public MacroAssemblerSpecific {
   FaultingCodeRange wasmTrap(wasm::Trap trap,
                              const wasm::TrapSiteDesc& trapSiteDesc);
 
-  // Load all pinned regs via InstanceReg.  If the trapOffset is something,
-  // give the first load a trap descriptor with type IndirectCallToNull, so that
-  // a null instance will cause a trap.
-  void loadWasmPinnedRegsFromInstance(
-      const wasm::MaybeTrapSiteDesc& trapSiteDesc);
+  // Load all pinned regs via InstanceReg.  Two variants: one that takes a
+  // TrapSiteDesc, attaches it to the first load, with type IndirectCallToNull,
+  // and returns the resulting FaultingCodeRange.  And one that does neither of
+  // those things.
+
+#ifdef WASM_HAS_HEAPREG
+  [[nodiscard]]
+  FaultingCodeRange loadWasmPinnedRegsFromInstance(
+      const wasm::TrapSiteDesc& trapSiteDesc);
+#endif
+  void loadWasmPinnedRegsFromInstance();
 
   // Branches to the fail label if the stack would overflow the current stack
   // limit. Returns the number of extra bytes of stack allocated prior to
@@ -4069,22 +4076,28 @@ class MacroAssembler : public MacroAssemblerSpecific {
   //
   // `boundsCheckFailedLabel` is non-null iff a bounds check is required.
   // `nullCheckFailedLabel` is non-null only on platforms that can't fold the
-  // null check into the rest of the call instructions.
-  void wasmCallIndirect(const wasm::CallSiteDesc& desc,
-                        const wasm::CalleeDesc& callee,
-                        Label* nullCheckFailedLabel, CodeOffset* fastCallOffset,
-                        CodeOffset* slowCallOffset);
+  // null check into the rest of the call instructions.  If a trap-based null
+  // check is generated, then its FaultingCodeRange is returned; otherwise
+  // `FaultingCodeRange()` is returned.
+  [[nodiscard]]
+  FaultingCodeRange wasmCallIndirect(const wasm::CallSiteDesc& desc,
+                                     const wasm::CalleeDesc& callee,
+                                     Label* nullCheckFailedLabel,
+                                     CodeOffset* fastCallOffset,
+                                     CodeOffset* slowCallOffset);
 
   // WasmTableCallIndexReg must contain the index of the indirect call.  This is
   // for wasm calls only.
   //
   // `boundsCheckFailedLabel` is non-null iff a bounds check is required.
   // `nullCheckFailedLabel` is non-null only on platforms that can't fold the
-  // null check into the rest of the call instructions.
-  void wasmReturnCallIndirect(const wasm::CallSiteDesc& desc,
-                              const wasm::CalleeDesc& callee,
-                              Label* nullCheckFailedLabel,
-                              const ReturnCallAdjustmentInfo& retCallInfo);
+  // null check into the rest of the call instructions.  If a trap-based null
+  // check is generated, then its FaultingCodeRange is returned; otherwise
+  // `FaultingCodeRange()` is returned.
+  [[nodiscard]]
+  FaultingCodeRange wasmReturnCallIndirect(
+      const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee,
+      Label* nullCheckFailedLabel, const ReturnCallAdjustmentInfo& retCallInfo);
 
   // This function takes care of loading the callee's instance and address from
   // pinned reg.
diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp
index aac4ea9b1be..64ac7bb5c50 100644
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -1714,10 +1714,10 @@ void BaseCompiler::endCall(FunctionCall& call, size_t stackSpace) {
 
   if (call.restoreState == RestoreState::All) {
     fr.loadInstancePtr(InstanceReg);
-    masm.loadWasmPinnedRegsFromInstance(mozilla::Nothing());
+    masm.loadWasmPinnedRegsFromInstance();
     masm.switchToWasmInstanceRealm(ABINonArgReturnReg0, ABINonArgReturnReg1);
   } else if (call.restoreState == RestoreState::PinnedRegs) {
-    masm.loadWasmPinnedRegsFromInstance(mozilla::Nothing());
+    masm.loadWasmPinnedRegsFromInstance();
   }
 }
 
@@ -2138,14 +2138,23 @@ bool BaseCompiler::callIndirect(uint32_t funcTypeIndex, uint32_t tableIndex,
   }
   nullCheckFailed = nullref->entry();
 #endif
+
+  FaultingCodeRange fcr;
   if (!tailCall) {
-    masm.wasmCallIndirect(desc, callee, nullCheckFailed, fastCallOffset,
-                          slowCallOffset);
+    fcr = masm.wasmCallIndirect(desc, callee, nullCheckFailed, fastCallOffset,
+                                slowCallOffset);
   } else {
     ReturnCallAdjustmentInfo retCallInfo = BuildReturnCallAdjustmentInfo(
         this->funcType(), (*codeMeta_.types)[funcTypeIndex].funcType());
-    masm.wasmReturnCallIndirect(desc, callee, nullCheckFailed, retCallInfo);
+    fcr =
+        masm.wasmReturnCallIndirect(desc, callee, nullCheckFailed, retCallInfo);
+  }
+  if (compilerEnv_.debugEnabled() && fcr.isValid() &&
+      !createStackMap(Some(Trap::IndirectCallToNull), fcr,
+                      HasDebugFrameWithLiveRefs::Maybe)) {
+    return false;
   }
+
   return true;
 }
 
@@ -13028,10 +13037,8 @@ bool js::wasm::BaselineCompileFunctions(const CodeMetadata& codeMeta,
 
     // Do the check.  This asserts if the check fails.
     auto checkThisTrapKind_debugMode = [](Trap t) -> bool {
-      // Trap kinds to check in debug mode
-      return t == Trap::InvalidConversionToInteger ||
-             t == Trap::IntegerOverflow || t == Trap::IntegerDivideByZero ||
-             t == Trap::NullPointerDereference || t == Trap::OutOfBounds;
+      // In debug mode, we check most trap kinds.
+      return t != Trap::IndirectCallBadSig && t != Trap::StackOverflow;
     };
     auto checkThisTrapKind_normalMode = [](Trap t) -> bool {
       // Trap kinds to check in non-debug mode
diff --git a/js/src/wasm/WasmCodegenTypes.h b/js/src/wasm/WasmCodegenTypes.h
index 67f4c8f1e77..c3bc0985645 100644
--- a/js/src/wasm/WasmCodegenTypes.h
+++ b/js/src/wasm/WasmCodegenTypes.h
@@ -241,6 +241,19 @@ const char* ToString(TrapMachineInsn tmi);
 // resulting FaultingCodeRange is ignored.  Hence the burden of
 // error/consistency checking is placed on the (infrequently used) access
 // routines rather than the constructors.
+//
+// In typical use, there are two reasons why FaultingCodeRange may fail
+// `isValid()`:
+//
+// * The generating assembler OOMd.  In this case, routines handling
+//   FaultingCodeRanges will ignore the ranges, and at the end of compilation
+//   the assembler's OOM flag is checked and as a result all code and metadata
+//   resulting from the compilation is discarded.
+//
+// * or, the FaultingCodeRange was deliberately created as invalid, using
+//   `FaultingCodeRange()`.  This can be used to signify to callers that "no
+//   trap required here / no stackmap required here" and is detected and handled
+//   accordingly.
 
 class FaultingCodeRange {
   // Definitions that summarise the range of instruction lengths in the code we
diff --git a/js/src/wasm/WasmStacks.cpp b/js/src/wasm/WasmStacks.cpp
index 3075e4960ad..8a66a4f1908 100644
--- a/js/src/wasm/WasmStacks.cpp
+++ b/js/src/wasm/WasmStacks.cpp
... (file diff truncated)
Loading diff…