Low firefox Race 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactlow
DescriptionRace condition in the IPC component
ComponentCore
Bug ClassRace
Tracker2071379
Fix commit04c91827ac91 (firefox) +2/-1
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Background

`PlatformPipeLink`
a class in xpcom/io/AsyncPlatformPipes.cpp that drives an asynchronous platform pipe for Firefox IPC, deriving (on applicable platforms) from MessageLoopForIO::Watcher so it can be notified of I/O readiness on the IO thread.
`MessageLoopForIO::Watcher`
a base that registers the object with the IO thread’s message loop; its registration/watch state is owned by and must be torn down on the IO thread.
`mIOThread`
the IO thread/event target this link operates on; the link’s I/O callbacks and watch (de)registration run there.
`NS_INLINE_DECL_THREADSAFE_REFCOUNTING`
a macro giving a class thread-safe (atomic) reference counting where the object is destroyed on whichever thread happens to drop the last reference.
`NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET(cls, target)`
the same, except the final Release proxies destruction to a specified event target, guaranteeing the destructor always runs on that thread.

Root Cause Analysis

PlatformPipeLink was declared with plain NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PlatformPipeLink). With that macro, the object is deleted on whatever thread releases the last strong reference. But PlatformPipeLink is a MessageLoopForIO::Watcher whose watch registration and I/O state are bound to the IO thread (mIOThread): its destructor tears down IO-thread-owned state (unregistering the watcher, closing/releasing the file-handle watch). If the last reference is dropped on a thread other than mIOThread — which can happen because references are held across threads in the IPC machinery — the destructor runs off the IO thread concurrently with, or in a wrong-thread relationship to, the IO thread’s own access to that watch state. That is the race: destruction on an arbitrary thread versus IO-thread use of the watcher, which can corrupt or use-after-free the watch registration.

The patch replaces the refcounting macro with NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET(PlatformPipeLink, mIOThread.GetEventTarget()). Now, whichever thread drops the final reference, the actual destruction is dispatched to and executed on the IO thread’s event target, so the destructor’s teardown of the Watcher registration always runs on the same thread that owns and services it. This serializes destruction against the IO thread’s use of the object and removes the cross-thread race. The advisory’s class (“race condition in IPC”) matches the diff exactly: it is a destruction-thread/lifetime race fixed by pinning deletion to the IO thread.

Key insight
A refcounted IO-thread watcher was allowed to be destroyed on whichever thread released it last, so its destructor could tear down IO-thread-owned watch state off-thread; forcing deletion onto mIOThread’s event target removes the race.

Attack Path

  1. IPC in use a feature using AsyncPlatformPipes holds PlatformPipeLink references on more than one thread (the IPC/IO threads), as the IPC runtime does (inferred: standard async-pipe usage).
  2. Cross-thread last release a non-IO thread drops the final reference (e.g. during connection teardown / error), triggering destruction off the IO thread.
  3. Concurrent IO-thread access the IO thread is simultaneously servicing or unwinding the same watcher’s registration.
  4. Race window the destructor mutates/frees the watcher state while the IO thread reads or uses it.
  5. Primitive a data race leading to use-after-free/corruption of the pipe link’s watch state (timing-dependent; exploitability inferred, consistent with the low severity rating).

Impact Assessment

The affected code is in XPCOM IPC plumbing shared across Firefox processes; triggering it requires the cross-thread last-release timing to coincide with IO-thread activity on the same link, so it is a timing-dependent race rather than a deterministic primitive. The consequence is a destruction/teardown race on IO-thread-owned watcher state — potential use-after-free or state corruption of the pipe link. Mozilla rates it low, consistent with the narrow, race-gated nature and lack of a shown direct attacker-controlled primitive; the fix deterministically eliminates the off-thread destruction.

Files Changed

  • xpcom/io/AsyncPlatformPipes.cpp

Audit Directions

  • Watchers/IO objects with plain threadsafe refcounting
    audit other MessageLoopForIO::Watcher subclasses and IO-thread-bound objects using NS_INLINE_DECL_THREADSAFE_REFCOUNTING to confirm they, too, pin destruction to the owning event target.
  • Cross-thread last-release hazards
    review refcounted objects whose destructor touches thread-affine state (socket/pipe/file-handle watches, nsITimer, event-loop registrations) for off-thread destruction possibilities.
  • `AsyncPlatformPipes` lifetime
    trace all holders of PlatformPipeLink references across threads to verify no other member is torn down on the wrong thread even with deletion now on the IO thread.
diff --git a/xpcom/io/AsyncPlatformPipes.cpp b/xpcom/io/AsyncPlatformPipes.cpp
index 51b9447b355..ba235e6128d 100644
--- a/xpcom/io/AsyncPlatformPipes.cpp
+++ b/xpcom/io/AsyncPlatformPipes.cpp
@@ -33,7 +33,8 @@ class PlatformPipeLink
     : public MessageLoopForIO::Watcher
 #endif
 {
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PlatformPipeLink)
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET(
+      PlatformPipeLink, mIOThread.GetEventTarget())
 
  public:
   PlatformPipeLink(UniqueFileHandle aHandle, uint32_t aBufferSize);
Loading diff…