diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index da8c2111ea62c..aade5752a7116 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -924,6 +924,7 @@ nsContentUtils::ParseSandboxAttributeToFlags(const nsAString& aSandboxAttrValue) // If there's a sandbox attribute at all (and there is if this is being // called), start off by setting all the restriction flags. uint32_t out = SANDBOXED_NAVIGATION | + SANDBOXED_AUXILIARY_NAVIGATION | SANDBOXED_TOPLEVEL_NAVIGATION | SANDBOXED_PLUGINS | SANDBOXED_ORIGIN | @@ -954,6 +955,8 @@ nsContentUtils::ParseSandboxAttributeToFlags(const nsAString& aSandboxAttrValue) out &= ~SANDBOXED_TOPLEVEL_NAVIGATION; } else if (token.LowerCaseEqualsLiteral("allow-pointer-lock")) { out &= ~SANDBOXED_POINTER_LOCK; + } else if (token.LowerCaseEqualsLiteral("allow-popups")) { + out &= ~SANDBOXED_AUXILIARY_NAVIGATION; } } } diff --git a/content/base/src/nsGkAtomList.h b/content/base/src/nsGkAtomList.h index f2f10917fd36d..11414bcaeda3a 100644 --- a/content/base/src/nsGkAtomList.h +++ b/content/base/src/nsGkAtomList.h @@ -72,12 +72,8 @@ GK_ATOM(align, "align") GK_ATOM(alink, "alink") GK_ATOM(all, "all") GK_ATOM(allowevents, "allowevents") -GK_ATOM(allowforms, "allow-forms") GK_ATOM(allownegativeassertions, "allownegativeassertions") GK_ATOM(allowfullscreen, "allowfullscreen") -GK_ATOM(allowsameorigin, "allow-same-origin") -GK_ATOM(allowscripts, "allow-scripts") -GK_ATOM(allowtopnavigation, "allow-top-navigation") GK_ATOM(allowuntrusted, "allowuntrusted") GK_ATOM(alt, "alt") GK_ATOM(alternate, "alternate") diff --git a/content/base/src/nsSandboxFlags.h b/content/base/src/nsSandboxFlags.h index 84acafa4b1c8a..48cd7306ead92 100644 --- a/content/base/src/nsSandboxFlags.h +++ b/content/base/src/nsSandboxFlags.h @@ -13,8 +13,11 @@ /** * This flag prevents content from navigating browsing contexts other than - * the sandboxed browsing context itself (or browsing contexts further - * nested inside it), and the top-level browsing context. + * itself, browsing contexts nested inside it, the top-level browsing context + * and browsing contexts that it has opened. + * As it is always on for sandboxed browsing contexts, it is used implicitly + * within the code by checking that the overall flags are non-zero. + * It is only uesd directly when the sandbox flags are initially set up. */ const unsigned long SANDBOXED_NAVIGATION = 0x1; @@ -65,4 +68,11 @@ const unsigned long SANDBOXED_POINTER_LOCK = 0x80; * This flag blocks the document from changing document.domain. */ const unsigned long SANDBOXED_DOMAIN = 0x100; + +/** + * This flag prevents content from creating new auxiliary browsing contexts, + * e.g. using the target attribute, the window.open() method, or the + * showModalDialog() method. + */ +const unsigned long SANDBOXED_AUXILIARY_NAVIGATION = 0x200; #endif diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 0d09019e57ca5..15d8fe0c5ca74 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -3190,62 +3190,11 @@ nsDocShell::FindItemWithName(const PRUnichar * aName, // DoFindItemWithName only returns active items and we don't check if // the item is active for the special cases. if (foundItem) { - - // If our document is sandboxed, we need to do some extra checks. - uint32_t sandboxFlags = 0; - - nsCOMPtr doc = do_GetInterface(aOriginalRequestor); - - if (doc) { - sandboxFlags = doc->GetSandboxFlags(); - } - - if (sandboxFlags) { - nsCOMPtr root; - GetSameTypeRootTreeItem(getter_AddRefs(root)); - - // Is the found item not a top level browsing context and not ourself ? - nsCOMPtr selfAsItem = static_cast(this); - if (foundItem != root && foundItem != selfAsItem) { - // Are we an ancestor of the foundItem ? - bool isAncestor = false; - - nsCOMPtr parentAsItem; - foundItem->GetSameTypeParent(getter_AddRefs(parentAsItem)); - while (parentAsItem) { - if (parentAsItem == selfAsItem) { - isAncestor = true; - break; - } - nsCOMPtr tmp = parentAsItem; - tmp->GetSameTypeParent(getter_AddRefs(parentAsItem)); - } - - if (!isAncestor) { - // No, we are not an ancestor and our document is - // sandboxed, we can't allow this. - foundItem = nullptr; - } - } else { - // Top level browsing context - is it an ancestor of ours ? - nsCOMPtr tmp; - GetSameTypeParent(getter_AddRefs(tmp)); - - while (tmp) { - if (tmp && tmp == foundItem) { - // This is an ancestor, and we are sandboxed. - // Unless allow-top-navigation is set, we can't allow this. - if (sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) { - foundItem = nullptr; - } - break; - } - tmp->GetParent(getter_AddRefs(tmp)); - } - } + if (IsSandboxedFrom(foundItem, aOriginalRequestor)) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } else { + foundItem.swap(*_retval); } - - foundItem.swap(*_retval); } return NS_OK; } @@ -3318,6 +3267,70 @@ nsDocShell::DoFindItemWithName(const PRUnichar* aName, return NS_OK; } +/* static */ +bool +nsDocShell::IsSandboxedFrom(nsIDocShellTreeItem* aTargetItem, + nsIDocShellTreeItem* aAccessingItem) +{ + // aAccessingItem cannot be sandboxed from itself. + if (SameCOMIdentity(aTargetItem, aAccessingItem)) { + return false; + } + + uint32_t sandboxFlags = 0; + + nsCOMPtr doc = do_GetInterface(aAccessingItem); + if (doc) { + sandboxFlags = doc->GetSandboxFlags(); + } + + // If no flags, aAccessingItem is not sandboxed at all. + if (!sandboxFlags) { + return false; + } + + // If aTargetItem has an ancestor, it is not top level. + nsCOMPtr ancestorOfTarget; + aTargetItem->GetSameTypeParent(getter_AddRefs(ancestorOfTarget)); + if (ancestorOfTarget) { + do { + // aAccessingItem is not sandboxed if it is an ancestor of target. + if (SameCOMIdentity(aAccessingItem, ancestorOfTarget)) { + return false; + } + nsCOMPtr tempTreeItem; + ancestorOfTarget->GetSameTypeParent(getter_AddRefs(tempTreeItem)); + tempTreeItem.swap(ancestorOfTarget); + } while (ancestorOfTarget); + + // Otherwise, aAccessingItem is sandboxed from aTargetItem. + return true; + } + + // aTargetItem is top level, is aAccessingItem the "one permitted sandboxed + // navigator", i.e. did aAccessingItem open aTargetItem? + nsCOMPtr targetDocShell = do_QueryInterface(aTargetItem); + nsCOMPtr permittedNavigator; + targetDocShell-> + GetOnePermittedSandboxedNavigator(getter_AddRefs(permittedNavigator)); + if (SameCOMIdentity(aAccessingItem, permittedNavigator)) { + return false; + } + + // If SANDBOXED_TOPLEVEL_NAVIGATION flag is not on, aAccessingItem is + // not sandboxed from its top. + if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION)) { + nsCOMPtr rootTreeItem; + aAccessingItem->GetSameTypeRootTreeItem(getter_AddRefs(rootTreeItem)); + if (SameCOMIdentity(aTargetItem, rootTreeItem)) { + return false; + } + } + + // Otherwise, aAccessingItem is sandboxed from aTargetItem. + return true; +} + NS_IMETHODIMP nsDocShell::GetTreeOwner(nsIDocShellTreeOwner ** aTreeOwner) { @@ -5055,6 +5068,8 @@ nsDocShell::Destroy() SetTreeOwner(nullptr); + mOnePermittedSandboxedNavigator = nullptr; + // required to break ref cycle mSecurityUI = nullptr; @@ -5403,6 +5418,31 @@ nsDocShell::GetSandboxFlags(uint32_t *aSandboxFlags) return NS_OK; } +NS_IMETHODIMP +nsDocShell::SetOnePermittedSandboxedNavigator(nsIDocShell* aSandboxedNavigator) +{ + if (mOnePermittedSandboxedNavigator) { + NS_ERROR("One Permitted Sandboxed Navigator should only be set once."); + return NS_OK; + } + + mOnePermittedSandboxedNavigator = do_GetWeakReference(aSandboxedNavigator); + NS_ASSERTION(mOnePermittedSandboxedNavigator, + "One Permitted Sandboxed Navigator must support weak references."); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetOnePermittedSandboxedNavigator(nsIDocShell** aSandboxedNavigator) +{ + NS_ENSURE_ARG_POINTER(aSandboxedNavigator); + nsCOMPtr permittedNavigator = + do_QueryReferent(mOnePermittedSandboxedNavigator); + NS_IF_ADDREF(*aSandboxedNavigator = permittedNavigator); + return NS_OK; +} + NS_IMETHODIMP nsDocShell::SetDefaultLoadFlags(uint32_t aDefaultLoadFlags) { @@ -8719,8 +8759,10 @@ nsDocShell::InternalLoad(nsIURI * aURI, if (aWindowTarget && *aWindowTarget) { // Locate the target DocShell. nsCOMPtr targetItem; - FindItemWithName(aWindowTarget, nullptr, this, - getter_AddRefs(targetItem)); + if (FindItemWithName(aWindowTarget, nullptr, this, + getter_AddRefs(targetItem)) == NS_ERROR_DOM_INVALID_ACCESS_ERR) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } targetDocShell = do_QueryInterface(targetItem); // If the targetDocShell doesn't exist, then this is a new docShell @@ -8847,19 +8889,17 @@ nsDocShell::InternalLoad(nsIURI * aURI, bool isNewWindow = false; if (!targetDocShell) { - // If the docshell's document is sandboxed and was trying to - // navigate/load a frame it wasn't allowed to access, the - // FindItemWithName above will have returned null for the target - // item - we don't want to actually open a new window in this case - // though. Check if we are sandboxed and bail out here if so. + // If the docshell's document is sandboxed, only open a new window + // if the document's SANDBOXED_AUXILLARY_NAVIGATION flag is not set. + // (i.e. if allow-popups is specified) NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE); nsIDocument* doc = mContentViewer->GetDocument(); uint32_t sandboxFlags = 0; if (doc) { sandboxFlags = doc->GetSandboxFlags(); - if (sandboxFlags & SANDBOXED_NAVIGATION) { - return NS_ERROR_FAILURE; + if (sandboxFlags & SANDBOXED_AUXILIARY_NAVIGATION) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; } } diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index 917b4c1e9be7d..9eb23c75f200d 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -771,6 +771,7 @@ class nsDocShell : public nsDocLoader, int32_t mLoadedTransIndex; uint32_t mSandboxFlags; + nsWeakPtr mOnePermittedSandboxedNavigator; // mFullscreenAllowed stores how we determine whether fullscreen is allowed // when GetFullscreenAllowed() is called. Fullscreen is allowed in a @@ -880,6 +881,10 @@ class nsDocShell : public nsDocLoader, nsIDocShellTreeItem* aOriginalRequestor, nsIDocShellTreeItem** _retval); + // Check whether accessing item is sandboxed from the target item. + static bool IsSandboxedFrom(nsIDocShellTreeItem* aTargetItem, + nsIDocShellTreeItem* aAccessingItem); + #ifdef DEBUG // We're counting the number of |nsDocShells| to help find leaks static unsigned long gNumberOfDocShells; diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl index 0439823b3e21b..9efec222d3f9d 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -43,7 +43,7 @@ interface nsIReflowObserver; typedef unsigned long nsLoadFlags; -[scriptable, builtinclass, uuid(5f4d82fc-3220-4f7e-9b00-626f1033318a)] +[scriptable, builtinclass, uuid(4ca172c3-67bf-4e6d-89a3-cbfb929c370d)] interface nsIDocShell : nsIDocShellTreeItem { /** @@ -798,6 +798,13 @@ interface nsIDocShell : nsIDocShellTreeItem */ attribute unsigned long sandboxFlags; + /** + * When a new browsing context is opened by a sandboxed document, it needs to + * keep track of the browsing context that opened it, so that it can be + * navigated by it. This is the "one permitted sandboxed navigator". + */ + attribute nsIDocShell onePermittedSandboxedNavigator; + /** * This member variable determines whether a document has Mixed Active Content that * was initially blocked from loading, but the user has choosen to override the diff --git a/embedding/components/windowwatcher/src/nsWindowWatcher.cpp b/embedding/components/windowwatcher/src/nsWindowWatcher.cpp index 09425b859ecc6..131a80b013840 100644 --- a/embedding/components/windowwatcher/src/nsWindowWatcher.cpp +++ b/embedding/components/windowwatcher/src/nsWindowWatcher.cpp @@ -475,7 +475,10 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow *aParent, // try to find an extant window with the given name nsCOMPtr foundWindow; - SafeGetWindowByName(name, aParent, getter_AddRefs(foundWindow)); + if (SafeGetWindowByName(name, aParent, getter_AddRefs(foundWindow)) == + NS_ERROR_DOM_INVALID_ACCESS_ERR) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } GetWindowTreeItem(foundWindow, getter_AddRefs(newDocShellItem)); // no extant window? make a new one. @@ -543,21 +546,26 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow *aParent, callerContextGuard.Push(cx); } + uint32_t activeDocsSandboxFlags = 0; if (!newDocShellItem) { // We're going to either open up a new window ourselves or ask a // nsIWindowProvider for one. In either case, we'll want to set the right // name on it. windowNeedsName = true; - // If the parent trying to open a new window is sandboxed, - // this is not allowed and we fail here. + // If the parent trying to open a new window is sandboxed + // without 'allow-popups', this is not allowed and we fail here. if (aParent) { nsCOMPtr domdoc; aParent->GetDocument(getter_AddRefs(domdoc)); nsCOMPtr doc = do_QueryInterface(domdoc); - if (doc && (doc->GetSandboxFlags() & SANDBOXED_NAVIGATION)) { - return NS_ERROR_FAILURE; + if (doc) { + // Save sandbox flags for copying to new browsing context (docShell). + activeDocsSandboxFlags = doc->GetSandboxFlags(); + if (activeDocsSandboxFlags & SANDBOXED_AUXILIARY_NAVIGATION) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } } } @@ -709,6 +717,16 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow *aParent, nsCOMPtr newDocShell(do_QueryInterface(newDocShellItem)); NS_ENSURE_TRUE(newDocShell, NS_ERROR_UNEXPECTED); + + // Set up sandboxing attributes if the window is new. + // The flags can only be non-zero for new windows. + if (activeDocsSandboxFlags != 0) { + newDocShell->SetSandboxFlags(activeDocsSandboxFlags); + nsCOMPtr window = do_QueryInterface(aParent); + if (window) { + newDocShell->SetOnePermittedSandboxedNavigator(window->GetDocShell()); + } + } rv = ReadyOpenedDocShellItem(newDocShellItem, aParent, windowIsNew, _retval); if (NS_FAILED(rv)) @@ -1299,6 +1317,7 @@ nsWindowWatcher::GetWindowByName(const PRUnichar *aTargetName, if (!aResult) { return NS_ERROR_INVALID_ARG; } + nsresult rv; *aResult = nullptr; @@ -1308,18 +1327,18 @@ nsWindowWatcher::GetWindowByName(const PRUnichar *aTargetName, GetWindowTreeItem(aCurrentWindow, getter_AddRefs(startItem)); if (startItem) { // Note: original requestor is null here, per idl comments - startItem->FindItemWithName(aTargetName, nullptr, nullptr, + rv = startItem->FindItemWithName(aTargetName, nullptr, nullptr, getter_AddRefs(treeItem)); } else { // Note: original requestor is null here, per idl comments - FindItemWithName(aTargetName, nullptr, nullptr, getter_AddRefs(treeItem)); + rv = FindItemWithName(aTargetName, nullptr, nullptr, getter_AddRefs(treeItem)); } nsCOMPtr domWindow = do_GetInterface(treeItem); domWindow.swap(*aResult); - return NS_OK; + return rv; } bool @@ -1731,6 +1750,7 @@ nsWindowWatcher::SafeGetWindowByName(const nsAString& aName, nsIDOMWindow** aResult) { *aResult = nullptr; + nsresult rv; nsCOMPtr startItem; GetWindowTreeItem(aCurrentWindow, getter_AddRefs(startItem)); @@ -1741,17 +1761,17 @@ nsWindowWatcher::SafeGetWindowByName(const nsAString& aName, nsCOMPtr foundItem; if (startItem) { - startItem->FindItemWithName(flatName.get(), nullptr, callerItem, + rv = startItem->FindItemWithName(flatName.get(), nullptr, callerItem, getter_AddRefs(foundItem)); } else { - FindItemWithName(flatName.get(), nullptr, callerItem, + rv = FindItemWithName(flatName.get(), nullptr, callerItem, getter_AddRefs(foundItem)); } nsCOMPtr foundWin = do_GetInterface(foundItem); foundWin.swap(*aResult); - return NS_OK; + return rv; } /* Fetch the nsIDOMWindow corresponding to the given nsIDocShellTreeItem.