CVE-2025-5268
Overview
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.
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
- 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.)
- Concurrent init/teardown
several threads enter
res_ninit(lazy init) and/orres_nclose(shutdown) at overlapping times. - Data race on shared resolver state unsynchronized access corrupts glibc resolver/global structures.
- Memory corruption heap/global state is left inconsistent; the fuzzing team saw evidence of corruption.
- 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
Files Changed
netwerk/dns/PlatformDNSUnix.cpp
Audit Directions
- Other platform resolver backendsreview the Windows/Android/macOS
PlatformDNS*.cppfor analogous unsynchronized calls into non-thread-safe system resolver APIs. - Non-reentrant libc usage on worker threadsaudit networking worker-thread code for other global-state libc calls (
gethostbyname,res_*,getaddrinfovariants, locale/env functions) invoked without serialization. - Lock scope sufficiencyverify the
sMutexscope covers every path that callsres_ninit/res_nclose(including error returns and alternative shutdown paths), since only these two sites were guarded.
Patch
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
}