CVE-2025-1013
Overview
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.
mPrivateBrowsingId and fails closed for private requests instead of leaking them into a normal window.Attack Path
- Open a private window
The user (or content in it) is in a private-browsing chrome window whose
OriginAttributescarry a non-defaultmPrivateBrowsingId. - Trigger a window-open
Content in the private context requests a new window via IPC, which the parent handles in
ContentParent::CommonCreateWindow. - Race away the private window
Between the request and its handling, the originating private chrome window is closed, so
CommonCreateWindowfinds no matchingouterWin. - Fall through to non-private fallback
The unguarded code calls
nsContentUtils::GetMostRecentNonPBWindowand opens the private navigation inside a normal browsing window. - 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
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifdom/ipc/ContentParent.cpp |
modified |
Files Changed
dom/ipc/ContentParent.cpp
Audit Directions
- Isolation-attribute fallbacksReview any code that falls back to a “most recent” or default window/context (e.g.
GetMostRecentNonPBWindow) and confirm it re-checksOriginAttributessuch asmPrivateBrowsingIdbefore reusing a mismatched target. - Cross-process lifetime racesLook 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 crossingsAudit 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.
Patch
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;