Firefox · SpiderMonkey
CVE-2026-16392
Logic Error in SpiderMonkey
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifjs/src/jit/BaselineBailouts.cpp |
modified | |
ifjs/src/jit/MIR.cpp |
modified | |
forjs/src/jit/MIR.cpp |
modified | |
forjs/src/jit/ScalarReplacement.cpp |
modified |
Files Changed
js/src/jit/BaselineBailouts.cppjs/src/jit/IonTypes.hjs/src/jit/MIR.cppjs/src/jit/MIR.hjs/src/jit/ScalarReplacement.cpp
Patch
diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp
index 6e2bc4f8146..a7db5affa23 100644
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -25,12 +25,14 @@
#include "jit/RematerializedFrame.h"
#include "jit/SharedICRegisters.h"
#include "jit/Simulator.h"
+#include "jit/VMFunctions.h"
#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit, js::ReportOverRecursed
#include "js/Utility.h"
#include "proxy/ScriptedProxyHandler.h"
#include "util/Memory.h"
#include "vm/ArgumentsObject.h"
#include "vm/BytecodeUtil.h"
+#include "vm/Iteration.h"
#include "vm/JitActivation.h"
#include "jit/JitFrames-inl.h"
@@ -918,6 +920,25 @@ bool BaselineStackBuilder::buildExpressionStack() {
}
}
+ if (resumeMode() == ResumeMode::ResumeAfterObjectKeys) {
+ JitSpew(JitSpew_BaselineBailouts,
+ " Converting Object.keys iterator to keys array");
+ // The result slot holds the internal PropertyIteratorObject produced by the
+ // Object.keys scalar-replacement optimization. Convert it back to the keys
+ // array so the internal iterator is never exposed to the baseline frame.
+ Value iterVal;
+ if (peekLastValue(&iterVal) && !iterVal.isMagic(JS_OPTIMIZED_OUT)) {
+ MOZ_RELEASE_ASSERT(iterVal.isObject());
+ MOZ_RELEASE_ASSERT(iterVal.toObject().is<PropertyIteratorObject>());
+ RootedObject iterObj(cx_, &iterVal.toObject());
+ JSObject* keys = ObjectKeysFromIterator(cx_, iterObj);
+ if (!keys) {
+ return false;
+ }
+ valuePointerAtStackOffset(0).set(ObjectValue(*keys));
+ }
+ }
+
return true;
}
diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h
index 8c42cb63263..9de8e5574e1 100644
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -795,6 +795,15 @@ enum class ResumeMode : uint8_t {
// of a proxy get trap aligns with what the spec requires.
ResumeAfterCheckProxyGetResult,
+ // Innermost frame. Resume at the next bytecode op when bailing out, but the
+ // value in the result slot is the internal PropertyIteratorObject created by
+ // the Object.keys scalar-replacement optimization instead of the keys array.
+ // On bailout we convert it back to the keys array so the internal iterator is
+ // never exposed to the baseline frame. This is used when the
+ // MObjectToIterator
+ // VM call bails out (e.g. an invalidation bailout caused by GC).
+ ResumeAfterObjectKeys,
+
// Innermost frame. Resume at the current bytecode op when bailing out.
ResumeAt,
@@ -826,6 +835,8 @@ inline const char* ResumeModeToString(ResumeMode mode) {
return "ResumeAfterCheckIsObject";
case ResumeMode::ResumeAfterCheckProxyGetResult:
return "ResumeAfterCheckProxyGetResult";
+ case ResumeMode::ResumeAfterObjectKeys:
+ return "ResumeAfterObjectKeys";
}
MOZ_CRASH("Invalid mode");
}
@@ -835,6 +846,7 @@ inline bool IsResumeAfter(ResumeMode mode) {
case ResumeMode::ResumeAfter:
case ResumeMode::ResumeAfterCheckIsObject:
case ResumeMode::ResumeAfterCheckProxyGetResult:
+ case ResumeMode::ResumeAfterObjectKeys:
return true;
default:
return false;
diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp
index 0daa5f95bef..dbf9fa5f222 100644
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -714,6 +714,16 @@ void MInstruction::stealResumePoint(MInstruction* other) {
setResumePoint(resumePoint);
}
+bool MInstruction::copyResumePointFrom(TempAllocator& alloc,
+ MInstruction* previous) {
+ MResumePoint* rp = previous->resumePoint_->clone(alloc);
+ if (!rp) {
+ return false;
+ }
+ setResumePoint(rp);
+ return true;
+}
+
void MInstruction::moveResumePointAsEntry() {
MOZ_ASSERT(isNop());
block()->clearEntryResumePoint();
@@ -4412,6 +4422,19 @@ MResumePoint* MResumePoint::New(TempAllocator& alloc, MBasicBlock* block,
return resume;
}
+MResumePoint* MResumePoint::clone(TempAllocator& alloc) {
+ MResumePoint* resume = new (alloc) MResumePoint(block(), pc_, mode_);
+ size_t n = this->numOperands();
+ if (!resume->operands_.init(alloc, n)) {
+ return nullptr;
+ }
+ for (size_t i = 0; i < n; i++) {
+ resume->initOperand(i, getOperand(i));
+ }
+ resume->stores_.copy(this->stores_);
+ return resume;
+}
+
MResumePoint::MResumePoint(MBasicBlock* block, jsbytecode* pc, ResumeMode mode)
: MNode(block, Kind::ResumePoint),
pc_(pc),
diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h
index 8e909e049f7..d1973605eaa 100644
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -1065,6 +1065,9 @@ class MInstruction : public MDefinition, public InlineListNode<MInstruction> {
void setResumePoint(MResumePoint* resumePoint);
void stealResumePoint(MInstruction* other);
+ // Copy resume point from the previous instruction.
+ [[nodiscard]] bool copyResumePointFrom(TempAllocator& alloc,
+ MInstruction* previous);
void moveResumePointAsEntry();
void clearResumePoint();
@@ -8878,6 +8881,9 @@ class MResumePoint final : public MNode
public:
static MResumePoint* New(TempAllocator& alloc, MBasicBlock* block,
jsbytecode* pc, ResumeMode mode);
+ // NOTE: instruction_ is left null; call setResumePoint to associate the
+ // clone with an instruction.
+ [[nodiscard]] MResumePoint* clone(TempAllocator& alloc);
MBasicBlock* block() const { return resumePointBlock(); }
@@ -8927,6 +8933,7 @@ class MResumePoint final : public MNode
instruction_ = nullptr;
}
ResumeMode mode() const { return mode_; }
+ void setMode(ResumeMode mode) { mode_ = mode; }
void releaseUses() {
for (size_t i = 0, e = numOperands(); i < e; i++) {
diff --git a/js/src/jit/ScalarReplacement.cpp b/js/src/jit/ScalarReplacement.cpp
index 140b90eb8db..8bcdbff5b89 100644
--- a/js/src/jit/ScalarReplacement.cpp
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -4491,6 +4491,28 @@ bool ObjectKeysReplacer::run(MInstructionIterator& outerIterator) {
auto* forRecovery = MObjectKeysFromIterator::New(alloc_, objToIter_);
arr_->block()->insertBefore(arr_, forRecovery);
+
+ auto* nop = MNop::New(alloc_);
+ arr_->block()->insertBefore(arr_, nop);
+ if (!nop->copyResumePointFrom(alloc_, objToIter_)) {
+ return false;
+ }
+
+ {
+ // Use the PropertyIteratorObject in the resume point for the
+ // MObjectToIterator instruction. If this instruction calls a VM function
+ // that triggers an invalidation, we use ResumeMode::ResumeAfterObjectKeys
+ // to create the array from the iterator object when we bail out.
+ MResumePoint* rp = objToIter_->resumePoint();
+ size_t n = rp->numOperands() - 1;
+ for (size_t i = 0; i < n; i++) {
+ MOZ_RELEASE_ASSERT(rp->getOperand(i) != arr_);
+ }
+ MOZ_RELEASE_ASSERT(rp->getOperand(n) == arr_);
+ rp->replaceOperand(n, objToIter_);
+ MOZ_RELEASE_ASSERT(rp->mode() == ResumeMode::ResumeAfter);
+ rp->setMode(ResumeMode::ResumeAfterObjectKeys);
+ }
arr_->replaceAllUsesWith(forRecovery);
// We need to explicitly discard the instruction since it's marked as
Loading diff…
References
On This Page