Medium firefox UAF 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionUse-after-free in the JavaScript Engine component
ComponentSpiderMonkey
Bug ClassUAF
Tracker2013612
Fix commitba89bf1fbd20 (firefox) +46/-14
CISA KEVNot listed
CreditedEvyatar Ben Asher, Keane Lucas, Nicholas Carlini, Newton Cheng, Daniel Freeman, Alex Gaynor, and Joel Weinberger using Claude from Anthropic
Disclosed2026-02-24

Changed Functions

FunctionChangeNotes
if
js/public/Realm.h
modified
BEGIN_TEST
js/src/jsapi-tests/testGCExactRooting.cpp
modified
if
js/src/vm/Realm.cpp
modified

Files Changed

  • js/public/Realm.h
  • js/src/jsapi-tests/testGCExactRooting.cpp
  • js/src/vm/Realm.cpp
  • js/src/vm/Realm.h
diff --git a/js/public/Realm.h b/js/public/Realm.h
index 072565e9e24..526a852ba1b 100644
--- a/js/public/Realm.h
+++ b/js/public/Realm.h
@@ -21,8 +21,8 @@
 
 namespace js {
 namespace gc {
-JS_PUBLIC_API void TraceRealm(JSTracer* trc, JS::Realm* realm,
-                              const char* name);
+JS_PUBLIC_API void TraceRealmRoot(JSTracer* trc, JS::Realm* realm,
+                                  const char* name);
 }  // namespace gc
 }  // namespace js
 
@@ -34,7 +34,7 @@ template <>
 struct GCPolicy<Realm*> : public NonGCPointerPolicy<Realm*> {
   static void trace(JSTracer* trc, Realm** vp, const char* name) {
     if (*vp) {
-      ::js::gc::TraceRealm(trc, *vp, name);
+      ::js::gc::TraceRealmRoot(trc, *vp, name);
     }
   }
 };
diff --git a/js/src/jsapi-tests/testGCExactRooting.cpp b/js/src/jsapi-tests/testGCExactRooting.cpp
index 7afcc7727e1..35993639f85 100644
--- a/js/src/jsapi-tests/testGCExactRooting.cpp
+++ b/js/src/jsapi-tests/testGCExactRooting.cpp
@@ -1069,3 +1069,27 @@ BEGIN_TEST(testRootedCopying) {
   return true;
 }
 END_TEST(testRootedCopying)
+
+BEGIN_TEST(testRootedRealm) {
+  // Create a new global and use Rooted<Realm*> to keep it alive.
+  Rooted<Realm*> realm(cx);
+  {
+    JS::RealmOptions globalOptions;
+    JSObject* otherGlobal = JS_NewGlobalObject(
+        cx, getGlobalClass(), nullptr, JS::FireOnNewGlobalHook, globalOptions);
+    CHECK(otherGlobal);
+    realm = JS::GetObjectRealmOrNull(otherGlobal);
+    CHECK(realm);
+  }
+
+  JS_GC(cx);
+
+  // Use the realm.
+  JSAutoRealm ar(cx, JS::GetRealmGlobalOrNull(realm));
+  JS::RootedValue v(cx);
+  EVAL("let x = -1234; Math.abs(x)", &v);
+  CHECK(v.toNumber() == 1234);
+
+  return true;
+}
+END_TEST(testRootedRealm)
diff --git a/js/src/vm/Realm.cpp b/js/src/vm/Realm.cpp
index f313294b833..6ef1690d999 100644
--- a/js/src/vm/Realm.cpp
+++ b/js/src/vm/Realm.cpp
@@ -252,6 +252,12 @@ void Realm::traceGlobalData(JSTracer* trc) {
   DebugAPI::traceFromRealm(trc, this);
 }
 
+void Realm::traceGlobalRoot(JSTracer* trc, const char* name) {
+  if (global_) {
+    TraceRoot(trc, global_.unbarrieredAddress(), name);
+  }
+}
+
 void ObjectRealm::trace(JSTracer* trc) {
   if (objectMetadataTable) {
     objectMetadataTable->trace(trc);
@@ -274,8 +280,8 @@ void Realm::traceRoots(JSTracer* trc,
     //
     // If a realm is on-stack, we mark its global so that JSContext::global()
     // remains valid.
-    if (shouldTraceGlobal() && global_) {
-      TraceRoot(trc, global_.unbarrieredAddress(), "on-stack realm global");
+    if (shouldTraceGlobal()) {
+      traceGlobalRoot(trc, "on-stack realm global");
     }
 
     // If the realm is still being initialized we set a flag so that it doesn't
@@ -690,16 +696,16 @@ void AutoSetNewObjectMetadata::setPendingMetadata() {
   (void)SetNewObjectMetadata(cx_, obj);
 }
 
-JS_PUBLIC_API void gc::TraceRealm(JSTracer* trc, JS::Realm* realm,
-                                  const char* name) {
-  // The way GC works with compartments is basically incomprehensible.
-  // For Realms, what we want is very simple: each Realm has a strong
-  // reference to its GlobalObject, and vice versa.
+JS_PUBLIC_API void gc::TraceRealmRoot(JSTracer* trc, JS::Realm* realm,
+                                      const char* name) {
+  // Trace the realm's global object to keep the realm alive.
   //
-  // Here we simply trace our side of that edge. During GC,
-  // GCRuntime::traceRuntimeCommon() marks all other realm roots, for
-  // all realms.
-  realm->traceGlobalData(trc);
+  // Note: this is called for Rooted<Realm*>. If a realm has been entered with
+  // AutoRealm, the global object is traced in Realm::traceRoots.
+  MOZ_RELEASE_ASSERT(realm->hasLiveGlobal(),
+                     "we need to have a global to keep the realm alive");
+  gc::AssertRootMarkingPhase(trc);
+  realm->traceGlobalRoot(trc, "rooted realm");
 }
 
 JS_PUBLIC_API JS::Realm* JS::GetCurrentRealmOrNull(JSContext* cx) {
diff --git a/js/src/vm/Realm.h b/js/src/vm/Realm.h
index aba85bc7b72..9816e52b848 100644
--- a/js/src/vm/Realm.h
+++ b/js/src/vm/Realm.h
@@ -557,6 +557,8 @@ class JS::Realm : public JS::shadow::Realm {
    */
   void traceGlobalData(JSTracer* trc);
 
+  void traceGlobalRoot(JSTracer* trc, const char* name);
+
   void traceWeakGlobalEdge(JSTracer* trc);
 
   /*
Loading diff…