High CVSS 9.8 firefox Memory Corruption 🔧 Commit mapped

Overview

High
Severity
9.8
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionMemory safety bugs present in Firefox 130, Firefox ESR 128.2, and Thunderbird 128.2. 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.
ComponentNetworking
Bug ClassMemory Corruption
Tracker1872744
Fix commit62a6584cc47b (firefox) +17/-20
CWECWE-119 (Buffer bounds error)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedAndrew Osmond, Sebastian Hengst, Andrew McCreight, Yury Delendik, the Mozilla Fuzzing Team,
Disclosed2024-10-01

Background

`DNSRequestSender`
the networking helper that owns a DNS request and drives its lifecycle, including cancellation, across the parent/child IPC boundary.
`mIPCActor`
the raw pointer held by DNSRequestSender to the underlying IPC actor (either a DNSRequestChild or a DNSRequestParent) used to send messages such as SendCancelDNSRequest.
`CanSend`
an IPC actor predicate that returns whether the actor is still connected and able to send messages, i.e. whether the underlying IPC channel has not been torn down.
`SchedulerGroup::Dispatch`
the mechanism used to marshal work onto the MainThread, since IPC sends are only permitted from that thread.

Root Cause Analysis

The vulnerable path was DNSRequestSender::Cancel, which needed to perform an IPC send but could only do so on the MainThread. The original code captured the actor pointer mIPCActor into an NS_NewRunnableFunction lambda and dispatched it via SchedulerGroup::Dispatch, deferring the CanSend liveness check until the runnable actually executed. Because the captured actor was a raw pointer to mIPCActor and the runnable ran asynchronously, the actor could be destroyed between the time Cancel queued the work and the time the runnable ran, so the deferred actor->CanSend() (and the subsequent AsDNSRequestChild/AsDNSRequestParent dereferences) operated on freed memory — a use-after-free.

The fix replaces the captured-pointer lambda with a NewRunnableMethod re-dispatch of Cancel itself onto the MainThread and checks mIPCActor->CanSend() synchronously up front, so the actor is validated and used within a single MainThread turn while the object is still alive. This works because the liveness check and the send now happen together against the live member, eliminating the asynchronous window in which the actor could be freed.

Key insight
The single core mistake was deferring the actor’s CanSend liveness check into an asynchronously dispatched lambda that captured a raw actor pointer, allowing the actor to be freed before the check and use; the fix moves the check and send into a synchronous MainThread execution of Cancel that references the live mIPCActor member directly.

Attack Path

  1. Trigger a DNS request The attacker causes content to issue a DNS resolution that creates a DNSRequestSender holding an IPC actor in mIPCActor.
  2. Cancel off the MainThread Cancellation is invoked so DNSRequestSender::Cancel runs off the MainThread and queues a lambda via SchedulerGroup::Dispatch capturing the raw actor pointer.
  3. Free the actor Before the queued runnable executes, the IPC channel is torn down and the actor object is destroyed, leaving the captured pointer dangling.
  4. Execute the stale runnable The dispatched lambda runs and calls actor->CanSend() and AsDNSRequestChild/AsDNSRequestParent on the freed actor, dereferencing reclaimed memory.
  5. Corrupt memory With heap grooming the freed allocation is replaced with attacker-controlled data, turning the use-after-free into memory corruption and potential code execution.

Impact Assessment

An attacker gains a use-after-free on a DNS IPC actor, which under heap manipulation yields memory corruption and potentially arbitrary code execution in the affected networking/content process context. The precondition is racing a Cancel against destruction of the underlying IPC actor so the deferred, captured-pointer access runs after the actor is freed. Rated high impact, consistent with the memory-safety cluster reported for Firefox 130 and ESR 128.2.

Changed Functions

FunctionChangeNotes
Cancel
netwerk/dns/DNSRequestChild.cpp
modified
if
netwerk/dns/DNSRequestChild.cpp
modified

Files Changed

  • netwerk/dns/DNSRequestChild.cpp

Audit Directions

  • Captured raw actor/object pointers in dispatched runnables
    Look for NS_NewRunnableFunction or lambda captures of raw IPC-actor or object pointers whose liveness is only checked inside the deferred body, where the object can be freed before execution.
  • Late liveness checks
    Audit code that defers CanSend or other validity checks into asynchronous work instead of validating synchronously before queuing, especially across thread hops via SchedulerGroup::Dispatch.
  • Cross-thread IPC send patterns
    Review other IPC senders that marshal onto the MainThread and confirm they re-dispatch the owning method (keeping strong references) rather than snapshotting raw pointers into a one-shot closure.
diff --git a/netwerk/dns/DNSRequestChild.cpp b/netwerk/dns/DNSRequestChild.cpp
index 2d02bd5c8ba..923d6e677f5 100644
--- a/netwerk/dns/DNSRequestChild.cpp
+++ b/netwerk/dns/DNSRequestChild.cpp
@@ -391,29 +391,26 @@ void DNSRequestSender::OnRecvCancelDNSRequest(
 
 NS_IMETHODIMP
 DNSRequestSender::Cancel(nsresult reason) {
-  if (!mIPCActor) {
+  if (!mIPCActor || !mIPCActor->CanSend()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  // We can only do IPC on the MainThread
-  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
-      "net::CancelDNSRequestEvent",
-      [actor(mIPCActor), host(mHost), trrServer(mTrrServer), port(mPort),
-       type(mType), originAttributes(mOriginAttributes), flags(mFlags),
-       reason]() {
-        if (!actor->CanSend()) {
-          return;
-        }
-
-        if (DNSRequestChild* child = actor->AsDNSRequestChild()) {
-          Unused << child->SendCancelDNSRequest(
-              host, trrServer, port, type, originAttributes, flags, reason);
-        } else if (DNSRequestParent* parent = actor->AsDNSRequestParent()) {
-          Unused << parent->SendCancelDNSRequest(
-              host, trrServer, port, type, originAttributes, flags, reason);
-        }
-      });
-  SchedulerGroup::Dispatch(runnable.forget());
+  // we can only do IPC on the MainThread
+  if (!NS_IsMainThread()) {
+    SchedulerGroup::Dispatch(
+        NewRunnableMethod<nsresult>("net::DNSRequestSender::Cancel", this,
+                                    &DNSRequestSender::Cancel, reason));
+    return NS_OK;
+  }
+
+  if (DNSRequestChild* child = mIPCActor->AsDNSRequestChild()) {
+    Unused << child->SendCancelDNSRequest(mHost, mTrrServer, mPort, mType,
+                                          mOriginAttributes, mFlags, reason);
+  } else if (DNSRequestParent* parent = mIPCActor->AsDNSRequestParent()) {
+    Unused << parent->SendCancelDNSRequest(mHost, mTrrServer, mPort, mType,
+                                           mOriginAttributes, mFlags, reason);
+  }
+
   return NS_OK;
 }
 
Loading diff…