High firefox UAF 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the JavaScript: GC component
ComponentSpiderMonkey
Bug ClassUAF
Tracker2013561
Fix commit5ee70a500bee (firefox) +33/-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
while
js/src/gc/Sweeping.cpp
modified
if
js/src/gc/WeakMap-inl.h
modified

Files Changed

  • js/src/gc/GCRuntime.h
  • js/src/gc/Marking.cpp
  • js/src/gc/Sweeping.cpp
  • js/src/gc/WeakMap-inl.h
diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h
index bfc30f41741..b426ca29a66 100644
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -871,6 +871,7 @@ class GCRuntime {
 
   template <class ZoneIterT>
   IncrementalProgress markWeakReferences(JS::SliceBudget& budget);
+  void markIncomingSymbolEdgesFromUncollectedZones();
   IncrementalProgress markWeakReferencesInCurrentGroup(JS::SliceBudget& budget);
   IncrementalProgress markGrayRoots(JS::SliceBudget& budget,
                                     gcstats::PhaseKind phase);
diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp
index 7bc2961f878..5b7e74e843e 100644
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2406,6 +2406,7 @@ IncrementalProgress JS::Zone::enterWeakMarkingMode(GCMarker* marker,
   // all values are marked if both their map and key are marked -- though note
   // that we may later leave weak marking mode, do some more marking, and then
   // enter back in.
+
   if (!isGCMarking()) {
     return IncrementalProgress::Finished;
   }
@@ -2413,8 +2414,8 @@ IncrementalProgress JS::Zone::enterWeakMarkingMode(GCMarker* marker,
   for (auto r = gcEphemeronEdges().all(); !r.empty(); r.popFront()) {
     Cell* src = r.front().key();
     CellColor srcColor = gc::detail::GetEffectiveColor(marker, src);
-    auto& edges = r.front().value();
 
+    auto& edges = r.front().value();
     size_t numEdges = edges.length();
     if (IsMarked(srcColor) && edges.length() > 0) {
       marker->markEphemeronEdges(edges, AsMarkColor(srcColor));
diff --git a/js/src/gc/Sweeping.cpp b/js/src/gc/Sweeping.cpp
index 23a2cb6e35c..323520bebef 100644
--- a/js/src/gc/Sweeping.cpp
+++ b/js/src/gc/Sweeping.cpp
@@ -47,6 +47,7 @@
 #include "vm/Time.h"
 #include "vm/WrapperObject.h"
 
+#include "gc/AtomMarking-inl.h"
 #include "gc/PrivateIterators-inl.h"
 #include "vm/GeckoProfiler-inl.h"
 #include "vm/JSObject-inl.h"
@@ -665,6 +666,8 @@ IncrementalProgress GCRuntime::markWeakReferences(
     }
   }
 
+  markIncomingSymbolEdgesFromUncollectedZones();
+
   bool markedAny = true;
   while (markedAny) {
     if (!marker().markUntilBudgetExhausted(budget)) {
@@ -689,6 +692,33 @@ IncrementalProgress GCRuntime::markWeakReferences(
   return Finished;
 }
 
+void GCRuntime::markIncomingSymbolEdgesFromUncollectedZones() {
+  // We need to mark ephemeron edges where the source is a live symbol that is
+  // referenced from an uncollected zone and which may not have been marked in
+  // this GC. At the same time we want to avoid unnecessarily holding on to
+  // symbols in zones GCs (by marking them as referenced in the atom marking
+  // bitmap), which is why we don't just mark all such symbols at the start of
+  // GC.
+  //
+  // Atoms referenced by uncollected zones will be marked later in
+  // updateAtomsBitmap() which prevents them dying, but since this is after
+  // we've done ephemeron marking it won't mark through the ephemeron edges.
+
+  if (!atomsZone()->isGCMarking()) {
+    return;
+  }
+
+  for (auto iter = atomsZone()->gcEphemeronEdges().iter(); !iter.done();
+       iter.next()) {
+    auto* symbol = iter.get().key()->as<JS::Symbol>();
+    if (isSymbolReferencedByUncollectedZone(symbol, marker().markColor())) {
+      TraceManuallyBarrieredEdge(marker().tracer(), &symbol,
+                                 "incoming symbol edge");
+      MOZ_ASSERT(symbol == iter.get().key());
+    }
+  }
+}
+
 IncrementalProgress GCRuntime::markWeakReferencesInCurrentGroup(
     SliceBudget& budget) {
   return markWeakReferences<SweepGroupZonesIter>(budget);
diff --git a/js/src/gc/WeakMap-inl.h b/js/src/gc/WeakMap-inl.h
index 2633610ad03..906217aad32 100644
--- a/js/src/gc/WeakMap-inl.h
+++ b/js/src/gc/WeakMap-inl.h
@@ -192,19 +192,6 @@ bool WeakMap<K, V, AP>::markEntry(GCMarker* marker, gc::CellColor mapColor,
   gc::Cell* keyCell = gc::ToMarkable(key);
   MOZ_ASSERT(keyCell);
 
-  bool keyIsSymbol = gc::detail::IsSymbol(key.get());
-  MOZ_ASSERT(keyIsSymbol == (keyCell->getTraceKind() == JS::TraceKind::Symbol));
-  if (keyIsSymbol) {
-    // For symbols, also check whether it it is referenced by an uncollected
-    // zone, and if so mark it now. There's no need to set |marked| as this
-    // would have been marked later anyway.
-    auto* sym = static_cast<JS::Symbol*>(keyCell);
-    if (marker->runtime()->gc.isSymbolReferencedByUncollectedZone(
-            sym, marker->markColor())) {
-      TraceEdge(trc, &key, "WeakMap symbol key");
-    }
-  }
-
   bool marked = false;
   CellColor markColor = AsCellColor(marker->markColor());
   CellColor keyColor = gc::detail::GetEffectiveColor(marker, key.get());
Loading diff…