Bug 785310 - HTML5 sandboxed iframe should not be able to perform top navigation when scripts are allowed. r=bholley, r=smaug

This commit is contained in:
Bob Owen 2014-01-13 07:58:16 +00:00
parent aabad2abb4
commit d8885ccdf2
9 changed files with 133 additions and 86 deletions

View File

@ -1323,6 +1323,7 @@ nsDocShell::LoadURI(nsIURI * aURI,
nsCOMPtr<nsISHEntry> shEntry;
nsXPIDLString target;
nsAutoString srcdoc;
nsCOMPtr<nsIDocShell> sourceDocShell;
uint32_t loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags);
@ -1352,6 +1353,7 @@ nsDocShell::LoadURI(nsIURI * aURI,
aLoadInfo->GetSendReferrer(&sendReferrer);
aLoadInfo->GetIsSrcdocLoad(&isSrcdoc);
aLoadInfo->GetSrcdocData(srcdoc);
aLoadInfo->GetSourceDocShell(getter_AddRefs(sourceDocShell));
}
#if defined(PR_LOGGING) && defined(DEBUG)
@ -1597,6 +1599,7 @@ nsDocShell::LoadURI(nsIURI * aURI,
nullptr, // No SHEntry
aFirstParty,
srcdoc,
sourceDocShell,
nullptr, // No nsIDocShell
nullptr); // No nsIRequest
}
@ -3303,11 +3306,7 @@ nsDocShell::FindItemWithName(const char16_t * aName,
// DoFindItemWithName only returns active items and we don't check if
// the item is active for the special cases.
if (foundItem) {
if (IsSandboxedFrom(foundItem, aOriginalRequestor)) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
} else {
foundItem.swap(*_retval);
}
foundItem.swap(*_retval);
}
return NS_OK;
}
@ -3380,35 +3379,38 @@ nsDocShell::DoFindItemWithName(const char16_t* aName,
return NS_OK;
}
/* static */
bool
nsDocShell::IsSandboxedFrom(nsIDocShellTreeItem* aTargetItem,
nsIDocShellTreeItem* aAccessingItem)
nsDocShell::IsSandboxedFrom(nsIDocShell* aTargetDocShell)
{
// aAccessingItem cannot be sandboxed from itself.
if (SameCOMIdentity(aTargetItem, aAccessingItem)) {
// If no target then not sandboxed.
if (!aTargetDocShell) {
return false;
}
// We cannot be sandboxed from ourselves.
if (aTargetDocShell == this) {
return false;
}
uint32_t sandboxFlags = 0;
nsCOMPtr<nsIDocument> doc = do_GetInterface(aAccessingItem);
nsCOMPtr<nsIDocument> doc = mContentViewer->GetDocument();
if (doc) {
sandboxFlags = doc->GetSandboxFlags();
}
// If no flags, aAccessingItem is not sandboxed at all.
// If no flags, we are not sandboxed at all.
if (!sandboxFlags) {
return false;
}
// If aTargetItem has an ancestor, it is not top level.
// If aTargetDocShell has an ancestor, it is not top level.
nsCOMPtr<nsIDocShellTreeItem> ancestorOfTarget;
aTargetItem->GetSameTypeParent(getter_AddRefs(ancestorOfTarget));
aTargetDocShell->GetSameTypeParent(getter_AddRefs(ancestorOfTarget));
if (ancestorOfTarget) {
do {
// aAccessingItem is not sandboxed if it is an ancestor of target.
if (SameCOMIdentity(aAccessingItem, ancestorOfTarget)) {
// We are not sandboxed if we are an ancestor of target.
if (ancestorOfTarget == this) {
return false;
}
nsCOMPtr<nsIDocShellTreeItem> tempTreeItem;
@ -3416,31 +3418,30 @@ nsDocShell::IsSandboxedFrom(nsIDocShellTreeItem* aTargetItem,
tempTreeItem.swap(ancestorOfTarget);
} while (ancestorOfTarget);
// Otherwise, aAccessingItem is sandboxed from aTargetItem.
// Otherwise, we are sandboxed from aTargetDocShell.
return true;
}
// aTargetItem is top level, is aAccessingItem the "one permitted sandboxed
// navigator", i.e. did aAccessingItem open aTargetItem?
nsCOMPtr<nsIDocShell> targetDocShell = do_QueryInterface(aTargetItem);
// aTargetDocShell is top level, are we the "one permitted sandboxed
// navigator", i.e. did we open aTargetDocShell?
nsCOMPtr<nsIDocShell> permittedNavigator;
targetDocShell->
aTargetDocShell->
GetOnePermittedSandboxedNavigator(getter_AddRefs(permittedNavigator));
if (SameCOMIdentity(aAccessingItem, permittedNavigator)) {
if (permittedNavigator == this) {
return false;
}
// If SANDBOXED_TOPLEVEL_NAVIGATION flag is not on, aAccessingItem is
// not sandboxed from its top.
// If SANDBOXED_TOPLEVEL_NAVIGATION flag is not on, we are not sandboxed
// from our top.
if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION)) {
nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
aAccessingItem->GetSameTypeRootTreeItem(getter_AddRefs(rootTreeItem));
if (SameCOMIdentity(aTargetItem, rootTreeItem)) {
GetSameTypeRootTreeItem(getter_AddRefs(rootTreeItem));
if (SameCOMIdentity(aTargetDocShell, rootTreeItem)) {
return false;
}
}
// Otherwise, aAccessingItem is sandboxed from aTargetItem.
// Otherwise, we are sandboxed from aTargetDocShell.
return true;
}
@ -4793,7 +4794,7 @@ nsDocShell::LoadErrorPage(nsIURI *aURI, const char16_t *aURL,
return InternalLoad(errorPageURI, nullptr, nullptr,
INTERNAL_LOAD_FLAGS_INHERIT_OWNER, nullptr, nullptr,
NullString(), nullptr, nullptr, LOAD_ERROR_PAGE,
nullptr, true, NullString(), nullptr,nullptr);
nullptr, true, NullString(), this, nullptr, nullptr);
}
@ -4861,6 +4862,7 @@ nsDocShell::Reload(uint32_t aReloadFlags)
nullptr, // No SHEntry
true,
srcdoc, // srcdoc argument for iframe
this, // For reloads we are the source
nullptr, // No nsIDocShell
nullptr); // No nsIRequest
}
@ -8636,7 +8638,7 @@ public:
const char* aTypeHint, nsIInputStream * aPostData,
nsIInputStream * aHeadersData, uint32_t aLoadType,
nsISHEntry * aSHEntry, bool aFirstParty,
const nsAString &aSrcdoc) :
const nsAString &aSrcdoc, nsIDocShell* aSourceDocShell) :
mSrcdoc(aSrcdoc),
mDocShell(aDocShell),
mURI(aURI),
@ -8647,7 +8649,8 @@ public:
mSHEntry(aSHEntry),
mFlags(aFlags),
mLoadType(aLoadType),
mFirstParty(aFirstParty)
mFirstParty(aFirstParty),
mSourceDocShell(aSourceDocShell)
{
// Make sure to keep null things null as needed
if (aTypeHint) {
@ -8660,7 +8663,7 @@ public:
nullptr, mTypeHint.get(),
NullString(), mPostData, mHeadersData,
mLoadType, mSHEntry, mFirstParty,
mSrcdoc, nullptr, nullptr);
mSrcdoc, mSourceDocShell, nullptr, nullptr);
}
private:
@ -8680,6 +8683,7 @@ private:
uint32_t mFlags;
uint32_t mLoadType;
bool mFirstParty;
nsCOMPtr<nsIDocShell> mSourceDocShell;
};
/**
@ -8713,6 +8717,7 @@ nsDocShell::InternalLoad(nsIURI * aURI,
nsISHEntry * aSHEntry,
bool aFirstParty,
const nsAString &aSrcdoc,
nsIDocShell* aSourceDocShell,
nsIDocShell** aDocShell,
nsIRequest** aRequest)
{
@ -8776,10 +8781,9 @@ nsDocShell::InternalLoad(nsIURI * aURI,
if (aWindowTarget && *aWindowTarget) {
// Locate the target DocShell.
nsCOMPtr<nsIDocShellTreeItem> targetItem;
if (FindItemWithName(aWindowTarget, nullptr, this,
getter_AddRefs(targetItem)) == NS_ERROR_DOM_INVALID_ACCESS_ERR) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
rv = FindItemWithName(aWindowTarget, nullptr, this,
getter_AddRefs(targetItem));
NS_ENSURE_SUCCESS(rv, rv);
targetDocShell = do_QueryInterface(targetItem);
// If the targetDocShell doesn't exist, then this is a new docShell
@ -8968,6 +8972,7 @@ nsDocShell::InternalLoad(nsIURI * aURI,
aSHEntry,
aFirstParty,
aSrcdoc,
aSourceDocShell,
aDocShell,
aRequest);
if (rv == NS_ERROR_NO_CONTENT) {
@ -9022,17 +9027,6 @@ nsDocShell::InternalLoad(nsIURI * aURI,
return rv;
}
// If this docshell is owned by a frameloader, make sure to cancel
// possible frameloader initialization before loading a new page.
nsCOMPtr<nsIDocShellTreeItem> parent;
GetParent(getter_AddRefs(parent));
if (parent) {
nsCOMPtr<nsIDocument> doc = do_GetInterface(parent);
if (doc) {
doc->TryCancelFrameLoaderInitialization(this);
}
}
if (mFiredUnloadEvent) {
if (IsOKToLoadURI(aURI)) {
NS_PRECONDITION(!aWindowTarget || !*aWindowTarget,
@ -9049,7 +9043,8 @@ nsDocShell::InternalLoad(nsIURI * aURI,
nsCOMPtr<nsIRunnable> ev =
new InternalLoadEvent(this, aURI, aReferrer, aOwner, aFlags,
aTypeHint, aPostData, aHeadersData,
aLoadType, aSHEntry, aFirstParty, aSrcdoc);
aLoadType, aSHEntry, aFirstParty, aSrcdoc,
aSourceDocShell);
return NS_DispatchToCurrentThread(ev);
}
@ -9057,6 +9052,23 @@ nsDocShell::InternalLoad(nsIURI * aURI,
return NS_OK;
}
// If a source docshell has been passed, check to see if we are sandboxed
// from it as the result of an iframe or CSP sandbox.
if (aSourceDocShell && aSourceDocShell->IsSandboxedFrom(this)) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
// If this docshell is owned by a frameloader, make sure to cancel
// possible frameloader initialization before loading a new page.
nsCOMPtr<nsIDocShellTreeItem> parent;
GetParent(getter_AddRefs(parent));
if (parent) {
nsCOMPtr<nsIDocument> doc = do_GetInterface(parent);
if (doc) {
doc->TryCancelFrameLoaderInitialization(this);
}
}
// Before going any further vet loads initiated by external programs.
if (aLoadType == LOAD_NORMAL_EXTERNAL) {
// Disallow external chrome: loads targetted at content windows
@ -11067,6 +11079,10 @@ nsDocShell::LoadHistoryEntry(nsISHEntry * aEntry, uint32_t aLoadType)
srcdoc = NullString();
}
// Passing nullptr as aSourceDocShell gives the same behaviour as before
// aSourceDocShell was introduced. According to spec we should be passing
// the source browsing context that was used when the history entry was
// first created. bug 947716 has been created to address this issue.
rv = InternalLoad(uri,
referrerURI,
owner,
@ -11080,6 +11096,7 @@ nsDocShell::LoadHistoryEntry(nsISHEntry * aEntry, uint32_t aLoadType)
aEntry, // SHEntry
true,
srcdoc,
nullptr, // Source docshell, see comment above
nullptr, // No nsIDocShell
nullptr); // No nsIRequest
return rv;
@ -12494,6 +12511,7 @@ nsDocShell::OnLinkClickSync(nsIContent *aContent,
nullptr, // No SHEntry
true, // first party site
NullString(), // No srcdoc
this, // We are the source
aDocShell, // DocShell out-param
aRequest); // Request out-param
if (NS_SUCCEEDED(rv)) {

View File

@ -889,10 +889,6 @@ private:
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;

View File

@ -9,6 +9,7 @@
#include "nsISHEntry.h"
#include "nsIInputStream.h"
#include "nsIURI.h"
#include "nsIDocShell.h"
//*****************************************************************************
//*** nsDocShellLoadInfo: Object Management
@ -210,6 +211,19 @@ NS_IMETHODIMP nsDocShellLoadInfo::SetSrcdocData(const nsAString &aSrcdocData)
return NS_OK;
}
NS_IMETHODIMP nsDocShellLoadInfo::GetSourceDocShell(nsIDocShell** aSourceDocShell)
{
MOZ_ASSERT(aSourceDocShell);
nsCOMPtr<nsIDocShell> result = mSourceDocShell;
result.forget(aSourceDocShell);
return NS_OK;
}
NS_IMETHODIMP nsDocShellLoadInfo::SetSourceDocShell(nsIDocShell* aSourceDocShell)
{
mSourceDocShell = aSourceDocShell;
return NS_OK;
}
//*****************************************************************************
// nsDocShellLoadInfo: Helpers

View File

@ -18,6 +18,7 @@
class nsIInputStream;
class nsISHEntry;
class nsIURI;
class nsIDocShell;
class nsDocShellLoadInfo : public nsIDocShellLoadInfo
{
@ -43,6 +44,7 @@ protected:
nsCOMPtr<nsIInputStream> mHeadersStream;
bool mIsSrcdocLoad;
nsString mSrcdocData;
nsCOMPtr<nsIDocShell> mSourceDocShell;
};
#endif /* nsDocShellLoadInfo_h__ */

