High firefox Memory Corruption 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionMemory safety bugs present in Firefox 135 and Thunderbird 135. Some of these bugs showed evidence of memory corruption and we presume that with enough effort some of these could have been exploited to run arbitrary code.
ComponentDOM
Bug ClassMemory Corruption
Tracker1869650
Fix commit3fedfa61ee4e (firefox) +59/-40
CISA KEVNot listed
Creditedthe Mozilla Fuzzing Team, Andrew McCreight, Sebastian Hengst, Randell Jesup
Disclosed2025-03-04

Changed Functions

FunctionChangeNotes
if
dom/fetch/FetchParent.cpp
modified
if
dom/fetch/FetchService.cpp
modified

Files Changed

  • dom/credentialmanagement/identity/IdentityCredential.cpp
  • dom/fetch/FetchParent.cpp
  • dom/fetch/FetchService.cpp
  • dom/fetch/FetchService.h
  • dom/serviceworkers/FetchEventOpProxyChild.cpp
  • dom/serviceworkers/FetchEventOpProxyChild.h
  • toolkit/components/contentanalysis/ContentAnalysis.cpp
  • toolkit/components/resistfingerprinting/nsUserCharacteristics.cpp
  • xpcom/threads/MozPromise.h
diff --git a/dom/credentialmanagement/identity/IdentityCredential.cpp b/dom/credentialmanagement/identity/IdentityCredential.cpp
index 323b020db68..d06c9078678 100644
--- a/dom/credentialmanagement/identity/IdentityCredential.cpp
+++ b/dom/credentialmanagement/identity/IdentityCredential.cpp
@@ -995,9 +995,7 @@ IdentityCredential::DiscoverFromExternalSourceInMainProcess(
     nsresult rv = NS_NewTimerWithCallback(
         getter_AddRefs(timeout),
         [=](auto) {
-          if (!result->IsResolved()) {
-            result->Reject(NS_ERROR_DOM_NETWORK_ERR, __func__);
-          }
+          result->Reject(NS_ERROR_DOM_NETWORK_ERR, __func__);
           IdentityCredential::CloseUserInterface(browsingContext);
         },
         StaticPrefs::
diff --git a/dom/fetch/FetchParent.cpp b/dom/fetch/FetchParent.cpp
index 8b82381abea..6f969246aab 100644
--- a/dom/fetch/FetchParent.cpp
+++ b/dom/fetch/FetchParent.cpp
@@ -199,8 +199,7 @@ IPCResult FetchParent::RecvFetchOp(FetchOpArgs&& aArgs) {
           })));
     }
 
