Firefox · SpiderMonkey
CVE-2026-109761
Logic Error in SpiderMonkey
Overview
Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifjs/src/jit/CodeGenerator.cpp |
modified | |
ifjs/src/jit/MacroAssembler.cpp |
modified | |
ifjs/src/wasm/WasmBaselineCompile.cpp |
modified | |
FaultingCodeRangejs/src/wasm/WasmCodegenTypes.h |
modified |
Files Changed
js/src/jit/CodeGenerator.cppjs/src/jit/MacroAssembler.cppjs/src/jit/MacroAssembler.hjs/src/wasm/WasmBaselineCompile.cppjs/src/wasm/WasmCodegenTypes.hjs/src/wasm/WasmStacks.cppjs/src/wasm/WasmStubs.cpp
Patch
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…
References
On This Page