High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionSame-origin policy bypass in the DOM: Navigation component
ComponentDOM
Bug ClassLogic Error
Tracker2046558
Fix commit584721263603 (firefox) +410/-46
CISA KEVNot listed
Creditedsatyamasd
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
if
docshell/base/BrowsingContext.cpp
modified
if
docshell/base/CanonicalBrowsingContext.cpp
modified
if
docshell/shistory/SessionHistoryEntry.cpp
modified

Files Changed

  • docshell/base/BrowsingContext.cpp
  • docshell/base/CanonicalBrowsingContext.cpp
  • docshell/base/CanonicalBrowsingContext.h
  • docshell/shistory/SessionHistoryEntry.cpp
  • docshell/shistory/SessionHistoryEntry.h
  • docshell/shistory/nsSHEntryShared.h
  • dom/ipc/ContentParent.cpp
  • dom/ipc/ContentParent.h
  • dom/ipc/PContent.ipdl
  • dom/ipc/ParentProcessChannelHandle.cpp
  • dom/ipc/ParentProcessChannelHandle.h
  • dom/ipc/WindowGlobalParent.cpp
  • dom/ipc/WindowGlobalParent.h
  • netwerk/ipc/DocumentLoadListener.cpp
diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp
index dba0c816b4a..76be93386d2 100644
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -4037,6 +4037,10 @@ void BrowsingContext::DidSet(FieldIndex<IDX_CurrentInnerWindowId>) {
         Canonical()->MaybeScheduleSessionStoreUpdate();
         mCurrentWindowContext->Canonical()->DidBecomeCurrentWindowGlobal(true);
       }
+      if (prevWindowContext && mCurrentWindowContext) {
+        Canonical()->MaybeRebindActiveEntry(prevWindowContext->Canonical(),
+                                            mCurrentWindowContext->Canonical());
+      }
     }
     BrowserParent::UpdateFocusFromBrowsingContext();
   }
