Medium firefox Cross Origin 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionSame-origin policy bypass in the Networking: Cookies component
ComponentDOM
Bug ClassCross Origin
Tracker2050482
Fix commit606afcd16b12 (firefox) +40/-1
CISA KEVNot listed
Credited5up3rh3i
Disclosed2026-08-18

Background

CookieStore API
a modern asynchronous DOM interface (dom/cookiestore) that lets pages and service workers read cookies and subscribe to cookie-change notifications for a given scope URL.
`CookieStoreParent`
the privileged parent-process IPC endpoint that services RecvGetSubscriptionsRequest and RecvSubscribeOrUnsubscribeRequest messages sent from content processes.
`nsIPrincipal::IsSameOrigin`
a method that compares a principal’s origin against a given nsIURI, returning true only when scheme, host, and port match under the same-origin policy.
`CookieCommons::DomainMatches`
a helper that tests whether a given Cookie applies to a specific host, used to gate delivery of cookie-change notifications.

Root Cause Analysis

The CookieStoreParent::RecvGetSubscriptionsRequest and CookieStoreParent::RecvSubscribeOrUnsubscribeRequest handlers accepted an attacker-controllable aScopeURL from the content process and used it to register and query cookie subscriptions without ever verifying that the URL’s origin matched the caller’s principal. Although the code validated that the principal was allowed for the process’s remote type, it never enforced that the subscription scope was same-origin with that principal, violating the same-origin invariant that a page may only observe cookies for its own origin. As a consequence, a content process could subscribe to and enumerate cookie-change subscriptions scoped to a foreign origin.

The fix introduces SubscriptionPrincipalMatchesScope, which parses aScopeURL into an nsIURI and calls principal->IsSameOrigin(scopeURI), causing both IPC handlers to IPC_FAIL when the principal is not same-origin with the requested scope. Complementary hardening in CookieStoreSubscriptionService::Observe re-derives the subscription’s host with nsContentUtils::GetHostOrIPv6WithBrackets and gates notification delivery on CookieCommons::DomainMatches, and CookieStoreNotifier::Create switches from GetAsciiHost to GetHostOrIPv6WithBrackets so IPv6 literal hosts are normalized consistently. Together these ensure both the registration and the delivery paths honor origin/host boundaries.

Key insight
The single core mistake was trusting the content-process-supplied aScopeURL without confirming it was same-origin with the authenticated principal, so a page could register or query cookie subscriptions for arbitrary origins. The fix closes this by adding an explicit IsSameOrigin check (SubscriptionPrincipalMatchesScope) at the IPC boundary and a DomainMatches host gate at notification-delivery time.

Attack Path

  1. Establish a content process An attacker gets script running in a normal page or service worker that can issue CookieStore IPC requests to the parent.
  2. Craft a foreign scope URL The script sends RecvSubscribeOrUnsubscribeRequest or RecvGetSubscriptionsRequest with an aScopeURL pointing at a victim origin rather than its own.
  3. Bypass the missing origin check The parent validates only the principal-to-remote-type mapping, not that aScopeURL matches the principal, so the request is accepted.
  4. Observe cross-origin cookie activity The attacker enumerates or subscribes to cookie-change notifications tied to the victim origin, leaking cookie state that should be same-origin restricted.

Impact Assessment

An attacker with script execution in a content process gains a same-origin policy bypass, letting it register for and read cookie-change subscriptions scoped to origins other than its own and thereby infer cross-origin cookie changes. The bypass occurs at the trusted parent-process IPC boundary of the CookieStore service, and the only precondition is the ability to run web content that speaks the CookieStore protocol. Per the metadata this is rated moderate/medium severity, an information-disclosure/cross-origin issue rather than memory corruption.

Changed Functions

FunctionChangeNotes
CookieStoreParent
dom/cookiestore/CookieStoreParent.cpp
modified
for
dom/cookiestore/CookieStoreSubscriptionService.cpp
modified

Files Changed

  • dom/cookiestore/CookieStoreNotifier.cpp
  • dom/cookiestore/CookieStoreParent.cpp
  • dom/cookiestore/CookieStoreSubscriptionService.cpp

Audit Directions

  • Missing origin binding on IPC scope parameters
    Audit other parent-process IPC handlers that accept a URL, scope, or domain from the child and confirm each verifies same-origin (or equivalent) against the authenticated principal rather than trusting the supplied value.
  • Registration versus delivery gaps
    Where a subscribe/observe pattern exists, check that both the registration path and the notification-delivery path enforce the origin/host boundary, since either alone can be insufficient.
  • Inconsistent host normalization
    Look for mixed use of GetAsciiHost versus GetHostOrIPv6WithBrackets (or other host extraction) in matching comparisons, since IPv6 bracket handling mismatches can defeat DomainMatches-style checks.
diff --git a/dom/cookiestore/CookieStoreNotifier.cpp b/dom/cookiestore/CookieStoreNotifier.cpp
index 2aeac8c2d49..e127ba4b64b 100644
--- a/dom/cookiestore/CookieStoreNotifier.cpp
+++ b/dom/cookiestore/CookieStoreNotifier.cpp
@@ -10,6 +10,7 @@
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/net/Cookie.h"
 #include "mozilla/net/CookieCommons.h"
