Bug 1810619 - Part 1: Be more precise in named lookup code, r=smaug,geckoview-reviewers,m_kato

This makes various changes to the named lookup/navigation code to make
them more precise, and avoid issues which could happen if a window is
closed while script is still executing.

This also should improve handling for inactive windows in some cases, by
more frequently working off of the WindowContext tree rather than the
BrowsingContext tree.

As part of these changes, some behaviour was changed around e.g. the
file URI exception to avoid the deprecated nsIPrincipal::GetURI method.
I don't believe the behaviour should have changed in a meaningful way.

Differential Revision: https://phabricator.services.mozilla.com/D171755
This commit is contained in:
Nika Layzell 2023-03-08 21:09:20 +00:00
Родитель 8a81a95209
Коммит eece505eef
17 изменённых файлов: 301 добавлений и 328 удалений

Просмотреть файл

@ -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<BrowsingContext> 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<RefPtr<BrowsingContext>> 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<BrowsingContext> 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<nsPIDOMWindowOuter> 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<nsPIDOMWindowOuter> 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())));

Просмотреть файл

@ -75,6 +75,7 @@ class SessionHistoryInfo;
class SessionStorageManager;
class StructuredCloneHolder;
class WindowContext;
class WindowGlobalChild;
struct WindowPostMessageOptions;
class WindowProxyHolder;
@ -659,32 +660,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, <return value>).
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,
@ -791,9 +786,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
@ -964,11 +956,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();

Просмотреть файл

@ -1438,61 +1438,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<nsIURI> originURI;
nsCOMPtr<nsIURI> targetURI;
nsCOMPtr<nsIURI> innerOriginURI;
nsCOMPtr<nsIURI> 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) {
@ -8499,7 +8444,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);
}

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -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
);
});
}

Просмотреть файл

@ -3966,9 +3966,11 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetTopOuter() {
already_AddRefed<BrowsingContext> 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<nsIWidget> nsGlobalWindowOuter::GetMainWidget() {

Просмотреть файл

@ -71,9 +71,6 @@ interface BrowsingContext {
sequence<BrowsingContext> getAllBrowsingContextsInSubtree();
BrowsingContext? findChildWithName(DOMString name, BrowsingContext accessor);
BrowsingContext? findWithName(DOMString name);
readonly attribute DOMString name;
readonly attribute BrowsingContext? parent;

Просмотреть файл

@ -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.
*

Просмотреть файл

@ -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<nsIWindowWatcher> 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> browsingContext =
static_cast<nsWindowWatcher*>(wwatch.get())
->GetBrowsingContextByName(sessionId, false, nullptr);
if (NS_WARN_IF(!browsingContext)) {
promise->Reject(NS_ERROR_FAILURE, __func__);
return NS_ERROR_FAILURE;
RefPtr<BrowsingContext> browsingContext;
nsresult rv = [&sessionId, &browsingContext]() -> nsresult {
nsresult rv;
nsCOMPtr<nsIWindowWatcher> wwatch =
do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIDOMWindowProxy> 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);

Просмотреть файл

@ -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<dom::BrowsingContext> 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<WindowGlobalChild> 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<RefPtr<dom::BrowsingContext>> 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);
}

Просмотреть файл

@ -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<JSObject*> aGivenProto) override;

Просмотреть файл

@ -193,10 +193,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;

Просмотреть файл

@ -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.

Просмотреть файл

@ -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<mozIDOMWindowProxy> 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<nsIBaseWindow> 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<BrowsingContext> 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<BrowsingContext>& 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<BrowsingContext> 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<BrowsingContext> 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<BrowsingContext> 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();

Просмотреть файл

@ -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<mozilla::dom::BrowsingContext> GetBrowsingContextByName(
const nsAString& aName, bool aForceNoOpener,
mozilla::dom::BrowsingContext* aCurrentContext);
static bool HaveSpecifiedSize(const mozilla::dom::WindowFeatures& features);
protected:

Просмотреть файл

@ -381,7 +381,7 @@ const AbuseReporter = {
* @returns {Window?}
*/
getOpenDialog() {
return Services.ww.getWindowByName(DIALOG_WINDOW_NAME, null);
return Services.ww.getWindowByName(DIALOG_WINDOW_NAME);
},
/**

Просмотреть файл

@ -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).