@@ -4471,8 +4475,8 @@ void BrowsingContext::SetActiveSessionHistoryEntry(
       }
     }
     ContentChild::GetSingleton()->SendSetActiveSessionHistoryEntry(
-        this, aPreviousScrollPos, *aInfo, aLoadType, aUpdatedCacheKey,
-        changeID);
+        GetCurrentWindowContext(), aPreviousScrollPos, *aInfo, aLoadType,
+        aUpdatedCacheKey, changeID, aInfo->NewSharedStateRequest());
   } else {
     Canonical()->SetActiveSessionHistoryEntry(
         aPreviousScrollPos, aInfo, aLoadType, aUpdatedCacheKey, nsID());
@@ -4482,8 +4486,8 @@ void BrowsingContext::SetActiveSessionHistoryEntry(
 void BrowsingContext::ReplaceActiveSessionHistoryEntry(
     SessionHistoryInfo* aInfo) {
   if (XRE_IsContentProcess()) {
-    ContentChild::GetSingleton()->SendReplaceActiveSessionHistoryEntry(this,
-                                                                       *aInfo);
+    ContentChild::GetSingleton()->SendReplaceActiveSessionHistoryEntry(
+        GetCurrentWindowContext(), *aInfo, aInfo->NewSharedStateRequest());
   } else {
     Canonical()->ReplaceActiveSessionHistoryEntry(aInfo);
   }
diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp
index 6f66e656381..b228b691674 100644
--- a/docshell/base/CanonicalBrowsingContext.cpp
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -59,7 +59,9 @@
 #include "nsIContentAnalysis.h"
 #include "nsIWebBrowserChrome.h"
 #include "nsIXULRuntime.h"
+#include "nsAboutProtocolUtils.h"
 #include "nsNetUtil.h"
+#include "nsDocShellLoadState.h"
 #include "nsSHistory.h"
 #include "nsSecureBrowserUI.h"
 #include "nsQueryObject.h"
@@ -1230,6 +1232,16 @@ void CanonicalBrowsingContext::SessionHistoryCommit(
       }
 
       RefPtr<SessionHistoryEntry> newActiveEntry = mLoadingEntries[i].mEntry;
+
+      uint64_t innerWindowId = 0;
+      if (WindowGlobalParent* wgp = GetCurrentWindowGlobal();
+          wgp && (newActiveEntry->SharedInfo()->InnerWindowId() ==
+                      wgp->InnerWindowId() ||
+                  wgp->DocumentHistoryLoadId() == aLoadId)) {
+        innerWindowId = wgp->InnerWindowId();
+      }
+      newActiveEntry->SharedInfo()->SetInnerWindowId(innerWindowId);
+
       if (aCacheKey != 0) {
         newActiveEntry->SetCacheKey(aCacheKey);
       }
@@ -1459,9 +1471,192 @@ void CanonicalBrowsingContext::NotifyOnHistoryReload(
   // the nsDocShell will create a load state based on its document.
 }
 
+// about:blank and about:srcdoc, with or without a query or fragment.
+static bool IsAboutBlankOrSrcdoc(nsIURI* aURI) {
+  nsAutoCString name;
+  return aURI->SchemeIs("about") &&
+         NS_SUCCEEDED(NS_GetAboutModuleName(aURI, name)) &&
+         (name.EqualsLiteral("blank") || name.EqualsLiteral("srcdoc"));
+}
+
+// Returns aPrincipal if it is a content principal, its precursor if it is a
+// null principal, and nullptr otherwise.
+static already_AddRefed<nsIPrincipal> GetPrincipalOrPrecursor(
+    nsIPrincipal* aPrincipal) {
+  if (aPrincipal->GetIsContentPrincipal()) {
+    return do_AddRef(aPrincipal);
+  }
+  if (aPrincipal->GetIsNullPrincipal()) {
+    return aPrincipal->GetPrecursorPrincipal();
+  }
+  return nullptr;
+}
+
+// Whether reloading aInfo would reproduce the origin of the document in aWgp.
+// about:blank, about:srcdoc and data: entries get their origin from the entry
+// when reloaded: the first two inherit the stored principal, data: takes it as
+// the precursor of its null principal.
+static bool ReloadWouldInheritDocumentPrincipal(
+    WindowGlobalParent* aWgp, const SessionHistoryInfo* aInfo) {
+  nsIURI* uri = aInfo->GetURI();
+  if (!IsAboutBlankOrSrcdoc(uri) && !uri->SchemeIs("data")) {
+    return true;
+  }
+
+  nsIPrincipal* inherited = aInfo->GetPrincipalToInherit();
+  if (!inherited) {
+    inherited = aInfo->GetTriggeringPrincipal();
+  }
+  if (!inherited) {
+    return false;
+  }
+
+  nsCOMPtr<nsIPrincipal> entryPrincipal = GetPrincipalOrPrecursor(inherited);
+  nsCOMPtr<nsIPrincipal> documentPrincipal =
+      GetPrincipalOrPrecursor(aWgp->DocumentPrincipal());
+  if (!entryPrincipal) {
+    return !documentPrincipal;
+  }
+  return entryPrincipal->Equals(documentPrincipal);
+}
+
+bool CanonicalBrowsingContext::CreateSharedState(
+    SessionHistoryInfo* aInfo, const NewSessionHistorySharedState& aNewState,
+    bool* aWasCreated) {
+  if (aWasCreated) {
+    *aWasCreated = false;
+  }
+  WindowGlobalParent* wgp = GetCurrentWindowGlobal();
+  if (!wgp) {
+    return false;
+  }
+
+  // The request must name the entry's own state.
+  if (SHEntrySharedParentState::Lookup(aNewState.mId)) {
+    return aInfo->SharedId() == aNewState.mId;
+  }
+
+  // The state gets the document's own principals, so it lets content do
+  // nothing the document couldn't. Only the id comes from content, and it must
+  // be one this process minted.
+  if (!nsContentUtils::IsProcessSpecificIdFrom(
+          aNewState.mId, wgp->GetContentParent()->ChildID())) {
+    return false;
+  }
+
+  RefPtr<SHEntrySharedParentState> sharedState = new SHEntrySharedParentState(
+      wgp->DocumentPrincipal(), wgp->DocumentPrincipal(),
+      wgp->DocumentPartitionedPrincipal(), aNewState.mPolicyContainer,
+      aNewState.mContentType);
+  sharedState->ChangeId(aNewState.mId);
+  sharedState->SetInnerWindowId(wgp->InnerWindowId());
+  aInfo->SetSharedState(sharedState);
+  if (aWasCreated) {
+    *aWasCreated = true;
+  }
+  return true;
+}
+
+bool CanonicalBrowsingContext::ValidateSessionHistoryInfo(
+    SessionHistoryInfo* aInfo) const {
+  WindowGlobalParent* wgp = GetCurrentWindowGlobal();
+  if (!wgp || !wgp->DocumentPrincipal()) {
+    return false;
+  }
+
+  // Content can only name shared state bound to its own document. Anything else
+  // belongs to some other document, possibly in another tab, or is the fresh
+  // state the deserializer gave an id the parent didn't know.
+  SHEntrySharedParentState* sharedState =
+      SHEntrySharedParentState::Lookup(aInfo->SharedId());
+  if (!sharedState || sharedState->InnerWindowId() != wgp->InnerWindowId()) {
+    return false;
+  }
+
+  // Content may only store a URI its own process could have loaded, e.g. no
+  // file: URI from a web content process. Entries whose URI inherits a
+  // principal (about:blank, about:srcdoc, data:) are kept from inheriting
+  // another origin's by InheritDocumentPrincipalIfNeeded.
+  ContentParent* contentParent = wgp->GetContentParent();
+  if (!aInfo->GetURI() || !contentParent ||
+      !ContentTriggeredURILoadIsAllowed(aInfo->GetURI(),
+                                        contentParent->GetRemoteType())) {
+    return false;
+  }
+
+  // For a network origin require that its site was loaded in this process.
+  // A sandboxed document's own site could be held only at the precursor level,
+  // so accept down to that level, which also admits the document's own
+  // same-site navigations. Opaque and non-network URIs are covered above.
+  // Use the innermost URI so a nested URI such as view-source: is checked by
+  // the origin it wraps, not the outer scheme.
+  nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(aInfo->GetURI());
+  if (innermostURI && net::SchemeIsHttpOrHttps(innermostURI)) {
+    nsCOMPtr<nsIPrincipal> uriPrincipal = BasePrincipal::CreateContentPrincipal(
+        innermostURI, wgp->DocumentPrincipal()->OriginAttributesRef());
+    if (!uriPrincipal ||
+        !contentParent->LoadedOrigins()->Has(
+            uriPrincipal, LoadedOriginSet::Level::PrecursorOnly,
+            OriginAttributes::STRIP_ALL)) {
+      return false;
+    }
+  }
+
+  // srcdoc content is only ever loaded into a subframe, under an about:srcdoc
+  // URI.
+  if (aInfo->GetSrcdocData().isSome() &&
+      (!NS_IsAboutSrcdoc(aInfo->GetURI()) || IsTopContent())) {
+    return false;
+  }
+
+  return true;
+}
+
+void CanonicalBrowsingContext::InheritDocumentPrincipalIfNeeded() {
+  WindowGlobalParent* wgp = GetCurrentWindowGlobal();
+  if (!wgp || !mActiveEntry) {
+    return;
+  }
+
+  SHEntrySharedParentState* sharedState = mActiveEntry->SharedInfo();
+  if (sharedState->InnerWindowId() != wgp->InnerWindowId() ||
+      ReloadWouldInheritDocumentPrincipal(wgp, &mActiveEntry->Info())) {
+    return;
+  }
+  // We only get here when content gave an entry created by a non-inheriting
+  // load a URI that inherits, which document.open() does when called from a
+  // same-origin about:srcdoc or about:blank frame: the document takes the
+  // caller's URI, but its origin is unchanged. Loading the entry again would
+  // inherit what that load stored, its principal to inherit (a link click
+  // stores the linking document's, possibly another site's) or else its
+  // triggering principal (system for the URL bar), rather than the document's
+  // origin, which is what the spec's document state stores for this. The
+  // principals come from the parent's WindowGlobalParent, and the state is
+  // bound to this document, so this only ever gives the document its own
+  // origin. Its other entries share the state, but either don't inherit or are
+  // this document too.
+  sharedState->mPrincipalToInherit = wgp->DocumentPrincipal();
+  sharedState->mPartitionedPrincipalToInherit =
+      wgp->DocumentPartitionedPrincipal();
+}
+
+void CanonicalBrowsingContext::MaybeRebindActiveEntry(
+    WindowGlobalParent* aPrevious, WindowGlobalParent* aCurrent) {
+  const uint64_t loadId = aCurrent->DocumentHistoryLoadId();
+  if (!mActiveEntry || loadId == 0 ||
+      loadId != aPrevious->DocumentHistoryLoadId() ||
+      mActiveEntry->SharedInfo()->InnerWindowId() !=
+          aPrevious->InnerWindowId() ||
+      !aPrevious->DocumentPrincipal()->Equals(aCurrent->DocumentPrincipal())) {
+    return;
+  }
+  mActiveEntry->SharedInfo()->SetInnerWindowId(aCurrent->InnerWindowId());
+}
+
 void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
     const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo* aInfo,
-    uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID) {
+    uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID,
+    bool aKeepSharedState) {
   nsISHistory* shistory = GetSessionHistory();
   if (!shistory) {
     return;
@@ -1475,7 +1670,12 @@ void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
   }
   mActiveEntry = MakeRefPtr<SessionHistoryEntry>(aInfo);
   mActiveEntry->SetDocshellID(GetHistoryID());
-  if (oldActiveEntry) {
+  // Entries of the same document share its state. The old entry can belong to
+  // another document, e.g. one a javascript: URL replaced, whose state must not
+  // replace the one aInfo was validated with.
+  if (oldActiveEntry && !aKeepSharedState &&
+      oldActiveEntry->SharedInfo()->InnerWindowId() ==
+          mActiveEntry->SharedInfo()->InnerWindowId()) {
     mActiveEntry->AdoptBFCacheEntry(oldActiveEntry);
   }
   if (aUpdatedCacheKey != 0) {
diff --git a/docshell/base/CanonicalBrowsingContext.h b/docshell/base/CanonicalBrowsingContext.h
index eddb4ed402c..be089602a01 100644
--- a/docshell/base/CanonicalBrowsingContext.h
+++ b/docshell/base/CanonicalBrowsingContext.h
@@ -184,15 +184,51 @@ class CanonicalBrowsingContext final : public BrowsingContext {
       Maybe<NotNull<RefPtr<nsDocShellLoadState>>>& aLoadState,
       Maybe<bool>& aReloadActiveEntry);
 
-  // See BrowsingContext::SetActiveSessionHistoryEntry.
+  // See BrowsingContext::SetActiveSessionHistoryEntry. If
+  // aKeepSharedState is true, aInfo's shared state is used even if the
+  // previous active entry belongs to the same document.
   void SetActiveSessionHistoryEntry(const Maybe<nsPoint>& aPreviousScrollPos,
                                     SessionHistoryInfo* aInfo,
                                     uint32_t aLoadType,
                                     uint32_t aUpdatedCacheKey,
-                                    const nsID& aChangeID);
+                                    const nsID& aChangeID,
+                                    bool aKeepSharedState = false);
 
   void ReplaceActiveSessionHistoryEntry(SessionHistoryInfo* aInfo);
 
+  // Gives `aInfo`, sent over IPC by the current document's process, the new
+  // shared state that process asked for in `aNewState`. The principals come
+  // from the document, and the state is bound to it. Returns false if the
+  // request is refused. `aWasCreated` gets set to true if not null and it was
+  // created.
+  bool CreateSharedState(SessionHistoryInfo* aInfo,
+                         const NewSessionHistorySharedState& aNewState,
+                         bool* aWasCreated = nullptr);
+
+  // Validates `aInfo` that has been sent over IPC: its shared state must be
+  // bound to the current document, and its URI one the current document's
+  // process could itself have loaded.
+  bool ValidateSessionHistoryInfo(SessionHistoryInfo* aInfo) const;
+
+  // The active entry's shared state keeps the principals of the load that
+  // created it, e.g. a system triggering principal for a URL typed into the URL
+  // bar, or the linking document's principal to inherit for a link click. If
+  // content changed the entry's URI to one that inherits its principal, such as
+  // document.open() from an about:srcdoc frame, and the principal the entry
+  // would inherit isn't the current document's, makes it inherit the current
+  // document's, so that loading the entry again still produces that document's
+  // origin. Call once content's update has been validated and applied, as
+  // applying it can replace the validated shared state with the one the
+  // previous active entry of the same document had.
+  void InheritDocumentPrincipalIfNeeded();
+
+  // Moves the active entry's shared state binding from aPrevious to aCurrent if
+  // both documents were produced by the same load and have the same principal,
+  // as with the parts of a multipart response, which don't commit to session
+  // history.
+  void MaybeRebindActiveEntry(WindowGlobalParent* aPrevious,
+                              WindowGlobalParent* aCurrent);
+
   void RemoveDynEntriesFromActiveSessionHistoryEntry();
 
   void RemoveFromSessionHistory(const nsID& aChangeID);
diff --git a/docshell/shistory/SessionHistoryEntry.cpp b/docshell/shistory/SessionHistoryEntry.cpp
index 3f5b24c5b2c..1388a83717e 100644
--- a/docshell/shistory/SessionHistoryEntry.cpp
+++ b/docshell/shistory/SessionHistoryEntry.cpp
@@ -190,6 +190,22 @@ uint64_t SessionHistoryInfo::SharedId() const {
   return mSharedState.Get()->mId;
 }
 
+Maybe<NewSessionHistorySharedState> SessionHistoryInfo::NewSharedStateRequest()
+    const {
+  MOZ_ASSERT(XRE_IsContentProcess());
+  SHEntrySharedState* state = mSharedState.Get();
+  if (!nsContentUtils::IsProcessSpecificIdFrom(state->mId, XRE_GetChildID())) {
+    return Nothing();
+  }
+  return Some(NewSessionHistorySharedState{state->mId, state->mPolicyContainer,
+                                           state->mContentType});
+}
+
+void SessionHistoryInfo::SetSharedState(SHEntrySharedParentState* aState) {
+  MOZ_ASSERT(XRE_IsParentProcess());
+  mSharedState.Set(aState);
+}
+
 nsILayoutHistoryState* SessionHistoryInfo::GetLayoutHistoryState() {
   return mSharedState.Get()->mLayoutHistoryState;
 }
@@ -1625,23 +1641,8 @@ bool ParamTraits<mozilla::dom::SessionHistoryInfo>::Read(
   if (sharedState) {
     aResult->mSharedState.Set(sharedState);
 
-    MOZ_ASSERT(triggeringPrincipal
-                   ? triggeringPrincipal->Equals(
-                         aResult->mSharedState.Get()->mTriggeringPrincipal)
-                   : !aResult->mSharedState.Get()->mTriggeringPrincipal,
-               "We don't expect this to change!");
-    MOZ_ASSERT(principalToInherit
-                   ? principalToInherit->Equals(
-                         aResult->mSharedState.Get()->mPrincipalToInherit)
-                   : !aResult->mSharedState.Get()->mPrincipalToInherit,
-               "We don't expect this to change!");
-    MOZ_ASSERT(
-        partitionedPrincipalToInherit
-            ? partitionedPrincipalToInherit->Equals(
-                  aResult->mSharedState.Get()->mPartitionedPrincipalToInherit)
-            : !aResult->mSharedState.Get()->mPartitionedPrincipalToInherit,
-        "We don't expect this to change!");
-
+    // The principals content sent are ignored. They can differ for state that
+    // content created, as the parent took those from the document instead.
     MOZ_ASSERT(policyContainer
                    ? PolicyContainer::Equals(
                          PolicyContainer::Cast(policyContainer),
@@ -1651,7 +1652,7 @@ bool ParamTraits<mozilla::dom::SessionHistoryInfo>::Read(
                "We don't expect this to change!");
     MOZ_ASSERT(contentType.Equals(aResult->mSharedState.Get()->mContentType),
                "We don't expect this to change!");
-  } else {
+  } else if (!XRE_IsParentProcess()) {
     aResult->mSharedState.ChangeId(sharedId);
     aResult->mSharedState.Get()->mTriggeringPrincipal =
         triggeringPrincipal.forget();
@@ -1662,6 +1663,10 @@ bool ParamTraits<mozilla::dom::SessionHistoryInfo>::Read(
     aResult->mSharedState.Get()->mPolicyContainer = policyContainer.forget();
     aResult->mSharedState.Get()->mContentType = contentType;
   }
... (file diff truncated)
Loading diff…