Moderate firefox Race 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionIncorrect boundary conditions in the DOM: Service Workers component
ComponentDOM
Bug ClassRace
Tracker2073660
Fix commitdcb704630a1d (firefox) +14/-1
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Background

`ServiceWorkerManager` (SWM)
The main-thread singleton that manages service worker registrations and, among other things, surfaces diagnostic/error messages to a worker’s controlled clients via LocalizeAndReportToAllClients in dom/serviceworkers/ServiceWorkerManager.cpp.
`ConsoleUtils::ReportForServiceWorkerScope`
Helper in dom/console/ConsoleUtils.cpp that posts a console message for a service worker scope; it is main-thread-only and begins with a thread assertion.
Main-thread affinity
ServiceWorkerManager::GetInstance() and the objects it touches (registration tables, client lists, console service) are main-thread-only. Calling into them from another thread is a thread-safety violation: it races main-thread state and can use objects not meant for concurrent access.
`MOZ_ASSERT` vs `MOZ_RELEASE_ASSERT`
MOZ_ASSERT is compiled out of release builds, so a debug-only assertion does nothing to stop the bad path in shipping Firefox. MOZ_RELEASE_ASSERT fires in all builds.

Root Cause Analysis

LocalizeAndReportToAllClients assumed it ran on the main thread — it immediately called ServiceWorkerManager::GetInstance() and operated on SWM state — but it could be invoked from a non-main thread (e.g. a worker/IPC path reporting a service-worker error). On that off-main-thread entry it touched main-thread-only service-worker and console machinery concurrently with the main thread, a thread-affinity violation. The only guard downstream, ConsoleUtils::ReportForServiceWorkerScope, was a MOZ_ASSERT(NS_IsMainThread()), which is a no-op in release builds, so shipping Firefox silently proceeded off-thread into main-thread-only code.

The patch makes LocalizeAndReportToAllClients thread-safe by bouncing to the main thread: if !NS_IsMainThread(), it builds a runnable that copies all arguments (aScope, a fresh nsCString(aStringKey), aParamArray.Clone(), flags, filename, line, line/column numbers) and re-invokes itself on the main thread via NS_DispatchToMainThread, then returns. The deep copies/Clone() ensure the dispatched work owns its data rather than referencing caller buffers that may go away. It also upgrades the ConsoleUtils::ReportForServiceWorkerScope guard from MOZ_ASSERT to MOZ_RELEASE_ASSERT(NS_IsMainThread()) so any remaining off-thread caller fails loudly in release builds instead of corrupting state. The advisory’s “incorrect boundary conditions” label does not match the code; the diff substantiates a thread-safety/affinity defect, not a bounds error.

Key insight
LocalizeAndReportToAllClients ran main-thread-only service-worker and console code without checking the thread, and the only check downstream was a release-build no-op MOZ_ASSERT, so it could execute on a non-main thread and race/misuse main-thread-only state.

Attack Path

  1. Trigger an SW error report Web content registers/controls a service worker and induces an error condition that routes through LocalizeAndReportToAllClients (inferred: reachable from service-worker error reporting).
  2. Off-main-thread entry The report is raised from a non-main thread, so the function runs off the main thread.
  3. No effective guard (pre-patch) The debug-only MOZ_ASSERT in ReportForServiceWorkerScope is compiled out; execution continues into ServiceWorkerManager::GetInstance() and client/console machinery.
  4. Concurrent main-thread access Main-thread-only SWM/console objects are touched off-thread while the main thread may also be using them, a data race.
  5. Primitive Racy access to main-thread-only objects; depending on timing this can corrupt service-worker/console state or crash (inferred potential use-after-free/memory corruption — not explicitly shown by the diff).

Impact Assessment

Parent/content main-thread service-worker path; reachable from web content that drives service-worker error reporting. The substantiated defect is a thread-affinity violation (main-thread-only code executed off-thread, guarded only by a release-build no-op assert). The concrete security impact is a data race on main-thread-only state; escalation to a definite memory-corruption or exploitable primitive is inferred, not demonstrated by the diff. Severity is rated “moderate.” After the patch, off-thread calls are marshaled to the main thread and any stray off-thread caller hits a release assertion.

Changed Functions

FunctionChangeNotes
if
dom/serviceworkers/ServiceWorkerManager.cpp
modified

Files Changed

  • dom/console/ConsoleUtils.cpp
  • dom/serviceworkers/ServiceWorkerManager.cpp

Audit Directions

  • Debug-only thread asserts on main-thread-only code
    Grep for MOZ_ASSERT(NS_IsMainThread()) guarding functions that touch singletons/DOM state; these are no-ops in release and may hide off-thread callers like this one.
  • Other SWM entry points
    Audit other ServiceWorkerManager methods callable from worker/IPC threads for a missing NS_IsMainThread() marshal.
  • Argument lifetime on dispatch
    Verify other NS_DispatchToMainThread sites copy/Clone() their arguments rather than capturing references to caller-owned buffers, as this fix does.
diff --git a/dom/console/ConsoleUtils.cpp b/dom/console/ConsoleUtils.cpp
index 7fab046991f..17c5d8503ab 100644
--- a/dom/console/ConsoleUtils.cpp
+++ b/dom/console/ConsoleUtils.cpp
@@ -47,7 +47,7 @@ void ConsoleUtils::ReportForServiceWorkerScope(const nsAString& aScope,
                                                uint32_t aLineNumber,
                                                uint32_t aColumnNumber,
                                                Level aLevel) {
-  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   RefPtr<ConsoleUtils> service = ConsoleUtils::GetOrCreate();
   if (NS_WARN_IF(!service)) {
diff --git a/dom/serviceworkers/ServiceWorkerManager.cpp b/dom/serviceworkers/ServiceWorkerManager.cpp
index 9d78131fddb..94b18851f61 100644
--- a/dom/serviceworkers/ServiceWorkerManager.cpp
+++ b/dom/serviceworkers/ServiceWorkerManager.cpp
@@ -1606,6 +1606,19 @@ void ServiceWorkerManager::LocalizeAndReportToAllClients(
     const nsTArray<nsString>& aParamArray, uint32_t aFlags,
     const nsCString& aFilename, const nsString& aLine, uint32_t aLineNumber,
     uint32_t aColumnNumber) {
+  if (!NS_IsMainThread()) {
+    NS_DispatchToMainThread(NS_NewRunnableFunction(
+        "ServiceWorkerManager::LocalizeAndReportToAllClients",
+        [scope = aScope, stringKey = nsCString(aStringKey),
+         paramArray = aParamArray.Clone(), aFlags, filename = aFilename,
+         line = aLine, aLineNumber, aColumnNumber]() {
+          LocalizeAndReportToAllClients(scope, stringKey.get(), paramArray,
+                                        aFlags, filename, line, aLineNumber,
+                                        aColumnNumber);
+        }));
+    return;
+  }
+
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   if (!swm) {
     return;
Loading diff…