High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the JavaScript Engine: JIT component
ComponentSpiderMonkey
Bug ClassLogic Error
Tracker2082051
Fix commitc95c2b35c942 (firefox) +71/-2
CISA KEVNot listed
CreditedOpenAI Codex Security (kylebot)
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
with
js/src/jit-test/tests/ion/call-generic-fun-call-realm.js
modified
for
js/src/jit-test/tests/ion/call-generic-fun-call-realm.js
modified
apply
js/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.js
  • js/src/jit/CodeGenerator.cpp
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), &notFunCall);
 
+  // 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…