From 634a0f15cf71c78cdfc37aa3075711fa13780d6c Mon Sep 17 00:00:00 2001 From: Luca Greco Date: Fri, 26 Jun 2020 18:13:46 +0000 Subject: [PATCH] Bug 1642676 - Ensure remote workers are launched in a child process based on the expected remoteType. r=asuth,mixedpuppy Differential Revision: https://phabricator.services.mozilla.com/D61708 --- .../ServiceWorkerPrivateImpl.cpp | 10 +- .../remoteworkers/RemoteWorkerChild.cpp | 9 + .../remoteworkers/RemoteWorkerManager.cpp | 237 +++++++++++++++--- .../remoteworkers/RemoteWorkerManager.h | 22 +- .../remoteworkers/RemoteWorkerTypes.ipdlh | 3 + .../gtest/TestMatchRemoteType.cpp | 65 +++++ dom/workers/remoteworkers/gtest/moz.build | 13 + dom/workers/remoteworkers/moz.build | 3 + dom/workers/sharedworkers/SharedWorker.cpp | 11 +- dom/workers/test/browser.ini | 4 + .../browser_privilegedmozilla_remoteworker.js | 117 +++++++++ dom/workers/test/file_service_worker.js | 3 + .../test/file_service_worker_container.html | 15 ++ .../test/xpcshell/test_ext_shared_workers.js | 57 +++++ .../test/xpcshell/xpcshell-common.ini | 1 + 15 files changed, 530 insertions(+), 40 deletions(-) create mode 100644 dom/workers/remoteworkers/gtest/TestMatchRemoteType.cpp create mode 100644 dom/workers/remoteworkers/gtest/moz.build create mode 100644 dom/workers/test/browser_privilegedmozilla_remoteworker.js create mode 100644 dom/workers/test/file_service_worker.js create mode 100644 dom/workers/test/file_service_worker_container.html create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_shared_workers.js diff --git a/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp b/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp index 56d342134ad1..f64e0f666b60 100644 --- a/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp +++ b/dom/serviceworkers/ServiceWorkerPrivateImpl.cpp @@ -42,6 +42,7 @@ #include "mozilla/dom/InternalRequest.h" #include "mozilla/dom/ReferrerInfo.h" #include "mozilla/dom/RemoteWorkerControllerChild.h" +#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::GetRemoteType #include "mozilla/dom/ServiceWorkerBinding.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/IPCStreamUtils.h" @@ -186,6 +187,12 @@ nsresult ServiceWorkerPrivateImpl::Initialize() { return rv; } + auto remoteType = RemoteWorkerManager::GetRemoteType( + principal, WorkerType::WorkerTypeService); + if (NS_WARN_IF(remoteType.isErr())) { + return remoteType.unwrapErr(); + } + mRemoteWorkerData = RemoteWorkerData( NS_ConvertUTF8toUTF16(mOuter->mInfo->ScriptSpec()), baseScriptURL, baseScriptURL, /* name */ VoidString(), @@ -204,7 +211,8 @@ nsresult ServiceWorkerPrivateImpl::Initialize() { // already_AddRefed<>. Let's set it to null. /* referrerInfo */ nullptr, - storageAccess, std::move(serviceWorkerData), regInfo->AgentClusterId()); + storageAccess, std::move(serviceWorkerData), regInfo->AgentClusterId(), + remoteType.unwrap()); mRemoteWorkerData.referrerInfo() = MakeAndAddRef(); diff --git a/dom/workers/remoteworkers/RemoteWorkerChild.cpp b/dom/workers/remoteworkers/RemoteWorkerChild.cpp index cf3735a21ef8..a0f3464994a7 100644 --- a/dom/workers/remoteworkers/RemoteWorkerChild.cpp +++ b/dom/workers/remoteworkers/RemoteWorkerChild.cpp @@ -33,6 +33,7 @@ #include "mozilla/dom/FetchEventOpProxyChild.h" #include "mozilla/dom/IndexedDatabaseManager.h" #include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::IsRemoteTypeAllowed #include "mozilla/dom/RemoteWorkerTypes.h" #include "mozilla/dom/ServiceWorkerDescriptor.h" #include "mozilla/dom/ServiceWorkerInterceptController.h" @@ -313,6 +314,14 @@ nsresult RemoteWorkerChild::ExecWorkerOnMainThread(RemoteWorkerData&& aData) { auto scopeExit = MakeScopeExit([&] { TransitionStateToTerminated(); }); + // Verify the the RemoteWorker should be really allowed to run in this + // process, and fail if it shouldn't (This shouldn't normally happen, + // unless the RemoteWorkerData has been tempered in the process it was + // sent from). + if (!RemoteWorkerManager::IsRemoteTypeAllowed(aData)) { + return NS_ERROR_UNEXPECTED; + } + auto principalOrErr = PrincipalInfoToPrincipal(aData.principalInfo()); if (NS_WARN_IF(principalOrErr.isErr())) { return principalOrErr.unwrapErr(); diff --git a/dom/workers/remoteworkers/RemoteWorkerManager.cpp b/dom/workers/remoteworkers/RemoteWorkerManager.cpp index 7e845a383b26..c440b861409e 100644 --- a/dom/workers/remoteworkers/RemoteWorkerManager.cpp +++ b/dom/workers/remoteworkers/RemoteWorkerManager.cpp @@ -10,6 +10,7 @@ #include "mozilla/SchedulerGroup.h" #include "mozilla/ScopeExit.h" +#include "mozilla/dom/ContentChild.h" // ContentChild::GetSingleton #include "mozilla/dom/RemoteWorkerParent.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/ipc/BackgroundUtils.h" @@ -37,18 +38,6 @@ bool IsServiceWorker(const RemoteWorkerData& aData) { OptionalServiceWorkerData::TServiceWorkerData; } -// Respecting COOP and COEP requires processing headers in the parent -// process in order to choose an appropriate content process, but the -// workers' ScriptLoader processes headers in content processes. An -// intermediary step that provides security guarantees is to simply never -// allow SharedWorkers and ServiceWorkers to exist in a COOP+COEP process. -// The ultimate goal is to allow these worker types to be put in such -// processes based on their script response headers. -// https://bugzilla.mozilla.org/show_bug.cgi?id=1595206 -bool IsServiceWorkerRemoteType(const nsAString& aRemoteType) { - return IsWebRemoteType(aRemoteType) && !IsWebCoopCoepRemoteType(aRemoteType); -} - void TransmitPermissionsAndBlobURLsForPrincipalInfo( ContentParent* aContentParent, const PrincipalInfo& aPrincipalInfo) { AssertIsOnMainThread(); @@ -70,6 +59,140 @@ void TransmitPermissionsAndBlobURLsForPrincipalInfo( } // namespace +// static +bool RemoteWorkerManager::MatchRemoteType(const nsAString& processRemoteType, + const nsAString& workerRemoteType) { + if (processRemoteType.Equals(workerRemoteType)) { + return true; + } + + // Respecting COOP and COEP requires processing headers in the parent + // process in order to choose an appropriate content process, but the + // workers' ScriptLoader processes headers in content processes. An + // intermediary step that provides security guarantees is to simply never + // allow SharedWorkers and ServiceWorkers to exist in a COOP+COEP process. + // The ultimate goal is to allow these worker types to be put in such + // processes based on their script response headers. + // https://bugzilla.mozilla.org/show_bug.cgi?id=1595206 + if (IsWebCoopCoepRemoteType(processRemoteType)) { + return false; + } + + // A worker for a non privileged child process can be launched in + // any web child process that is not COOP and COEP. + if ((workerRemoteType.IsEmpty() || IsWebRemoteType(workerRemoteType)) && + IsWebRemoteType(processRemoteType)) { + return true; + } + + return false; +} + +// static +Result RemoteWorkerManager::GetRemoteType( + const nsCOMPtr& aPrincipal, WorkerType aWorkerType) { + AssertIsOnMainThread(); + + if (aWorkerType != WorkerType::WorkerTypeService && + aWorkerType != WorkerType::WorkerTypeShared) { + // This methods isn't expected to be called for worker type that + // aren't remote workers (currently Service and Shared workers). + return Err(NS_ERROR_UNEXPECTED); + } + + nsString remoteType; + + // If Gecko is running in single process mode, there is no child process + // to select, return without assigning any remoteType. + if (!BrowserTabsRemoteAutostart()) { + return remoteType; + } + + auto* contentChild = ContentChild::GetSingleton(); + + bool isSystem = !!BasePrincipal::Cast(aPrincipal)->IsSystemPrincipal(); + bool isMozExtension = + !isSystem && !!BasePrincipal::Cast(aPrincipal)->AddonPolicy(); + + if (aWorkerType == WorkerType::WorkerTypeShared && !contentChild && + !isSystem) { + // Prevent content principals SharedWorkers to be launched in the main + // process while running in multiprocess mode. + // + // NOTE this also prevents moz-extension SharedWorker to be created + // while the extension process is disabled by prefs, allowing it would + // also trigger an assertion failure in + // RemoteWorkerManager::SelectorTargetActor, due to an unexpected + // content-principal parent-process workers while e10s is on). + return Err(NS_ERROR_ABORT); + } + + bool separatePrivilegedMozilla = Preferences::GetBool( + "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", false); + + if (isMozExtension) { + remoteType.Assign(NS_LITERAL_STRING(EXTENSION_REMOTE_TYPE)); + } else if (separatePrivilegedMozilla) { + bool isPrivilegedMozilla = false; + aPrincipal->IsURIInPrefList("browser.tabs.remote.separatedMozillaDomains", + &isPrivilegedMozilla); + + if (isPrivilegedMozilla) { + remoteType.Assign(NS_LITERAL_STRING(PRIVILEGEDMOZILLA_REMOTE_TYPE)); + } else { + remoteType.Assign(aWorkerType == WorkerType::WorkerTypeShared && + contentChild + ? contentChild->GetRemoteType() + : NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE)); + } + } else { + remoteType.Assign(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE)); + } + + return remoteType; +} + +// static +bool RemoteWorkerManager::IsRemoteTypeAllowed(const RemoteWorkerData& aData) { + AssertIsOnMainThread(); + + // If Gecko is running in single process mode, there is no child process + // to select and we have to just consider it valid (if it should haven't + // been launched it should have been already prevented before reaching + // a RemoteWorkerChild instance). + if (!BrowserTabsRemoteAutostart()) { + return true; + } + + const auto& principalInfo = aData.principalInfo(); + + auto* contentChild = ContentChild::GetSingleton(); + if (!contentChild) { + // If e10s isn't disabled, only workers related to the system principal + // should be allowed to run in the parent process. + return principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo; + } + + auto principalOrErr = PrincipalInfoToPrincipal(principalInfo); + if (NS_WARN_IF(principalOrErr.isErr())) { + return false; + } + nsCOMPtr principal = principalOrErr.unwrap(); + + // Recompute the remoteType based on the principal, to double-check that it + // has not been tempered to select a different child process than the one + // expected. + bool isServiceWorker = aData.serviceWorkerData().type() == + OptionalServiceWorkerData::TServiceWorkerData; + auto remoteType = GetRemoteType( + principal, isServiceWorker ? WorkerTypeService : WorkerTypeShared); + if (NS_WARN_IF(remoteType.isErr())) { + return false; + } + + return MatchRemoteType(remoteType.unwrap(), contentChild->GetRemoteType()); +} + /* static */ already_AddRefed RemoteWorkerManager::GetOrCreate() { AssertIsInMainProcess(); @@ -111,27 +234,24 @@ void RemoteWorkerManager::RegisterActor(RemoteWorkerServiceParent* aActor) { MOZ_ASSERT(!mChildActors.Contains(aActor)); mChildActors.AppendElement(aActor); - nsTArray unlaunched; - - RefPtr contentParent = - BackgroundParent::GetContentParent(aActor->Manager()); - auto scopeExit = - MakeScopeExit([&] { NS_ReleaseOnMainThread(contentParent.forget()); }); - const auto& remoteType = contentParent->GetRemoteType(); - if (!mPendings.IsEmpty()) { + const auto& remoteType = GetRemoteTypeForActor(aActor); + nsTArray unlaunched; + // Flush pending launching. for (Pending& p : mPendings) { if (p.mController->IsTerminated()) { continue; } - if (IsServiceWorker(p.mData) && !IsServiceWorkerRemoteType(remoteType)) { + const auto& workerRemoteType = p.mData.remoteType(); + + if (MatchRemoteType(remoteType, workerRemoteType)) { + LaunchInternal(p.mController, aActor, p.mData); + } else { unlaunched.AppendElement(std::move(p)); continue; } - - LaunchInternal(p.mController, aActor, p.mData); } std::swap(mPendings, unlaunched); @@ -227,6 +347,28 @@ void RemoteWorkerManager::AsyncCreationFailed( NS_DispatchToCurrentThread(r.forget()); } +/* static */ +nsString RemoteWorkerManager::GetRemoteTypeForActor( + const RemoteWorkerServiceParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + MOZ_ASSERT(aActor); + + RefPtr contentParent = + BackgroundParent::GetContentParent(aActor->Manager()); + auto scopeExit = + MakeScopeExit([&] { NS_ReleaseOnMainThread(contentParent.forget()); }); + + if (NS_WARN_IF(!contentParent)) { + return EmptyString(); + } + + nsString aRemoteType(contentParent->GetRemoteType()); + + return aRemoteType; +} + template void RemoteWorkerManager::ForEachActor(Callback&& aCallback) const { AssertIsOnBackgroundThread(); @@ -290,11 +432,13 @@ RemoteWorkerManager::SelectTargetActorForServiceWorker( RemoteWorkerServiceParent* actor = nullptr; + const auto& workerRemoteType = aData.remoteType(); + ForEachActor([&](RemoteWorkerServiceParent* aActor, RefPtr&& aContentParent) { const auto& remoteType = aContentParent->GetRemoteType(); - if (IsServiceWorkerRemoteType(remoteType)) { + if (MatchRemoteType(remoteType, workerRemoteType)) { auto lock = aContentParent->mRemoteWorkerActorData.Lock(); if (lock->mCount || !lock->mShutdownStarted) { @@ -327,7 +471,7 @@ RemoteWorkerManager::SelectTargetActorForServiceWorker( RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActorForSharedWorker( - base::ProcessId aProcessId) const { + base::ProcessId aProcessId, const RemoteWorkerData& aData) const { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mChildActors.IsEmpty()); @@ -335,7 +479,10 @@ RemoteWorkerManager::SelectTargetActorForSharedWorker( ForEachActor([&](RemoteWorkerServiceParent* aActor, RefPtr&& aContentParent) { - if (IsWebCoopCoepRemoteType(aContentParent->GetRemoteType())) { + bool matchRemoteType = + MatchRemoteType(aContentParent->GetRemoteType(), aData.remoteType()); + + if (!matchRemoteType) { return true; } @@ -378,8 +525,9 @@ RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActor( return nullptr; } - return IsServiceWorker(aData) ? SelectTargetActorForServiceWorker(aData) - : SelectTargetActorForSharedWorker(aProcessId); + return IsServiceWorker(aData) + ? SelectTargetActorForServiceWorker(aData) + : SelectTargetActorForSharedWorker(aProcessId, aData); } void RemoteWorkerManager::LaunchNewContentProcess( @@ -401,7 +549,8 @@ void RemoteWorkerManager::LaunchNewContentProcess( principalInfo = aData.principalInfo(), bgEventTarget = std::move(bgEventTarget), self = RefPtr(this)]( - const CallbackParamType& aValue) mutable { + const CallbackParamType& aValue, + const nsString& remoteType) mutable { if (aValue.IsResolve()) { if (isServiceWorker) { TransmitPermissionsAndBlobURLsForPrincipalInfo(aValue.ResolveValue(), @@ -413,12 +562,21 @@ void RemoteWorkerManager::LaunchNewContentProcess( NS_ProxyRelease(__func__, bgEventTarget, self.forget()); } else { // The "real" failure callback. - nsCOMPtr r = - NS_NewRunnableFunction(__func__, [self = std::move(self)] { + nsCOMPtr r = NS_NewRunnableFunction( + __func__, [self = std::move(self), remoteType] { + nsTArray uncancelled; auto pendings = std::move(self->mPendings); + for (const auto& pending : pendings) { - pending.mController->CreationFailed(); + const auto& workerRemoteType = pending.mData.remoteType(); + if (self->MatchRemoteType(remoteType, workerRemoteType)) { + pending.mController->CreationFailed(); + } else { + uncancelled.AppendElement(pending); + } } + + std::swap(self->mPendings, uncancelled); }); bgEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); @@ -426,14 +584,19 @@ void RemoteWorkerManager::LaunchNewContentProcess( }; nsCOMPtr r = NS_NewRunnableFunction( - __func__, [callback = std::move(processLaunchCallback)]() mutable { + __func__, [callback = std::move(processLaunchCallback), + workerRemoteType = aData.remoteType()]() mutable { + auto remoteType = workerRemoteType.IsEmpty() + ? NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE) + : workerRemoteType; + ContentParent::GetNewOrUsedBrowserProcessAsync( /* aFrameElement = */ nullptr, - /* aRemoteType = */ NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE)) + /* aRemoteType = */ remoteType) ->Then(GetCurrentSerialEventTarget(), __func__, - [callback = std::move(callback)]( - const CallbackParamType& aValue) mutable { - callback(aValue); + [callback = std::move(callback), + remoteType](const CallbackParamType& aValue) mutable { + callback(aValue, remoteType); }); }); diff --git a/dom/workers/remoteworkers/RemoteWorkerManager.h b/dom/workers/remoteworkers/RemoteWorkerManager.h index 4d45fa859682..ecf5218498cc 100644 --- a/dom/workers/remoteworkers/RemoteWorkerManager.h +++ b/dom/workers/remoteworkers/RemoteWorkerManager.h @@ -11,6 +11,7 @@ #include "mozilla/RefPtr.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/RemoteWorkerTypes.h" +#include "mozilla/dom/WorkerPrivate.h" // WorkerType enum #include "nsISupportsImpl.h" #include "nsTArray.h" @@ -34,6 +35,22 @@ class RemoteWorkerManager final { void Launch(RemoteWorkerController* aController, const RemoteWorkerData& aData, base::ProcessId aProcessId); + static bool MatchRemoteType(const nsAString& processRemoteType, + const nsAString& workerRemoteType); + + /** + * Get the child process RemoteType where a RemoteWorker should be + * launched. + */ + static Result GetRemoteType( + const nsCOMPtr& aPrincipal, WorkerType aWorkerType); + + /** + * Verify if a remote worker should be allowed to run in the current + * child process remoteType. + */ + static bool IsRemoteTypeAllowed(const RemoteWorkerData& aData); + private: RemoteWorkerManager(); ~RemoteWorkerManager(); @@ -45,7 +62,7 @@ class RemoteWorkerManager final { const RemoteWorkerData& aData) const; RemoteWorkerServiceParent* SelectTargetActorForSharedWorker( - base::ProcessId aProcessId) const; + base::ProcessId aProcessId, const RemoteWorkerData& aData) const; void LaunchInternal(RemoteWorkerController* aController, RemoteWorkerServiceParent* aTargetActor, @@ -56,6 +73,9 @@ class RemoteWorkerManager final { void AsyncCreationFailed(RemoteWorkerController* aController); + static nsString GetRemoteTypeForActor( + const RemoteWorkerServiceParent* aActor); + // Iterate through all RemoteWorkerServiceParent actors, starting from a // random index (as if iterating through a circular array). // diff --git a/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh b/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh index fac0d2293d89..c9018fbc04bc 100644 --- a/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh +++ b/dom/workers/remoteworkers/RemoteWorkerTypes.ipdlh @@ -70,6 +70,9 @@ struct RemoteWorkerData OptionalServiceWorkerData serviceWorkerData; nsID agentClusterId; + + // Child process remote type where the worker should only run on. + nsString remoteType; }; // ErrorData/ErrorDataNote correspond to WorkerErrorReport/WorkerErrorNote diff --git a/dom/workers/remoteworkers/gtest/TestMatchRemoteType.cpp b/dom/workers/remoteworkers/gtest/TestMatchRemoteType.cpp new file mode 100644 index 000000000000..c8b4e7c379eb --- /dev/null +++ b/dom/workers/remoteworkers/gtest/TestMatchRemoteType.cpp @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "../RemoteWorkerManager.h" + +using namespace mozilla::dom; + +TEST(RemoteWorkerManager, TestMatchRemoteType) +{ + static const struct { + const nsString processRemoteType; + const nsString workerRemoteType; + const bool shouldMatch; + } tests[] = { + // Test exact matches between process and worker remote types. + {NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), + NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), true}, + {NS_LITERAL_STRING(EXTENSION_REMOTE_TYPE), + NS_LITERAL_STRING(EXTENSION_REMOTE_TYPE), true}, + {NS_LITERAL_STRING(PRIVILEGEDMOZILLA_REMOTE_TYPE), + NS_LITERAL_STRING(PRIVILEGEDMOZILLA_REMOTE_TYPE), true}, + + // Test workers with remoteType "web" not launched on non-web or coop+coep + // processes. + {NS_LITERAL_STRING(PRIVILEGEDMOZILLA_REMOTE_TYPE), + NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), false}, + {NS_LITERAL_STRING(PRIVILEGEDABOUT_REMOTE_TYPE), + NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), false}, + {NS_LITERAL_STRING(EXTENSION_REMOTE_TYPE), + NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), false}, + {NS_LITERAL_STRING(FILE_REMOTE_TYPE), + NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), false}, + {NS_LITERAL_STRING(WITH_COOP_COEP_REMOTE_TYPE_PREFIX), + NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), false}, + + // Test workers with remoteType "web" launched in web child processes. + {NS_LITERAL_STRING(LARGE_ALLOCATION_REMOTE_TYPE), + NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), true}, + {NS_LITERAL_STRING(FISSION_WEB_REMOTE_TYPE), + NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), true}, + + // Test empty remoteType default to "web" remoteType. + {NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE), EmptyString(), true}, + {NS_LITERAL_STRING(WITH_COOP_COEP_REMOTE_TYPE_PREFIX), EmptyString(), + false}, + {NS_LITERAL_STRING(PRIVILEGEDMOZILLA_REMOTE_TYPE), EmptyString(), false}, + {NS_LITERAL_STRING(EXTENSION_REMOTE_TYPE), EmptyString(), false}, + }; + + for (const auto& test : tests) { + auto message = + nsPrintfCString(R"(MatchRemoteType("%s", "%s") should return %s)", + NS_ConvertUTF16toUTF8(test.processRemoteType).get(), + NS_ConvertUTF16toUTF8(test.workerRemoteType).get(), + test.shouldMatch ? "true" : "false"); + ASSERT_EQ(RemoteWorkerManager::MatchRemoteType(test.processRemoteType, + test.workerRemoteType), + test.shouldMatch) + << message; + } +} diff --git a/dom/workers/remoteworkers/gtest/moz.build b/dom/workers/remoteworkers/gtest/moz.build new file mode 100644 index 000000000000..0ba3a929f368 --- /dev/null +++ b/dom/workers/remoteworkers/gtest/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES = [ + 'TestMatchRemoteType.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul-gtest' diff --git a/dom/workers/remoteworkers/moz.build b/dom/workers/remoteworkers/moz.build index a512f8ed53f6..e7a65b1399e1 100644 --- a/dom/workers/remoteworkers/moz.build +++ b/dom/workers/remoteworkers/moz.build @@ -9,6 +9,7 @@ EXPORTS.mozilla.dom += [ 'RemoteWorkerController.h', 'RemoteWorkerControllerChild.h', 'RemoteWorkerControllerParent.h', + 'RemoteWorkerManager.h', 'RemoteWorkerParent.h', 'RemoteWorkerService.h', 'RemoteWorkerServiceChild.h', @@ -39,6 +40,8 @@ IPDL_SOURCES += [ 'RemoteWorkerTypes.ipdlh', ] +TEST_DIRS += ['gtest'] + include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'xul' diff --git a/dom/workers/sharedworkers/SharedWorker.cpp b/dom/workers/sharedworkers/SharedWorker.cpp index 1ed1897134c3..a1ccafd46a9f 100644 --- a/dom/workers/sharedworkers/SharedWorker.cpp +++ b/dom/workers/sharedworkers/SharedWorker.cpp @@ -13,6 +13,7 @@ #include "mozilla/dom/MessageChannel.h" #include "mozilla/dom/MessagePort.h" #include "mozilla/dom/PMessagePort.h" +#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::GetRemoteType #include "mozilla/dom/RemoteWorkerTypes.h" #include "mozilla/dom/SharedWorkerBinding.h" #include "mozilla/dom/SharedWorkerChild.h" @@ -198,13 +199,21 @@ already_AddRefed SharedWorker::Constructor( MOZ_ASSERT(loadInfo.mCookieJarSettings); net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings)->Serialize(cjsData); + auto remoteType = RemoteWorkerManager::GetRemoteType( + loadInfo.mPrincipal, WorkerType::WorkerTypeShared); + if (NS_WARN_IF(remoteType.isErr())) { + aRv.Throw(remoteType.unwrapErr()); + return nullptr; + } + RemoteWorkerData remoteWorkerData( nsString(aScriptURL), baseURL, resolvedScriptURL, name, loadingPrincipalInfo, principalInfo, partitionedPrincipalInfo, loadInfo.mUseRegularPrincipal, loadInfo.mHasStorageAccessPermissionGranted, cjsData, loadInfo.mDomain, isSecureContext, ipcClientInfo, loadInfo.mReferrerInfo, storageAllowed, - void_t() /* OptionalServiceWorkerData */, agentClusterId); + void_t() /* OptionalServiceWorkerData */, agentClusterId, + remoteType.unwrap()); PSharedWorkerChild* pActor = actorChild->SendPSharedWorkerConstructor( remoteWorkerData, loadInfo.mWindowID, portIdentifier.release()); diff --git a/dom/workers/test/browser.ini b/dom/workers/test/browser.ini index 3e90f8fb8b07..d50e94c18c6c 100644 --- a/dom/workers/test/browser.ini +++ b/dom/workers/test/browser.ini @@ -18,3 +18,7 @@ support-files = file_use_counter_worker.js file_use_counter_shared_worker.js file_use_counter_service_worker.js +[browser_privilegedmozilla_remoteworker.js] +support-files = + file_service_worker.js + file_service_worker_container.html diff --git a/dom/workers/test/browser_privilegedmozilla_remoteworker.js b/dom/workers/test/browser_privilegedmozilla_remoteworker.js new file mode 100644 index 000000000000..c13ccf7098e8 --- /dev/null +++ b/dom/workers/test/browser_privilegedmozilla_remoteworker.js @@ -0,0 +1,117 @@ +add_task(async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", true], + ["browser.tabs.remote.separatedMozillaDomains", "example.org"], + ["dom.ipc.processCount.web", 1], + ["dom.ipc.processCount.privilegedmozilla", 1], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ["dom.serviceworkers.parent_intercept", true], + ], + }); +}); + +// This test attempts to verify proper placement of spawned remoteworkers +// by spawning them and then verifying that they were spawned in the expected +// process by way of nsIWorkerDebuggerManager enumeration. +// +// Unfortunately, there's no other way to introspect where a worker global was +// spawned at this time. (devtools just ends up enumerating all workers in all +// processes and we don't want to depend on devtools in this test). +// +// As a result, this test currently only tests situations where it's known that +// a remote worker will be spawned in the same process that is initiating its +// spawning. +// +// This should be enhanced in the future. +add_task(async function test_serviceworker() { + const basePath = "browser/dom/workers/test"; + const pagePath = `${basePath}/file_service_worker_container.html`; + const scriptPath = `${basePath}/file_service_worker.js`; + + Services.ppmm.releaseCachedProcesses(); + + async function runWorkerInProcess() { + function getActiveWorkerURLs() { + const wdm = Cc[ + "@mozilla.org/dom/workers/workerdebuggermanager;1" + ].getService(Ci.nsIWorkerDebuggerManager); + + const workerDebuggerUrls = Array.from( + wdm.getWorkerDebuggerEnumerator() + ).map(wd => { + return wd.url; + }); + + return workerDebuggerUrls; + } + + return new Promise(resolve => { + content.navigator.serviceWorker.ready.then(({ active }) => { + const { port1, port2 } = new content.MessageChannel(); + active.postMessage("webpage->serviceworker", [port2]); + port1.onmessage = evt => { + resolve({ + msg: evt.data, + workerUrls: getActiveWorkerURLs(), + }); + }; + }); + }).then(async res => { + // Unregister the service worker used in this test. + const registration = await content.navigator.serviceWorker.ready; + await registration.unregister(); + return res; + }); + } + + const testCaseList = [ + // TODO: find a reasonable way to test the non-privileged scenario + // (because more than 1 process is usually available and the worker + // can be launched in a different one from the one where the tab + // is running). + /*{ + remoteType: "web", + hostname: "example.com", + },*/ + { + remoteType: "privilegedmozilla", + hostname: `example.org`, + }, + ]; + + for (const testCase of testCaseList) { + const { remoteType, hostname } = testCase; + + info(`Test remote serviceworkers launch selects a ${remoteType} process`); + + const tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: `https://${hostname}/${pagePath}`, + }); + + is( + tab.linkedBrowser.remoteType, + remoteType, + `Got the expected remoteType for ${hostname} tab` + ); + + const results = await SpecialPowers.spawn( + tab.linkedBrowser, + [], + runWorkerInProcess + ); + + Assert.deepEqual( + results, + { + msg: "serviceworker-reply", + workerUrls: [`https://${hostname}/${scriptPath}`], + }, + `Got the expected results for ${hostname} tab` + ); + + BrowserTestUtils.removeTab(tab); + } +}); diff --git a/dom/workers/test/file_service_worker.js b/dom/workers/test/file_service_worker.js new file mode 100644 index 000000000000..dd264e340eab --- /dev/null +++ b/dom/workers/test/file_service_worker.js @@ -0,0 +1,3 @@ +self.onmessage = evt => { + evt.ports[0].postMessage("serviceworker-reply"); +}; diff --git a/dom/workers/test/file_service_worker_container.html b/dom/workers/test/file_service_worker_container.html new file mode 100644 index 000000000000..625e911adc1c --- /dev/null +++ b/dom/workers/test/file_service_worker_container.html @@ -0,0 +1,15 @@ + + + + + + + + Service Worker Container + + diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_shared_workers.js b/toolkit/components/extensions/test/xpcshell/test_ext_shared_workers.js new file mode 100644 index 000000000000..66281f1f9522 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_shared_workers.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// This test attemps to verify that: +// - SharedWorkers can be created and successfully spawned by web extensions +// when web-extensions run in their own child process. +// - SharedWorkers cannot be created by web extensions when web extensions +// are being run in the main process (because SharedWorkers are only +// allowed to be spawned in the parent process if they have a system principal). +add_task(async function test_spawn_shared_worker() { + const background = WebExtensionPolicy.useRemoteWebExtensions + ? async function() { + const worker = new SharedWorker("worker.js"); + await new Promise(resolve => { + worker.port.onmessage = resolve; + worker.port.postMessage("bgpage->worker"); + }); + browser.test.sendMessage("test-shared-worker:done"); + } + : function() { + // This test covers the builds where the extensions are still + // running in the main process (it just checks that we don't + // allow it). + browser.test.assertThrows( + () => { + try { + new SharedWorker("worker.js"); + } catch (e) { + // assertThrows is currently failing to match the error message + // automatically, let's cheat a little bit for now. + throw new Error(`${e}`); + } + }, + /NS_ERROR_ABORT/, + "Got the expected failure in non-remote mode" + ); + + browser.test.sendMessage("test-shared-worker:done"); + }; + const extension = ExtensionTestUtils.loadExtension({ + background, + files: { + "worker.js": function() { + self.onconnect = evt => { + const port = evt.ports[0]; + port.onmessage = () => port.postMessage("worker-reply"); + }; + }, + }, + }); + + await extension.startup(); + await extension.awaitMessage("test-shared-worker:done"); + await extension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini index 56fcba70f9f8..c149f71c4090 100644 --- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini +++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini @@ -140,6 +140,7 @@ skip-if = ccov && os == 'linux' # bug 1607581 [test_ext_same_site_cookies.js] [test_ext_sandbox_var.js] [test_ext_schema.js] +[test_ext_shared_workers.js] [test_ext_shutdown_cleanup.js] [test_ext_simple.js] [test_ext_startupData.js]