Medium CVSS 8.1 firefox Race 🔧 Commit mapped

Overview

Medium
Severity
8.1
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionMemory safety bugs present in Firefox 138, Thunderbird 138, Firefox ESR 128.10, and Thunderbird 128.10. 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 ClassRace
Tracker1950136
Fix commitffc5d04c816c (firefox) +12/-4
CWECWE-119 (Buffer bounds error)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
Creditedthe Mozilla Fuzzing Team, Masayuki Nakano
Disclosed2025-05-27

Background

`PlatformDNSUnix.cpp`
Firefox’s Unix backend for resolving HTTPS (SVCB/HTTPS RR) DNS records using the platform resolver, in netwerk/dns/.
`res_ninit` / `res_nclose`
glibc reentrant resolver routines operating on a caller-provided struct __res_state. Despite the _n (“new”) API, these routines touch process-global resolver configuration/state internally and are not fully thread-safe across concurrent initialization/teardown.
`mozilla::StaticMutex` / `StaticMutexAutoLock`
a static, zero-initialized mutex and its RAII lock guard used to serialize a critical section.

Root Cause Analysis

Each DNS worker thread lazily initialized its own __res_state via res_ninit(resState.get()) in ResolveHTTPSRecordImpl, and tore it down with res_nclose(res) in DNSThreadShutdown. Although each thread owns its own __res_state, res_ninit/res_nclose reach into shared, process-global resolver state (configuration parsing, file descriptors, internal glibc structures). With multiple DNS threads concurrently entering res_ninit/res_nclose, these calls raced on that shared state, producing the memory corruption the Mozilla fuzzing team observed (CWE-119 buffer bounds error per the advisory, but the diff shows the mechanism is a data race, not a bounds-checking bug).

The fix introduces a file-scope mozilla::StaticMutex sMutex and wraps both resolver calls in critical sections: ResolveHTTPSRecordImpl takes StaticMutexAutoLock lock(sMutex) around the res_ninit call (and its failure return), and DNSThreadShutdown takes the same lock around res_nclose(res). Serializing init/close eliminates the concurrent access to the shared resolver state that led to corruption. Note the lock scopes only the resolver library calls, not the surrounding per-thread bookkeeping, which is the minimal correct fix. The advisory’s generic “memory corruption / buffer bounds error” wording and the diff disagree on class: the diff substantiates a concurrency/race root cause whose consequence is memory corruption, so the operative class is Race.

Key insight
Multiple DNS threads called the glibc res_ninit/res_nclose resolver routines concurrently; those routines mutate shared process-global resolver state and are not thread-safe, so the unsynchronized init/teardown raced and corrupted memory. The fix serializes both calls under a single StaticMutex.

Attack Path

  1. Trigger concurrent resolution attacker-controlled web content issues many connections to names requiring HTTPS-RR resolution, fanning work across multiple DNS worker threads. (Reachability of the DNS path from web content is implied by the networking component; the exact trigger cadence is inferred.)
  2. Concurrent init/teardown several threads enter res_ninit (lazy init) and/or res_nclose (shutdown) at overlapping times.
  3. Data race on shared resolver state unsynchronized access corrupts glibc resolver/global structures.
  4. Memory corruption heap/global state is left inconsistent; the fuzzing team saw evidence of corruption.
  5. Primitive memory corruption in the process running the DNS backend; the advisory presumes arbitrary-code-execution potential “with enough effort” (escalation to RCE is inferred, not demonstrated by the diff).

Impact Assessment

This affects the process hosting Firefox’s Unix DNS resolution (socket/parent process). No elevated privilege is needed to drive DNS lookups, but reliable exploitation of a resolver-state race into controlled corruption is hard (CVSS AC:H), and the advisory itself only “presumes” code execution is possible. The demonstrated primitive from the diff is a thread-safety violation yielding memory corruption; weaponization into RCE is unproven here and marked inferred. Impact is rated high (C/I/A:H) on the assumption of exploitable corruption.

Files Changed

  • netwerk/dns/PlatformDNSUnix.cpp

Audit Directions

  • Other platform resolver backends
    review the Windows/Android/macOS PlatformDNS*.cpp for analogous unsynchronized calls into non-thread-safe system resolver APIs.
  • Non-reentrant libc usage on worker threads
    audit networking worker-thread code for other global-state libc calls (gethostbyname, res_*, getaddrinfo variants, locale/env functions) invoked without serialization.
  • Lock scope sufficiency
    verify the sMutex scope covers every path that calls res_ninit/res_nclose (including error returns and alternative shutdown paths), since only these two sites were guarded.
diff --git a/netwerk/dns/PlatformDNSUnix.cpp b/netwerk/dns/PlatformDNSUnix.cpp
index 0acf4d024a6..c104146a42e 100644
--- a/netwerk/dns/PlatformDNSUnix.cpp
+++ b/netwerk/dns/PlatformDNSUnix.cpp
@@ -9,6 +9,7 @@
 #include "mozilla/net/DNSPacket.h"
 #include "nsIDNSService.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/StaticPrefs_network.h"
 #include "mozilla/ThreadLocal.h"
 
@@ -22,6 +23,7 @@ namespace mozilla::net {
 
 #if defined(HAVE_RES_NINIT)
 MOZ_THREAD_LOCAL(struct __res_state*) sThreadRes;
+mozilla::StaticMutex sMutex MOZ_UNANNOTATED;
 #endif
 
 #define LOG(msg, ...) \
@@ -44,9 +46,12 @@ nsresult ResolveHTTPSRecordImpl(const nsACString& aHost,
   if (!sThreadRes.get()) {
     UniquePtr<struct __res_state> resState(new struct __res_state);
     memset(resState.get(), 0, sizeof(struct __res_state));
-    if (int ret = res_ninit(resState.get())) {
-      LOG("res_ninit failed: %d", ret);
-      return NS_ERROR_UNKNOWN_HOST;
+    {
+      StaticMutexAutoLock lock(sMutex);
+      if (int ret = res_ninit(resState.get())) {
+        LOG("res_ninit failed: %d", ret);
+        return NS_ERROR_UNKNOWN_HOST;
+      }
     }
     sThreadRes.set(resState.release());
   }
@@ -90,7 +95,10 @@ void DNSThreadShutdown() {
   }
 
   sThreadRes.set(nullptr);
-  res_nclose(res);
+  {
+    StaticMutexAutoLock lock(sMutex);
+    res_nclose(res);
+  }
   free(res);
 #endif
 }
Loading diff…