Bug 1575092 - don't spawn Shared/Service Workers in "web COOP+COEP" processes r=asuth
authorPerry Jiang <perry@mozilla.com>
Wed, 20 Nov 2019 20:02:23 +0000
changeset 503100 da39e98af6a27ebaed99292f7bd50ea90b1cea84
parent 503099 036e6a66c52588f24e76ed4041fc56fa3799ef2b
child 503101 a43202115869ca910305cdd4c718c99b0392239c
push id101115
push userpjiang@mozilla.com
push dateWed, 20 Nov 2019 22:08:52 +0000
treeherderautoland@da39e98af6a2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs1575092
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1575092 - don't spawn Shared/Service Workers in "web COOP+COEP" processes r=asuth Differential Revision: https://phabricator.services.mozilla.com/D50815
dom/workers/remoteworkers/RemoteWorkerManager.cpp
dom/workers/remoteworkers/RemoteWorkerManager.h
--- a/dom/workers/remoteworkers/RemoteWorkerManager.cpp
+++ b/dom/workers/remoteworkers/RemoteWorkerManager.cpp
@@ -3,20 +3,18 @@
 /* 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 "RemoteWorkerManager.h"
 
 #include <utility>
 
-#include "mozilla/RefPtr.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/SystemGroup.h"
-#include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/RemoteWorkerParent.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundParent.h"
 #include "nsCOMPtr.h"
 #include "nsIXULRuntime.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
@@ -182,16 +180,49 @@ void RemoteWorkerManager::AsyncCreationF
   RefPtr<RemoteWorkerController> controller = aController;
   nsCOMPtr<nsIRunnable> r =
       NS_NewRunnableFunction("RemoteWorkerManager::AsyncCreationFailed",
                              [controller]() { controller->CreationFailed(); });
 
   NS_DispatchToCurrentThread(r.forget());
 }
 
+template <typename Callback>
+void RemoteWorkerManager::ForEachActor(Callback&& aCallback) const {
+  AssertIsOnBackgroundThread();
+
+  const auto length = mChildActors.Length();
+  const auto end = static_cast<uint32_t>(rand()) % length;
+  uint32_t i = end;
+
+  nsTArray<RefPtr<ContentParent>> proxyReleaseArray;
+
+  do {
+    MOZ_ASSERT(i < mChildActors.Length());
+    RemoteWorkerServiceParent* actor = mChildActors[i];
+
+    RefPtr<ContentParent> contentParent =
+        BackgroundParent::GetContentParent(actor->Manager());
+
+    auto scopeExit = MakeScopeExit(
+        [&]() { proxyReleaseArray.AppendElement(std::move(contentParent)); });
+
+    if (!aCallback(actor, std::move(contentParent))) {
+      break;
+    }
+
+    i = (i + 1) % length;
+  } while (i != end);
+
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+      __func__, [proxyReleaseArray = std::move(proxyReleaseArray)] {});
+
+  MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget()));
+}
+
 /**
  * Service Workers can spawn even when their registering page/script isn't
  * active (e.g. push notifications), so we don't attempt to spawn the worker
  * in its registering script's process. We search linearly and choose the
  * search's starting position randomly.
  *
  * Spawning the workers in a random process makes the process selection criteria
  * a little tricky, as a candidate process may imminently shutdown due to a
@@ -205,71 +236,91 @@ void RemoteWorkerManager::AsyncCreationF
  * main thread, so here on the background thread,  while
  * `ContentParent::mRemoteWorkerActorData` is locked, if `mCount` > 0, we can
  * register a remote worker actor "early" and guarantee that the corresponding
  * content process will not shutdown.
  */
 RemoteWorkerServiceParent*
 RemoteWorkerManager::SelectTargetActorForServiceWorker(
     const RemoteWorkerData& aData) const {
-  AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mChildActors.IsEmpty());
   MOZ_ASSERT(IsServiceWorker(aData));
 
-  nsTArray<RefPtr<ContentParent>> contentParents;
+  RemoteWorkerServiceParent* actor = nullptr;
 
-  auto scopeExit = MakeScopeExit([&] {
-    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
-        __func__,
-        [doomed = std::move(contentParents)]() mutable { doomed.Clear(); });
-
-    SystemGroup::Dispatch(TaskCategory::Other, r.forget());
-  });
-
-  uint32_t random = uint32_t(rand() % mChildActors.Length());
-  uint32_t i = random;
+  ForEachActor([&](RemoteWorkerServiceParent* aActor,
+                   RefPtr<ContentParent>&& aContentParent) {
+    const auto& remoteType = aContentParent->GetRemoteType();
 
-  do {
-    auto actor = mChildActors[i];
-    PBackgroundParent* bgParent = actor->Manager();
-    MOZ_ASSERT(bgParent);
-
-    RefPtr<ContentParent> contentParent =
-        BackgroundParent::GetContentParent(bgParent);
-
-    auto scopeExit = MakeScopeExit(
-        [&] { contentParents.AppendElement(std::move(contentParent)); });
-
-    if (IsWebRemoteType(contentParent->GetRemoteType())) {
-      auto lock = contentParent->mRemoteWorkerActorData.Lock();
+    // 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 (IsWebRemoteType(remoteType) && !IsWebCoopCoepRemoteType(remoteType)) {
+      auto lock = aContentParent->mRemoteWorkerActorData.Lock();
 
       if (lock->mCount || !lock->mShutdownStarted) {
         ++lock->mCount;
 
         // This won't cause any race conditions because the content process
         // should wait for the permissions to be received before executing the
         // Service Worker.
         nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
-            __func__, [contentParent = std::move(contentParent),
+            __func__, [contentParent = std::move(aContentParent),
                        principalInfo = aData.principalInfo()] {
               TransmitPermissionsForPrincipalInfo(contentParent, principalInfo);
             });
 
         MOZ_ALWAYS_SUCCEEDS(
             SystemGroup::Dispatch(TaskCategory::Other, r.forget()));
 
-        return actor;
+        actor = aActor;
+        return false;
       }
     }
 
-    i = (i + 1) % mChildActors.Length();
-  } while (i != random);
+    MOZ_ASSERT(!actor);
+    return true;
+  });
+
+  return actor;
+}
+
+RemoteWorkerServiceParent*
+RemoteWorkerManager::SelectTargetActorForSharedWorker(
+    base::ProcessId aProcessId) const {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mChildActors.IsEmpty());
+
+  RemoteWorkerServiceParent* actor = nullptr;
 
