Low firefox Logic Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactlow
DescriptionUse-after-free in the JavaScript Engine: JIT component
ComponentSpiderMonkey
Bug ClassLogic Error
Tracker2057465
Fix commit81626f8d9ef1 (firefox) +37/-2
CISA KEVNot listed
Creditedx0e
Disclosed2026-09-29

Changed Functions

FunctionChangeNotes
if
js/src/vm/Shape.cpp
modified
for
js/src/jit-test/tests/fuses/objectfuse-property-remove-oom.js
modified
if
js/src/jit-test/tests/fuses/objectfuse-property-remove-oom.js
modified

Files Changed

  • js/src/jit-test/tests/fuses/objectfuse-property-remove-oom.js
  • js/src/vm/ObjectFuse.cpp
  • js/src/vm/ObjectFuse.h
  • js/src/vm/Shape.cpp
  • js/src/vm/Watchtower.cpp
  • js/src/vm/Watchtower.h
diff --git a/js/src/vm/ObjectFuse.cpp b/js/src/vm/ObjectFuse.cpp
index ae42c0e5159..3bcbc7d321d 100644
--- a/js/src/vm/ObjectFuse.cpp
+++ b/js/src/vm/ObjectFuse.cpp
@@ -147,8 +147,15 @@ void ObjectFuse::handlePropertyRemove(JSContext* cx, PropertyInfo prop,
   bumpGeneration();
   invalidateDependentIonScriptsForProperty(cx, prop, "removed property");
 
-  // Ensure a new property with this slot number will have the correct initial
-  // state.
+  // We want to mark the removed property's slot Untracked, but the property
+  // removal can still fail due to OOM, leaving existing SetSlot stubs valid.
+  // Mark the property NotConstant until we call finishPropertyRemove.
+  setPropertyState(prop, PropertyState::NotConstant);
+}
+
+void ObjectFuse::finishPropertyRemove(PropertyInfo prop) {
+  // See ObjectFuse::handlePropertyRemove comment above.
+  MOZ_ASSERT(getPropertyState(prop) == PropertyState::NotConstant);
   setPropertyState(prop, PropertyState::Untracked);
 }
 
diff --git a/js/src/vm/ObjectFuse.h b/js/src/vm/ObjectFuse.h
index 3db30c050ee..2ed0780df99 100644
--- a/js/src/vm/ObjectFuse.h
+++ b/js/src/vm/ObjectFuse.h
@@ -226,6 +226,7 @@ class ObjectFuse {
   void handlePropertyValueChange(JSContext* cx, PropertyInfo prop);
   void handlePropertyRemove(JSContext* cx, PropertyInfo prop,
                             bool* wasTrackedProp);
+  void finishPropertyRemove(PropertyInfo prop);
   void handleTeleportingShadowedProperty(JSContext* cx, PropertyInfo prop);
   void handleTeleportingProtoMutation(JSContext* cx);
   void handleShadowedGlobalProperty(JSContext* cx, PropertyInfo prop);
diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp
index bcced8d7553..7652e6c85a3 100644
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -980,6 +980,10 @@ bool NativeObject::removeProperty(JSContext* cx, Handle<NativeObject*> obj,
     obj->maybeFreeDictionaryPropSlots(cx, dictMap, mapLength);
   }
 
+  if (wasTrackedObjectFuseProp) {
+    Watchtower::finishPropertyRemove(cx, obj, prop);
+  }
+
   return true;
 }
 
diff --git a/js/src/vm/Watchtower.cpp b/js/src/vm/Watchtower.cpp
index 2bf9062e36c..fb29715ab16 100644
--- a/js/src/vm/Watchtower.cpp
+++ b/js/src/vm/Watchtower.cpp
@@ -648,6 +648,15 @@ bool Watchtower::watchPropertyRemoveSlow(JSContext* cx,
   return true;
 }
 
+// static
+void Watchtower::finishPropertyRemove(JSContext* cx, NativeObject* obj,
+                                      PropertyInfo propInfo) {
+  MOZ_ASSERT(obj->hasObjectFuse());
+  if (auto* objFuse = cx->zone()->objectFuses.get(obj)) {
+    objFuse->finishPropertyRemove(propInfo);
+  }
+}
+
 // static
 bool Watchtower::watchPropertyFlagsChangeSlow(JSContext* cx,
                                               Handle<NativeObject*> obj,
diff --git a/js/src/vm/Watchtower.h b/js/src/vm/Watchtower.h
index a5f5a68ab74..a54fe01c7c2 100644
--- a/js/src/vm/Watchtower.h
+++ b/js/src/vm/Watchtower.h
@@ -118,6 +118,8 @@ class Watchtower {
     return watchPropertyRemoveSlow(cx, obj, id, propInfo,
                                    wasTrackedObjectFuseProp);
   }
+  static void finishPropertyRemove(JSContext* cx, NativeObject* obj,
+                                   PropertyInfo propInfo);
   static bool watchPropertyFlagsChange(JSContext* cx, Handle<NativeObject*> obj,
                                        HandleId id, PropertyInfo propInfo,
                                        PropertyFlags newFlags) {
diff --git a/js/src/jit-test/tests/fuses/objectfuse-property-remove-oom.js b/js/src/jit-test/tests/fuses/objectfuse-property-remove-oom.js
new file mode 100644
index 00000000000..36499da5e39
--- /dev/null
+++ b/js/src/jit-test/tests/fuses/objectfuse-property-remove-oom.js
@@ -0,0 +1,12 @@
+function f() {
+  for (var i = 0; i < 50; i++) {
+    var v = {};
+    globalProp = v;
+    assertEq(globalProp, v);
+    if (i == 40) {
+      delete this.globalProp;
+    }
+  }
+}
+f();
+oomTest(f);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/js/src/jit-test/tests/fuses/objectfuse-property-remove-oom.js b/js/src/jit-test/tests/fuses/objectfuse-property-remove-oom.js
new file mode 100644
index 00000000000..36499da5e39
--- /dev/null
+++ b/js/src/jit-test/tests/fuses/objectfuse-property-remove-oom.js
@@ -0,0 +1,12 @@
+function f() {
+  for (var i = 0; i < 50; i++) {
+    var v = {};
+    globalProp = v;
+    assertEq(globalProp, v);
+    if (i == 40) {
+      delete this.globalProp;
+    }
+  }
+}
+f();
+oomTest(f);
Loading diff…