Firefox · DOM
CVE-2026-117860
Logic Error in DOM
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifdocshell/base/BrowsingContext.cpp |
modified | |
ifdocshell/base/CanonicalBrowsingContext.cpp |
modified | |
ifdocshell/shistory/SessionHistoryEntry.cpp |
modified |
Files Changed
docshell/base/BrowsingContext.cppdocshell/base/CanonicalBrowsingContext.cppdocshell/base/CanonicalBrowsingContext.hdocshell/shistory/SessionHistoryEntry.cppdocshell/shistory/SessionHistoryEntry.hdocshell/shistory/nsSHEntryShared.hdom/ipc/ContentParent.cppdom/ipc/ContentParent.hdom/ipc/PContent.ipdldom/ipc/ParentProcessChannelHandle.cppdom/ipc/ParentProcessChannelHandle.hdom/ipc/WindowGlobalParent.cppdom/ipc/WindowGlobalParent.hnetwerk/ipc/DocumentLoadListener.cpp
Patch
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…
References
On This Page