-  return nullptr;
+  ForEachActor([&](RemoteWorkerServiceParent* aActor,
+                   RefPtr<ContentParent>&& aContentParent) {
+    if (IsWebCoopCoepRemoteType(aContentParent->GetRemoteType())) {
+      return true;
+    }
+
+    if (aActor->OtherPid() == aProcessId) {
+      actor = aActor;
+      return false;
+    }
+
+    if (!actor) {
+      actor = aActor;
+    }
+
+    return true;
+  });
+
+  return actor;
 }
 
 RemoteWorkerServiceParent* RemoteWorkerManager::SelectTargetActor(
     const RemoteWorkerData& aData, base::ProcessId aProcessId) {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
 
   // System principal workers should run on the parent process.
@@ -286,30 +337,18 @@ RemoteWorkerServiceParent* RemoteWorkerM
 
   // We shouldn't have to worry about content-principal parent-process workers.
   MOZ_ASSERT(aProcessId != base::GetCurrentProcId());
 
   if (mChildActors.IsEmpty()) {
     return nullptr;
   }
 
-  if (IsServiceWorker(aData)) {
-    return SelectTargetActorForServiceWorker(aData);
-  }
-
-  for (RemoteWorkerServiceParent* actor : mChildActors) {
-    // Let's execute the RemoteWorker on the same process.
-    if (actor->OtherPid() == aProcessId) {
-      return actor;
-    }
-  }
-
-  // Let's choose an actor, randomly.
-  uint32_t id = uint32_t(rand()) % mChildActors.Length();
-  return mChildActors[id];
+  return IsServiceWorker(aData) ? SelectTargetActorForServiceWorker(aData)
+                                : SelectTargetActorForSharedWorker(aProcessId);
 }
 
 void RemoteWorkerManager::LaunchNewContentProcess(
     const RemoteWorkerData& aData) {
   AssertIsInMainProcess();
   AssertIsOnBackgroundThread();
 
   // This runnable will spawn a new process if it doesn't exist yet.
--- a/dom/workers/remoteworkers/RemoteWorkerManager.h
+++ b/dom/workers/remoteworkers/RemoteWorkerManager.h
@@ -3,16 +3,18 @@
 /* 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/. */
 
 #ifndef mozilla_dom_RemoteWorkerManager_h
 #define mozilla_dom_RemoteWorkerManager_h
 
 #include "base/process.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/RemoteWorkerTypes.h"
 #include "nsISupportsImpl.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 namespace dom {
 
 class RemoteWorkerController;
@@ -37,25 +39,43 @@ class RemoteWorkerManager final {
   ~RemoteWorkerManager();
 
   RemoteWorkerServiceParent* SelectTargetActor(const RemoteWorkerData& aData,
                                                base::ProcessId aProcessId);
 
   RemoteWorkerServiceParent* SelectTargetActorForServiceWorker(
       const RemoteWorkerData& aData) const;
 
+  RemoteWorkerServiceParent* SelectTargetActorForSharedWorker(
+      base::ProcessId aProcessId) const;
+
   void LaunchInternal(RemoteWorkerController* aController,
                       RemoteWorkerServiceParent* aTargetActor,
                       const RemoteWorkerData& aData,
                       bool aRemoteWorkerAlreadyRegistered = false);
 
   void LaunchNewContentProcess(const RemoteWorkerData& aData);
 
   void AsyncCreationFailed(RemoteWorkerController* aController);
 
+  // Iterate through all RemoteWorkerServiceParent actors, starting from a
+  // random index (as if iterating through a circular array).
+  //
+  // aCallback should be a invokable object with a function signature of
+  //   bool (RemoteWorkerServiceParent*, RefPtr<ContentParent>&&)
+  //
+  // aCallback is called with the actor and corresponding ContentParent, should
+  // return false to abort iteration before all actors have been traversed (e.g.
+  // if the desired actor is found), and must not mutate mChildActors (which
+  // shouldn't be an issue because this function is const). aCallback also
+  // doesn't need to worry about proxy-releasing the ContentParent if it isn't
+  // moved out of the parameter.
+  template <typename Callback>
+  void ForEachActor(Callback&& aCallback) const;
+
   // The list of existing RemoteWorkerServiceParent actors for child processes.
   // Raw pointers because RemoteWorkerServiceParent actors unregister themselves
   // when destroyed.
   // XXX For Fission, where we could have a lot of child actors, should we maybe
   // instead keep either a hash table (PID->actor) or perhaps store the actors
   // in order, sorted by PID, to avoid linear lookup times?
   nsTArray<RemoteWorkerServiceParent*> mChildActors;
   RemoteWorkerServiceParent* mParentActor;