gecko-dev/dom/clients/manager/ClientManager.cpp
Andrew Sutherland a212459d62 Bug 1544232 - Limit lifetime extension of SWs by SWs to the sender's lifetime. r=edenchuang
This patch introduces an explicit concept of lifetimes with mechanisms
in place so that actions taken by Clients (windows or non-ServiceWorker
orkers) will extend the lifetime of a ServiceWorker, but a ServiceWorker
cannot extend the life of another ServiceWorker.

The areas of concern are:
- ServiceWorker.postMessage: With ServiceWorkers exposed on workers and
  the ability to access other registrations via ServiceWorkerContainer
  being exposed, ServiceWorkers can message other ServiceWorkers.  It's
  essential that they never be allowed to give a ServiceWorker a
  lifetime longer than their own.
- ServiceWorkerRegistration.update(): Requesting an update of a
  registration should not allow any installed/updated ServiceWorker to
  have a lifetime longer than the ServiceWorker creating the request.
- ServiceWorkerContainer.register(): Requesting the installation of a
  new ServiceWorker should likewise constrain the lifetime of the newly
  installed ServiceWorker.

Note that in cases where we would potentially spawn a ServiceWorker,
whether it be in response to postMessage or as part of an install or
update, a key criteria is whether the lifetime extends far enough into
the future for us to believe the ServiceWorker can accomplish anything.
Currently we have a constant of 5 seconds against a normal full
lifetime of 30 seconds (before 30 second grace period).  So once a SW
has < 5 seconds of lifetime left, it won't be able to spawn a SW.  Note
that in the case of install/update, we do not prevent the creation of
the job at this time, instead the job will fail during the check script
evaluation step as failure to spawn the ServiceWorker is equivalent to
a script load failure.

A somewhat ugly part of this implementation is that because Bug 1853706
is not yet implemented, our actors that are fundamentally associated
with a global don't have an inherent understanding of their relationship
to that global.  So we approximate that by:
- For postMessage, we always have a ServiceWorkerDescriptor if we are
  being messaged by a ServiceWorker, allowing us direct lookup.
- ServiceWorkerRegistration.update(): In a previous patch in the stack
  we had ServiceWorkerRegistrationProxy latch the ClientInfo of its
  owning global when it was created.  Note that in the case of a
  ServiceWorker's own registration, this will be created at startup
  before the worker hits the execution ready state.
  - Note that because we have at most one live
    ServiceWorkerRegistration per global at a time, and the
    registration is fundamentally associated with the
    ServiceWorkerGlobalScope, that registration and its proxy will
    remain alive for the duration of the global.
- ServiceWorkerContainer.register(): We already were sending the client
  info along with the register call (as well as all other calls on the
  container).

Looking up the ServiceWorker from its client is not something that was
really intended.  This is further complicated by ServiceWorkerManager
being authoritative for ServiceWorkers on the parent process main thread
whereas the ClientManagerService is authoritative on PBackground and
actor-centric, making sketchy multi-threaded maps not really an option.

Looking up the ServiceWorker from a ServiceWorkerDescriptor is intended,
but the primary intent in those cases is so that the recipient of such a
descriptor can easily create a ServiceWorker instance that is
live-updating (by way of its owning ServiceWorkerRegistration; we don't
have IPC actors directly for ServiceWorkers, just the registration).
Adding the descriptor to clients until Bug 1853706 is implemented would
be an exceedingly ugly workaround because it would greatly complicate
the existing plumbing code, and a lot of the code is confusing enough
as-is.

This patch initially adopted an approach of encoding the scope of a
ServiceWorker as its client URL, but it turns out web extension
ServiceWorker support (reasonably) assumed the URL would be the script
URL so the original behavior was restored and when performing our
lookup we just check all registrations associated with the given
origin.  This is okay because register and update calls are inherently
expensive, rare operations and the overhead of the additional checks is
marginal.  Additionally, we can remove this logic once Bug 1853706 is
implemented.

As part of that initial scope tunneling was that, as noted above, we
do sample the ClientInfo for a ServiceWorker's own registration before
the worker is execution-ready.  And prior to this patch, we only would
populate the URL during execution-ready because for most globals, we
can't possibly know the URL when the ClientSource is created.  However,
for ServiceWorkers we can.  Because we also want to know what the id of
the ServiceWorker client would be, we also change the creation of the
ServiceWorker ClientSource so that it uses a ClientInfo created by the
authoritative ServiceWorkerPrivate in its Initialize method.