View File

@ -44,7 +44,7 @@ interface nsIReflowObserver;
typedef unsigned long nsLoadFlags;
[scriptable, builtinclass, uuid(e3ea830d-2614-4aeb-9ec3-b8f744b03b80)]
[scriptable, builtinclass, uuid(24732b66-e37e-444e-96a2-8b8d1fa292fc)]
interface nsIDocShell : nsIDocShellTreeItem
{
/**
@ -138,6 +138,7 @@ interface nsIDocShell : nsIDocShellTreeItem
* @param aSrcdoc When INTERNAL_LOAD_FLAGS_IS_SRCDOC is set, the
* contents of this parameter will be loaded instead
* of aURI.
* @param aSourceDocShell - The source browsing context for the navigation.
*/
[noscript]void internalLoad(in nsIURI aURI,
in nsIURI aReferrer,
@ -152,6 +153,7 @@ interface nsIDocShell : nsIDocShellTreeItem
in nsISHEntry aSHEntry,
in boolean firstParty,
in AString aSrcdoc,
in nsIDocShell aSourceDocShell,
out nsIDocShell aDocShell,
out nsIRequest aRequest);
@ -785,6 +787,12 @@ interface nsIDocShell : nsIDocShellTreeItem
*/
attribute nsIDocShell onePermittedSandboxedNavigator;
/**
* Returns true if we are sandboxed from aTargetDocShell.
* aTargetDocShell - the browsing context we are attempting to navigate.
*/
[noscript,notxpcom,nostdcall] bool isSandboxedFrom(in nsIDocShell aTargetDocShell);
/**
* 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

View File

@ -14,10 +14,11 @@
interface nsIURI;
interface nsIInputStream;
interface nsISHEntry;
interface nsIDocShell;
typedef long nsDocShellInfoLoadType;
[scriptable, uuid(5df91211-37db-47e9-8f83-2d5b0cb2eb9e)]
[scriptable, uuid(c6b15de3-2f4f-4e80-bb20-95f43b5598c7)]
interface nsIDocShellLoadInfo : nsISupports
{
/** This is the referrer for the load. */
@ -95,4 +96,6 @@ interface nsIDocShellLoadInfo : nsISupports
*/
attribute AString srcdocData;
/** When set, this is the Source Browsing Context for the navigation. */
attribute nsIDocShell sourceDocShell;
};