+#include "nsContentUtils.h"
 #include "nsGlobalWindowInner.h"
 #include "nsICookie.h"
 #include "nsICookieNotification.h"
@@ -49,7 +50,9 @@ already_AddRefed<CookieStoreNotifier> CookieStoreNotifier::Create(
   }
 
   nsCString host;
-  if (NS_WARN_IF(NS_FAILED(principal->GetAsciiHost(host))) || host.IsEmpty()) {
+  if (NS_WARN_IF(NS_FAILED(
+          nsContentUtils::GetHostOrIPv6WithBrackets(principal, host))) ||
+      host.IsEmpty()) {
     return nullptr;
   }
 
diff --git a/dom/cookiestore/CookieStoreParent.cpp b/dom/cookiestore/CookieStoreParent.cpp
index b9a51c2d18f..8620a653f63 100644
--- a/dom/cookiestore/CookieStoreParent.cpp
+++ b/dom/cookiestore/CookieStoreParent.cpp
@@ -24,6 +24,7 @@
 #include "nsICookieManager.h"
 #include "nsICookieService.h"
 #include "nsIEffectiveTLDService.h"
+#include "nsNetUtil.h"
 #include "nsProxyRelease.h"
 
 using namespace mozilla::ipc;
@@ -65,6 +66,16 @@ bool CheckContentProcessSecurity(ThreadsafeContentParentHandle* aParent,
   return cs->ContentProcessHasCookie(aDomain, aOriginAttributes);
 }
 
+bool SubscriptionPrincipalMatchesScope(nsIPrincipal* aPrincipal,
+                                       const nsACString& aScopeURL) {
+  nsCOMPtr<nsIURI> scopeURI;
+  if (NS_WARN_IF(NS_FAILED(NS_NewURI(getter_AddRefs(scopeURI), aScopeURL)))) {
+    return false;
+  }
+
+  return aPrincipal->IsSameOrigin(scopeURI);
+}
+
 }  // namespace
 
 CookieStoreParent::CookieStoreParent() { AssertIsOnBackgroundThread(); }
@@ -218,6 +229,10 @@ mozilla::ipc::IPCResult CookieStoreParent::RecvGetSubscriptionsRequest(
     return IPC_FAIL(this, "principal not allowed for remote type");
   }
 
+  if (!SubscriptionPrincipalMatchesScope(principal, aScopeURL)) {
+    return IPC_FAIL(this, "principal not same-origin with scope");
+  }
+
   InvokeAsync(GetMainThreadSerialEventTarget(), __func__,
               [self = RefPtr(this), aPrincipalInfo, aScopeURL]() {
                 CookieStoreSubscriptionService* service =
@@ -268,6 +283,10 @@ mozilla::ipc::IPCResult CookieStoreParent::RecvSubscribeOrUnsubscribeRequest(
     return IPC_FAIL(this, "principal not allowed for remote type");
   }
 
+  if (!SubscriptionPrincipalMatchesScope(principal, aScopeURL)) {
+    return IPC_FAIL(this, "principal not same-origin with scope");
+  }
+
   InvokeAsync(GetMainThreadSerialEventTarget(), __func__,
               [self = RefPtr(this), aPrincipalInfo, aScopeURL, aSubscriptions,
                aSubscription]() {
diff --git a/dom/cookiestore/CookieStoreSubscriptionService.cpp b/dom/cookiestore/CookieStoreSubscriptionService.cpp
index 004375623f5..2ee66f224ee 100644
--- a/dom/cookiestore/CookieStoreSubscriptionService.cpp
+++ b/dom/cookiestore/CookieStoreSubscriptionService.cpp
@@ -12,6 +12,7 @@
 #include "mozilla/net/Cookie.h"
 #include "mozilla/net/CookieCommons.h"
 #include "nsAppDirectoryServiceDefs.h"
+#include "nsContentUtils.h"
 #include "nsICookieNotification.h"
 
 using namespace mozilla::dom;
@@ -321,6 +322,22 @@ CookieStoreSubscriptionService::Observe(nsISupports* aSubject,
       continue;
     }
 
+    nsCOMPtr<nsIURI> principalURI;
+    rv = NS_NewURI(getter_AddRefs(principalURI), principalInfo.spec());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      continue;
+    }
+
+    nsAutoCString host;
+    rv = nsContentUtils::GetHostOrIPv6WithBrackets(principalURI, host);
+    if (NS_WARN_IF(NS_FAILED(rv)) || host.IsEmpty()) {
+      continue;
+    }
+
+    if (!CookieCommons::DomainMatches(Cookie::Cast(cookie), host)) {
+      continue;
+    }
+
     for (const CookieSubscription& subscription : data.mSubscriptions) {
       if (subscription.name().isSome() && subscription.name().value() != name) {
         continue;
Loading diff…