-    bool isResolved =
-        self->mResponsePromises->GetResponseEndPromise()->IsResolved();
+    bool isResolved = self->mResponsePromises->IsResponseEndPromiseResolved();
     if (!isResolved && self->mIsWorkerFetch) {
       // track only unresolved promises for worker fetch requests
       // this is needed for clean-up of keepalive requests
diff --git a/dom/fetch/FetchService.cpp b/dom/fetch/FetchService.cpp
index b854d1e9882..b5e60bbd27f 100644
--- a/dom/fetch/FetchService.cpp
+++ b/dom/fetch/FetchService.cpp
@@ -68,6 +68,7 @@ FetchServicePromises::GetResponseEndPromise() {
 void FetchServicePromises::ResolveResponseAvailablePromise(
     FetchServiceResponse&& aResponse, StaticString aMethodName) {
   if (mAvailablePromise) {
+    mAvailablePromiseResolved = true;
     mAvailablePromise->Resolve(std::move(aResponse), aMethodName);
   }
 }
@@ -82,6 +83,7 @@ void FetchServicePromises::RejectResponseAvailablePromise(
 void FetchServicePromises::ResolveResponseTimingPromise(
     ResponseTiming&& aTiming, StaticString aMethodName) {
   if (mTimingPromise) {
+    mTimingPromiseResolved = true;
     mTimingPromise->Resolve(std::move(aTiming), aMethodName);
   }
 }
@@ -96,6 +98,7 @@ void FetchServicePromises::RejectResponseTimingPromise(
 void FetchServicePromises::ResolveResponseEndPromise(ResponseEndArgs&& aArgs,
                                                      StaticString aMethodName) {
   if (mEndPromise) {
+    mEndPromiseResolved = true;
     mEndPromise->Resolve(std::move(aArgs), aMethodName);
   }
 }
@@ -345,15 +348,11 @@ void FetchService::FetchInstance::Cancel(bool aForceAbort) {
 
           mArgs.as<WorkerFetchArgs>().mResponseEndPromiseHolder.Disconnect();
 
-          MOZ_ASSERT(
-              !mArgs.as<WorkerFetchArgs>().mFetchParentPromise->IsResolved());
-          if (!mArgs.as<WorkerFetchArgs>().mFetchParentPromise->IsResolved()) {
-            // the parent promise resolution leads to deleting of actors
-            // mActorDying prevents further access to FetchParent
-            mActorDying = true;
-            mArgs.as<WorkerFetchArgs>().mFetchParentPromise->Resolve(true,
-                                                                     __func__);
-          }
+          // the parent promise resolution leads to deleting of actors
+          // mActorDying prevents further access to FetchParent
+          mActorDying = true;
+          mArgs.as<WorkerFetchArgs>().mFetchParentPromise->Resolve(true,
+                                                                   __func__);
         }
       }
       return;
@@ -412,24 +411,21 @@ void FetchService::FetchInstance::OnResponseEnd(
 
   if (aReason == eAborted) {
     // If ResponseAvailablePromise has not resolved yet, resolved with
-    // NS_ERROR_DOM_ABORT_ERR response.
-    if (!mPromises->GetResponseAvailablePromise()->IsResolved()) {
-      mPromises->ResolveResponseAvailablePromise(
-          InternalResponse::NetworkError(NS_ERROR_DOM_ABORT_ERR), __func__);
-    }
+    // NS_ERROR_DOM_ABORT_ERR response. If the promise is already resolved,
+    // this will have no effect.
+    mPromises->ResolveResponseAvailablePromise(
+        InternalResponse::NetworkError(NS_ERROR_DOM_ABORT_ERR), __func__);
 
     // If ResponseTimingPromise has not resolved yet, resolved with empty
-    // ResponseTiming.
-    if (!mPromises->GetResponseTimingPromise()->IsResolved()) {
-      mPromises->ResolveResponseTimingPromise(ResponseTiming(), __func__);
-    }
+    // ResponseTiming. If the promise is already resolved, this has no effect.
+    mPromises->ResolveResponseTimingPromise(ResponseTiming(), __func__);
     // Resolve the ResponseEndPromise
     mPromises->ResolveResponseEndPromise(ResponseEndArgs(aReason), __func__);
     return;
   }
 
-  MOZ_ASSERT(mPromises->GetResponseAvailablePromise()->IsResolved() &&
-             mPromises->GetResponseTimingPromise()->IsResolved());
+  MOZ_ASSERT(mPromises->IsResponseAvailablePromiseResolved() &&
+             mPromises->IsResponseTimingPromiseResolved());
 
   // Resolve the ResponseEndPromise
   mPromises->ResolveResponseEndPromise(ResponseEndArgs(aReason), __func__);
@@ -542,7 +538,7 @@ void FetchService::FetchInstance::OnReportPerformanceTiming() {
   MOZ_ASSERT(mFetchDriver);
   MOZ_ASSERT(mPromises);
 
-  if (mPromises->GetResponseTimingPromise()->IsResolved()) {
+  if (mPromises->IsResponseTimingPromiseResolved()) {
     return;
   }
 
@@ -872,7 +868,7 @@ RefPtr<FetchServicePromises> FetchService::Fetch(FetchArgs&& aArgs) {
   RefPtr<FetchServicePromises> promises = fetch->Fetch();
   MOZ_ASSERT(promises);
 
-  if (!promises->GetResponseAvailablePromise()->IsResolved()) {
+  if (!promises->IsResponseAvailablePromiseResolved()) {
     // Insert the created FetchInstance into FetchInstanceTable.
     if (!mFetchInstanceTable.WithEntryHandle(promises, [&](auto&& entry) {
           if (entry.HasEntry()) {
diff --git a/dom/fetch/FetchService.h b/dom/fetch/FetchService.h
index 5af939bea49..e98aaa4d278 100644
--- a/dom/fetch/FetchService.h
+++ b/dom/fetch/FetchService.h
@@ -47,6 +47,12 @@ class FetchServicePromises final {
   RefPtr<FetchServiceResponseTimingPromise> GetResponseTimingPromise();
   RefPtr<FetchServiceResponseEndPromise> GetResponseEndPromise();
 
+  bool IsResponseAvailablePromiseResolved() {
+    return mAvailablePromiseResolved;
+  }
+  bool IsResponseTimingPromiseResolved() { return mTimingPromiseResolved; }
+  bool IsResponseEndPromiseResolved() { return mEndPromiseResolved; }
+
   void ResolveResponseAvailablePromise(FetchServiceResponse&& aResponse,
                                        StaticString aMethodName);
   void RejectResponseAvailablePromise(const CopyableErrorResult&& aError,
@@ -66,6 +72,13 @@ class FetchServicePromises final {
   RefPtr<FetchServiceResponseAvailablePromise::Private> mAvailablePromise;
   RefPtr<FetchServiceResponseTimingPromise::Private> mTimingPromise;
   RefPtr<FetchServiceResponseEndPromise::Private> mEndPromise;
+
+  // The MozPromise interface intentionally does not expose synchronous access
+  // to the internal resolved/rejected state. Instead, we track whether or not
+  // we've called Resolve on the FetchServicePromises.
+  bool mAvailablePromiseResolved = false;
+  bool mTimingPromiseResolved = false;
+  bool mEndPromiseResolved = false;
 };
 
 /**
diff --git a/dom/serviceworkers/FetchEventOpProxyChild.cpp b/dom/serviceworkers/FetchEventOpProxyChild.cpp
index 8e437356ec5..3fd531373c0 100644
--- a/dom/serviceworkers/FetchEventOpProxyChild.cpp
+++ b/dom/serviceworkers/FetchEventOpProxyChild.cpp
@@ -75,6 +75,7 @@ void FetchEventOpProxyChild::Initialize(
             __func__);
     mPreloadResponseAvailablePromise->UseSynchronousTaskDispatch(__func__);
     if (aArgs.preloadResponse().isSome()) {
+      mPreloadResponseAvailablePromiseResolved = true;
       mPreloadResponseAvailablePromise->Resolve(
           InternalResponse::FromIPC(aArgs.preloadResponse().ref()), __func__);
     }
@@ -91,6 +92,7 @@ void FetchEventOpProxyChild::Initialize(
         MakeRefPtr<FetchEventPreloadResponseEndPromise::Private>(__func__);
     mPreloadResponseEndPromise->UseSynchronousTaskDispatch(__func__);
     if (aArgs.preloadResponseEndArgs().isSome()) {
+      mPreloadResponseEndPromiseResolved = true;
       mPreloadResponseEndPromise->Resolve(aArgs.preloadResponseEndArgs().ref(),
                                           __func__);
     }
@@ -107,8 +109,8 @@ void FetchEventOpProxyChild::Initialize(
     // result until RecvPreloadResponseEnd is called, such that the preload
     // response could be completed.
     if (self->mPreloadResponseEndPromise &&
-        !self->mPreloadResponseEndPromise->IsResolved() &&
-        self->mPreloadResponseAvailablePromise->IsResolved()) {
+        !self->mPreloadResponseEndPromiseResolved &&
+        self->mPreloadResponseAvailablePromiseResolved) {
       self->mCachedOpResult = Some(aResult);
       return;
     }
@@ -203,6 +205,7 @@ mozilla::ipc::IPCResult FetchEventOpProxyChild::RecvPreloadResponse(
   // Initialize() should have created this promise.
   MOZ_ASSERT(mPreloadResponseAvailablePromise);
 
+  mPreloadResponseAvailablePromiseResolved = true;
   mPreloadResponseAvailablePromise->Resolve(
       InternalResponse::FromIPC(aResponse), __func__);
 
@@ -225,6 +228,7 @@ mozilla::ipc::IPCResult FetchEventOpProxyChild::RecvPreloadResponseEnd(
Loading diff…