View File

@ -237,6 +237,13 @@ nsLocation::SetURI(nsIURI* aURI, bool aReplace)
loadInfo->SetLoadType(nsIDocShellLoadInfo::loadStopContent);
}
// Get the incumbent script's browsing context to set as source.
nsCOMPtr<nsPIDOMWindow> sourceWindow =
do_QueryInterface(mozilla::dom::GetIncumbentGlobal());
if (sourceWindow) {
loadInfo->SetSourceDocShell(sourceWindow->GetDocShell());
}
return docShell->LoadURI(aURI, loadInfo,
nsIWebNavigation::LOAD_FLAGS_NONE, true);
}

View File

@ -474,13 +474,24 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow *aParent,
}
// try to find an extant window with the given name
nsCOMPtr<nsIDOMWindow> foundWindow;
if (SafeGetWindowByName(name, aParent, getter_AddRefs(foundWindow)) ==
NS_ERROR_DOM_INVALID_ACCESS_ERR) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
nsCOMPtr<nsIDOMWindow> foundWindow = SafeGetWindowByName(name, aParent);
GetWindowTreeItem(foundWindow, getter_AddRefs(newDocShellItem));
// 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
// because of sandboxing, we wouldn't want that to happen.
nsCOMPtr<nsPIDOMWindow> parentWindow = do_QueryInterface(aParent);
nsCOMPtr<nsIDocShell> parentDocShell;
if (parentWindow) {
parentDocShell = parentWindow->GetDocShell();
if (parentDocShell) {
nsCOMPtr<nsIDocShell> foundDocShell = do_QueryInterface(newDocShellItem);
if (parentDocShell->IsSandboxedFrom(foundDocShell)) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
}
}
// no extant window? make a new one.
// If no parent, consider it chrome.
@ -671,9 +682,9 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow *aParent,
bool popupConditions = false;
// is the parent under popup conditions?
nsCOMPtr<nsPIDOMWindow> piWindow(do_QueryInterface(aParent));
if (piWindow)
popupConditions = piWindow->IsLoadingOrRunningTimeout();
if (parentWindow) {
popupConditions = parentWindow->IsLoadingOrRunningTimeout();
}
// chrome is always allowed, so clear the flag if the opener is chrome
if (popupConditions) {
@ -722,9 +733,9 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow *aParent,
// The flags can only be non-zero for new windows.
if (activeDocsSandboxFlags != 0) {
newDocShell->SetSandboxFlags(activeDocsSandboxFlags);
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aParent);
if (window) {
newDocShell->SetOnePermittedSandboxedNavigator(window->GetDocShell());
if (parentWindow) {
newDocShell->
SetOnePermittedSandboxedNavigator(parentWindow->GetDocShell());
}
}
@ -908,11 +919,6 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow *aParent,
}
// Copy the current session storage for the current domain.
nsCOMPtr<nsPIDOMWindow> piWindow = do_QueryInterface(aParent);
nsIDocShell* parentDocShell = nullptr;
if (piWindow)
parentDocShell = piWindow->GetDocShell();
if (subjectPrincipal && parentDocShell) {
nsCOMPtr<nsIDOMStorageManager> parentStorageManager = do_QueryInterface(parentDocShell);
nsCOMPtr<nsIDOMStorageManager> newStorageManager = do_QueryInterface(newDocShell);
@ -1278,7 +1284,6 @@ nsWindowWatcher::GetWindowByName(const char16_t *aTargetName,
if (!aResult) {
return NS_ERROR_INVALID_ARG;
}
nsresult rv;
*aResult = nullptr;
@ -1288,18 +1293,18 @@ nsWindowWatcher::GetWindowByName(const char16_t *aTargetName,
GetWindowTreeItem(aCurrentWindow, getter_AddRefs(startItem));
if (startItem) {
// Note: original requestor is null here, per idl comments
rv = startItem->FindItemWithName(aTargetName, nullptr, nullptr,
startItem->FindItemWithName(aTargetName, nullptr, nullptr,
getter_AddRefs(treeItem));
}
else {
// Note: original requestor is null here, per idl comments
rv = FindItemWithName(aTargetName, nullptr, nullptr, getter_AddRefs(treeItem));
FindItemWithName(aTargetName, nullptr, nullptr, getter_AddRefs(treeItem));
}
nsCOMPtr<nsIDOMWindow> domWindow = do_GetInterface(treeItem);
domWindow.swap(*aResult);
return rv;
return NS_OK;
}
bool
@ -1705,14 +1710,10 @@ nsWindowWatcher::GetCallerTreeItem(nsIDocShellTreeItem* aParentItem)
return callerItem.forget();
}
nsresult
already_AddRefed<nsIDOMWindow>
nsWindowWatcher::SafeGetWindowByName(const nsAString& aName,
nsIDOMWindow* aCurrentWindow,
nsIDOMWindow** aResult)
nsIDOMWindow* aCurrentWindow)
{
*aResult = nullptr;
nsresult rv;
nsCOMPtr<nsIDocShellTreeItem> startItem;
GetWindowTreeItem(aCurrentWindow, getter_AddRefs(startItem));
@ -1722,17 +1723,16 @@ nsWindowWatcher::SafeGetWindowByName(const nsAString& aName,
nsCOMPtr<nsIDocShellTreeItem> foundItem;
if (startItem) {
rv = startItem->FindItemWithName(flatName.get(), nullptr, callerItem,
startItem->FindItemWithName(flatName.get(), nullptr, callerItem,
getter_AddRefs(foundItem));
}
else {
rv = FindItemWithName(flatName.get(), nullptr, callerItem,
FindItemWithName(flatName.get(), nullptr, callerItem,
getter_AddRefs(foundItem));
}
nsCOMPtr<nsIDOMWindow> foundWin = do_GetInterface(foundItem);
foundWin.swap(*aResult);
return rv;
return foundWin.forget();
}
/* Fetch the nsIDOMWindow corresponding to the given nsIDocShellTreeItem.

View File

@ -64,9 +64,8 @@ protected:
// Unlike GetWindowByName this will look for a caller on the JS
// stack, and then fall back on aCurrentWindow if it can't find one.
nsresult SafeGetWindowByName(const nsAString& aName,
nsIDOMWindow* aCurrentWindow,
nsIDOMWindow** aResult);
already_AddRefed<nsIDOMWindow>
SafeGetWindowByName(const nsAString& aName, nsIDOMWindow* aCurrentWindow);
// Just like OpenWindowJS, but knows whether it got called via OpenWindowJS
// (which means called from script) or called via OpenWindow.