diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp index fe597d2b95ba..5008bdc0cbf3 100644 --- a/docshell/base/BrowsingContext.cpp +++ b/docshell/base/BrowsingContext.cpp @@ -1194,104 +1194,15 @@ void BrowsingContext::GetAllBrowsingContextsInSubtree( }); } -// FindWithName follows the rules for choosing a browsing context, -// with the exception of sandboxing for iframes. The implementation -// for arbitrarily choosing between two browsing contexts with the -// same name is as follows: -// -// 1) The start browsing context, i.e. 'this' -// 2) Descendants in insertion order -// 3) The parent -// 4) Siblings and their children, both in insertion order -// 5) After this we iteratively follow the parent chain, repeating 3 -// and 4 until -// 6) If there is no parent, consider all other top level browsing -// contexts and their children, both in insertion order -// -// See -// https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name -BrowsingContext* BrowsingContext::FindWithName( - const nsAString& aName, bool aUseEntryGlobalForAccessCheck) { - RefPtr requestingContext = this; - if (aUseEntryGlobalForAccessCheck) { - if (nsGlobalWindowInner* caller = nsContentUtils::EntryInnerWindow()) { - if (caller->GetBrowsingContextGroup() == Group()) { - requestingContext = caller->GetBrowsingContext(); - } else { - MOZ_RELEASE_ASSERT(caller->GetPrincipal()->IsSystemPrincipal(), - "caller must be either same-group or system"); - } - } - } - MOZ_ASSERT(requestingContext, "must have a requestingContext"); - - BrowsingContext* found = nullptr; - if (aName.IsEmpty()) { - // You can't find a browsing context with an empty name. - found = nullptr; - } else if (aName.LowerCaseEqualsLiteral("_blank")) { - // Just return null. Caller must handle creating a new window with - // a blank name. - found = nullptr; - } else if (nsContentUtils::IsSpecialName(aName)) { - found = FindWithSpecialName(aName, *requestingContext); - } else if (BrowsingContext* child = - FindWithNameInSubtree(aName, *requestingContext)) { - found = child; - } else { - BrowsingContext* current = this; - - do { - Span> siblings; - BrowsingContext* parent = current->GetParent(); - - if (!parent) { - // We've reached the root of the tree, consider browsing - // contexts in the same browsing context group. - siblings = mGroup->Toplevels(); - } else if (parent->NameEquals(aName) && - requestingContext->CanAccess(parent) && - parent->IsTargetable()) { - found = parent; - break; - } else { - siblings = parent->NonSyntheticChildren(); - } - - for (BrowsingContext* sibling : siblings) { - if (sibling == current) { - continue; - } - - if (BrowsingContext* relative = - sibling->FindWithNameInSubtree(aName, *requestingContext)) { - found = relative; - // Breaks the outer loop - parent = nullptr; - break; - } - } - - current = parent; - } while (current); - } - - // Helpers should perform access control checks, which means that we - // only need to assert that we can access found. - MOZ_DIAGNOSTIC_ASSERT(!found || requestingContext->CanAccess(found)); - - return found; -} - BrowsingContext* BrowsingContext::FindChildWithName( - const nsAString& aName, BrowsingContext& aRequestingContext) { + const nsAString& aName, WindowGlobalChild& aRequestingWindow) { if (aName.IsEmpty()) { // You can't find a browsing context with the empty name. return nullptr; } for (BrowsingContext* child : NonSyntheticChildren()) { - if (child->NameEquals(aName) && aRequestingContext.CanAccess(child) && + if (child->NameEquals(aName) && aRequestingWindow.CanNavigate(child) && child->IsTargetable()) { return child; } @@ -1301,7 +1212,7 @@ BrowsingContext* BrowsingContext::FindChildWithName( } BrowsingContext* BrowsingContext::FindWithSpecialName( - const nsAString& aName, BrowsingContext& aRequestingContext) { + const nsAString& aName, WindowGlobalChild& aRequestingWindow) { // TODO(farre): Neither BrowsingContext nor nsDocShell checks if the // browsing context pointed to by a special name is active. Should // it be? See Bug 1527913. @@ -1311,7 +1222,7 @@ BrowsingContext* BrowsingContext::FindWithSpecialName( if (aName.LowerCaseEqualsLiteral("_parent")) { if (BrowsingContext* parent = GetParent()) { - return aRequestingContext.CanAccess(parent) ? parent : nullptr; + return aRequestingWindow.CanNavigate(parent) ? parent : nullptr; } return this; } @@ -1319,24 +1230,25 @@ BrowsingContext* BrowsingContext::FindWithSpecialName( if (aName.LowerCaseEqualsLiteral("_top")) { BrowsingContext* top = Top(); - return aRequestingContext.CanAccess(top) ? top : nullptr; + return aRequestingWindow.CanNavigate(top) ? top : nullptr; } return nullptr; } BrowsingContext* BrowsingContext::FindWithNameInSubtree( - const nsAString& aName, BrowsingContext& aRequestingContext) { + const nsAString& aName, WindowGlobalChild* aRequestingWindow) { MOZ_DIAGNOSTIC_ASSERT(!aName.IsEmpty()); - if (NameEquals(aName) && aRequestingContext.CanAccess(this) && + if (NameEquals(aName) && + (!aRequestingWindow || aRequestingWindow->CanNavigate(this)) && IsTargetable()) { return this; } for (BrowsingContext* child : NonSyntheticChildren()) { if (BrowsingContext* found = - child->FindWithNameInSubtree(aName, aRequestingContext)) { + child->FindWithNameInSubtree(aName, aRequestingWindow)) { return found; } } @@ -1344,48 +1256,6 @@ BrowsingContext* BrowsingContext::FindWithNameInSubtree( return nullptr; } -// For historical context, see: -// -// Bug 13871: Prevent frameset spoofing -// Bug 103638: Targets with same name in different windows open in wrong -// window with javascript -// Bug 408052: Adopt "ancestor" frame navigation policy -// Bug 1570207: Refactor logic to rely on BrowsingContextGroups to enforce -// origin attribute isolation. -bool BrowsingContext::CanAccess(BrowsingContext* aTarget, - bool aConsiderOpener) { - MOZ_ASSERT( - mDocShell, - "CanAccess() may only be called in the process of the accessing window"); - MOZ_ASSERT(aTarget, "Must have a target"); - - MOZ_DIAGNOSTIC_ASSERT( - Group() == aTarget->Group(), - "A BrowsingContext should never see a context from a different group"); - - // A frame can navigate itself and its own root. - if (aTarget == this || aTarget == Top()) { - return true; - } - - // A frame can navigate any frame with a same-origin ancestor. - for (BrowsingContext* bc = aTarget; bc; bc = bc->GetParent()) { - if (bc->mDocShell && nsDocShell::ValidateOrigin(this, bc)) { - return true; - } - } - - // If the target is a top-level document, a frame can navigate it if it can - // navigate its opener. - if (aConsiderOpener && !aTarget->GetParent()) { - if (RefPtr opener = aTarget->GetOpener()) { - return CanAccess(opener, false); - } - } - - return false; -} - bool BrowsingContext::IsSandboxedFrom(BrowsingContext* aTarget) { // If no target then not sandboxed. if (!aTarget) { @@ -2070,13 +1940,12 @@ nsresult BrowsingContext::LoadURI(nsDocShellLoadState* aLoadState, MOZ_DIAGNOSTIC_ASSERT(!sourceBC || sourceBC->Group() == Group()); if (sourceBC && sourceBC->IsInProcess()) { - if (!sourceBC->CanAccess(this)) { - return NS_ERROR_DOM_PROP_ACCESS_DENIED; - } - nsCOMPtr win(sourceBC->GetDOMWindow()); if (WindowGlobalChild* wgc = win->GetCurrentInnerWindow()->GetWindowGlobalChild()) { + if (!wgc->CanNavigate(this)) { + return NS_ERROR_DOM_PROP_ACCESS_DENIED; + } wgc->SendLoadURI(this, aLoadState, aSetNavigating); } } else if (XRE_IsParentProcess()) { @@ -2175,16 +2044,15 @@ nsresult BrowsingContext::InternalLoad(nsDocShellLoadState* aLoadState) { MOZ_DIAGNOSTIC_ASSERT(sourceBC); MOZ_DIAGNOSTIC_ASSERT(sourceBC->Group() == Group()); - if (!sourceBC->CanAccess(this)) { - return NS_ERROR_DOM_PROP_ACCESS_DENIED; - } - nsCOMPtr win(sourceBC->GetDOMWindow()); WindowGlobalChild* wgc = win->GetCurrentInnerWindow()->GetWindowGlobalChild(); if (!wgc || !wgc->CanSend()) { return NS_ERROR_FAILURE; } + if (!wgc->CanNavigate(this)) { + return NS_ERROR_DOM_PROP_ACCESS_DENIED; + } MOZ_ALWAYS_SUCCEEDS( SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier()))); diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h index 1d1a3f082101..7d8d7eec38a8 100644 --- a/docshell/base/BrowsingContext.h +++ b/docshell/base/BrowsingContext.h @@ -75,6 +75,7 @@ class SessionHistoryInfo; class SessionStorageManager; class StructuredCloneHolder; class WindowContext; +class WindowGlobalChild; struct WindowPostMessageOptions; class WindowProxyHolder; @@ -666,32 +667,26 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { } } - // Using the rules for choosing a browsing context we try to find - // the browsing context with the given name in the set of - // transitively reachable browsing contexts. Performs access control - // checks with regard to this. - // See - // https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name. - // - // BrowsingContext::FindWithName(const nsAString&) is equivalent to - // calling nsIDocShellTreeItem::FindItemWithName(aName, nullptr, - // nullptr, false, ). - BrowsingContext* FindWithName(const nsAString& aName, - bool aUseEntryGlobalForAccessCheck = true); - // Find a browsing context in this context's list of // children. Doesn't consider the special names, '_self', '_parent', // '_top', or '_blank'. Performs access control checks with regard to // 'this'. BrowsingContext* FindChildWithName(const nsAString& aName, - BrowsingContext& aRequestingContext); + WindowGlobalChild& aRequestingWindow); // Find a browsing context in the subtree rooted at 'this' Doesn't // consider the special names, '_self', '_parent', '_top', or - // '_blank'. Performs access control checks with regard to - // 'aRequestingContext'. + // '_blank'. + // + // If passed, performs access control checks with regard to + // 'aRequestingContext', otherwise performs no access checks. BrowsingContext* FindWithNameInSubtree(const nsAString& aName, - BrowsingContext& aRequestingContext); + WindowGlobalChild* aRequestingWindow); + + // Find the special browsing context if aName is '_self', '_parent', + // '_top', but not '_blank'. The latter is handled in FindWithName + BrowsingContext* FindWithSpecialName(const nsAString& aName, + WindowGlobalChild& aRequestingWindow); nsISupports* GetParentObject() const; JSObject* WrapObject(JSContext* aCx, @@ -798,9 +793,6 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { BrowsingContextGroup* aGroup, ContentParent* aOriginProcess); - // Performs access control to check that 'this' can access 'aTarget'. - bool CanAccess(BrowsingContext* aTarget, bool aConsiderOpener = true); - bool IsSandboxedFrom(BrowsingContext* aTarget); // The runnable will be called once there is idle time, or the top level @@ -974,11 +966,6 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { // parent WC changes. void RecomputeCanExecuteScripts(); - // Find the special browsing context if aName is '_self', '_parent', - // '_top', but not '_blank'. The latter is handled in FindWithName - BrowsingContext* FindWithSpecialName(const nsAString& aName, - BrowsingContext& aRequestingContext); - // Is it early enough in the BrowsingContext's lifecycle that it is still // OK to set OriginAttributes? bool CanSetOriginAttributes(); diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index c7e2a196af17..eab30155acc7 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -1437,61 +1437,6 @@ nsDOMNavigationTiming* nsDocShell::GetNavigationTiming() const { return mTiming; } -// -// Bug 13871: Prevent frameset spoofing -// -// This routine answers: 'Is origin's document from same domain as -// target's document?' -// -// file: uris are considered the same domain for the purpose of -// frame navigation regardless of script accessibility (bug 420425) -// -/* static */ -bool nsDocShell::ValidateOrigin(BrowsingContext* aOrigin, - BrowsingContext* aTarget) { - nsIDocShell* originDocShell = aOrigin->GetDocShell(); - MOZ_ASSERT(originDocShell, "originDocShell must not be null"); - Document* originDocument = originDocShell->GetDocument(); - NS_ENSURE_TRUE(originDocument, false); - - nsIDocShell* targetDocShell = aTarget->GetDocShell(); - MOZ_ASSERT(targetDocShell, "targetDocShell must not be null"); - Document* targetDocument = targetDocShell->GetDocument(); - NS_ENSURE_TRUE(targetDocument, false); - - bool equal; - nsresult rv = originDocument->NodePrincipal()->Equals( - targetDocument->NodePrincipal(), &equal); - if (NS_SUCCEEDED(rv) && equal) { - return true; - } - // Not strictly equal, special case if both are file: uris - nsCOMPtr originURI; - nsCOMPtr targetURI; - nsCOMPtr innerOriginURI; - nsCOMPtr innerTargetURI; - - // Casting to BasePrincipal, as we can't get InnerMost URI otherwise - auto* originDocumentBasePrincipal = - BasePrincipal::Cast(originDocument->NodePrincipal()); - - rv = originDocumentBasePrincipal->GetURI(getter_AddRefs(originURI)); - if (NS_SUCCEEDED(rv) && originURI) { - innerOriginURI = NS_GetInnermostURI(originURI); - } - - auto* targetDocumentBasePrincipal = - BasePrincipal::Cast(targetDocument->NodePrincipal()); - - rv = targetDocumentBasePrincipal->GetURI(getter_AddRefs(targetURI)); - if (NS_SUCCEEDED(rv) && targetURI) { - innerTargetURI = NS_GetInnermostURI(targetURI); - } - - return innerOriginURI && innerTargetURI && SchemeIsFile(innerOriginURI) && - SchemeIsFile(innerTargetURI); -} - nsPresContext* nsDocShell::GetEldestPresContext() { nsIContentViewer* viewer = mContentViewer; while (viewer) { @@ -8486,7 +8431,11 @@ nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) { aLoadState->Target().LowerCaseEqualsLiteral("_self") || aLoadState->Target().LowerCaseEqualsLiteral("_parent") || aLoadState->Target().LowerCaseEqualsLiteral("_top")) { - targetContext = mBrowsingContext->FindWithName( + Document* document = GetDocument(); + NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); + WindowGlobalChild* wgc = document->GetWindowGlobalChild(); + NS_ENSURE_TRUE(wgc, NS_ERROR_FAILURE); + targetContext = wgc->FindBrowsingContextWithName( aLoadState->Target(), /* aUseEntryGlobalForAccessCheck */ false); } diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index 28f02a3ce860..4b7af3a55d92 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -558,11 +558,6 @@ class nsDocShell final : public nsDocLoader, nsDocShell(mozilla::dom::BrowsingContext* aBrowsingContext, uint64_t aContentWindowID); - // Security check to prevent frameset spoofing. See comments at - // implementation site. - static bool ValidateOrigin(mozilla::dom::BrowsingContext* aOrigin, - mozilla::dom::BrowsingContext* aTarget); - static inline uint32_t PRTimeToSeconds(PRTime aTimeUsec) { return uint32_t(aTimeUsec / PR_USEC_PER_SEC); } diff --git a/docshell/test/browser/browser_browsingContext-02.js b/docshell/test/browser/browser_browsingContext-02.js index 5d09802412ba..b3108b2617ff 100644 --- a/docshell/test/browser/browser_browsingContext-02.js +++ b/docshell/test/browser/browser_browsingContext-02.js @@ -107,12 +107,14 @@ add_task(async function() { // wish to confirm that targeting is able to find // appropriate browsing contexts. - // BrowsingContext.findWithName requires access checks, which - // can only be performed in the process of the accessor BC's - // docShell. + // WindowGlobalChild.findBrowsingContextWithName requires access + // checks, which can only be performed in the process of the accessor + // WindowGlobalChild. function findWithName(bc, name) { - return content.SpecialPowers.spawn(bc, [bc, name], (bc, name) => { - return bc.findWithName(name); + return content.SpecialPowers.spawn(bc, [name], name => { + return content.windowGlobalChild.findBrowsingContextWithName( + name + ); }); } diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp index 42a7f290153b..a94e641c2127 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -3966,9 +3966,11 @@ Nullable nsGlobalWindowOuter::GetTopOuter() { already_AddRefed nsGlobalWindowOuter::GetChildWindow( const nsAString& aName) { NS_ENSURE_TRUE(mBrowsingContext, nullptr); + NS_ENSURE_TRUE(mInnerWindow, nullptr); + NS_ENSURE_TRUE(mInnerWindow->GetWindowGlobalChild(), nullptr); - return do_AddRef( - mBrowsingContext->FindChildWithName(aName, *mBrowsingContext)); + return do_AddRef(mBrowsingContext->FindChildWithName( + aName, *mInnerWindow->GetWindowGlobalChild())); } bool nsGlobalWindowOuter::DispatchCustomEvent( @@ -4036,7 +4038,10 @@ bool nsGlobalWindowOuter::WindowExists(const nsAString& aName, aName.LowerCaseEqualsLiteral("_parent"); } - return !!mBrowsingContext->FindWithName(aName, aLookForCallerOnJSStack); + if (WindowGlobalChild* wgc = mInnerWindow->GetWindowGlobalChild()) { + return wgc->FindBrowsingContextWithName(aName, aLookForCallerOnJSStack); + } + return false; } already_AddRefed nsGlobalWindowOuter::GetMainWidget() { diff --git a/dom/chrome-webidl/BrowsingContext.webidl b/dom/chrome-webidl/BrowsingContext.webidl index 0e3bea12a33b..8325a2615740 100644 --- a/dom/chrome-webidl/BrowsingContext.webidl +++ b/dom/chrome-webidl/BrowsingContext.webidl @@ -71,9 +71,6 @@ interface BrowsingContext { sequence getAllBrowsingContextsInSubtree(); - BrowsingContext? findChildWithName(DOMString name, BrowsingContext accessor); - BrowsingContext? findWithName(DOMString name); - readonly attribute DOMString name; readonly attribute BrowsingContext? parent; diff --git a/dom/chrome-webidl/WindowGlobalActors.webidl b/dom/chrome-webidl/WindowGlobalActors.webidl index e860e98891f5..b116c569d102 100644 --- a/dom/chrome-webidl/WindowGlobalActors.webidl +++ b/dom/chrome-webidl/WindowGlobalActors.webidl @@ -177,6 +177,8 @@ interface WindowGlobalChild { static WindowGlobalChild? getByInnerWindowId(unsigned long long innerWIndowId); + BrowsingContext? findBrowsingContextWithName(DOMString name); + /** * Get or create the JSWindowActor with the given name. * diff --git a/dom/clients/manager/ClientOpenWindowUtils.cpp b/dom/clients/manager/ClientOpenWindowUtils.cpp index 4c6524a425bc..c9edd51892d5 100644 --- a/dom/clients/manager/ClientOpenWindowUtils.cpp +++ b/dom/clients/manager/ClientOpenWindowUtils.cpp @@ -17,6 +17,7 @@ #include "nsFocusManager.h" #include "nsIBrowserDOMWindow.h" #include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" #include "nsIDOMChromeWindow.h" #include "nsIURI.h" #include "nsIBrowser.h" @@ -327,23 +328,27 @@ void GeckoViewOpenWindow(const ClientOpenWindowArgsParsed& aArgsValidated, promiseResult->Then( GetMainThreadSerialEventTarget(), __func__, [aArgsValidated, promise](nsString sessionId) { - nsresult rv; - nsCOMPtr wwatch = - do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); - if (NS_WARN_IF(NS_FAILED(rv))) { - promise->Reject(rv, __func__); - return rv; - } - // Retrieve the browsing context by using the GeckoSession ID. The // window is named the same as the ID of the GeckoSession it is // associated with. - RefPtr browsingContext = - static_cast(wwatch.get()) - ->GetBrowsingContextByName(sessionId, false, nullptr); - if (NS_WARN_IF(!browsingContext)) { - promise->Reject(NS_ERROR_FAILURE, __func__); - return NS_ERROR_FAILURE; + RefPtr browsingContext; + nsresult rv = [&sessionId, &browsingContext]() -> nsresult { + nsresult rv; + nsCOMPtr wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr chromeWindow; + rv = wwatch->GetWindowByName(sessionId, getter_AddRefs(chromeWindow)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(chromeWindow, NS_ERROR_FAILURE); + browsingContext = + nsPIDOMWindowOuter::From(chromeWindow)->GetBrowsingContext(); + NS_ENSURE_TRUE(browsingContext, NS_ERROR_FAILURE); + return NS_OK; + }(); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->Reject(rv, __func__); + return rv; } WaitForLoad(aArgsValidated, browsingContext, promise); diff --git a/dom/ipc/WindowGlobalChild.cpp b/dom/ipc/WindowGlobalChild.cpp index 8f3fe436d73c..f400bef2b42d 100644 --- a/dom/ipc/WindowGlobalChild.cpp +++ b/dom/ipc/WindowGlobalChild.cpp @@ -41,6 +41,7 @@ #include "mozilla/dom/JSActorService.h" #include "nsIHttpChannelInternal.h" #include "nsIURIMutator.h" +#include "nsURLHelper.h" using namespace mozilla::ipc; using namespace mozilla::dom::ipc; @@ -641,6 +642,169 @@ bool WindowGlobalChild::SameOriginWithTop() { return IsSameOriginWith(WindowContext()->TopWindowContext()); } +// For historical context, see: +// +// Bug 13871: Prevent frameset spoofing +// Bug 103638: Targets with same name in different windows open in wrong +// window with javascript +// Bug 408052: Adopt "ancestor" frame navigation policy +// Bug 1570207: Refactor logic to rely on BrowsingContextGroups to enforce +// origin attribute isolation +// Bug 1810619: Crash at null in nsDocShell::ValidateOrigin +bool WindowGlobalChild::CanNavigate(dom::BrowsingContext* aTarget, + bool aConsiderOpener) { + MOZ_DIAGNOSTIC_ASSERT(WindowContext()->Group() == aTarget->Group(), + "A WindowGlobalChild should never try to navigate a " + "BrowsingContext from another group"); + + auto isFileScheme = [](nsIPrincipal* aPrincipal) -> bool { + // NOTE: This code previously checked for a file scheme using + // `nsIPrincipal::GetURI()` combined with `NS_GetInnermostURI`. We no longer + // use GetURI, as it has been deprecated, and it makes more sense to take + // advantage of the pre-computed origin, which will already use the + // innermost URI (bug 1810619) + nsAutoCString origin, scheme; + return NS_SUCCEEDED(aPrincipal->GetOriginNoSuffix(origin)) && + NS_SUCCEEDED(net_ExtractURLScheme(origin, scheme)) && + scheme == "file"_ns; + }; + + // A frame can navigate itself and its own root. + if (aTarget == BrowsingContext() || aTarget == BrowsingContext()->Top()) { + return true; + } + + // If the target frame doesn't yet have a WindowContext, start checking + // principals from its direct ancestor instead. It would inherit its principal + // from this document upon creation. + dom::WindowContext* initialWc = aTarget->GetCurrentWindowContext(); + if (!initialWc) { + initialWc = aTarget->GetParentWindowContext(); + } + + // A frame can navigate any frame with a same-origin ancestor. + bool isFileDocument = isFileScheme(DocumentPrincipal()); + for (dom::WindowContext* wc = initialWc; wc; + wc = wc->GetParentWindowContext()) { + dom::WindowGlobalChild* wgc = wc->GetWindowGlobalChild(); + if (!wgc) { + continue; // out-of process, so not same-origin. + } + + if (DocumentPrincipal()->Equals(wgc->DocumentPrincipal())) { + return true; + } + + // Not strictly equal, special case if both are file: URIs. + // + // file: URIs are considered the same domain for the purpose of frame + // navigation, regardless of script accessibility (bug 420425). + if (isFileDocument && isFileScheme(wgc->DocumentPrincipal())) { + return true; + } + } + + // If the target is a top-level document, a frame can navigate it if it can + // navigate its opener. + if (aConsiderOpener && !aTarget->GetParent()) { + if (RefPtr opener = aTarget->GetOpener()) { + return CanNavigate(opener, false); + } + } + + return false; +} + +// FindWithName follows the rules for choosing a browsing context, +// with the exception of sandboxing for iframes. The implementation +// for arbitrarily choosing between two browsing contexts with the +// same name is as follows: +// +// 1) The start browsing context, i.e. 'this' +// 2) Descendants in insertion order +// 3) The parent +// 4) Siblings and their children, both in insertion order +// 5) After this we iteratively follow the parent chain, repeating 3 +// and 4 until +// 6) If there is no parent, consider all other top level browsing +// contexts and their children, both in insertion order +// +// See +// https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name +dom::BrowsingContext* WindowGlobalChild::FindBrowsingContextWithName( + const nsAString& aName, bool aUseEntryGlobalForAccessCheck) { + RefPtr requestingContext = this; + if (aUseEntryGlobalForAccessCheck) { + if (nsGlobalWindowInner* caller = nsContentUtils::EntryInnerWindow()) { + if (caller->GetBrowsingContextGroup() == WindowContext()->Group()) { + requestingContext = caller->GetWindowGlobalChild(); + } else { + MOZ_RELEASE_ASSERT(caller->GetPrincipal()->IsSystemPrincipal(), + "caller must be either same-group or system"); + } + } + } + MOZ_ASSERT(requestingContext, "must have a requestingContext"); + + dom::BrowsingContext* found = nullptr; + if (aName.IsEmpty()) { + // You can't find a browsing context with an empty name. + found = nullptr; + } else if (aName.LowerCaseEqualsLiteral("_blank")) { + // Just return null. Caller must handle creating a new window with + // a blank name. + found = nullptr; + } else if (nsContentUtils::IsSpecialName(aName)) { + found = BrowsingContext()->FindWithSpecialName(aName, *requestingContext); + } else if (dom::BrowsingContext* child = + BrowsingContext()->FindWithNameInSubtree(aName, + requestingContext)) { + found = child; + } else { + dom::WindowContext* current = WindowContext(); + + do { + Span> siblings; + dom::WindowContext* parent = current->GetParentWindowContext(); + + if (!parent) { + // We've reached the root of the tree, consider browsing + // contexts in the same browsing context group. + siblings = WindowContext()->Group()->Toplevels(); + } else if (dom::BrowsingContext* bc = parent->GetBrowsingContext(); + bc && bc->NameEquals(aName) && + requestingContext->CanNavigate(bc) && bc->IsTargetable()) { + found = bc; + break; + } else { + siblings = parent->NonSyntheticChildren(); + } + + for (dom::BrowsingContext* sibling : siblings) { + if (sibling == current->GetBrowsingContext()) { + continue; + } + + if (dom::BrowsingContext* relative = + sibling->FindWithNameInSubtree(aName, requestingContext)) { + found = relative; + // Breaks the outer loop + parent = nullptr; + break; + } + } + + current = parent; + } while (current); + } + + // Helpers should perform access control checks, which means that we + // only need to assert that we can access found. + MOZ_DIAGNOSTIC_ASSERT(!found || requestingContext->CanNavigate(found)); + + return found; +} + void WindowGlobalChild::UnblockBFCacheFor(BFCacheStatus aStatus) { SendUpdateBFCacheStatus(0, aStatus); } diff --git a/dom/ipc/WindowGlobalChild.h b/dom/ipc/WindowGlobalChild.h index d5eca95ef391..057238678813 100644 --- a/dom/ipc/WindowGlobalChild.h +++ b/dom/ipc/WindowGlobalChild.h @@ -123,6 +123,23 @@ class WindowGlobalChild final : public WindowGlobalActor, bool SameOriginWithTop(); + // Returns `true` if this WindowGlobal is allowed to navigate the given + // BrowsingContext. BrowsingContexts which are currently out-of-process are + // supported, and assumed to be cross-origin. + // + // The given BrowsingContext must be in the same BrowsingContextGroup as this + // WindowGlobal. + bool CanNavigate(dom::BrowsingContext* aTarget, bool aConsiderOpener = true); + + // Using the rules for choosing a browsing context we try to find + // the browsing context with the given name in the set of + // transitively reachable browsing contexts. Performs access control + // checks with regard to this. + // See + // https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name. + dom::BrowsingContext* FindBrowsingContextWithName( + const nsAString& aName, bool aUseEntryGlobalForAccessCheck = true); + nsISupports* GetParentObject(); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; diff --git a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm index daa7086e5f26..8aec08322075 100644 --- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm +++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm @@ -194,10 +194,7 @@ class GeckoViewNavigation extends GeckoViewModule { let triggeringPrincipal, referrerInfo, csp; if (referrerSessionId) { - const referrerWindow = Services.ww.getWindowByName( - referrerSessionId, - this.window - ); + const referrerWindow = Services.ww.getWindowByName(referrerSessionId); triggeringPrincipal = referrerWindow.browser.contentPrincipal; csp = referrerWindow.browser.csp; diff --git a/toolkit/components/windowwatcher/nsIWindowWatcher.idl b/toolkit/components/windowwatcher/nsIWindowWatcher.idl index c330e4dc8daa..85a40970d02a 100644 --- a/toolkit/components/windowwatcher/nsIWindowWatcher.idl +++ b/toolkit/components/windowwatcher/nsIWindowWatcher.idl @@ -137,18 +137,17 @@ interface nsIWindowWatcher : nsISupports { nsIWebBrowserChrome getChromeForWindow(in mozIDOMWindowProxy aWindow); /** - Retrieve an existing window (or frame). + Retrieve an existing chrome window (or frame). @param aTargetName the window name - @param aCurrentWindow a starting point in the window hierarchy to - begin the search. If null, each toplevel window - will be searched. + + Note: This method will not consider special names like "_blank", "_top", + "_self", or "_parent", as there is no reference window. Note: This method will search all open windows for any window or frame with the given window name. Make sure you understand the security implications of this before using this method! */ - mozIDOMWindowProxy getWindowByName(in AString aTargetName, - in mozIDOMWindowProxy aCurrentWindow); + mozIDOMWindowProxy getWindowByName(in AString aTargetName); /** Retrieves the active window from the focus manager. diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.cpp b/toolkit/components/windowwatcher/nsWindowWatcher.cpp index 6d6a91821e0d..d21b1fa97755 100644 --- a/toolkit/components/windowwatcher/nsWindowWatcher.cpp +++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp @@ -71,6 +71,7 @@ #include "mozilla/dom/BrowserParent.h" #include "mozilla/dom/BrowserHost.h" #include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/WindowGlobalChild.h" #include "mozilla/dom/SessionStorageManager.h" #include "nsIAppWindow.h" #include "nsIXULBrowserWindow.h" @@ -696,8 +697,35 @@ nsresult nsWindowWatcher::OpenWindowInternal( return NS_ERROR_ABORT; } + // If no parent, consider it chrome when running in the parent process. + bool hasChromeParent = !XRE_IsContentProcess(); + if (aParent) { + // Check if the parent document has chrome privileges. + hasChromeParent = parentDoc && nsContentUtils::IsChromeDoc(parentDoc); + } + + bool isCallerChrome = nsContentUtils::LegacyIsCallerChromeOrNativeCode(); + // try to find an extant browsing context with the given name - targetBC = GetBrowsingContextByName(name, aForceNoOpener, parentBC); + if (!name.IsEmpty() && + (!aForceNoOpener || nsContentUtils::IsSpecialName(name))) { + if (parentInnerWin && parentInnerWin->GetWindowGlobalChild()) { + // If we have a parent window, perform the look-up relative to the parent + // inner window. + targetBC = + parentInnerWin->GetWindowGlobalChild()->FindBrowsingContextWithName( + name); + } else if (hasChromeParent && isCallerChrome && + !nsContentUtils::IsSpecialName(name)) { + // Otherwise, if this call is from chrome, perform the lookup relative + // to the system group. + nsCOMPtr chromeWindow; + MOZ_ALWAYS_SUCCEEDS(GetWindowByName(name, getter_AddRefs(chromeWindow))); + if (chromeWindow) { + targetBC = nsPIDOMWindowOuter::From(chromeWindow)->GetBrowsingContext(); + } + } + } // Do sandbox checks here, instead of waiting until nsIDocShell::LoadURI. // The state of the window can change before this call and if we are blocked @@ -714,15 +742,6 @@ nsresult nsWindowWatcher::OpenWindowInternal( // no extant window? make a new one. - // If no parent, consider it chrome when running in the parent process. - bool hasChromeParent = !XRE_IsContentProcess(); - if (aParent) { - // Check if the parent document has chrome privileges. - hasChromeParent = parentDoc && nsContentUtils::IsChromeDoc(parentDoc); - } - - bool isCallerChrome = nsContentUtils::LegacyIsCallerChromeOrNativeCode(); - CSSToDesktopScale cssToDesktopScale(1.0); if (nsCOMPtr win = do_QueryInterface(parentDocShell)) { cssToDesktopScale = win->GetUnscaledCSSToDesktopScale(); @@ -1694,7 +1713,6 @@ nsWindowWatcher::GetChromeForWindow(mozIDOMWindowProxy* aWindow, NS_IMETHODIMP nsWindowWatcher::GetWindowByName(const nsAString& aTargetName, - mozIDOMWindowProxy* aCurrentWindow, mozIDOMWindowProxy** aResult) { if (!aResult) { return NS_ERROR_INVALID_ARG; @@ -1702,17 +1720,22 @@ nsWindowWatcher::GetWindowByName(const nsAString& aTargetName, *aResult = nullptr; - BrowsingContext* currentContext = - aCurrentWindow - ? nsPIDOMWindowOuter::From(aCurrentWindow)->GetBrowsingContext() - : nullptr; + // We won't be able to find any windows with a special or empty name. + if (aTargetName.IsEmpty() || nsContentUtils::IsSpecialName(aTargetName)) { + return NS_OK; + } - RefPtr context = - GetBrowsingContextByName(aTargetName, false, currentContext); - - if (context) { - *aResult = do_AddRef(context->GetDOMWindow()).take(); - MOZ_ASSERT(*aResult); + // Search each toplevel in the chrome BrowsingContextGroup for a window with + // the given name. + for (const RefPtr& toplevel : + BrowsingContextGroup::GetChromeGroup()->Toplevels()) { + BrowsingContext* context = + toplevel->FindWithNameInSubtree(aTargetName, nullptr); + if (context) { + *aResult = do_AddRef(context->GetDOMWindow()).take(); + MOZ_ASSERT(*aResult); + return NS_OK; + } } return NS_OK; @@ -2029,36 +2052,6 @@ uint32_t nsWindowWatcher::CalculateChromeFlagsForSystem( return chromeFlags; } -already_AddRefed nsWindowWatcher::GetBrowsingContextByName( - const nsAString& aName, bool aForceNoOpener, - BrowsingContext* aCurrentContext) { - if (aName.IsEmpty()) { - return nullptr; - } - - if (aForceNoOpener && !nsContentUtils::IsSpecialName(aName)) { - // Ignore all other names in the noopener case. - return nullptr; - } - - RefPtr foundContext; - if (aCurrentContext) { - foundContext = aCurrentContext->FindWithName(aName); - } else if (!nsContentUtils::IsSpecialName(aName)) { - // If we are looking for an item and we don't have a docshell we are - // checking on, let's just look in the chrome browsing context group! - for (RefPtr toplevel : - BrowsingContextGroup::GetChromeGroup()->Toplevels()) { - foundContext = toplevel->FindWithNameInSubtree(aName, *toplevel); - if (foundContext) { - break; - } - } - } - - return foundContext.forget(); -} - // public static bool nsWindowWatcher::HaveSpecifiedSize(const WindowFeatures& features) { return CalcSizeSpec(features, false, CSSToDesktopScale()).SizeSpecified(); diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.h b/toolkit/components/windowwatcher/nsWindowWatcher.h index 89ee25241af3..e34865f510be 100644 --- a/toolkit/components/windowwatcher/nsWindowWatcher.h +++ b/toolkit/components/windowwatcher/nsWindowWatcher.h @@ -56,13 +56,6 @@ class nsWindowWatcher : public nsIWindowWatcher, uint32_t aChromeFlags, bool aCalledFromJS, bool aIsForPrinting); - // Will first look for a caller on the JS stack, and then fall back on - // aCurrentContext if it can't find one. - // It also knows to not look for things if aForceNoOpener is set. - already_AddRefed GetBrowsingContextByName( - const nsAString& aName, bool aForceNoOpener, - mozilla::dom::BrowsingContext* aCurrentContext); - static bool HaveSpecifiedSize(const mozilla::dom::WindowFeatures& features); protected: diff --git a/toolkit/mozapps/extensions/AbuseReporter.jsm b/toolkit/mozapps/extensions/AbuseReporter.jsm index 309d654aaf55..843744be2855 100644 --- a/toolkit/mozapps/extensions/AbuseReporter.jsm +++ b/toolkit/mozapps/extensions/AbuseReporter.jsm @@ -381,7 +381,7 @@ const AbuseReporter = { * @returns {Window?} */ getOpenDialog() { - return Services.ww.getWindowByName(DIALOG_WINDOW_NAME, null); + return Services.ww.getWindowByName(DIALOG_WINDOW_NAME); }, /** diff --git a/toolkit/mozapps/extensions/test/browser/head_abuse_report.js b/toolkit/mozapps/extensions/test/browser/head_abuse_report.js index 6ef1786081b2..bb277072b769 100644 --- a/toolkit/mozapps/extensions/test/browser/head_abuse_report.js +++ b/toolkit/mozapps/extensions/test/browser/head_abuse_report.js @@ -188,7 +188,7 @@ const AbuseReportTestUtils = { // Returns the currently open abuse report dialog window (if any). getReportDialog() { - return Services.ww.getWindowByName("addons-abuse-report-dialog", null); + return Services.ww.getWindowByName("addons-abuse-report-dialog"); }, // Returns the parameters related to the report dialog (if any).