Firefox · SpiderMonkey
CVE-2026-109729
Logic Error in SpiderMonkey
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
js/src/jit/CodeGenerator.cppjs/src/jit/CodeGenerator.hjs/src/jit/Ion.cppjs/src/jit/JitRuntime.hjs/src/vm/Interpreter.cppjs/src/vm/NativeObject.cpp
Patch
diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp
index b7fa075d98b..ceb19ca60bf 100644
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -6443,6 +6443,19 @@ static void LoadDOMPrivate(MacroAssembler& masm, Register obj, Register priv,
}
}
+void CodeGenerator::maybeEmitSetInPureCall(MInstruction* mir, bool value,
+ Register scratch) {
+ AliasSet aliasSet = mir->getAliasSet();
+ if (aliasSet.isStore() && (aliasSet.flags() & AliasSet::ObjectFields) != 0) {
+ // If the alias set already indicates that this can reallocate slots, the
+ // flag is unnecessary.
+ return;
+ }
+ const void* addr = gen->jitRuntime()->addressOfInPureCall();
+ masm.move32(Imm32(value), scratch);
+ masm.store32(scratch, AbsoluteAddress(addr));
+}
+
void CodeGenerator::visitCallDOMNative(LCallDOMNative* call) {
WrappedFunction* target = call->getSingleTarget();
MOZ_ASSERT(target);
@@ -6530,6 +6543,8 @@ void CodeGenerator::visitCallDOMNative(LCallDOMNative* call) {
markSafepointAt(safepointOffset, call);
+ maybeEmitSetInPureCall(call->mir(), true, /*scratch =*/argJSContext);
+
// Construct and execute call.
masm.setupAlignedABICall();
masm.loadJSContext(argJSContext);
@@ -6542,6 +6557,9 @@ void CodeGenerator::visitCallDOMNative(LCallDOMNative* call) {
ABIType::General,
CheckUnsafeCallWithABI::DontCheckHasExitFrame);
+ Register notReturnReg = argJSContext == ReturnReg ? argObj : argJSContext;
+ maybeEmitSetInPureCall(call->mir(), false, /*scratch =*/notReturnReg);
+
if (target->jitInfo()->isInfallible) {
masm.loadValue(Address(masm.getStackPointer(),
IonDOMMethodExitFrameLayout::offsetOfResult()),
@@ -20362,6 +20380,8 @@ void CodeGenerator::visitGetDOMProperty(LGetDOMProperty* ins) {
markSafepointAt(safepointOffset, ins);
+ maybeEmitSetInPureCall(ins->mir(), true, /*scratch =*/JSContextReg);
+
masm.setupAlignedABICall();
masm.loadJSContext(JSContextReg);
masm.passABIArg(JSContextReg);
@@ -20373,6 +20393,9 @@ void CodeGenerator::visitGetDOMProperty(LGetDOMProperty* ins) {
ABIType::General,
CheckUnsafeCallWithABI::DontCheckHasExitFrame);
+ Register notReturnReg = JSContextReg == ReturnReg ? ObjectReg : JSContextReg;
+ maybeEmitSetInPureCall(ins->mir(), false, /*scratch =*/notReturnReg);
+
if (ins->mir()->isInfallible()) {
masm.loadValue(Address(masm.getStackPointer(),
IonDOMExitFrameLayout::offsetOfResult()),
diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h
index 58c047dea41..59198885c38 100644
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -469,6 +469,8 @@ class CodeGenerator final : public CodeGeneratorSpecific {
}
return false;
}
+
+ void maybeEmitSetInPureCall(MInstruction* mir, bool value, Register scratch);
};
} // namespace jit
diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp
index 93b36a4806b..bdee800915a 100644
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -70,6 +70,7 @@
#include "gc/GC-inl.h"
#include "gc/StableCellHasher-inl.h"
#include "jit/InlineScriptTree-inl.h"
+#include "jit/JSJitFrameIter-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "jit/SafepointIndex-inl.h"
#include "vm/GeckoProfiler-inl.h"
@@ -433,6 +434,34 @@ void JitRuntime::TraceWeakJitcodeGlobalTable(JSRuntime* rt, JSTracer* trc) {
}
}
+void JitRuntime::handleGrowSlotsForPureCall(JSContext* cx) {
+ MOZ_ASSERT(inPureCall());
+
+ // We only set the inPureCall flag for calls in Ion code with alias
+ // sets that claim not to have arbitrary side-effects. Therefore, we
+ // should not have re-entered JIT code, and the call that set the
+ // flag should be the innermost frame (aside from the exit frame).
+ MOZ_RELEASE_ASSERT(cx->activation()->isJit());
+ JitActivation* activation = cx->activation()->asJit();
+ MOZ_RELEASE_ASSERT(activation->hasExitFP());
+
+ JSJitFrameIter frame(activation);
+ // We currently only set this flag for DOM calls.
+ MOZ_RELEASE_ASSERT(frame.isExitFrameLayout<IonDOMExitFrameLayout>());
+ ++frame;
+ MOZ_RELEASE_ASSERT(frame.isIonJS());
+
+ // If we've already invalidated, we've cleared the IonScript
+ // pointer, so we can't invalidate again.
+ if (frame.checkInvalidation()) {
+ return;
+ }
+
+ JitSpew(JitSpew_IonInvalidate,
+ "Invalidating frame: slots reallocated during pure call");
+ Invalidate(cx, frame.script());
+}
+
bool JitZone::addInlinedCompilation(const IonScriptKey& ionScriptKey,
JSScript* inlined) {
MOZ_ASSERT(inlined != ionScriptKey.script());
diff --git a/js/src/jit/JitRuntime.h b/js/src/jit/JitRuntime.h
index 5754a839afa..25b92d8e01b 100644
--- a/js/src/jit/JitRuntime.h
+++ b/js/src/jit/JitRuntime.h
@@ -240,6 +240,11 @@ class JitRuntime {
MainThreadData<uint32_t> disallowArbitraryCode_{false};
#endif
+ // Flag that can be set from JIT code to indicate that we're calling
+ // a function whose alias set does not include stores to ObjectFields.
+ // See handleGrowSlotsForPureCall.
+ MainThreadData<uint32_t> inPureCall_{false};
+
bool generateTrampolines(JSContext* cx);
bool generateBaselineICFallbackCode(JSContext* cx);
@@ -361,6 +366,9 @@ class JitRuntime {
}
#endif
+ bool inPureCall() const { return inPureCall_; }
+ const void* addressOfInPureCall() const { return &inPureCall_.refNoCheck(); }
+
uint8_t* allocateIonOsrTempData(size_t size);
void freeIonOsrTempData();
@@ -515,6 +523,8 @@ class JitRuntime {
void ionLazyLinkListRemove(JSRuntime* rt, js::jit::IonCompileTask* task);
void ionLazyLinkListAdd(JSRuntime* rt, js::jit::IonCompileTask* task);
+
+ void handleGrowSlotsForPureCall(JSContext* cx);
};
} // namespace jit
diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp
index 174fc38056c..c03af9d42bd 100644
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -441,6 +441,8 @@ bool js::RunScript(JSContext* cx, RunState& state) {
MOZ_ASSERT_IF(cx->runtime()->hasJitRuntime(),
!cx->runtime()->jitRuntime()->disallowArbitraryCode());
+ MOZ_ASSERT_IF(cx->runtime()->hasJitRuntime(),
+ !cx->runtime()->jitRuntime()->inPureCall());
// Since any script can conceivably GC, make sure it's safe to do so.
cx->verifyIsSafeToGC();
diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp
index 7bba6b1f5b0..e828f188cd3 100644
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -9,6 +9,7 @@
#include "gc/MaybeRooted.h"
#include "gc/StableCellHasher.h"
+#include "jit/JitRuntime.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
#include "js/Printer.h" // js::GenericPrinter
@@ -357,6 +358,12 @@ bool NativeObject::growSlots(JSContext* cx, uint32_t oldCapacity,
gc::MemoryReleaseFence(zone());
slots_ = newSlots;
+ if (cx->runtime()->hasJitRuntime() &&
+ MOZ_UNLIKELY(cx->runtime()->jitRuntime()->inPureCall()) &&
+ is<GlobalObject>()) {
+ cx->runtime()->jitRuntime()->handleGrowSlotsForPureCall(cx);
+ }
+
MOZ_ASSERT(hasDynamicSlots());
return true;
}
Loading diff…
References
On This Page