Firefox · SpiderMonkey
CVE-2026-118014
Logic Error in SpiderMonkey
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
withjs/src/jit-test/tests/ion/call-generic-fun-call-realm.js |
modified | |
forjs/src/jit-test/tests/ion/call-generic-fun-call-realm.js |
modified | |
applyjs/src/jit-test/tests/ion/call-generic-fun-call-realm.js |
modified |
Files Changed
js/src/jit-test/tests/ion/call-generic-fun-call-realm.jsjs/src/jit/CodeGenerator.cpp
Patch
diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp
index effb4f0be48..c5cb23c93b8 100644
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -6769,8 +6769,6 @@ void JitRuntime::generateIonGenericCallStub(MacroAssembler& masm,
masm.branchTestNull(Assembler::Equal, thisAddr, &vmCall);
}
- masm.switchToObjectRealm(calleeReg, scratch);
-
// Load jitCodeRaw for callee if it exists.
masm.branchIfFunctionHasNoJitEntry(calleeReg, &noJitEntry);
@@ -6780,6 +6778,7 @@ void JitRuntime::generateIonGenericCallStub(MacroAssembler& masm,
generateIonGenericHandleUnderflow(masm, isConstructing, &vmCall);
+ masm.switchToObjectRealm(calleeReg, scratch);
masm.loadJitCodeRaw(calleeReg, scratch2);
// Construct the JitFrameLayout.
@@ -7057,6 +7056,9 @@ void JitRuntime::generateIonGenericCallNativeFunction(MacroAssembler& masm,
// Initialize |NativeExitFrameLayout::savedCallee_|.
masm.push(calleeReg);
+ // Enter the callee's realm. It's kept alive by |savedCallee_|.
+ masm.switchToObjectRealm(calleeReg, scratch);
+
// Load the callee address into calleeReg.
#ifdef JS_SIMULATOR
masm.movePtr(ImmPtr(RedirectedCallAnyNative()), calleeReg);
@@ -7117,6 +7119,12 @@ void JitRuntime::generateIonGenericCallFunCall(MacroAssembler& masm,
Address(calleeReg, JSFunction::offsetOfNativeOrEnv()),
ImmPtr(js::fun_call), ¬FunCall);
+ // Only optimize fun_call for the caller's realm. If |this| is a proxy, the
+ // arguments array passed to the proxy's apply trap must be allocated in
+ // fun_call's realm. We can't enter that realm here because fun_call, which
+ // keeps it alive, is about to be removed from the stack. See bug 2082051.
+ masm.guardObjectHasSameRealm(calleeReg, scratch, vmCall);
+
// In general, we can implement fun_call by replacing calleeReg with
// |this|, sliding all the other arguments down, and decrementing argc.
//
diff --git a/js/src/jit-test/tests/ion/call-generic-fun-call-realm.js b/js/src/jit-test/tests/ion/call-generic-fun-call-realm.js
new file mode 100644
index 00000000000..328e2bf6c9a
--- /dev/null
+++ b/js/src/jit-test/tests/ion/call-generic-fun-call-realm.js
@@ -0,0 +1,61 @@
+// |jit-test| --fast-warmup; --no-threads
+
+var proxy = new Proxy(function() { return arguments.length; }, {
+ get apply() {
+ receiver.m = Math.abs;
+ gc();
+ return undefined;
+ }
+});
+var receiver = proxy;
+
+function hot0() { return receiver.m(); }
+function hot2() { return receiver.m(null, 1, 2); }
+
+var natives = [Math.abs, Math.acos, Math.atan, Math.ceil, Math.cos, Math.exp,
+ Math.floor, Math.log, Math.max, Math.min, Math.pow, Math.round,
+ Math.sin, Math.sqrt, Math.tan, parseInt, parseFloat, isNaN,
+ isFinite, Number, Boolean, String];
+
+function warm() {
+ with ({}) {}
+ for (var i = 0; i < 2000; i++) {
+ receiver.m = natives[i % natives.length];
+ hot0();
+ hot2();
+ }
+}
+
+function install() {
+ receiver.m = newGlobal({sameCompartmentAs: proxy}).evaluate(
+ "Function.prototype.call");
+}
+
+with ({}) {}
+
+warm();
+install();
+assertEq(hot0(), 0);
+
+warm();
+install();
+assertEq(hot2(), 2);
+
+receiver = proxy.bind(null, 1);
+warm();
+install();
+assertEq(hot0(), 1);
+
+warm();
+install();
+assertEq(hot2(), 3);
+
+// A proxy's arguments array must still be allocated in fun_call's realm.
+receiver = new Proxy(function() {}, {
+ apply(target, thisArg, args) { return args; }
+});
+var realm = newGlobal({sameCompartmentAs: proxy});
+warm();
+receiver.m = realm.Function.prototype.call;
+var args = hot2();
+assertEq(Object.getPrototypeOf(args), realm.Array.prototype);
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/js/src/jit-test/tests/ion/call-generic-fun-call-realm.js b/js/src/jit-test/tests/ion/call-generic-fun-call-realm.js
new file mode 100644
index 00000000000..328e2bf6c9a
--- /dev/null
+++ b/js/src/jit-test/tests/ion/call-generic-fun-call-realm.js
@@ -0,0 +1,61 @@
+// |jit-test| --fast-warmup; --no-threads
+
+var proxy = new Proxy(function() { return arguments.length; }, {
+ get apply() {
+ receiver.m = Math.abs;
+ gc();
+ return undefined;
+ }
+});
+var receiver = proxy;
+
+function hot0() { return receiver.m(); }
+function hot2() { return receiver.m(null, 1, 2); }
+
+var natives = [Math.abs, Math.acos, Math.atan, Math.ceil, Math.cos, Math.exp,
+ Math.floor, Math.log, Math.max, Math.min, Math.pow, Math.round,
+ Math.sin, Math.sqrt, Math.tan, parseInt, parseFloat, isNaN,
+ isFinite, Number, Boolean, String];
+
+function warm() {
+ with ({}) {}
+ for (var i = 0; i < 2000; i++) {
+ receiver.m = natives[i % natives.length];
+ hot0();
+ hot2();
+ }
+}
+
+function install() {
+ receiver.m = newGlobal({sameCompartmentAs: proxy}).evaluate(
+ "Function.prototype.call");
+}
+
+with ({}) {}
+
+warm();
+install();
+assertEq(hot0(), 0);
+
+warm();
+install();
+assertEq(hot2(), 2);
+
+receiver = proxy.bind(null, 1);
+warm();
+install();
+assertEq(hot0(), 1);
+
+warm();
+install();
+assertEq(hot2(), 3);
+
+// A proxy's arguments array must still be allocated in fun_call's realm.
+receiver = new Proxy(function() {}, {
+ apply(target, thisArg, args) { return args; }
+});
+var realm = newGlobal({sameCompartmentAs: proxy});
+warm();
+receiver.m = realm.Function.prototype.call;
+var args = hot2();
+assertEq(Object.getPrototypeOf(args), realm.Array.prototype);
Loading diff…
References
On This Page