A minor retained hack is that because the worker scriptloader propagates
its CSP structure onto its ClientInfo (but not its ClientSource, which
feels weird, but makes sense) and that does get sent via register(), we
do also need to normalize the ClientInfo in the parent when we do
equality checking to have it ignore the CSP.

Differential Revision: https://phabricator.services.mozilla.com/D180915
2024-10-24 03:02:42 +00:00

401 lines
13 KiB
C++

/* -*- 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 "ClientManager.h"
#include "ClientHandle.h"
#include "ClientManagerChild.h"
#include "ClientManagerOpChild.h"
#include "ClientSource.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ClearOnShutdown.h" // PastShutdownPhase
#include "mozilla/StaticPrefs_dom.h"
#include "prthread.h"
namespace mozilla::dom {
using mozilla::ipc::BackgroundChild;
using mozilla::ipc::PBackgroundChild;
using mozilla::ipc::PrincipalInfo;
namespace {
const uint32_t kBadThreadLocalIndex = -1;
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
const uint32_t kThreadLocalMagic1 = 0x8d57eea6;
const uint32_t kThreadLocalMagic2 = 0x59f375c9;
#endif
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
uint32_t sClientManagerThreadLocalMagic1 = kThreadLocalMagic1;
#endif
uint32_t sClientManagerThreadLocalIndex = kBadThreadLocalIndex;
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
uint32_t sClientManagerThreadLocalMagic2 = kThreadLocalMagic2;
uint32_t sClientManagerThreadLocalIndexDuplicate = kBadThreadLocalIndex;
#endif
} // anonymous namespace
ClientManager::ClientManager() {
PBackgroundChild* parentActor =
BackgroundChild::GetOrCreateForCurrentThread();
if (NS_WARN_IF(!parentActor)) {
Shutdown();
return;
}
RefPtr<ClientManagerChild> actor = ClientManagerChild::Create();
if (NS_WARN_IF(!actor)) {
Shutdown();
return;
}
PClientManagerChild* sentActor =
parentActor->SendPClientManagerConstructor(actor);
if (NS_WARN_IF(!sentActor)) {
Shutdown();
return;
}
MOZ_DIAGNOSTIC_ASSERT(sentActor == actor);
ActivateThing(actor);
}
ClientManager::~ClientManager() {
NS_ASSERT_OWNINGTHREAD(ClientManager);
Shutdown();
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic1 == kThreadLocalMagic1);
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic2 == kThreadLocalMagic2);
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex != kBadThreadLocalIndex);
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex ==
sClientManagerThreadLocalIndexDuplicate);
MOZ_DIAGNOSTIC_ASSERT(this ==
PR_GetThreadPrivate(sClientManagerThreadLocalIndex));
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
PRStatus status =
#endif
PR_SetThreadPrivate(sClientManagerThreadLocalIndex, nullptr);
MOZ_DIAGNOSTIC_ASSERT(status == PR_SUCCESS);
}
void ClientManager::Shutdown() {
NS_ASSERT_OWNINGTHREAD(ClientManager);
if (IsShutdown()) {
return;
}
ShutdownThing();
}
UniquePtr<ClientSource> ClientManager::CreateSourceInternal(
ClientType aType, nsISerialEventTarget* aEventTarget,
const PrincipalInfo& aPrincipal) {
NS_ASSERT_OWNINGTHREAD(ClientManager);
nsID id;
nsresult rv = nsID::GenerateUUIDInPlace(id);
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
if (NS_WARN_IF(NS_FAILED(rv))) {
// If we can't even get a UUID, at least make sure not to use a garbage
// value. Instead return a shutdown ClientSource with a zero'd id.
// This should be exceptionally rare, if it happens at all.
id.Clear();
ClientSourceConstructorArgs args(id, Nothing(), aType, aPrincipal,
TimeStamp::Now(), VoidCString(),
FrameType::None);
UniquePtr<ClientSource> source(new ClientSource(this, aEventTarget, args));
source->Shutdown();
return source;
}
ClientSourceConstructorArgs args(id, Nothing(), aType, aPrincipal,
TimeStamp::Now(), VoidCString(),
FrameType::None);
UniquePtr<ClientSource> source(new ClientSource(this, aEventTarget, args));
if (IsShutdown()) {
source->Shutdown();
return source;
}
source->Activate(GetActor());
return source;
}
UniquePtr<ClientSource> ClientManager::CreateSourceInternal(
const ClientInfo& aClientInfo, nsISerialEventTarget* aEventTarget) {
NS_ASSERT_OWNINGTHREAD(ClientManager);
ClientSourceConstructorArgs args(
aClientInfo.Id(), aClientInfo.AgentClusterId(), aClientInfo.Type(),
aClientInfo.PrincipalInfo(), aClientInfo.CreationTime(),
aClientInfo.URL(), aClientInfo.FrameType());
UniquePtr<ClientSource> source(new ClientSource(this, aEventTarget, args));
if (IsShutdown()) {
source->Shutdown();
return source;
}
source->Activate(GetActor());
return source;
}
already_AddRefed<ClientHandle> ClientManager::CreateHandleInternal(
const ClientInfo& aClientInfo, nsISerialEventTarget* aSerialEventTarget) {
NS_ASSERT_OWNINGTHREAD(ClientManager);
MOZ_DIAGNOSTIC_ASSERT(aSerialEventTarget);
RefPtr<ClientHandle> handle =
new ClientHandle(this, aSerialEventTarget, aClientInfo);
if (IsShutdown()) {
handle->Shutdown();
return handle.forget();
}
handle->Activate(GetActor());
return handle.forget();
}
RefPtr<ClientOpPromise> ClientManager::StartOp(
const ClientOpConstructorArgs& aArgs,
nsISerialEventTarget* aSerialEventTarget) {
RefPtr<ClientOpPromise::Private> promise =
new ClientOpPromise::Private(__func__);
// Hold a ref to the client until the remote operation completes. Otherwise
// the ClientHandle might get de-refed and teardown the actor before we
// get an answer.
RefPtr<ClientManager> kungFuGrip = this;
MaybeExecute(
[&aArgs, promise, kungFuGrip](ClientManagerChild* aActor) {
ClientManagerOpChild* actor =
new ClientManagerOpChild(kungFuGrip, aArgs, promise);
if (!aActor->SendPClientManagerOpConstructor(actor, aArgs)) {
// Constructor failure will reject promise via ActorDestroy()
return;
}
},
[promise] {
CopyableErrorResult rv;
rv.ThrowInvalidStateError("Client has been destroyed");
promise->Reject(rv, __func__);
});
return promise;
}
// static
already_AddRefed<ClientManager> ClientManager::GetOrCreateForCurrentThread() {
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic1 == kThreadLocalMagic1);
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic2 == kThreadLocalMagic2);
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex != kBadThreadLocalIndex);
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex ==
sClientManagerThreadLocalIndexDuplicate);
RefPtr<ClientManager> cm = static_cast<ClientManager*>(
PR_GetThreadPrivate(sClientManagerThreadLocalIndex));
if (!cm) {
cm = new ClientManager();
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
PRStatus status =
#endif
PR_SetThreadPrivate(sClientManagerThreadLocalIndex, cm.get());
MOZ_DIAGNOSTIC_ASSERT(status == PR_SUCCESS);
}
MOZ_DIAGNOSTIC_ASSERT(cm);
if (StaticPrefs::dom_workers_testing_enabled()) {
// Check that the ClientManager instance associated to the current thread
// has not been kept alive when it was expected to have been already
// deallocated (e.g. due to a leak ClientManager's mShutdown can have ben
// set to true from its RevokeActor method but never fully deallocated and
// unset from the thread locals).
MOZ_DIAGNOSTIC_ASSERT(!cm->IsShutdown());
}
return cm.forget();
}
WorkerPrivate* ClientManager::GetWorkerPrivate() const {
NS_ASSERT_OWNINGTHREAD(ClientManager);
MOZ_DIAGNOSTIC_ASSERT(GetActor());
return GetActor()->GetWorkerPrivate();
}
// Used to share logic between ExpectFutureSource and ForgetFutureSource.
/* static */ bool ClientManager::ExpectOrForgetFutureSource(
const ClientInfo& aClientInfo,
bool (PClientManagerChild::*aMethod)(const IPCClientInfo&)) {
// Return earlier if called late in the XPCOM shutdown path,
// ClientManager would be already shutdown at the point.
if (NS_WARN_IF(PastShutdownPhase(ShutdownPhase::XPCOMShutdown))) {
return false;
}
bool rv = true;
RefPtr<ClientManager> mgr = ClientManager::GetOrCreateForCurrentThread();
mgr->MaybeExecute(
[&](ClientManagerChild* aActor) {
if (!(aActor->*aMethod)(aClientInfo.ToIPC())) {
rv = false;
}
},
[&] { rv = false; });
return rv;
}
/* static */ bool ClientManager::ExpectFutureSource(
const ClientInfo& aClientInfo) {
return ExpectOrForgetFutureSource(
aClientInfo, &PClientManagerChild::SendExpectFutureClientSource);
}
/* static */ bool ClientManager::ForgetFutureSource(
const ClientInfo& aClientInfo) {
return ExpectOrForgetFutureSource(
aClientInfo, &PClientManagerChild::SendForgetFutureClientSource);
}
// static
void ClientManager::Startup() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic1 == kThreadLocalMagic1);
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalMagic2 == kThreadLocalMagic2);
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex == kBadThreadLocalIndex);
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex ==
sClientManagerThreadLocalIndexDuplicate);
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
PRStatus status =
#endif
PR_NewThreadPrivateIndex(&sClientManagerThreadLocalIndex, nullptr);
MOZ_DIAGNOSTIC_ASSERT(status == PR_SUCCESS);
MOZ_DIAGNOSTIC_ASSERT(sClientManagerThreadLocalIndex != kBadThreadLocalIndex);
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
sClientManagerThreadLocalIndexDuplicate = sClientManagerThreadLocalIndex;
#endif
}
// static
UniquePtr<ClientSource> ClientManager::CreateSource(
ClientType aType, nsISerialEventTarget* aEventTarget,
nsIPrincipal* aPrincipal) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPrincipal);
PrincipalInfo principalInfo;
nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_CRASH("ClientManager::CreateSource() cannot serialize bad principal");
}
RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
return mgr->CreateSourceInternal(aType, aEventTarget, principalInfo);
}
// static
UniquePtr<ClientSource> ClientManager::CreateSource(
ClientType aType, nsISerialEventTarget* aEventTarget,
const PrincipalInfo& aPrincipal) {
RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
return mgr->CreateSourceInternal(aType, aEventTarget, aPrincipal);
}
// static
UniquePtr<ClientSource> ClientManager::CreateSourceFromInfo(
const ClientInfo& aClientInfo, nsISerialEventTarget* aEventTarget) {
RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
return mgr->CreateSourceInternal(aClientInfo, aEventTarget);
}
Maybe<ClientInfo> ClientManager::CreateInfo(ClientType aType,
nsIPrincipal* aPrincipal) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPrincipal);
PrincipalInfo principalInfo;
nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_CRASH("ClientManager::CreateSource() cannot serialize bad principal");
}
nsID id;
rv = nsID::GenerateUUIDInPlace(id);
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
if (NS_WARN_IF(NS_FAILED(rv))) {
return Nothing();
}
return Some(ClientInfo(id, Nothing(), aType, principalInfo, TimeStamp::Now(),
""_ns, FrameType::None));
}
// static
already_AddRefed<ClientHandle> ClientManager::CreateHandle(
const ClientInfo& aClientInfo, nsISerialEventTarget* aSerialEventTarget) {
RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
return mgr->CreateHandleInternal(aClientInfo, aSerialEventTarget);
}
// static
RefPtr<ClientOpPromise> ClientManager::MatchAll(
const ClientMatchAllArgs& aArgs, nsISerialEventTarget* aSerialEventTarget) {
RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
return mgr->StartOp(aArgs, aSerialEventTarget);
}
// static
RefPtr<ClientOpPromise> ClientManager::Claim(
const ClientClaimArgs& aArgs, nsISerialEventTarget* aSerialEventTarget) {
RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
return mgr->StartOp(aArgs, aSerialEventTarget);
}
// static
RefPtr<ClientOpPromise> ClientManager::GetInfoAndState(
const ClientGetInfoAndStateArgs& aArgs,
nsISerialEventTarget* aSerialEventTarget) {
RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
return mgr->StartOp(aArgs, aSerialEventTarget);
}
// static
RefPtr<ClientOpPromise> ClientManager::Navigate(
const ClientNavigateArgs& aArgs, nsISerialEventTarget* aSerialEventTarget) {
RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
return mgr->StartOp(aArgs, aSerialEventTarget);
}
// static
RefPtr<ClientOpPromise> ClientManager::OpenWindow(
const ClientOpenWindowArgs& aArgs,
nsISerialEventTarget* aSerialEventTarget) {
RefPtr<ClientManager> mgr = GetOrCreateForCurrentThread();
return mgr->StartOp(aArgs, aSerialEventTarget);
}
} // namespace mozilla::dom