Bug 1172870 - Part 2 - Enable ServiceWorkerClients::OpenWindow on e10s desktop. r=smaug

This commit is contained in:
Catalin Badea 2015-10-30 01:30:57 +02:00
parent add4b60e82
commit 32b74a9761
11 changed files with 185 additions and 97 deletions

View File

@ -36,6 +36,7 @@
#include "mozilla/CheckedInt.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/DOMTypes.h"
#include "mozilla/dom/Element.h"
@ -188,6 +189,7 @@
#include "nsViewManager.h"
#include "nsViewportInfo.h"
#include "nsWidgetsCID.h"
#include "nsIWindowProvider.h"
#include "nsWrapperCacheInlines.h"
#include "nsXULPopupManager.h"
#include "xpcprivate.h" // nsXPConnect
@ -5206,6 +5208,14 @@ nsContentUtils::RemoveScriptBlocker()
sBlockedScriptRunners->RemoveElementsAt(originalFirstBlocker, blockersCount);
}
/* static */
nsIWindowProvider*
nsContentUtils::GetWindowProviderForContentProcess()
{
MOZ_ASSERT(XRE_IsContentProcess());
return ContentChild::GetSingleton();
}
/* static */
void
nsContentUtils::WarnScriptWasIgnored(nsIDocument* aDocument)

View File

