High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionMitigation bypass in the JavaScript: GC component
ComponentSpiderMonkey
Bug ClassLogic Error
Tracker2053688
Fix commit8f6686c3c8c5 (firefox) +7/-0
CISA KEVNot listed
CreditedAmy Burnett of OpenAI
Disclosed2026-08-18

Background

`RemapRemoteWindowProxies`
SpiderMonkey routine that transplants a set of remote (cross-process) WindowProxy objects onto a new target proxy in another compartment.
Dead wrapper
a placeholder JSObject that replaces a wrapper whose referent is gone, forwarding or trapping accesses that would otherwise dangle.
Finalization observer
GC bookkeeping for FinalizationRegistry/WeakRef entries that must be notified or updated when their observed target object moves or is replaced.
`relocateFinalizationObserverTarget`
GCRuntime method that repoints any finalization observer currently watching one object value at a replacement target value.

Root Cause Analysis

In js::RemapRemoteWindowProxies, each old remote proxy in otherProxies was turned into a deadWrapper and remapped onto target via js::RemapDeadWrapper, but the GC’s finalization observer tables were never told that the old object’s identity was being folded into target. This violated the invariant that every finalization observer must track the live object that supersedes its original target; observers registered against the object that becomes a dead wrapper were left pointing at a stale entry rather than at target. Because the remap is a mitigation-relevant identity change, leaving observers unrelocated is a logic error that lets FinalizationRegistry/WeakRef state diverge from the actual object graph after the transplant.

The fix obtains the GCRuntime via cx->runtime()->gc and, for each proxy, calls gc->relocateFinalizationObserverTarget(ObjectValue(*deadWrapper), ObjectValue(*target)) before RemapDeadWrapper, treating an allocation failure as fatal through oomUnsafe.crash. This restores the invariant so finalization observation follows the object to its new target instead of observing the discarded dead wrapper.

Key insight
The single core mistake was remapping remote window proxies onto a new target without also migrating the GC’s finalization observers that were watching the replaced objects. The fix inserts an explicit relocateFinalizationObserverTarget call in the remap loop so observer state is moved to target in lockstep with the proxy transplant.

Attack Path

  1. Register an observer Script places a remote WindowProxy under observation by a FinalizationRegistry or WeakRef.
  2. Trigger a remap A navigation or process/compartment change drives js::RemapRemoteWindowProxies, converting that proxy into a deadWrapper and transplanting it onto a new target.
  3. Observer left stale Without relocation, the finalization observer keeps referencing the pre-remap identity instead of following it to target.
  4. Bypass the mitigation The divergence between observer state and the actual object identity defeats the GC bookkeeping the remap was meant to preserve, yielding inconsistent finalization behavior an attacker can steer.

Impact Assessment

An attacker running script in the content process can drive finalization observer state out of sync with the real object graph across a remote window proxy remap, bypassing a GC mitigation and producing inconsistent FinalizationRegistry/WeakRef behavior. Preconditions are the ability to register a finalization observer on a remote WindowProxy and to induce a remap of that proxy. The metadata classifies this as a high-impact logic error / mitigation bypass in the JavaScript GC component.

Changed Functions

FunctionChangeNotes
for
js/src/jsapi.cpp
modified

Files Changed

  • js/src/jsapi.cpp

Audit Directions

  • Identity transplants
    Wherever an object is replaced by a dead wrapper or remapped onto a new target, verify GC observer tables (FinalizationRegistry, WeakRef, weak maps) are relocated in the same step.
  • Cross-compartment remap paths
    Audit other callers of RemapDeadWrapper and proxy transplant helpers for missing relocateFinalizationObserverTarget-style updates.
  • OOM handling in GC-critical loops
    Confirm relocation calls that can fail treat failure as fatal via oomUnsafe.crash rather than silently continuing with partially migrated observer state.
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index f7fd53a47e2..9daab1a54ba 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -803,9 +803,16 @@ JS_PUBLIC_API void js::RemapRemoteWindowProxies(
     target.set(targetCompartmentProxy);
   }
 
+  gc::GCRuntime* gc = &cx->runtime()->gc;
   RootedField<JSObject*, 2> deadWrapper(roots);
   for (JSObject*& obj : otherProxies) {
     deadWrapper = obj;
+
+    if (!gc->relocateFinalizationObserverTarget(ObjectValue(*deadWrapper),
+                                                ObjectValue(*target))) {
+      oomUnsafe.crash("js::RemapRemoteWindowProxies");
+    }
+
     js::RemapDeadWrapper(cx, deadWrapper, target);
   }
 }
Loading diff…