tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RemoteWorkerManager.cpp (18840B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "RemoteWorkerManager.h"
      8 
      9 #include <utility>
     10 
     11 #include "RemoteWorkerServiceParent.h"
     12 #include "mozilla/AppShutdown.h"
     13 #include "mozilla/SchedulerGroup.h"
     14 #include "mozilla/StaticPrefs_extensions.h"
     15 #include "mozilla/dom/ContentChild.h"  // ContentChild::GetSingleton
     16 #include "mozilla/dom/PRemoteWorkerNonLifeCycleOpControllerChild.h"
     17 #include "mozilla/dom/PRemoteWorkerNonLifeCycleOpControllerParent.h"
     18 #include "mozilla/dom/ProcessIsolation.h"
     19 #include "mozilla/dom/RemoteWorkerController.h"
     20 #include "mozilla/dom/RemoteWorkerNonLifeCycleOpControllerParent.h"
     21 #include "mozilla/dom/RemoteWorkerParent.h"
     22 #include "mozilla/ipc/BackgroundParent.h"
     23 #include "mozilla/ipc/BackgroundUtils.h"
     24 #include "mozilla/ipc/PBackgroundParent.h"
     25 #include "mozilla/net/CookieServiceParent.h"
     26 #include "mozilla/net/NeckoParent.h"
     27 #include "nsCOMPtr.h"
     28 #include "nsIXULRuntime.h"
     29 #include "nsImportModule.h"
     30 #include "nsTArray.h"
     31 #include "nsThreadUtils.h"
     32 
     33 mozilla::LazyLogModule gRemoteWorkerManagerLog("RemoteWorkerManager");
     34 
     35 #ifdef LOG
     36 #  undef LOG
     37 #endif
     38 #define LOG(fmt) \
     39  MOZ_LOG(gRemoteWorkerManagerLog, mozilla::LogLevel::Verbose, fmt)
     40 
     41 namespace mozilla {
     42 
     43 using namespace ipc;
     44 using namespace net;
     45 
     46 namespace dom {
     47 
     48 namespace {
     49 
     50 // Raw pointer because this object is kept alive by RemoteWorkerServiceParent
     51 // actors.
     52 RemoteWorkerManager* sRemoteWorkerManager;
     53 
     54 bool IsServiceWorker(const RemoteWorkerData& aData) {
     55  return aData.serviceWorkerData().type() ==
     56         OptionalServiceWorkerData::TServiceWorkerData;
     57 }
     58 
     59 void TransmitPermissionsAndCookiesAndBlobURLsForPrincipalInfo(
     60    ContentParent* aContentParent, const PrincipalInfo& aPrincipalInfo) {
     61  AssertIsOnMainThread();
     62  MOZ_ASSERT(aContentParent);
     63 
     64  auto principalOrErr = PrincipalInfoToPrincipal(aPrincipalInfo);
     65 
     66  if (NS_WARN_IF(principalOrErr.isErr())) {
     67    return;
     68  }
     69 
     70  nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
     71 
     72  aContentParent->TransmitBlobURLsForPrincipal(principal);
     73 
     74  MOZ_ALWAYS_SUCCEEDS(
     75      aContentParent->TransmitPermissionsForPrincipal(principal));
     76 
     77  CookieServiceParent* cs = nullptr;
     78 
     79  PNeckoParent* neckoParent =
     80      LoneManagedOrNullAsserts(aContentParent->ManagedPNeckoParent());
     81  if (neckoParent) {
     82    PCookieServiceParent* csParent =
     83        LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent());
     84    if (csParent) {
     85      cs = static_cast<CookieServiceParent*>(csParent);
     86    }
     87  }
     88 
     89  if (cs) {
     90    nsCOMPtr<nsIURI> uri = principal->GetURI();
     91    cs->UpdateCookieInContentList(uri, principal->OriginAttributesRef());
     92  } else {
     93    aContentParent->AddPrincipalToCookieInProcessCache(principal);
     94  }
     95 }
     96 
     97 }  // namespace
     98 
     99 // static
    100 bool RemoteWorkerManager::MatchRemoteType(const nsACString& processRemoteType,
    101                                          const nsACString& workerRemoteType) {
    102  LOG(("MatchRemoteType [processRemoteType=%s, workerRemoteType=%s]",
    103       PromiseFlatCString(processRemoteType).get(),
    104       PromiseFlatCString(workerRemoteType).get()));
    105 
    106  // Respecting COOP and COEP requires processing headers in the parent
    107  // process in order to choose an appropriate content process, but the
    108  // workers' ScriptLoader processes headers in content processes. An
    109  // intermediary step that provides security guarantees is to simply never
    110  // allow SharedWorkers and ServiceWorkers to exist in a COOP+COEP process.
    111  // The ultimate goal is to allow these worker types to be put in such
    112  // processes based on their script response headers.
    113  // https://bugzilla.mozilla.org/show_bug.cgi?id=1595206
    114  //
    115  // RemoteWorkerManager::GetRemoteType should not select this remoteType
    116  // and so workerRemoteType is not expected to be set to a coop+coep
    117  // remoteType and here we can just assert that it is not happening.
    118  MOZ_ASSERT(!IsWebCoopCoepRemoteType(workerRemoteType));
    119 
    120  return processRemoteType.Equals(workerRemoteType);
    121 }
    122 
    123 // static
    124 Result<nsCString, nsresult> RemoteWorkerManager::GetRemoteType(
    125    const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind) {
    126  AssertIsOnMainThread();
    127 
    128  MOZ_ASSERT_IF(aWorkerKind == WorkerKind::WorkerKindService,
    129                aPrincipal->GetIsContentPrincipal());
    130 
    131  // If E10S is fully disabled, there are no decisions to be made, and we need
    132  // to finish the load in the parent process.
    133  if (!BrowserTabsRemoteAutostart()) {
    134    LOG(("GetRemoteType: Loading in parent process as e10s is disabled"));
    135    return NOT_REMOTE_TYPE;
    136  }
    137 
    138  nsCString preferredRemoteType = DEFAULT_REMOTE_TYPE;
    139  if (aWorkerKind == WorkerKind::WorkerKindShared) {
    140    if (auto* contentChild = ContentChild::GetSingleton()) {
    141      // For a shared worker set the preferred remote type to the content
    142      // child process remote type.
    143      preferredRemoteType = contentChild->GetRemoteType();
    144    } else if (aPrincipal->IsSystemPrincipal()) {
    145      preferredRemoteType = NOT_REMOTE_TYPE;
    146    }
    147  }
    148 
    149  auto result = IsolationOptionsForWorker(
    150      aPrincipal, aWorkerKind, preferredRemoteType, FissionAutostart());
    151  if (NS_WARN_IF(result.isErr())) {
    152    LOG(("GetRemoteType Abort: IsolationOptionsForWorker failed"));
    153    return Err(NS_ERROR_DOM_ABORT_ERR);
    154  }
    155  auto options = result.unwrap();
    156 
    157  if (MOZ_LOG_TEST(gRemoteWorkerManagerLog, LogLevel::Verbose)) {
    158    nsCString principalOrigin;
    159    aPrincipal->GetOrigin(principalOrigin);
    160 
    161    LOG(
    162        ("GetRemoteType workerType=%s, principal=%s, "
    163         "preferredRemoteType=%s, selectedRemoteType=%s",
    164         aWorkerKind == WorkerKind::WorkerKindService ? "service" : "shared",
    165         principalOrigin.get(), preferredRemoteType.get(),
    166         options.mRemoteType.get()));
    167  }
    168 
    169  return options.mRemoteType;
    170 }
    171 
    172 // static
    173 bool RemoteWorkerManager::HasExtensionPrincipal(const RemoteWorkerData& aData) {
    174  auto principalInfo = aData.principalInfo();
    175  return principalInfo.type() == PrincipalInfo::TContentPrincipalInfo &&
    176         // This helper method is also called from the background thread and so
    177         // we can't check if the principal does have an addonPolicy object
    178         // associated and we have to resort to check the url scheme instead.
    179         StringBeginsWith(principalInfo.get_ContentPrincipalInfo().spec(),
    180                          "moz-extension://"_ns);
    181 }
    182 
    183 /* static */
    184 already_AddRefed<RemoteWorkerManager> RemoteWorkerManager::GetOrCreate() {
    185  AssertIsInMainProcess();
    186  AssertIsOnBackgroundThread();
    187 
    188  if (!sRemoteWorkerManager) {
    189    sRemoteWorkerManager = new RemoteWorkerManager();
    190  }
    191 
    192  RefPtr<RemoteWorkerManager> rwm = sRemoteWorkerManager;
    193  return rwm.forget();
    194 }
    195 
    196 RemoteWorkerManager::RemoteWorkerManager() : mParentActor(nullptr) {
    197  AssertIsInMainProcess();
    198  AssertIsOnBackgroundThread();
    199  MOZ_ASSERT(!sRemoteWorkerManager);
    200 }
    201 
    202 RemoteWorkerManager::~RemoteWorkerManager() {
    203  AssertIsInMainProcess();
    204  AssertIsOnBackgroundThread();
    205  MOZ_ASSERT(sRemoteWorkerManager == this);
    206  sRemoteWorkerManager = nullptr;
    207 }
    208 
    209 void RemoteWorkerManager::RegisterActor(RemoteWorkerServiceParent* aActor) {
    210  AssertIsInMainProcess();
    211  AssertIsOnBackgroundThread();
    212  MOZ_ASSERT(aActor);
    213 
    214  if (!aActor->IsOtherProcessActor()) {
    215    MOZ_ASSERT(!mParentActor);
    216    mParentActor = aActor;
    217    return;
    218  }
    219 
    220  MOZ_ASSERT(!mChildActors.Contains(aActor));
    221  mChildActors.AppendElement(aActor);
    222 }
    223 
    224 void RemoteWorkerManager::UnregisterActor(RemoteWorkerServiceParent* aActor) {
    225  AssertIsInMainProcess();
    226  AssertIsOnBackgroundThread();
    227  MOZ_ASSERT(aActor);
    228 
    229  if (aActor == mParentActor) {
    230    mParentActor = nullptr;
    231  } else {
    232    MOZ_ASSERT(mChildActors.Contains(aActor));
    233    mChildActors.RemoveElement(aActor);
    234  }
    235 }
    236 
    237 void RemoteWorkerManager::Launch(RemoteWorkerController* aController,
    238                                 const RemoteWorkerData& aData,
    239                                 base::ProcessId aProcessId) {
    240  AssertIsInMainProcess();
    241  AssertIsOnBackgroundThread();
    242 
    243  TargetActorAndKeepAlive target = SelectTargetActor(aData, aProcessId);
    244 
    245  // If there is no available actor, try to start a process, and connect to it.
    246  if (!target.mActor) {
    247    // Launching is async, so we cannot check for failures right here.
    248    LaunchNewContentProcess(aData)->Then(
    249        GetCurrentSerialEventTarget(), __func__,
    250        [self = RefPtr{this}, controller = RefPtr{aController},
    251         data = aData](TargetActorAndKeepAlive&& aTarget) {
    252          if (aTarget.mActor->CanSend()) {
    253            self->LaunchInternal(controller, aTarget.mActor,
    254                                 std::move(aTarget.mKeepAlive), data);
    255          } else {
    256            controller->CreationFailed();
    257          }
    258        },
    259        [controller = RefPtr{aController}](nsresult) {
    260          controller->CreationFailed();
    261        });
    262    return;
    263  }
    264 
    265  LaunchInternal(aController, target.mActor, std::move(target.mKeepAlive),
    266                 aData);
    267 }
    268 
    269 void RemoteWorkerManager::LaunchInternal(
    270    RemoteWorkerController* aController,
    271    RemoteWorkerServiceParent* aTargetActor,
    272    UniqueThreadsafeContentParentKeepAlive&& aKeepAlive,
    273    const RemoteWorkerData& aData) {
    274  AssertIsInMainProcess();
    275  AssertIsOnBackgroundThread();
    276  MOZ_ASSERT(aController);
    277  MOZ_ASSERT(aTargetActor);
    278  MOZ_ASSERT(aTargetActor == mParentActor ||
    279             mChildActors.Contains(aTargetActor));
    280 
    281  // We need to send permissions to content processes, but not if we're spawning
    282  // the worker here in the parent process.
    283  if (aTargetActor != mParentActor) {
    284    MOZ_ASSERT(aKeepAlive);
    285 
    286    // This won't cause any race conditions because the content process
    287    // should wait for the permissions to be received before executing the
    288    // Service Worker.
    289    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
    290        __func__, [contentHandle = RefPtr{aKeepAlive.get()},
    291                   principalInfo = aData.principalInfo()] {
    292          AssertIsOnMainThread();
    293          if (RefPtr<ContentParent> contentParent =
    294                  contentHandle->GetContentParent()) {
    295            TransmitPermissionsAndCookiesAndBlobURLsForPrincipalInfo(
    296                contentParent, principalInfo);
    297          }
    298        });
    299 
    300    MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget()));
    301  }
    302 
    303  RefPtr<RemoteWorkerParent> workerActor =
    304      MakeAndAddRef<RemoteWorkerParent>(std::move(aKeepAlive));
    305 
    306  mozilla::ipc::Endpoint<PRemoteWorkerNonLifeCycleOpControllerParent> parentEp;
    307  mozilla::ipc::Endpoint<PRemoteWorkerNonLifeCycleOpControllerChild> childEp;
    308  MOZ_ALWAYS_SUCCEEDS(PRemoteWorkerNonLifeCycleOpController::CreateEndpoints(
    309      &parentEp, &childEp));
    310 
    311  MOZ_ASSERT(!aController->mNonLifeCycleOpController);
    312  aController->mNonLifeCycleOpController =
    313      MakeAndAddRef<RemoteWorkerNonLifeCycleOpControllerParent>(aController);
    314 
    315  parentEp.Bind(aController->mNonLifeCycleOpController);
    316 
    317  if (!aTargetActor->SendPRemoteWorkerConstructor(workerActor, aData,
    318                                                  std::move(childEp))) {
    319    AsyncCreationFailed(aController);
    320    return;
    321  }
    322 
    323  // This makes the link better the 2 actors.
    324  aController->SetWorkerActor(workerActor);
    325  workerActor->SetController(aController);
    326 }
    327 
    328 void RemoteWorkerManager::AsyncCreationFailed(
    329    RemoteWorkerController* aController) {
    330  RefPtr<RemoteWorkerController> controller = aController;
    331  nsCOMPtr<nsIRunnable> r =
    332      NS_NewRunnableFunction("RemoteWorkerManager::AsyncCreationFailed",
    333                             [controller]() { controller->CreationFailed(); });
    334 
    335  NS_DispatchToCurrentThread(r.forget());
    336 }
    337 
    338 template <typename Callback>
    339 void RemoteWorkerManager::ForEachActor(
    340    Callback&& aCallback, const nsACString& aRemoteType,
    341    Maybe<base::ProcessId> aProcessId) const {
    342  AssertIsOnBackgroundThread();
    343 
    344  const auto length = mChildActors.Length();
    345 
    346  auto end = static_cast<uint32_t>(rand()) % length;
    347  if (aProcessId) {
    348    // Start from the actor with the given processId instead of starting from
    349    // a random index.
    350    for (auto j = length - 1; j > 0; j--) {
    351      if (mChildActors[j]->OtherPid() == *aProcessId) {
    352        end = j;
    353        break;
    354      }
    355    }
    356  }
    357 
    358  uint32_t i = end;
    359 
    360  do {
    361    MOZ_ASSERT(i < mChildActors.Length());
    362    RemoteWorkerServiceParent* actor = mChildActors[i];
    363 
    364    if (MatchRemoteType(actor->GetRemoteType(), aRemoteType)) {
    365      ThreadsafeContentParentHandle* contentHandle =
    366          actor->GetContentParentHandle();
    367 
    368      if (!aCallback(actor, contentHandle)) {
    369        break;
    370      }
    371    }
    372 
    373    i = (i + 1) % length;
    374  } while (i != end);
    375 }
    376 
    377 /**
    378 * When selecting a target actor for a given remote worker, we have to consider
    379 * that:
    380 *
    381 * - Service Workers can spawn even when their registering page/script isn't
    382 *   active (e.g. push notifications), so we don't attempt to spawn the worker
    383 *   in its registering script's process. We search linearly and choose the
    384 *   search's starting position randomly.
    385 *
    386 * - When Fission is enabled, Shared Workers may have to be spawned into
    387 *   different child process from the one where it has been registered from, and
    388 *   that child process may be going to be marked as dead and shutdown.
    389 *
    390 * ContentParent provides a way to add a KeepAlive, which will prevent the
    391 * process from being shut down, through a ThreadsafeContentParentHandle in an
    392 * atomic way. This call will fail if the process is already being shut down.
    393 * When selecting a content process on the PBackground thread, we'll acquire the
    394 * KeepAlive in that way.
    395 */
    396 RemoteWorkerManager::TargetActorAndKeepAlive
    397 RemoteWorkerManager::SelectTargetActorInternal(
    398    const RemoteWorkerData& aData, base::ProcessId aProcessId) const {
    399  AssertIsOnBackgroundThread();
    400  MOZ_ASSERT(!mChildActors.IsEmpty());
    401 
    402  RemoteWorkerServiceParent* actor = nullptr;
    403  UniqueThreadsafeContentParentKeepAlive keepAlive;
    404 
    405  const auto& workerRemoteType = aData.remoteType();
    406 
    407  ForEachActor(
    408      [&](RemoteWorkerServiceParent* aActor,
    409          ThreadsafeContentParentHandle* aContentHandle) {
    410        // Make sure to choose an actor related to a child process that is not
    411        // going to shutdown while we are still in the process of launching the
    412        // remote worker.
    413        //
    414        // ForEachActor will start from the child actor coming from the child
    415        // process with a pid equal to aProcessId if any, otherwise it would
    416        // start from a random actor in the mChildActors array, this guarantees
    417        // that we will choose that actor if it does also match the remote type.
    418        if ((keepAlive = aContentHandle->TryAddKeepAlive())) {
    419          actor = aActor;
    420          return false;
    421        }
    422        MOZ_ASSERT(!actor);
    423        return true;
    424      },
    425      workerRemoteType, IsServiceWorker(aData) ? Nothing() : Some(aProcessId));
    426 
    427  return {actor, std::move(keepAlive)};
    428 }
    429 
    430 RemoteWorkerManager::TargetActorAndKeepAlive
    431 RemoteWorkerManager::SelectTargetActor(const RemoteWorkerData& aData,
    432                                       base::ProcessId aProcessId) {
    433  AssertIsInMainProcess();
    434  AssertIsOnBackgroundThread();
    435 
    436  // System principal workers should run on the parent process.
    437  if (aData.principalInfo().type() == PrincipalInfo::TSystemPrincipalInfo) {
    438    MOZ_ASSERT(mParentActor);
    439    return {mParentActor, nullptr};
    440  }
    441 
    442  // Extension principal workers are allowed to run on the parent process
    443  // when "extensions.webextensions.remote" pref is false.
    444  if (aProcessId == base::GetCurrentProcId() &&
    445      aData.remoteType().Equals(NOT_REMOTE_TYPE) &&
    446      !StaticPrefs::extensions_webextensions_remote() &&
    447      HasExtensionPrincipal(aData)) {
    448    MOZ_ASSERT(mParentActor);
    449    return {mParentActor, nullptr};
    450  }
    451 
    452  // If e10s is off, use the parent process.
    453  if (!BrowserTabsRemoteAutostart()) {
    454    MOZ_ASSERT(mParentActor);
    455    return {mParentActor, nullptr};
    456  }
    457 
    458  // We shouldn't have to worry about content-principal parent-process workers.
    459  MOZ_ASSERT(aProcessId != base::GetCurrentProcId());
    460 
    461  if (mChildActors.IsEmpty()) {
    462    return {nullptr, nullptr};
    463  }
    464 
    465  return SelectTargetActorInternal(aData, aProcessId);
    466 }
    467 
    468 RefPtr<RemoteWorkerManager::LaunchProcessPromise>
    469 RemoteWorkerManager::LaunchNewContentProcess(const RemoteWorkerData& aData) {
    470  AssertIsInMainProcess();
    471  AssertIsOnBackgroundThread();
    472 
    473  // Request a process making sure to specify aPreferUsed=true.  For a given
    474  // remoteType there's a pool size limit.  If we pass aPreferUsed here, then if
    475  // there's any process in the pool already, we will use that.  If we pass
    476  // false (which is the default if omitted), then this call will spawn a new
    477  // process if the pool isn't at its limit yet.
    478  //
    479  // (Our intent is never to grow the pool size here.  Our logic gets here
    480  // because our current logic on PBackground is only aware of
    481  // RemoteWorkerServiceParent actors that have registered themselves, which is
    482  // fundamentally unaware of processes that will match in the future when they
    483  // register.  So we absolutely are fine with and want any existing processes.)
    484  return InvokeAsync(GetMainThreadSerialEventTarget(), __func__,
    485                     [remoteType = aData.remoteType()]() {
    486                       if (AppShutdown::IsInOrBeyond(
    487                               ShutdownPhase::AppShutdownConfirmed)) {
    488                         return ContentParent::LaunchPromise::CreateAndReject(
    489                             NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
    490                       }
    491 
    492                       return ContentParent::GetNewOrUsedBrowserProcessAsync(
    493                           /* aRemoteType = */ remoteType,
    494                           /* aGroup */ nullptr,
    495                           hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND,
    496                           /* aPreferUsed */ true);
    497                     })
    498      ->Then(
    499          GetMainThreadSerialEventTarget(), __func__,
    500          [](UniqueContentParentKeepAlive&& aContentParent) {
    501            RefPtr<RemoteWorkerServiceParent> actor =
    502                aContentParent->GetRemoteWorkerServiceParent();
    503            MOZ_ASSERT(actor, "RemoteWorkerServiceParent not initialized?");
    504            return RemoteWorkerManager::LaunchProcessPromise::CreateAndResolve(
    505                TargetActorAndKeepAlive{
    506                    actor, UniqueContentParentKeepAliveToThreadsafe(
    507                               std::move(aContentParent))},
    508                __func__);
    509          },
    510          [](nsresult aError) {
    511            return RemoteWorkerManager::LaunchProcessPromise::CreateAndReject(
    512                aError, __func__);
    513          });
    514 }
    515 
    516 }  // namespace dom
    517 }  // namespace mozilla