Low CVSS 6.5 firefox Race 🔧 Commit mapped

Overview

Low
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactlow
DescriptionA race condition could have led to private browsing tabs being opened in normal browsing windows. This could have resulted in a potential privacy leak.
ComponentDOM
Bug ClassRace
Tracker1932555
Fix commit7afb37a3f7f6 (firefox) +8/-1
CWECWE-362 (Race condition)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
CISA KEVNot listed
CreditedMaruf Bin Murtuza
Disclosed2025-02-04

Background

Private Browsing Mode (PBM)
a browsing context isolated from normal browsing so that its cookies, history, and cache are never persisted or shared with non-private windows.
`OriginAttributes`
a per-context bundle of isolation keys (including mPrivateBrowsingId) that Gecko uses to partition storage and enforce the private/normal separation.
`ContentParent`
the parent-process IPC actor that services window-open requests coming from a content process, deciding which chrome window a new browsing context is created in.
`nsContentUtils::GetMostRecentNonPBWindow`
a helper that returns the most recently used non-private-browsing chrome window as a fallback target for opening new windows.

Root Cause Analysis

When a content process asked the parent to open a new window, ContentParent::CommonCreateWindow first tried to locate a suitable chrome window (outerWin) matching the request’s OriginAttributes. If no matching window was found — for instance because the originating private window had been closed in a race between the child’s open request and the parent’s handling of it — the code unconditionally fell back to nsContentUtils::GetMostRecentNonPBWindow, which returns a normal (non-private) window. This violated the invariant that a private-browsing navigation must only ever land in a private-browsing chrome window, so a request carrying a non-default mPrivateBrowsingId could be satisfied by opening its content in a normal window.

The fix restores the invariant by checking, before taking the fallback path, whether aOriginAttributes.mPrivateBrowsingId differs from nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID; if so it aborts with NS_ERROR_FAILURE rather than reusing a non-private window.

Key insight
The core mistake was applying a non-private-window fallback to all window-open requests without confirming the request was itself non-private; the fix simply gates that fallback on mPrivateBrowsingId and fails closed for private requests instead of leaking them into a normal window.

Attack Path

  1. Open a private window The user (or content in it) is in a private-browsing chrome window whose OriginAttributes carry a non-default mPrivateBrowsingId.
  2. Trigger a window-open Content in the private context requests a new window via IPC, which the parent handles in ContentParent::CommonCreateWindow.
  3. Race away the private window Between the request and its handling, the originating private chrome window is closed, so CommonCreateWindow finds no matching outerWin.
  4. Fall through to non-private fallback The unguarded code calls nsContentUtils::GetMostRecentNonPBWindow and opens the private navigation inside a normal browsing window.
  5. Privacy leak The private tab now lives in a non-private window, so its activity may be persisted or observed as normal-browsing state.

Impact Assessment

An attacker or an unlucky timing window causes private-browsing content to be opened in a normal browsing window, leaking what should be isolated activity into persisted, non-private state. The confusion occurs in the parent process’s window-creation path but affects the user’s privacy guarantee rather than granting code execution. The precondition is a race in which the private chrome window is destroyed before the parent resolves the pending window-open request, making the impact low and non-deterministic.

Changed Functions

FunctionChangeNotes
if
dom/ipc/ContentParent.cpp
modified

Files Changed

  • dom/ipc/ContentParent.cpp

Audit Directions

  • Isolation-attribute fallbacks
    Review any code that falls back to a “most recent” or default window/context (e.g. GetMostRecentNonPBWindow) and confirm it re-checks OriginAttributes such as mPrivateBrowsingId before reusing a mismatched target.
  • Cross-process lifetime races
    Look for parent-process IPC handlers that resolve a target object (windows, docshells, browsing contexts) which the child may have destroyed concurrently, and verify they fail closed rather than substituting a differently-isolated object.
  • Private/normal boundary crossings
    Audit window-open, redirect, and reparenting paths for places where a private context can be silently attached to a normal one when its original container disappears.
diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp
index 59cb5b5b51a..817d5fdfdfa 100644
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5249,8 +5249,15 @@ mozilla::ipc::IPCResult ContentParent::CommonCreateWindow(
   }
 
   // If we haven't found a chrome window to open in, just use the most recently
-  // opened one.
+  // opened non PBM window.
   if (!outerWin) {
+    // The parent was a private window but it's no longer available.
+    if (aOriginAttributes.mPrivateBrowsingId !=
+        nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID) {
+      aResult = NS_ERROR_FAILURE;
+      return IPC_OK();
+    }
+
     outerWin = nsContentUtils::GetMostRecentNonPBWindow();
     if (NS_WARN_IF(!outerWin)) {
       aResult = NS_ERROR_FAILURE;
Loading diff…