Critical firefox OOB 🔧 Commit mapped

Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactcritical
DescriptionAn attacker was able to perform an out-of-bounds read or write on a JavaScript `Promise` object.
ComponentSpiderMonkey
Bug ClassOOB
Tracker1966612
Fix commitd03d25aec14c (firefox) +33/-13
CISA KEVNot listed
CreditedEdouard Bochin and Tao Yan from Palo Alto Networks working with Trend Micro's Zero Day Initiative
Disclosed2025-05-17

Changed Functions

FunctionChangeNotes
if
js/src/builtin/Promise.cpp
modified

Files Changed

  • js/src/builtin/Promise.cpp
diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp
index 34f9c28961a..60d09e03a13 100644
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -99,7 +99,7 @@ enum RejectFunctionSlots {
 
 enum PromiseCombinatorElementFunctionSlots {
   PromiseCombinatorElementFunctionSlot_Data = 0,
-  PromiseCombinatorElementFunctionSlot_ElementIndex,
+  PromiseCombinatorElementFunctionSlot_ElementIndexOrResolveFunc,
 };
 
 enum ReactionJobSlots {
@@ -3241,7 +3241,8 @@ static bool Promise_static_all(JSContext* cx, unsigned argc, Value* vp) {
 
 static JSFunction* NewPromiseCombinatorElementFunction(
     JSContext* cx, Native native,
-    Handle<PromiseCombinatorDataHolder*> dataHolder, uint32_t index);
+    Handle<PromiseCombinatorDataHolder*> dataHolder, uint32_t index,
+    Handle<Value> maybeResolveFunc);
 
 static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc,
                                              Value* vp);
@@ -3343,7 +3344,8 @@ static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc,
 
       // Steps 4.j-q.
       JSFunction* resolveFunc = NewPromiseCombinatorElementFunction(
-          cx, PromiseAllResolveElementFunction, dataHolder, index);
+          cx, PromiseAllResolveElementFunction, dataHolder, index,
+          UndefinedHandleValue);
       if (!resolveFunc) {
         return nullptr;
       }
@@ -3990,7 +3992,8 @@ template <typename T>
 
 static JSFunction* NewPromiseCombinatorElementFunction(
     JSContext* cx, Native native,
-    Handle<PromiseCombinatorDataHolder*> dataHolder, uint32_t index) {
+    Handle<PromiseCombinatorDataHolder*> dataHolder, uint32_t index,
+    Handle<Value> maybeResolveFunc) {
   JSFunction* fn = NewNativeFunction(
       cx, native, 1, nullptr, gc::AllocKind::FUNCTION_EXTENDED, GenericObject);
   if (!fn) {
@@ -3999,8 +4002,15 @@ static JSFunction* NewPromiseCombinatorElementFunction(
 
   fn->setExtendedSlot(PromiseCombinatorElementFunctionSlot_Data,
                       ObjectValue(*dataHolder));
-  fn->setExtendedSlot(PromiseCombinatorElementFunctionSlot_ElementIndex,
-                      Int32Value(index));
+  if (maybeResolveFunc.isObject()) {
+    fn->setExtendedSlot(
+        PromiseCombinatorElementFunctionSlot_ElementIndexOrResolveFunc,
+        maybeResolveFunc);
+  } else {
+    fn->setExtendedSlot(
+        PromiseCombinatorElementFunctionSlot_ElementIndexOrResolveFunc,
+        Int32Value(index));
+  }
   return fn;
 }
 
@@ -4033,6 +4043,14 @@ static bool PromiseCombinatorElementFunctionAlreadyCalled(
   // Step 1. Let F be the active function object.
   JSFunction* fn = &args.callee().as<JSFunction>();
 
+  constexpr size_t indexOrResolveFuncSlot =
+      PromiseCombinatorElementFunctionSlot_ElementIndexOrResolveFunc;
+  if (fn->getExtendedSlot(indexOrResolveFuncSlot).isObject()) {
+    Value slotVal = fn->getExtendedSlot(indexOrResolveFuncSlot);
+    fn = &slotVal.toObject().as<JSFunction>();
+  }
+  MOZ_RELEASE_ASSERT(fn->getExtendedSlot(indexOrResolveFuncSlot).isInt32());
+
   // Promise.all functions
   // Step 2. If F.[[AlreadyCalled]] is true, return undefined.
   // Promise.allSettled functions
@@ -4061,9 +4079,7 @@ static bool PromiseCombinatorElementFunctionAlreadyCalled(
   // Step 4. Let index be F.[[Index]].
   // Promise.allSettled functions
   // Step 5. Let index be F.[[Index]].
-  int32_t idx =
-      fn->getExtendedSlot(PromiseCombinatorElementFunctionSlot_ElementIndex)
-          .toInt32();
+  int32_t idx = fn->getExtendedSlot(indexOrResolveFuncSlot).toInt32();
   MOZ_ASSERT(idx >= 0);
   *index = uint32_t(idx);
 
@@ -4117,7 +4133,8 @@ static bool PromiseCombinatorElementFunctionAlreadyCalled(
 
     // Steps 4.j-q.
     JSFunction* resolveFunc = NewPromiseCombinatorElementFunction(
-        cx, PromiseAllResolveElementFunction, dataHolder, index);
+        cx, PromiseAllResolveElementFunction, dataHolder, index,
+        UndefinedHandleValue);
     if (!resolveFunc) {
       return false;
     }
@@ -4341,7 +4358,8 @@ static bool Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) {
 
     // Steps 4.j-r.
     JSFunction* resolveFunc = NewPromiseCombinatorElementFunction(
-        cx, PromiseAllSettledResolveElementFunction, dataHolder, index);
+        cx, PromiseAllSettledResolveElementFunction, dataHolder, index,
+        UndefinedHandleValue);
     if (!resolveFunc) {
       return false;
     }
@@ -4349,7 +4367,8 @@ static bool Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) {
 
     // Steps 4.s-z.
     JSFunction* rejectFunc = NewPromiseCombinatorElementFunction(
-        cx, PromiseAllSettledRejectElementFunction, dataHolder, index);
+        cx, PromiseAllSettledRejectElementFunction, dataHolder, index,
+        resolveFunVal);
     if (!rejectFunc) {
       return false;
     }
@@ -4578,7 +4597,8 @@ static void ThrowAggregateError(JSContext* cx,
 
     // Steps 4.j-q.
     JSFunction* rejectFunc = NewPromiseCombinatorElementFunction(
-        cx, PromiseAnyRejectElementFunction, dataHolder, index);
+        cx, PromiseAnyRejectElementFunction, dataHolder, index,
+        UndefinedHandleValue);
     if (!rejectFunc) {
       return false;
     }
Loading diff…