@ -102,6 +102,7 @@ class nsWrapperCache;
class nsAttrValue;
class nsITransferable;
class nsPIWindowRoot;
class nsIWindowProvider;
struct JSPropertyDescriptor;
struct JSRuntime;
@ -1640,6 +1641,11 @@ public:
return sScriptBlockerCount == 0;
}
// XXXcatalinb: workaround for weird include error when trying to reference
// ipdl types in WindowWatcher.
static nsIWindowProvider*
GetWindowProviderForContentProcess();
/**
* Call this function if !IsSafeToRunScript() and we fail to run the script
* (rather than using AddScriptRunner as we usually do). |aDocument| is

View File

@ -776,34 +776,47 @@ ContentChild::ProvideWindowCommon(TabChild* aTabOpener,
bool* aWindowIsNew,
nsIDOMWindow** aReturn)
{
MOZ_ASSERT(aTabOpener);
*aReturn = nullptr;
const TabId openerTabId = aTabOpener->GetTabId();
nsAutoPtr<IPCTabContext> ipcContext;
TabId openerTabId = TabId(0);
PopupIPCTabContext context;
context.opener() = openerTabId;
context.isBrowserElement() = aTabOpener->IsBrowserElement();
IPCTabContext ipcContext(context);
if (aTabOpener) {
PopupIPCTabContext context;
openerTabId = aTabOpener->GetTabId();
context.opener() = openerTabId;
context.isBrowserElement() = aTabOpener->IsBrowserElement();
ipcContext = new IPCTabContext(context);
} else {
// It's possible to not have a TabChild opener in the case
// of ServiceWorker::OpenWindow.
UnsafeIPCTabContext unsafeTabContext;
ipcContext = new IPCTabContext(unsafeTabContext);
}
MOZ_ASSERT(ipcContext);
TabId tabId;
SendAllocateTabId(openerTabId,
ipcContext,
*ipcContext,
GetID(),
&tabId);
TabContext newTabContext = aTabOpener ? *aTabOpener : TabContext();
RefPtr<TabChild> newChild = new TabChild(this, tabId,
*aTabOpener, aChromeFlags);
newTabContext, aChromeFlags);
if (NS_FAILED(newChild->Init())) {
return NS_ERROR_ABORT;
}
context.opener() = aTabOpener;
if (aTabOpener) {
MOZ_ASSERT(ipcContext->type() == IPCTabContext::TPopupIPCTabContext);
ipcContext->get_PopupIPCTabContext().opener() = aTabOpener;
}
unused << SendPBrowserConstructor(
// We release this ref in DeallocPBrowserChild
RefPtr<TabChild>(newChild).forget().take(),
tabId, IPCTabContext(context), aChromeFlags,
tabId, *ipcContext, aChromeFlags,
GetID(), IsForApp(), IsForBrowser());
nsAutoCString spec;
@ -823,16 +836,18 @@ ContentChild::ProvideWindowCommon(TabChild* aTabOpener,
NS_ConvertUTF8toUTF16(features),
aWindowIsNew);
} else {
nsCOMPtr<nsPIDOMWindow> opener = do_QueryInterface(aParent);
nsCOMPtr<nsIDocument> doc = opener->GetDoc();
nsCOMPtr<nsIURI> baseURI = doc->GetDocBaseURI();
if (!baseURI) {
NS_ERROR("nsIDocument didn't return a base URI");
return NS_ERROR_FAILURE;
}
nsAutoCString baseURIString;
baseURI->GetSpec(baseURIString);
if (aTabOpener) {
nsCOMPtr<nsPIDOMWindow> opener = do_QueryInterface(aParent);
nsCOMPtr<nsIDocument> doc = opener->GetDoc();
nsCOMPtr<nsIURI> baseURI = doc->GetDocBaseURI();
if (!baseURI) {
NS_ERROR("nsIDocument didn't return a base URI");
return NS_ERROR_FAILURE;
}
baseURI->GetSpec(baseURIString);
}
nsresult rv;
if (!SendCreateWindow(aTabOpener, newChild,

View File

@ -5405,10 +5405,12 @@ ContentParent::RecvCreateWindow(PBrowserParent* aThisTab,
MOZ_ASSERT(!(aChromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME));
MOZ_ASSERT(!(aChromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW));
TabParent* thisTabParent = TabParent::GetFrom(aThisTab);
MOZ_ASSERT(thisTabParent);
TabParent* thisTabParent = nullptr;
if (aThisTab) {
thisTabParent = TabParent::GetFrom(aThisTab);
}
if (NS_WARN_IF(thisTabParent->IsBrowserOrApp())) {
if (NS_WARN_IF(thisTabParent && thisTabParent->IsBrowserOrApp())) {
return false;
}
@ -5426,7 +5428,10 @@ ContentParent::RecvCreateWindow(PBrowserParent* aThisTab,
// we must have an opener.
newTab->SetHasContentOpener(true);
nsCOMPtr<nsIContent> frame(do_QueryInterface(thisTabParent->GetOwnerElement()));
nsCOMPtr<nsIContent> frame;
if (thisTabParent) {
frame = do_QueryInterface(thisTabParent->GetOwnerElement());
}
nsCOMPtr<nsPIDOMWindow> parent;
if (frame) {
@ -5439,7 +5444,10 @@ ContentParent::RecvCreateWindow(PBrowserParent* aThisTab,
}
}
nsCOMPtr<nsIBrowserDOMWindow> browserDOMWin = thisTabParent->GetBrowserDOMWindow();
nsCOMPtr<nsIBrowserDOMWindow> browserDOMWin;
if (thisTabParent) {
browserDOMWin = thisTabParent->GetBrowserDOMWindow();
}
// If we haven't found a chrome window to open in, just use the most recently
// opened one.
@ -5471,8 +5479,10 @@ ContentParent::RecvCreateWindow(PBrowserParent* aThisTab,
}
bool isPrivate = false;
nsCOMPtr<nsILoadContext> loadContext = thisTabParent->GetLoadContext();
loadContext->GetUsePrivateBrowsing(&isPrivate);
if (thisTabParent) {
nsCOMPtr<nsILoadContext> loadContext = thisTabParent->GetLoadContext();
loadContext->GetUsePrivateBrowsing(&isPrivate);
}
nsCOMPtr<nsIOpenURIInFrameParams> params = new nsOpenURIInFrameParams();
params->SetReferrer(aBaseURI);

View File

@ -150,10 +150,9 @@ ContentProcessManager::AllocateTabId(const TabId& aOpenerTabId,
struct RemoteFrameInfo info;
const IPCTabContextUnion& contextUnion = aContext.contextUnion();
// If it's a PopupIPCTabContext, it's the case that a TabChild want to
// open a new tab. aOpenerTabId has to be it's parent frame's opener id.
if (contextUnion.type() == IPCTabContextUnion::TPopupIPCTabContext) {
if (aContext.type() == IPCTabContext::TPopupIPCTabContext) {
auto remoteFrameIter = iter->second.mRemoteFrames.find(aOpenerTabId);
if (remoteFrameIter == iter->second.mRemoteFrames.end()) {
ASSERT_UNLESS_FUZZING("Failed to find parent frame's opener id.");
@ -162,7 +161,7 @@ ContentProcessManager::AllocateTabId(const TabId& aOpenerTabId,
info.mOpenerTabId = remoteFrameIter->second.mOpenerTabId;
const PopupIPCTabContext &ipcContext = contextUnion.get_PopupIPCTabContext();
const PopupIPCTabContext &ipcContext = aContext.get_PopupIPCTabContext();
MOZ_ASSERT(ipcContext.opener().type() == PBrowserOrId::TTabId);
remoteFrameIter = iter->second.mRemoteFrames.find(ipcContext.opener().get_TabId());

View File

@ -1118,7 +1118,7 @@ parent:
sync GetGraphicsDeviceInitData()
returns (DeviceInitData aData);
sync CreateWindow(PBrowser aThisTab,
sync CreateWindow(nullable PBrowser aThisTab,
PBrowser aNewTab,
uint32_t aChromeFlags,
bool aCalledFromJS,

View File

@ -44,22 +44,25 @@ struct FrameIPCTabContext
nsCString signedPkgOriginNoSuffix;
};
// XXXcatalinb: This is only used by ServiceWorkerClients::OpenWindow.
// Because service workers don't have an associated TabChild
// we can't satisfy the security constraints on b2g. As such, the parent
// process will accept this tab context only on desktop.
struct UnsafeIPCTabContext
{ };
// IPCTabContext is an analog to mozilla::dom::TabContext. Both specify an
// iframe/PBrowser's own and containing app-ids and tell you whether the
// iframe/PBrowser is a browser frame. But only IPCTabContext is allowed to
// travel over IPC.
//
// We need IPCTabContext (specifically, PopupIPCTabContext) to prevent a
// privilege escalation attack by a compromised child process. See the comment
// on AllocPBrowser for details.
union IPCTabContextUnion
// privilege escalation attack by a compromised child process.
union IPCTabContext
{
PopupIPCTabContext;
FrameIPCTabContext;
};
struct IPCTabContext {
IPCTabContextUnion contextUnion;
UnsafeIPCTabContext;
};
}

View File

@ -232,10 +232,9 @@ MaybeInvalidTabContext::MaybeInvalidTabContext(const IPCTabContext& aParams)
nsAutoCString originSuffix;
nsAutoCString signedPkgOriginNoSuffix;
const IPCTabContextUnion& contextUnion = aParams.contextUnion();
switch(contextUnion.type()) {
case IPCTabContextUnion::TPopupIPCTabContext: {
const PopupIPCTabContext &ipcContext = contextUnion.get_PopupIPCTabContext();
switch(aParams.type()) {
case IPCTabContext::TPopupIPCTabContext: {
const PopupIPCTabContext &ipcContext = aParams.get_PopupIPCTabContext();
TabContext *context;
if (ipcContext.opener().type() == PBrowserOrId::TPBrowserParent) {
@ -281,9 +280,9 @@ MaybeInvalidTabContext::MaybeInvalidTabContext(const IPCTabContext& aParams)
}
break;
}
case IPCTabContextUnion::TFrameIPCTabContext: {
case IPCTabContext::TFrameIPCTabContext: {
const FrameIPCTabContext &ipcContext =
contextUnion.get_FrameIPCTabContext();
aParams.get_FrameIPCTabContext();
containingAppId = ipcContext.frameOwnerAppId();
signedPkgOriginNoSuffix = ipcContext.signedPkgOriginNoSuffix();
@ -291,6 +290,23 @@ MaybeInvalidTabContext::MaybeInvalidTabContext(const IPCTabContext& aParams)
originAttributes.PopulateFromSuffix(originSuffix);
break;
}
case IPCTabContext::TUnsafeIPCTabContext: {
// XXXcatalinb: This used *only* by ServiceWorkerClients::OpenWindow.
// It is meant as a temporary solution until service workers can
// provide a TabChild equivalent. Don't allow this on b2g since
// it might be used to escalate privileges.
#ifdef MOZ_B2G
mInvalidReason = "ServiceWorkerClients::OpenWindow is not supported.";
return;
#endif
if (!Preferences::GetBool("dom.serviceWorkers.enabled", false)) {
mInvalidReason = "ServiceWorkers should be enabled.";
return;
}
containingAppId = NO_APP_ID;
break;
}
default: {
MOZ_CRASH();
}

View File

@ -71,35 +71,36 @@ nsIContentParent::DeallocPJavaScriptParent(PJavaScriptParent* aParent)
bool
nsIContentParent::CanOpenBrowser(const IPCTabContext& aContext)
{
const IPCTabContextUnion& contextUnion = aContext.contextUnion();
// We don't trust the IPCTabContext we receive from the child, so we'll bail
// if we receive an IPCTabContext that's not a PopupIPCTabContext.
// (PopupIPCTabContext lets the child process prove that it has access to
// the app it's trying to open.)
if (contextUnion.type() != IPCTabContextUnion::TPopupIPCTabContext) {
// On e10s we also allow UnsafeTabContext to allow service workers to open
// windows. This is enforced in MaybeInvalidTabContext.
if (aContext.type() != IPCTabContext::TPopupIPCTabContext &&
aContext.type() != IPCTabContext::TUnsafeIPCTabContext) {
ASSERT_UNLESS_FUZZING("Unexpected IPCTabContext type. Aborting AllocPBrowserParent.");
return false;
}
const PopupIPCTabContext& popupContext = contextUnion.get_PopupIPCTabContext();
if (popupContext.opener().type() != PBrowserOrId::TPBrowserParent) {
ASSERT_UNLESS_FUZZING("Unexpected PopupIPCTabContext type. Aborting AllocPBrowserParent.");
return false;
}
if (aContext.type() == IPCTabContext::TPopupIPCTabContext) {
const PopupIPCTabContext& popupContext = aContext.get_PopupIPCTabContext();
if (popupContext.opener().type() != PBrowserOrId::TPBrowserParent) {
ASSERT_UNLESS_FUZZING("Unexpected PopupIPCTabContext type. Aborting AllocPBrowserParent.");
return false;
}
auto opener = TabParent::GetFrom(popupContext.opener().get_PBrowserParent());
if (!opener) {
ASSERT_UNLESS_FUZZING("Got null opener from child; aborting AllocPBrowserParent.");
return false;
}
auto opener = TabParent::GetFrom(popupContext.opener().get_PBrowserParent());
if (!opener) {
ASSERT_UNLESS_FUZZING("Got null opener from child; aborting AllocPBrowserParent.");
return false;
}
// Popup windows of isBrowser frames must be isBrowser if the parent
// isBrowser. Allocating a !isBrowser frame with same app ID would allow
// the content to access data it's not supposed to.
if (!popupContext.isBrowserElement() && opener->IsBrowserElement()) {
ASSERT_UNLESS_FUZZING("Child trying to escalate privileges! Aborting AllocPBrowserParent.");
return false;
// Popup windows of isBrowser frames must be isBrowser if the parent
// isBrowser. Allocating a !isBrowser frame with same app ID would allow
// the content to access data it's not supposed to.
if (!popupContext.isBrowserElement() && opener->IsBrowserElement()) {
ASSERT_UNLESS_FUZZING("Child trying to escalate privileges! Aborting AllocPBrowserParent.");
return false;
}
}
MaybeInvalidTabContext tc(aContext);
@ -129,26 +130,25 @@ nsIContentParent::AllocPBrowserParent(const TabId& aTabId,
return nullptr;
}
const IPCTabContextUnion& contextUnion = aContext.contextUnion();
const PopupIPCTabContext& popupContext = contextUnion.get_PopupIPCTabContext();
uint32_t chromeFlags = aChromeFlags;
if (aContext.type() == IPCTabContext::TPopupIPCTabContext) {
// CanOpenBrowser has ensured that the IPCTabContext is of
// type PopupIPCTabContext, and that the opener TabParent is
// reachable.
const PopupIPCTabContext& popupContext = aContext.get_PopupIPCTabContext();
auto opener = TabParent::GetFrom(popupContext.opener().get_PBrowserParent());
// We must ensure that the private browsing and remoteness flags
// match those of the opener.
nsCOMPtr<nsILoadContext> loadContext = opener->GetLoadContext();
if (!loadContext) {
return nullptr;
}
// CanOpenBrowser has ensured that the IPCTabContext is of
// type PopupIPCTabContext, and that the opener TabParent is
// reachable.
auto opener = TabParent::GetFrom(popupContext.opener().get_PBrowserParent());
// We must ensure that the private browsing and remoteness flags
// match those of the opener.
nsCOMPtr<nsILoadContext> loadContext = opener->GetLoadContext();
if (!loadContext) {
return nullptr;
}
bool isPrivate;
loadContext->GetUsePrivateBrowsing(&isPrivate);
if (isPrivate) {
chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
bool isPrivate;
loadContext->GetUsePrivateBrowsing(&isPrivate);
if (isPrivate) {
chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
}
}
// And because we're allocating a remote browser, of course the

View File

@ -20,9 +20,12 @@
#include "nsIDOMChromeWindow.h"
#include "nsIDOMWindow.h"
#include "nsIWebNavigation.h"
#include "nsIWindowMediator.h"
#include "nsIWebProgress.h"
#include "nsIWebProgressListener.h"
#include "nsIWindowMediator.h"
#include "nsIWindowWatcher.h"
#include "nsPIWindowWatcher.h"
#include "nsWindowWatcher.h"
#include "nsWeakReference.h"
using namespace mozilla;
@ -498,6 +501,31 @@ private:
return rv;
}
if (XRE_IsContentProcess()) {
// ContentProcess
nsCOMPtr<nsIWindowWatcher> wwatch =
do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
NS_ENSURE_STATE(pwwatch);
nsCString spec;
uri->GetSpec(spec);
nsCOMPtr<nsIDOMWindow> newWindow;
pwwatch->OpenWindow2(nullptr,
spec.get(),
nullptr,
nullptr,
false, false, true, nullptr, nullptr,
getter_AddRefs(newWindow));
nsCOMPtr<nsPIDOMWindow> pwindow = do_QueryInterface(newWindow);
pwindow.forget(aWindow);
return NS_OK;
}
// Find the most recent browser window and open a new tab in it.
nsCOMPtr<nsIDOMWindow> browserWindow;
rv = wm->GetMostRecentWindow(MOZ_UTF16("navigator:browser"),
@ -580,13 +608,6 @@ already_AddRefed<Promise>
ServiceWorkerClients::OpenWindow(const nsAString& aUrl,
ErrorResult& aRv)
{
// XXXcatalinb: This works only on non-multiprocess for now, bail if we're
// running in a content process.
if (XRE_IsContentProcess()) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);

View File

@ -545,8 +545,8 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow* aParent,
// no extant window? make a new one.
// If no parent, consider it chrome.
bool hasChromeParent = true;
// If no parent, consider it chrome when running in the parent process.
bool hasChromeParent = XRE_IsContentProcess() ? false : true;
if (aParent) {
// Check if the parent document has chrome privileges.
nsIDocument* doc = parentWindow->GetDoc();
@ -613,7 +613,9 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow* aParent,
// based on whether the docshell type is chrome or content.
nsCOMPtr<nsIGlobalObject> parentGlobalObject = do_QueryInterface(aParent);
if (NS_WARN_IF(!jsapiChromeGuard.Init(parentGlobalObject))) {
if (!aParent) {
jsapiChromeGuard.Init();
} else if (NS_WARN_IF(!jsapiChromeGuard.Init(parentGlobalObject))) {
return NS_ERROR_UNEXPECTED;
}
}
@ -645,10 +647,16 @@ nsWindowWatcher::OpenWindowInternal(nsIDOMWindow* aParent,
!(chromeFlags & (nsIWebBrowserChrome::CHROME_MODAL |
nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) {
nsCOMPtr<nsIWindowProvider> provider = do_GetInterface(parentTreeOwner);
if (provider) {
NS_ASSERTION(aParent, "We've _got_ to have a parent here!");
nsCOMPtr<nsIWindowProvider> provider;
if (parentTreeOwner) {
provider = do_GetInterface(parentTreeOwner);
} else if (XRE_IsContentProcess()) {
// we're in a content process but we don't have a tabchild we can
// use.
provider = nsContentUtils::GetWindowProviderForContentProcess();
}
if (provider) {
nsCOMPtr<nsIDOMWindow> newWindow;
rv = provider->ProvideWindow(aParent, chromeFlags, aCalledFromJS,
sizeSpec.PositionSpecified(),