diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /ipc/glue | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/glue')
203 files changed, 35613 insertions, 0 deletions
diff --git a/ipc/glue/AsyncBlockers.h b/ipc/glue/AsyncBlockers.h new file mode 100644 index 0000000000..16e7aa7528 --- /dev/null +++ b/ipc/glue/AsyncBlockers.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* 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_ipc_AsyncBlockers_h +#define mozilla_ipc_AsyncBlockers_h + +#include "mozilla/MozPromise.h" +#include "mozilla/ThreadSafety.h" +#include "nsTArray.h" + +// FIXME: when bug 1760855 is fixed, it should not be required anymore + +namespace mozilla::ipc { + +/** + * AsyncBlockers provide a simple registration service that allows to suspend + * completion of a particular task until all registered entries have been + * cleared. This can be used to implement a similar service to + * nsAsyncShutdownService in processes where it wouldn't normally be available. + * This class is thread-safe. + */ +class AsyncBlockers { + public: + AsyncBlockers() + : mLock("AsyncRegistrar"), + mPromise(new GenericPromise::Private(__func__)) {} + void Register(void* aBlocker) { + MutexAutoLock lock(mLock); + mBlockers.InsertElementSorted(aBlocker); + } + void Deregister(void* aBlocker) { + MutexAutoLock lock(mLock); + MOZ_ASSERT(mBlockers.ContainsSorted(aBlocker)); + MOZ_ALWAYS_TRUE(mBlockers.RemoveElementSorted(aBlocker)); + MaybeResolve(); + } + RefPtr<GenericPromise> WaitUntilClear(uint32_t aTimeOutInMs = 0) { + { + MutexAutoLock lock(mLock); + MaybeResolve(); + } + + if (aTimeOutInMs > 0) { + GetCurrentSerialEventTarget()->DelayedDispatch( + NS_NewRunnableFunction("AsyncBlockers::WaitUntilClear", + [promise = mPromise]() { + // The AsyncBlockers object may have been + // deleted by now and the object isn't + // refcounted (nor do we want it to be). We + // can unconditionally resolve the promise + // even it has already been resolved as + // MozPromise are thread-safe and will just + // ignore the action if already resolved. + promise->Resolve(true, __func__); + }), + aTimeOutInMs); + } + + return mPromise; + } + + virtual ~AsyncBlockers() { mPromise->Resolve(true, __func__); } + + private: + void MaybeResolve() MOZ_REQUIRES(mLock) { + mLock.AssertCurrentThreadOwns(); + if (!mBlockers.IsEmpty()) { + return; + } + mPromise->Resolve(true, __func__); + } + Mutex mLock; + nsTArray<void*> mBlockers MOZ_GUARDED_BY(mLock); + const RefPtr<GenericPromise::Private> mPromise; +}; + +} // namespace mozilla::ipc + +#endif // mozilla_ipc_AsyncBlockers_h diff --git a/ipc/glue/BackgroundChild.h b/ipc/glue/BackgroundChild.h new file mode 100644 index 0000000000..89de00795f --- /dev/null +++ b/ipc/glue/BackgroundChild.h @@ -0,0 +1,76 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_backgroundchild_h__ +#define mozilla_ipc_backgroundchild_h__ + +#include "mozilla/Attributes.h" + +class nsIEventTarget; + +namespace mozilla { +namespace dom { + +class BlobImpl; +class ContentChild; +class ContentParent; +class ContentProcess; + +} // namespace dom + +namespace ipc { + +class PBackgroundChild; +class PBackgroundStarterChild; + +// This class allows access to the PBackground protocol. PBackground allows +// communication between any thread (in the parent or a child process) and a +// single background thread in the parent process. Each PBackgroundChild +// instance is tied to the thread on which it is created and must not be shared +// across threads. Each PBackgroundChild is unique and valid as long as its +// designated thread lives. +// +// Creation of PBackground is synchronous. GetOrCreateForCurrentThread will +// create the actor if it doesn't exist yet. Thereafter (assuming success) +// GetForCurrentThread() will return the same actor every time. +// +// CloseForCurrentThread() will close the current PBackground actor. Subsequent +// calls to GetForCurrentThread will return null. CloseForCurrentThread() may +// only be called exactly once for each thread-specific actor. Currently it is +// illegal to call this before the PBackground actor has been created. +// +// The PBackgroundChild actor and all its sub-protocol actors will be +// automatically destroyed when its designated thread completes. +// +// InitContentStarter must be called on the main thread +// with an actor bridging to the relevant target process type before these +// methods can be used. +class BackgroundChild final { + friend class mozilla::dom::ContentParent; + friend class mozilla::dom::ContentProcess; + + public: + // See above. + static PBackgroundChild* GetForCurrentThread(); + + // See above. + static PBackgroundChild* GetOrCreateForCurrentThread(); + + // See above. + static void CloseForCurrentThread(); + + // See above. + static void InitContentStarter(mozilla::dom::ContentChild* aContent); + + private: + // Only called by this class's friends. + static void Startup(); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_backgroundchild_h__ diff --git a/ipc/glue/BackgroundChildImpl.cpp b/ipc/glue/BackgroundChildImpl.cpp new file mode 100644 index 0000000000..5bcfbd2838 --- /dev/null +++ b/ipc/glue/BackgroundChildImpl.cpp @@ -0,0 +1,459 @@ +/* -*- 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 "BackgroundChildImpl.h" + +#include "BroadcastChannelChild.h" +#ifdef MOZ_WEBRTC +# include "CamerasChild.h" +#endif +#include "mozilla/Assertions.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/dom/ClientManagerActors.h" +#include "mozilla/dom/FileCreatorChild.h" +#include "mozilla/dom/PBackgroundLSDatabaseChild.h" +#include "mozilla/dom/PBackgroundLSObserverChild.h" +#include "mozilla/dom/PBackgroundLSRequestChild.h" +#include "mozilla/dom/PBackgroundLSSimpleRequestChild.h" +#include "mozilla/dom/PBackgroundSDBConnectionChild.h" +#include "mozilla/dom/PFileSystemRequestChild.h" +#include "mozilla/dom/EndpointForReportChild.h" +#include "mozilla/dom/PVsync.h" +#include "mozilla/dom/TemporaryIPCBlobChild.h" +#include "mozilla/dom/cache/ActorUtils.h" +#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsChild.h" +#include "mozilla/dom/indexedDB/ThreadLocal.h" +#include "mozilla/dom/quota/PQuotaChild.h" +#include "mozilla/dom/RemoteWorkerChild.h" +#include "mozilla/dom/RemoteWorkerControllerChild.h" +#include "mozilla/dom/RemoteWorkerServiceChild.h" +#include "mozilla/dom/ServiceWorkerChild.h" +#include "mozilla/dom/SharedWorkerChild.h" +#include "mozilla/dom/StorageIPC.h" +#include "mozilla/dom/MessagePortChild.h" +#include "mozilla/dom/ServiceWorkerContainerChild.h" +#include "mozilla/dom/ServiceWorkerManagerChild.h" +#include "mozilla/ipc/PBackgroundTestChild.h" +#include "mozilla/net/PUDPSocketChild.h" +#include "mozilla/dom/network/UDPSocketChild.h" +#include "mozilla/dom/WebAuthnTransactionChild.h" +#include "mozilla/dom/MIDIPortChild.h" +#include "mozilla/dom/MIDIManagerChild.h" +#include "nsID.h" + +namespace { + +class TestChild final : public mozilla::ipc::PBackgroundTestChild { + friend class mozilla::ipc::BackgroundChildImpl; + + nsCString mTestArg; + + explicit TestChild(const nsACString& aTestArg) : mTestArg(aTestArg) { + MOZ_COUNT_CTOR(TestChild); + } + + protected: + ~TestChild() override { MOZ_COUNT_DTOR(TestChild); } + + public: + mozilla::ipc::IPCResult Recv__delete__(const nsACString& aTestArg) override; +}; + +} // namespace + +namespace mozilla::ipc { + +using mozilla::dom::UDPSocketChild; +using mozilla::net::PUDPSocketChild; + +using mozilla::dom::PRemoteWorkerChild; +using mozilla::dom::PServiceWorkerChild; +using mozilla::dom::PServiceWorkerContainerChild; +using mozilla::dom::PServiceWorkerRegistrationChild; +using mozilla::dom::RemoteWorkerChild; +using mozilla::dom::StorageDBChild; +using mozilla::dom::cache::PCacheChild; +using mozilla::dom::cache::PCacheStreamControlChild; + +using mozilla::dom::WebAuthnTransactionChild; + +using mozilla::dom::PMIDIManagerChild; +using mozilla::dom::PMIDIPortChild; + +// ----------------------------------------------------------------------------- +// BackgroundChildImpl::ThreadLocal +// ----------------------------------------------------------------------------- + +BackgroundChildImpl::ThreadLocal::ThreadLocal() : mCurrentFileHandle(nullptr) { + // May happen on any thread! + MOZ_COUNT_CTOR(mozilla::ipc::BackgroundChildImpl::ThreadLocal); +} + +BackgroundChildImpl::ThreadLocal::~ThreadLocal() { + // May happen on any thread! + MOZ_COUNT_DTOR(mozilla::ipc::BackgroundChildImpl::ThreadLocal); +} + +// ----------------------------------------------------------------------------- +// BackgroundChildImpl +// ----------------------------------------------------------------------------- + +BackgroundChildImpl::BackgroundChildImpl() { + // May happen on any thread! + MOZ_COUNT_CTOR(mozilla::ipc::BackgroundChildImpl); +} + +BackgroundChildImpl::~BackgroundChildImpl() { + // May happen on any thread! + MOZ_COUNT_DTOR(mozilla::ipc::BackgroundChildImpl); +} + +void BackgroundChildImpl::ProcessingError(Result aCode, const char* aReason) { + // May happen on any thread! + + nsAutoCString abortMessage; + + switch (aCode) { + case MsgDropped: + return; + +#define HANDLE_CASE(_result) \ + case _result: \ + abortMessage.AssignLiteral(#_result); \ + break + + HANDLE_CASE(MsgNotKnown); + HANDLE_CASE(MsgNotAllowed); + HANDLE_CASE(MsgPayloadError); + HANDLE_CASE(MsgProcessingError); + HANDLE_CASE(MsgRouteError); + HANDLE_CASE(MsgValueError); + +#undef HANDLE_CASE + + default: + MOZ_CRASH("Unknown error code!"); + } + + nsDependentCString reason(aReason); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ipc_channel_error, reason); + + MOZ_CRASH_UNSAFE_PRINTF("%s: %s", abortMessage.get(), aReason); +} + +void BackgroundChildImpl::ActorDestroy(ActorDestroyReason aWhy) { + // May happen on any thread! +} + +PBackgroundTestChild* BackgroundChildImpl::AllocPBackgroundTestChild( + const nsACString& aTestArg) { + return new TestChild(aTestArg); +} + +bool BackgroundChildImpl::DeallocPBackgroundTestChild( + PBackgroundTestChild* aActor) { + MOZ_ASSERT(aActor); + + delete static_cast<TestChild*>(aActor); + return true; +} + +BackgroundChildImpl::PBackgroundIndexedDBUtilsChild* +BackgroundChildImpl::AllocPBackgroundIndexedDBUtilsChild() { + MOZ_CRASH( + "PBackgroundIndexedDBUtilsChild actors should be manually " + "constructed!"); +} + +bool BackgroundChildImpl::DeallocPBackgroundIndexedDBUtilsChild( + PBackgroundIndexedDBUtilsChild* aActor) { + MOZ_ASSERT(aActor); + + delete aActor; + return true; +} + +BackgroundChildImpl::PBackgroundLSObserverChild* +BackgroundChildImpl::AllocPBackgroundLSObserverChild( + const uint64_t& aObserverId) { + MOZ_CRASH("PBackgroundLSObserverChild actor should be manually constructed!"); +} + +bool BackgroundChildImpl::DeallocPBackgroundLSObserverChild( + PBackgroundLSObserverChild* aActor) { + MOZ_ASSERT(aActor); + + delete aActor; + return true; +} + +BackgroundChildImpl::PBackgroundLSRequestChild* +BackgroundChildImpl::AllocPBackgroundLSRequestChild( + const LSRequestParams& aParams) { + MOZ_CRASH("PBackgroundLSRequestChild actor should be manually constructed!"); +} + +bool BackgroundChildImpl::DeallocPBackgroundLSRequestChild( + PBackgroundLSRequestChild* aActor) { + MOZ_ASSERT(aActor); + + delete aActor; + return true; +} + +BackgroundChildImpl::PBackgroundLocalStorageCacheChild* +BackgroundChildImpl::AllocPBackgroundLocalStorageCacheChild( + const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey, + const uint32_t& aPrivateBrowsingId) { + MOZ_CRASH( + "PBackgroundLocalStorageChild actors should be manually " + "constructed!"); +} + +bool BackgroundChildImpl::DeallocPBackgroundLocalStorageCacheChild( + PBackgroundLocalStorageCacheChild* aActor) { + MOZ_ASSERT(aActor); + + delete aActor; + return true; +} + +BackgroundChildImpl::PBackgroundLSSimpleRequestChild* +BackgroundChildImpl::AllocPBackgroundLSSimpleRequestChild( + const LSSimpleRequestParams& aParams) { + MOZ_CRASH( + "PBackgroundLSSimpleRequestChild actor should be manually " + "constructed!"); +} + +bool BackgroundChildImpl::DeallocPBackgroundLSSimpleRequestChild( + PBackgroundLSSimpleRequestChild* aActor) { + MOZ_ASSERT(aActor); + + delete aActor; + return true; +} + +BackgroundChildImpl::PBackgroundStorageChild* +BackgroundChildImpl::AllocPBackgroundStorageChild( + const nsAString& aProfilePath, const uint32_t& aPrivateBrowsingId) { + MOZ_CRASH("PBackgroundStorageChild actors should be manually constructed!"); +} + +bool BackgroundChildImpl::DeallocPBackgroundStorageChild( + PBackgroundStorageChild* aActor) { + MOZ_ASSERT(aActor); + + StorageDBChild* child = static_cast<StorageDBChild*>(aActor); + child->ReleaseIPDLReference(); + return true; +} + +already_AddRefed<PRemoteWorkerChild> +BackgroundChildImpl::AllocPRemoteWorkerChild(const RemoteWorkerData& aData) { + return MakeAndAddRef<RemoteWorkerChild>(aData); +} + +IPCResult BackgroundChildImpl::RecvPRemoteWorkerConstructor( + PRemoteWorkerChild* aActor, const RemoteWorkerData& aData) { + dom::RemoteWorkerChild* actor = static_cast<dom::RemoteWorkerChild*>(aActor); + actor->ExecWorker(aData); + return IPC_OK(); +} + +dom::PSharedWorkerChild* BackgroundChildImpl::AllocPSharedWorkerChild( + const dom::RemoteWorkerData& aData, const uint64_t& aWindowID, + const dom::MessagePortIdentifier& aPortIdentifier) { + RefPtr<dom::SharedWorkerChild> agent = new dom::SharedWorkerChild(); + return agent.forget().take(); +} + +bool BackgroundChildImpl::DeallocPSharedWorkerChild( + dom::PSharedWorkerChild* aActor) { + RefPtr<dom::SharedWorkerChild> actor = + dont_AddRef(static_cast<dom::SharedWorkerChild*>(aActor)); + return true; +} + +dom::PTemporaryIPCBlobChild* +BackgroundChildImpl::AllocPTemporaryIPCBlobChild() { + MOZ_CRASH("This is not supposed to be called."); + return nullptr; +} + +bool BackgroundChildImpl::DeallocPTemporaryIPCBlobChild( + dom::PTemporaryIPCBlobChild* aActor) { + RefPtr<dom::TemporaryIPCBlobChild> actor = + dont_AddRef(static_cast<dom::TemporaryIPCBlobChild*>(aActor)); + return true; +} + +dom::PFileCreatorChild* BackgroundChildImpl::AllocPFileCreatorChild( + const nsAString& aFullPath, const nsAString& aType, const nsAString& aName, + const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck, + const bool& aIsFromNsIFile) { + return new dom::FileCreatorChild(); +} + +bool BackgroundChildImpl::DeallocPFileCreatorChild(PFileCreatorChild* aActor) { + delete static_cast<dom::FileCreatorChild*>(aActor); + return true; +} + +PUDPSocketChild* BackgroundChildImpl::AllocPUDPSocketChild( + const Maybe<PrincipalInfo>& aPrincipalInfo, const nsACString& aFilter) { + MOZ_CRASH("AllocPUDPSocket should not be called"); + return nullptr; +} + +bool BackgroundChildImpl::DeallocPUDPSocketChild(PUDPSocketChild* child) { + UDPSocketChild* p = static_cast<UDPSocketChild*>(child); + p->ReleaseIPDLReference(); + return true; +} + +// ----------------------------------------------------------------------------- +// BroadcastChannel API +// ----------------------------------------------------------------------------- + +dom::PBroadcastChannelChild* BackgroundChildImpl::AllocPBroadcastChannelChild( + const PrincipalInfo& aPrincipalInfo, const nsACString& aOrigin, + const nsAString& aChannel) { + RefPtr<dom::BroadcastChannelChild> agent = new dom::BroadcastChannelChild(); + return agent.forget().take(); +} + +bool BackgroundChildImpl::DeallocPBroadcastChannelChild( + PBroadcastChannelChild* aActor) { + RefPtr<dom::BroadcastChannelChild> child = + dont_AddRef(static_cast<dom::BroadcastChannelChild*>(aActor)); + MOZ_ASSERT(child); + return true; +} + +camera::PCamerasChild* BackgroundChildImpl::AllocPCamerasChild() { +#ifdef MOZ_WEBRTC + RefPtr<camera::CamerasChild> agent = new camera::CamerasChild(); + return agent.forget().take(); +#else + return nullptr; +#endif +} + +bool BackgroundChildImpl::DeallocPCamerasChild(camera::PCamerasChild* aActor) { +#ifdef MOZ_WEBRTC + RefPtr<camera::CamerasChild> child = + dont_AddRef(static_cast<camera::CamerasChild*>(aActor)); + MOZ_ASSERT(aActor); + camera::Shutdown(); +#endif + return true; +} + +// ----------------------------------------------------------------------------- +// ServiceWorkerManager +// ----------------------------------------------------------------------------- + +dom::PServiceWorkerManagerChild* +BackgroundChildImpl::AllocPServiceWorkerManagerChild() { + RefPtr<dom::ServiceWorkerManagerChild> agent = + new dom::ServiceWorkerManagerChild(); + return agent.forget().take(); +} + +bool BackgroundChildImpl::DeallocPServiceWorkerManagerChild( + PServiceWorkerManagerChild* aActor) { + RefPtr<dom::ServiceWorkerManagerChild> child = + dont_AddRef(static_cast<dom::ServiceWorkerManagerChild*>(aActor)); + MOZ_ASSERT(child); + return true; +} + +// ----------------------------------------------------------------------------- +// Cache API +// ----------------------------------------------------------------------------- + +already_AddRefed<PCacheChild> BackgroundChildImpl::AllocPCacheChild() { + return dom::cache::AllocPCacheChild(); +} + +already_AddRefed<PCacheStreamControlChild> +BackgroundChildImpl::AllocPCacheStreamControlChild() { + return dom::cache::AllocPCacheStreamControlChild(); +} + +// ----------------------------------------------------------------------------- +// MessageChannel/MessagePort API +// ----------------------------------------------------------------------------- + +dom::PMessagePortChild* BackgroundChildImpl::AllocPMessagePortChild( + const nsID& aUUID, const nsID& aDestinationUUID, + const uint32_t& aSequenceID) { + RefPtr<dom::MessagePortChild> agent = new dom::MessagePortChild(); + return agent.forget().take(); +} + +bool BackgroundChildImpl::DeallocPMessagePortChild(PMessagePortChild* aActor) { + RefPtr<dom::MessagePortChild> child = + dont_AddRef(static_cast<dom::MessagePortChild*>(aActor)); + MOZ_ASSERT(child); + return true; +} + +dom::PWebAuthnTransactionChild* +BackgroundChildImpl::AllocPWebAuthnTransactionChild() { + MOZ_CRASH("PWebAuthnTransaction actor should be manually constructed!"); + return nullptr; +} + +bool BackgroundChildImpl::DeallocPWebAuthnTransactionChild( + PWebAuthnTransactionChild* aActor) { + MOZ_ASSERT(aActor); + RefPtr<dom::WebAuthnTransactionChild> child = + dont_AddRef(static_cast<dom::WebAuthnTransactionChild*>(aActor)); + return true; +} + +already_AddRefed<PServiceWorkerChild> +BackgroundChildImpl::AllocPServiceWorkerChild( + const IPCServiceWorkerDescriptor&) { + MOZ_CRASH("Shouldn't be called."); + return {}; +} + +already_AddRefed<PServiceWorkerContainerChild> +BackgroundChildImpl::AllocPServiceWorkerContainerChild() { + return mozilla::dom::ServiceWorkerContainerChild::Create(); +} + +already_AddRefed<PServiceWorkerRegistrationChild> +BackgroundChildImpl::AllocPServiceWorkerRegistrationChild( + const IPCServiceWorkerRegistrationDescriptor&) { + MOZ_CRASH("Shouldn't be called."); + return {}; +} + +dom::PEndpointForReportChild* BackgroundChildImpl::AllocPEndpointForReportChild( + const nsAString& aGroupName, const PrincipalInfo& aPrincipalInfo) { + return new dom::EndpointForReportChild(); +} + +bool BackgroundChildImpl::DeallocPEndpointForReportChild( + PEndpointForReportChild* aActor) { + MOZ_ASSERT(aActor); + delete static_cast<dom::EndpointForReportChild*>(aActor); + return true; +} + +} // namespace mozilla::ipc + +mozilla::ipc::IPCResult TestChild::Recv__delete__(const nsACString& aTestArg) { + MOZ_RELEASE_ASSERT(aTestArg == mTestArg, + "BackgroundTest message was corrupted!"); + + return IPC_OK(); +} diff --git a/ipc/glue/BackgroundChildImpl.h b/ipc/glue/BackgroundChildImpl.h new file mode 100644 index 0000000000..bedae64c2c --- /dev/null +++ b/ipc/glue/BackgroundChildImpl.h @@ -0,0 +1,193 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_backgroundchildimpl_h__ +#define mozilla_ipc_backgroundchildimpl_h__ + +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace dom { + +class IDBFileHandle; + +namespace indexedDB { + +class ThreadLocal; + +} // namespace indexedDB +} // namespace dom + +namespace ipc { + +// Instances of this class should never be created directly. This class is meant +// to be inherited in BackgroundImpl. +class BackgroundChildImpl : public PBackgroundChild { + public: + class ThreadLocal; + + // Get the ThreadLocal for the current thread if + // BackgroundChild::GetOrCreateForCurrentThread() has been called and true was + // returned (e.g. a valid PBackgroundChild actor has been created or is in the + // process of being created). Otherwise this function returns null. + // This functions is implemented in BackgroundImpl.cpp. + static ThreadLocal* GetThreadLocalForCurrentThread(); + + protected: + BackgroundChildImpl(); + virtual ~BackgroundChildImpl(); + + virtual void ProcessingError(Result aCode, const char* aReason) override; + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual PBackgroundTestChild* AllocPBackgroundTestChild( + const nsACString& aTestArg) override; + + virtual bool DeallocPBackgroundTestChild( + PBackgroundTestChild* aActor) override; + + virtual PBackgroundIndexedDBUtilsChild* AllocPBackgroundIndexedDBUtilsChild() + override; + + virtual bool DeallocPBackgroundIndexedDBUtilsChild( + PBackgroundIndexedDBUtilsChild* aActor) override; + + virtual PBackgroundLSObserverChild* AllocPBackgroundLSObserverChild( + const uint64_t& aObserverId) override; + + virtual bool DeallocPBackgroundLSObserverChild( + PBackgroundLSObserverChild* aActor) override; + + virtual PBackgroundLSRequestChild* AllocPBackgroundLSRequestChild( + const LSRequestParams& aParams) override; + + virtual bool DeallocPBackgroundLSRequestChild( + PBackgroundLSRequestChild* aActor) override; + + virtual PBackgroundLSSimpleRequestChild* AllocPBackgroundLSSimpleRequestChild( + const LSSimpleRequestParams& aParams) override; + + virtual bool DeallocPBackgroundLSSimpleRequestChild( + PBackgroundLSSimpleRequestChild* aActor) override; + + virtual PBackgroundLocalStorageCacheChild* + AllocPBackgroundLocalStorageCacheChild( + const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey, + const uint32_t& aPrivateBrowsingId) override; + + virtual bool DeallocPBackgroundLocalStorageCacheChild( + PBackgroundLocalStorageCacheChild* aActor) override; + + virtual PBackgroundStorageChild* AllocPBackgroundStorageChild( + const nsAString& aProfilePath, + const uint32_t& aPrivateBrowsingId) override; + + virtual bool DeallocPBackgroundStorageChild( + PBackgroundStorageChild* aActor) override; + + virtual PTemporaryIPCBlobChild* AllocPTemporaryIPCBlobChild() override; + + virtual bool DeallocPTemporaryIPCBlobChild( + PTemporaryIPCBlobChild* aActor) override; + + virtual PFileCreatorChild* AllocPFileCreatorChild( + const nsAString& aFullPath, const nsAString& aType, + const nsAString& aName, const Maybe<int64_t>& aLastModified, + const bool& aExistenceCheck, const bool& aIsFromNsIFile) override; + + virtual bool DeallocPFileCreatorChild(PFileCreatorChild* aActor) override; + + already_AddRefed<mozilla::dom::PRemoteWorkerChild> AllocPRemoteWorkerChild( + const RemoteWorkerData& aData) override; + + virtual mozilla::ipc::IPCResult RecvPRemoteWorkerConstructor( + PRemoteWorkerChild* aActor, const RemoteWorkerData& aData) override; + + virtual mozilla::dom::PSharedWorkerChild* AllocPSharedWorkerChild( + const mozilla::dom::RemoteWorkerData& aData, const uint64_t& aWindowID, + const mozilla::dom::MessagePortIdentifier& aPortIdentifier) override; + + virtual bool DeallocPSharedWorkerChild( + mozilla::dom::PSharedWorkerChild* aActor) override; + + virtual PCamerasChild* AllocPCamerasChild() override; + + virtual bool DeallocPCamerasChild(PCamerasChild* aActor) override; + + virtual PUDPSocketChild* AllocPUDPSocketChild( + const Maybe<PrincipalInfo>& aPrincipalInfo, + const nsACString& aFilter) override; + virtual bool DeallocPUDPSocketChild(PUDPSocketChild* aActor) override; + + virtual PBroadcastChannelChild* AllocPBroadcastChannelChild( + const PrincipalInfo& aPrincipalInfo, const nsACString& aOrigin, + const nsAString& aChannel) override; + + virtual bool DeallocPBroadcastChannelChild( + PBroadcastChannelChild* aActor) override; + + virtual PServiceWorkerManagerChild* AllocPServiceWorkerManagerChild() + override; + + virtual bool DeallocPServiceWorkerManagerChild( + PServiceWorkerManagerChild* aActor) override; + + virtual already_AddRefed<dom::cache::PCacheChild> AllocPCacheChild() override; + + virtual already_AddRefed<dom::cache::PCacheStreamControlChild> + AllocPCacheStreamControlChild() override; + + virtual PMessagePortChild* AllocPMessagePortChild( + const nsID& aUUID, const nsID& aDestinationUUID, + const uint32_t& aSequenceID) override; + + virtual bool DeallocPMessagePortChild(PMessagePortChild* aActor) override; + + virtual PWebAuthnTransactionChild* AllocPWebAuthnTransactionChild() override; + + virtual bool DeallocPWebAuthnTransactionChild( + PWebAuthnTransactionChild* aActor) override; + + already_AddRefed<PServiceWorkerChild> AllocPServiceWorkerChild( + const IPCServiceWorkerDescriptor&); + + already_AddRefed<PServiceWorkerContainerChild> + AllocPServiceWorkerContainerChild(); + + already_AddRefed<PServiceWorkerRegistrationChild> + AllocPServiceWorkerRegistrationChild( + const IPCServiceWorkerRegistrationDescriptor&); + + virtual PEndpointForReportChild* AllocPEndpointForReportChild( + const nsAString& aGroupName, + const PrincipalInfo& aPrincipalInfo) override; + + virtual bool DeallocPEndpointForReportChild( + PEndpointForReportChild* aActor) override; +}; + +class BackgroundChildImpl::ThreadLocal final { + friend class mozilla::DefaultDelete<ThreadLocal>; + + public: + mozilla::UniquePtr<mozilla::dom::indexedDB::ThreadLocal> + mIndexedDBThreadLocal; + mozilla::dom::IDBFileHandle* mCurrentFileHandle; + + public: + ThreadLocal(); + + private: + // Only destroyed by UniquePtr<ThreadLocal>. + ~ThreadLocal(); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_backgroundchildimpl_h__ diff --git a/ipc/glue/BackgroundImpl.cpp b/ipc/glue/BackgroundImpl.cpp new file mode 100644 index 0000000000..bba09c261a --- /dev/null +++ b/ipc/glue/BackgroundImpl.cpp @@ -0,0 +1,1268 @@ +/* -*- 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 "BackgroundChild.h" +#include "BackgroundParent.h" + +#include "BackgroundChildImpl.h" +#include "BackgroundParentImpl.h" +#include "MainThreadUtils.h" +#include "base/process_util.h" +#include "base/task.h" +#include "FileDescriptor.h" +#include "GeckoProfiler.h" +#include "InputStreamUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/MozPromise.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/ipc/BackgroundStarterChild.h" +#include "mozilla/ipc/BackgroundStarterParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/PBackgroundStarter.h" +#include "mozilla/ipc/ProtocolTypes.h" +#include "nsCOMPtr.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIRunnable.h" +#include "nsISupportsImpl.h" +#include "nsIThread.h" +#include "nsITimer.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsTraceRefcnt.h" +#include "nsXULAppAPI.h" +#include "nsXPCOMPrivate.h" +#include "prthread.h" + +#include <functional> + +#ifdef RELEASE_OR_BETA +# define THREADSAFETY_ASSERT MOZ_ASSERT +#else +# define THREADSAFETY_ASSERT MOZ_RELEASE_ASSERT +#endif + +#define CRASH_IN_CHILD_PROCESS(_msg) \ + do { \ + if (XRE_IsParentProcess()) { \ + MOZ_ASSERT(false, _msg); \ + } else { \ + MOZ_CRASH(_msg); \ + } \ + } while (0) + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::ipc; +using namespace mozilla::net; + +namespace { + +class ChildImpl; + +// ----------------------------------------------------------------------------- +// Utility Functions +// ----------------------------------------------------------------------------- + +void AssertIsOnMainThread() { THREADSAFETY_ASSERT(NS_IsMainThread()); } + +// ----------------------------------------------------------------------------- +// ParentImpl Declaration +// ----------------------------------------------------------------------------- + +class ParentImpl final : public BackgroundParentImpl { + friend class ChildImpl; + friend class mozilla::ipc::BackgroundParent; + friend class mozilla::ipc::BackgroundStarterParent; + + private: + class ShutdownObserver; + + struct MOZ_STACK_CLASS TimerCallbackClosure { + nsIThread* mThread; + nsTArray<IToplevelProtocol*>* mLiveActors; + + TimerCallbackClosure(nsIThread* aThread, + nsTArray<IToplevelProtocol*>* aLiveActors) + : mThread(aThread), mLiveActors(aLiveActors) { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(aThread); + MOZ_ASSERT(aLiveActors); + } + }; + + // The length of time we will wait at shutdown for all actors to clean + // themselves up before forcing them to be destroyed. + static const uint32_t kShutdownTimerDelayMS = 10000; + + // This is only modified on the main thread. It is null if the thread does not + // exist or is shutting down. + static StaticRefPtr<nsIThread> sBackgroundThread; + + // This is created and destroyed on the main thread but only modified on the + // background thread. It is specific to each instance of sBackgroundThread. + static nsTArray<IToplevelProtocol*>* sLiveActorsForBackgroundThread; + + // This is only modified on the main thread. + static StaticRefPtr<nsITimer> sShutdownTimer; + + // This exists so that that [Assert]IsOnBackgroundThread() can continue to + // work during shutdown. + static Atomic<PRThread*> sBackgroundPRThread; + + // Maintains a count of live actors so that the background thread can be shut + // down when it is no longer needed. + // May be incremented on either the background thread (by an existing actor) + // or on the main thread, but must be decremented on the main thread. + static Atomic<uint64_t> sLiveActorCount; + + // This is only modified on the main thread. It is true after the shutdown + // observer is registered and is never unset thereafter. + static bool sShutdownObserverRegistered; + + // This is only modified on the main thread. It prevents us from trying to + // create the background thread after application shutdown has started. + static bool sShutdownHasStarted; + + // null if this is a same-process actor. + const RefPtr<ThreadsafeContentParentHandle> mContent; + + // Set when the actor is opened successfully and used to handle shutdown + // hangs. Only touched on the background thread. + nsTArray<IToplevelProtocol*>* mLiveActorArray; + + // Set at construction to indicate whether this parent actor corresponds to a + // child actor in another process or to a child actor from a different thread + // in the same process. + const bool mIsOtherProcessActor; + + // Set after ActorDestroy has been called. Only touched on the background + // thread. + bool mActorDestroyed; + + public: + static bool IsOnBackgroundThread() { + return PR_GetCurrentThread() == sBackgroundPRThread; + } + + static void AssertIsOnBackgroundThread() { + THREADSAFETY_ASSERT(IsOnBackgroundThread()); + } + + // `ParentImpl` instances need to be deleted on the main thread, despite IPC + // controlling them on a background thread. Use `_WITH_DELETE_ON_MAIN_THREAD` + // to force destruction to occur on the desired thread. + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD(ParentImpl, + override) + + void Destroy(); + + private: + // Forwarded from BackgroundParent. + static bool IsOtherProcessActor(PBackgroundParent* aBackgroundActor); + + // Forwarded from BackgroundParent. + static ThreadsafeContentParentHandle* GetContentParentHandle( + PBackgroundParent* aBackgroundActor); + + // Forwarded from BackgroundParent. + static uint64_t GetChildID(PBackgroundParent* aBackgroundActor); + + // Forwarded from BackgroundParent. + static void KillHardAsync(PBackgroundParent* aBackgroundActor, + const nsACString& aReason); + + // Forwarded from BackgroundParent. + static bool AllocStarter(ContentParent* aContent, + Endpoint<PBackgroundStarterParent>&& aEndpoint, + bool aCrossProcess = true); + + static bool CreateBackgroundThread(); + + static void ShutdownBackgroundThread(); + + static void ShutdownTimerCallback(nsITimer* aTimer, void* aClosure); + + // NOTE: ParentImpl could be used in 2 cases below. + // 1. Within the parent process. + // 2. Between parent process and content process. + // |aContent| should be not null for case 2. For cases 1, it's null. + explicit ParentImpl(ThreadsafeContentParentHandle* aContent, + bool aIsOtherProcessActor) + : mContent(aContent), + mLiveActorArray(nullptr), + mIsOtherProcessActor(aIsOtherProcessActor), + mActorDestroyed(false) { + AssertIsInMainProcess(); + MOZ_ASSERT_IF(!aIsOtherProcessActor, XRE_IsParentProcess()); + } + + ~ParentImpl() { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + } + + void MainThreadActorDestroy(); + + void SetLiveActorArray(nsTArray<IToplevelProtocol*>* aLiveActorArray) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aLiveActorArray); + MOZ_ASSERT(!aLiveActorArray->Contains(this)); + MOZ_ASSERT(!mLiveActorArray); + MOZ_ASSERT(mIsOtherProcessActor); + + mLiveActorArray = aLiveActorArray; + mLiveActorArray->AppendElement(this); + } + + // These methods are only called by IPDL. + virtual void ActorDestroy(ActorDestroyReason aWhy) override; +}; + +// ----------------------------------------------------------------------------- +// ChildImpl Declaration +// ----------------------------------------------------------------------------- + +class ChildImpl final : public BackgroundChildImpl { + friend class mozilla::ipc::BackgroundChild; + friend class mozilla::ipc::BackgroundChildImpl; + friend class mozilla::ipc::BackgroundStarterChild; + + typedef base::ProcessId ProcessId; + + class ShutdownObserver; + + public: + struct ThreadLocalInfo { + ThreadLocalInfo() +#ifdef DEBUG + : mClosed(false) +#endif + { + } + + RefPtr<ChildImpl> mActor; + UniquePtr<BackgroundChildImpl::ThreadLocal> mConsumerThreadLocal; +#ifdef DEBUG + bool mClosed; +#endif + }; + + private: + // A thread-local index that is not valid. + static constexpr unsigned int kBadThreadLocalIndex = + static_cast<unsigned int>(-1); + + // ThreadInfoWrapper encapsulates ThreadLocalInfo and ThreadLocalIndex and + // also provides some common functions for creating PBackground IPC actor. + class ThreadInfoWrapper final { + friend class ChildImpl; + + public: + using ActorCreateFunc = void (*)(ThreadLocalInfo*, unsigned int, + nsIEventTarget*, ChildImpl**); + + ThreadInfoWrapper() = default; + + void Startup() { + MOZ_ASSERT(mThreadLocalIndex == kBadThreadLocalIndex, + "ThreadInfoWrapper::Startup() called more than once!"); + + PRStatus status = + PR_NewThreadPrivateIndex(&mThreadLocalIndex, ThreadLocalDestructor); + MOZ_RELEASE_ASSERT(status == PR_SUCCESS, + "PR_NewThreadPrivateIndex failed!"); + + MOZ_ASSERT(mThreadLocalIndex != kBadThreadLocalIndex); + } + + void Shutdown() { + if (sShutdownHasStarted) { + MOZ_ASSERT_IF(mThreadLocalIndex != kBadThreadLocalIndex, + !PR_GetThreadPrivate(mThreadLocalIndex)); + return; + } + + if (mThreadLocalIndex == kBadThreadLocalIndex) { + return; + } + + RefPtr<BackgroundStarterChild> starter; + { + auto lock = mStarter.Lock(); + starter = lock->forget(); + } + if (starter) { + CloseStarter(starter); + } + + ThreadLocalInfo* threadLocalInfo; +#ifdef DEBUG + threadLocalInfo = + static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(mThreadLocalIndex)); + MOZ_ASSERT(!threadLocalInfo); +#endif + + threadLocalInfo = mMainThreadInfo; + if (threadLocalInfo) { +#ifdef DEBUG + MOZ_ASSERT(!threadLocalInfo->mClosed); + threadLocalInfo->mClosed = true; +#endif + + ThreadLocalDestructor(threadLocalInfo); + mMainThreadInfo = nullptr; + } + } + + template <typename Actor> + void InitStarter(Actor* aActor) { + AssertIsOnMainThread(); + + // Create a pair of endpoints and send them to the other process. + Endpoint<PBackgroundStarterParent> parent; + Endpoint<PBackgroundStarterChild> child; + MOZ_ALWAYS_SUCCEEDS(PBackgroundStarter::CreateEndpoints( + aActor->OtherPid(), base::GetCurrentProcId(), &parent, &child)); + MOZ_ALWAYS_TRUE(aActor->SendInitBackground(std::move(parent))); + + InitStarter(std::move(child)); + } + + void InitStarter(Endpoint<PBackgroundStarterChild>&& aEndpoint) { + AssertIsOnMainThread(); + + base::ProcessId otherPid = aEndpoint.OtherPid(); + + nsCOMPtr<nsISerialEventTarget> taskQueue; + MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue( + "PBackgroundStarter Queue", getter_AddRefs(taskQueue))); + + RefPtr<BackgroundStarterChild> starter = + new BackgroundStarterChild(otherPid, taskQueue); + + taskQueue->Dispatch(NS_NewRunnableFunction( + "PBackgroundStarterChild Init", + [starter, endpoint = std::move(aEndpoint)]() mutable { + MOZ_ALWAYS_TRUE(endpoint.Bind(starter)); + })); + + // Swap in the newly initialized `BackgroundStarterChild`, and close the + // previous one if we're replacing an existing PBackgroundStarterChild + // instance. + RefPtr<BackgroundStarterChild> prevStarter; + { + auto lock = mStarter.Lock(); + prevStarter = lock->forget(); + *lock = starter.forget(); + } + if (prevStarter) { + CloseStarter(prevStarter); + } + } + + void CloseForCurrentThread() { + MOZ_ASSERT(!NS_IsMainThread()); + + if (mThreadLocalIndex == kBadThreadLocalIndex) { + return; + } + + auto* threadLocalInfo = + static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(mThreadLocalIndex)); + + if (!threadLocalInfo) { + return; + } + +#ifdef DEBUG + MOZ_ASSERT(!threadLocalInfo->mClosed); + threadLocalInfo->mClosed = true; +#endif + + // Clearing the thread local will synchronously close the actor. + DebugOnly<PRStatus> status = + PR_SetThreadPrivate(mThreadLocalIndex, nullptr); + MOZ_ASSERT(status == PR_SUCCESS); + } + + PBackgroundChild* GetOrCreateForCurrentThread() { + // Processes can be told to do final CC's during shutdown even though + // they never finished starting (and thus call this), because they + // hadn't gotten far enough to call Startup() before shutdown began. + if (mThreadLocalIndex == kBadThreadLocalIndex) { + NS_ERROR("BackgroundChild::Startup() was never called"); + return nullptr; + } + if (NS_IsMainThread() && ChildImpl::sShutdownHasStarted) { + return nullptr; + } + + auto* threadLocalInfo = NS_IsMainThread() + ? mMainThreadInfo + : static_cast<ThreadLocalInfo*>( + PR_GetThreadPrivate(mThreadLocalIndex)); + + if (!threadLocalInfo) { + auto newInfo = MakeUnique<ThreadLocalInfo>(); + + if (NS_IsMainThread()) { + mMainThreadInfo = newInfo.get(); + } else { + if (PR_SetThreadPrivate(mThreadLocalIndex, newInfo.get()) != + PR_SUCCESS) { + CRASH_IN_CHILD_PROCESS("PR_SetThreadPrivate failed!"); + return nullptr; + } + } + + threadLocalInfo = newInfo.release(); + } + + if (threadLocalInfo->mActor) { + return threadLocalInfo->mActor; + } + + RefPtr<BackgroundStarterChild> starter; + { + auto lock = mStarter.Lock(); + starter = *lock; + } + if (!starter) { + CRASH_IN_CHILD_PROCESS("No BackgroundStarterChild"); + return nullptr; + } + + Endpoint<PBackgroundParent> parent; + Endpoint<PBackgroundChild> child; + nsresult rv; + rv = PBackground::CreateEndpoints( + starter->mOtherPid, base::GetCurrentProcId(), &parent, &child); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create top level actor!"); + return nullptr; + } + + RefPtr<ChildImpl> strongActor = new ChildImpl(); + if (!child.Bind(strongActor)) { + CRASH_IN_CHILD_PROCESS("Failed to bind ChildImpl!"); + return nullptr; + } + strongActor->SetActorAlive(); + threadLocalInfo->mActor = strongActor; + + // Dispatch to the background task queue to create the relevant actor in + // the remote process. + starter->mTaskQueue->Dispatch(NS_NewRunnableFunction( + "PBackground GetOrCreateForCurrentThread", + [starter, endpoint = std::move(parent)]() mutable { + if (!starter->SendInitBackground(std::move(endpoint))) { + NS_WARNING("Failed to create toplevel actor"); + } + })); + return strongActor; + } + + private: + static void CloseStarter(BackgroundStarterChild* aStarter) { + aStarter->mTaskQueue->Dispatch(NS_NewRunnableFunction( + "PBackgroundStarterChild Close", + [starter = RefPtr{aStarter}] { starter->Close(); })); + } + + // This is only modified on the main thread. It is the thread-local index + // that we use to store the BackgroundChild for each thread. + unsigned int mThreadLocalIndex = kBadThreadLocalIndex; + + // On the main thread, we store TLS in this global instead of in + // mThreadLocalIndex. That way, cooperative main threads all share the same + // thread info. + ThreadLocalInfo* mMainThreadInfo = nullptr; + + // The starter which will be used to launch PBackground instances of this + // type. Only modified on the main thread, but may be read by any thread + // wanting to start background actors. + StaticDataMutex<StaticRefPtr<BackgroundStarterChild>> mStarter{"mStarter"}; + }; + + // For PBackground between parent and content process. + static ThreadInfoWrapper sParentAndContentProcessThreadInfo; + + // This is only modified on the main thread. It prevents us from trying to + // create the background thread after application shutdown has started. + static bool sShutdownHasStarted; + +#if defined(DEBUG) || !defined(RELEASE_OR_BETA) + nsISerialEventTarget* mOwningEventTarget; +#endif + +#ifdef DEBUG + bool mActorWasAlive; + bool mActorDestroyed; +#endif + + public: + static void Shutdown(); + + void AssertIsOnOwningThread() { + THREADSAFETY_ASSERT(mOwningEventTarget); + +#ifdef RELEASE_OR_BETA + DebugOnly<bool> current; +#else + bool current; +#endif + THREADSAFETY_ASSERT( + NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t))); + THREADSAFETY_ASSERT(current); + } + + void AssertActorDestroyed() { + MOZ_ASSERT(mActorDestroyed, "ChildImpl::ActorDestroy not called in time"); + } + + explicit ChildImpl() +#if defined(DEBUG) || !defined(RELEASE_OR_BETA) + : mOwningEventTarget(GetCurrentSerialEventTarget()) +#endif +#ifdef DEBUG + , + mActorWasAlive(false), + mActorDestroyed(false) +#endif + { + AssertIsOnOwningThread(); + } + + void SetActorAlive() { + AssertIsOnOwningThread(); + MOZ_ASSERT(!mActorWasAlive); + MOZ_ASSERT(!mActorDestroyed); + +#ifdef DEBUG + mActorWasAlive = true; +#endif + } + + NS_INLINE_DECL_REFCOUNTING(ChildImpl, override) + + private: + // Forwarded from BackgroundChild. + static void Startup(); + + // Forwarded from BackgroundChild. + static PBackgroundChild* GetForCurrentThread(); + + // Forwarded from BackgroundChild. + static PBackgroundChild* GetOrCreateForCurrentThread(); + + static void CloseForCurrentThread(); + + // Forwarded from BackgroundChildImpl. + static BackgroundChildImpl::ThreadLocal* GetThreadLocalForCurrentThread(); + + // Forwarded from BackgroundChild. + static void InitContentStarter(mozilla::dom::ContentChild* aContent); + + static void ThreadLocalDestructor(void* aThreadLocal); + + // This class is reference counted. + ~ChildImpl() { MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed); } + + // Only called by IPDL. + virtual void ActorDestroy(ActorDestroyReason aWhy) override; +}; + +// ----------------------------------------------------------------------------- +// ParentImpl Helper Declarations +// ----------------------------------------------------------------------------- + +class ParentImpl::ShutdownObserver final : public nsIObserver { + public: + ShutdownObserver() { AssertIsOnMainThread(); } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + private: + ~ShutdownObserver() { AssertIsOnMainThread(); } +}; + +// ----------------------------------------------------------------------------- +// ChildImpl Helper Declarations +// ----------------------------------------------------------------------------- + +class ChildImpl::ShutdownObserver final : public nsIObserver { + public: + ShutdownObserver() { AssertIsOnMainThread(); } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + private: + ~ShutdownObserver() { AssertIsOnMainThread(); } +}; + +} // namespace + +namespace mozilla { +namespace ipc { + +bool IsOnBackgroundThread() { return ParentImpl::IsOnBackgroundThread(); } + +#ifdef DEBUG + +void AssertIsOnBackgroundThread() { ParentImpl::AssertIsOnBackgroundThread(); } + +#endif // DEBUG + +} // namespace ipc +} // namespace mozilla + +// ----------------------------------------------------------------------------- +// BackgroundParent Public Methods +// ----------------------------------------------------------------------------- + +// static +bool BackgroundParent::IsOtherProcessActor( + PBackgroundParent* aBackgroundActor) { + return ParentImpl::IsOtherProcessActor(aBackgroundActor); +} + +// static +ThreadsafeContentParentHandle* BackgroundParent::GetContentParentHandle( + PBackgroundParent* aBackgroundActor) { + return ParentImpl::GetContentParentHandle(aBackgroundActor); +} + +// static +uint64_t BackgroundParent::GetChildID(PBackgroundParent* aBackgroundActor) { + return ParentImpl::GetChildID(aBackgroundActor); +} + +// static +void BackgroundParent::KillHardAsync(PBackgroundParent* aBackgroundActor, + const nsACString& aReason) { + ParentImpl::KillHardAsync(aBackgroundActor, aReason); +} + +// static +bool BackgroundParent::AllocStarter( + ContentParent* aContent, Endpoint<PBackgroundStarterParent>&& aEndpoint) { + return ParentImpl::AllocStarter(aContent, std::move(aEndpoint)); +} + +// ----------------------------------------------------------------------------- +// BackgroundChild Public Methods +// ----------------------------------------------------------------------------- + +// static +void BackgroundChild::Startup() { ChildImpl::Startup(); } + +// static +PBackgroundChild* BackgroundChild::GetForCurrentThread() { + return ChildImpl::GetForCurrentThread(); +} + +// static +PBackgroundChild* BackgroundChild::GetOrCreateForCurrentThread() { + return ChildImpl::GetOrCreateForCurrentThread(); +} + +// static +void BackgroundChild::CloseForCurrentThread() { + ChildImpl::CloseForCurrentThread(); +} + +// static +void BackgroundChild::InitContentStarter(ContentChild* aContent) { + ChildImpl::InitContentStarter(aContent); +} + +// ----------------------------------------------------------------------------- +// BackgroundChildImpl Public Methods +// ----------------------------------------------------------------------------- + +// static +BackgroundChildImpl::ThreadLocal* +BackgroundChildImpl::GetThreadLocalForCurrentThread() { + return ChildImpl::GetThreadLocalForCurrentThread(); +} + +// ----------------------------------------------------------------------------- +// ParentImpl Static Members +// ----------------------------------------------------------------------------- + +StaticRefPtr<nsIThread> ParentImpl::sBackgroundThread; + +nsTArray<IToplevelProtocol*>* ParentImpl::sLiveActorsForBackgroundThread; + +StaticRefPtr<nsITimer> ParentImpl::sShutdownTimer; + +Atomic<PRThread*> ParentImpl::sBackgroundPRThread; + +Atomic<uint64_t> ParentImpl::sLiveActorCount; + +bool ParentImpl::sShutdownObserverRegistered = false; + +bool ParentImpl::sShutdownHasStarted = false; + +// ----------------------------------------------------------------------------- +// ChildImpl Static Members +// ----------------------------------------------------------------------------- + +ChildImpl::ThreadInfoWrapper ChildImpl::sParentAndContentProcessThreadInfo; + +bool ChildImpl::sShutdownHasStarted = false; + +// ----------------------------------------------------------------------------- +// ParentImpl Implementation +// ----------------------------------------------------------------------------- + +// static +bool ParentImpl::IsOtherProcessActor(PBackgroundParent* aBackgroundActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + + return static_cast<ParentImpl*>(aBackgroundActor)->mIsOtherProcessActor; +} + +// static +ThreadsafeContentParentHandle* ParentImpl::GetContentParentHandle( + PBackgroundParent* aBackgroundActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + + return static_cast<ParentImpl*>(aBackgroundActor)->mContent.get(); +} + +// static +uint64_t ParentImpl::GetChildID(PBackgroundParent* aBackgroundActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + + auto actor = static_cast<ParentImpl*>(aBackgroundActor); + if (actor->mContent) { + return actor->mContent->ChildID(); + } + + return 0; +} + +// static +void ParentImpl::KillHardAsync(PBackgroundParent* aBackgroundActor, + const nsACString& aReason) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + MOZ_ASSERT(BackgroundParent::IsOtherProcessActor(aBackgroundActor)); + + RefPtr<ThreadsafeContentParentHandle> handle = + GetContentParentHandle(aBackgroundActor); + MOZ_ASSERT(handle); + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( + NS_NewRunnableFunction( + "ParentImpl::KillHardAsync", + [handle = std::move(handle), reason = nsCString{aReason}]() { + mozilla::AssertIsOnMainThread(); + + if (RefPtr<ContentParent> contentParent = + handle->GetContentParent()) { + contentParent->KillHard(reason.get()); + } + }), + NS_DISPATCH_NORMAL)); + + // After we've scheduled killing of the remote process, also ensure we induce + // a connection error in the IPC channel to immediately stop all IPC + // communication on this channel. + if (aBackgroundActor->CanSend()) { + aBackgroundActor->GetIPCChannel()->InduceConnectionError(); + } +} + +// static +bool ParentImpl::AllocStarter(ContentParent* aContent, + Endpoint<PBackgroundStarterParent>&& aEndpoint, + bool aCrossProcess) { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + + MOZ_ASSERT(aEndpoint.IsValid()); + + if (!sBackgroundThread && !CreateBackgroundThread()) { + NS_WARNING("Failed to create background thread!"); + return false; + } + + sLiveActorCount++; + + RefPtr<BackgroundStarterParent> actor = new BackgroundStarterParent( + aContent ? aContent->ThreadsafeHandle() : nullptr, aCrossProcess); + + if (NS_FAILED(sBackgroundThread->Dispatch(NS_NewRunnableFunction( + "BackgroundStarterParent::ConnectActorRunnable", + [actor = std::move(actor), endpoint = std::move(aEndpoint), + liveActorArray = sLiveActorsForBackgroundThread]() mutable { + MOZ_ASSERT(endpoint.IsValid()); + MOZ_ALWAYS_TRUE(endpoint.Bind(actor)); + actor->SetLiveActorArray(liveActorArray); + })))) { + NS_WARNING("Failed to dispatch connect runnable!"); + + MOZ_ASSERT(sLiveActorCount); + sLiveActorCount--; + } + + return true; +} + +// static +bool ParentImpl::CreateBackgroundThread() { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(!sBackgroundThread); + MOZ_ASSERT(!sLiveActorsForBackgroundThread); + + if (sShutdownHasStarted) { + NS_WARNING( + "Trying to create background thread after shutdown has " + "already begun!"); + return false; + } + + nsCOMPtr<nsITimer> newShutdownTimer; + + if (!sShutdownTimer) { + newShutdownTimer = NS_NewTimer(); + if (!newShutdownTimer) { + return false; + } + } + + if (!sShutdownObserverRegistered) { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return false; + } + + nsCOMPtr<nsIObserver> observer = new ShutdownObserver(); + + nsresult rv = obs->AddObserver( + observer, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + sShutdownObserverRegistered = true; + } + + nsCOMPtr<nsIThread> thread; + if (NS_FAILED(NS_NewNamedThread( + "IPDL Background", getter_AddRefs(thread), + NS_NewRunnableFunction( + "Background::ParentImpl::CreateBackgroundThreadRunnable", []() { + DebugOnly<PRThread*> oldBackgroundThread = + sBackgroundPRThread.exchange(PR_GetCurrentThread()); + + MOZ_ASSERT_IF(oldBackgroundThread, + PR_GetCurrentThread() != oldBackgroundThread); + })))) { + NS_WARNING("NS_NewNamedThread failed!"); + return false; + } + + sBackgroundThread = thread.forget(); + + sLiveActorsForBackgroundThread = new nsTArray<IToplevelProtocol*>(1); + + if (!sShutdownTimer) { + MOZ_ASSERT(newShutdownTimer); + sShutdownTimer = newShutdownTimer; + } + + return true; +} + +// static +void ParentImpl::ShutdownBackgroundThread() { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(sShutdownHasStarted); + MOZ_ASSERT_IF(!sBackgroundThread, !sLiveActorCount); + MOZ_ASSERT_IF(sBackgroundThread, sShutdownTimer); + + nsCOMPtr<nsITimer> shutdownTimer = sShutdownTimer.get(); + sShutdownTimer = nullptr; + + if (sBackgroundThread) { + nsCOMPtr<nsIThread> thread = sBackgroundThread.get(); + sBackgroundThread = nullptr; + + UniquePtr<nsTArray<IToplevelProtocol*>> liveActors( + sLiveActorsForBackgroundThread); + sLiveActorsForBackgroundThread = nullptr; + + MOZ_ASSERT_IF(!sShutdownHasStarted, !sLiveActorCount); + + if (sLiveActorCount) { + // We need to spin the event loop while we wait for all the actors to be + // cleaned up. We also set a timeout to force-kill any hanging actors. + TimerCallbackClosure closure(thread, liveActors.get()); + + MOZ_ALWAYS_SUCCEEDS(shutdownTimer->InitWithNamedFuncCallback( + &ShutdownTimerCallback, &closure, kShutdownTimerDelayMS, + nsITimer::TYPE_ONE_SHOT, "ParentImpl::ShutdownTimerCallback")); + + SpinEventLoopUntil("ParentImpl::ShutdownBackgroundThread"_ns, + [&]() { return !sLiveActorCount; }); + + MOZ_ASSERT(liveActors->IsEmpty()); + + MOZ_ALWAYS_SUCCEEDS(shutdownTimer->Cancel()); + } + + // Dispatch this runnable to unregister the PR thread from the profiler. + MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(NS_NewRunnableFunction( + "Background::ParentImpl::ShutdownBackgroundThreadRunnable", []() { + // It is possible that another background thread was created while + // this thread was shutting down. In that case we can't assert + // anything about sBackgroundPRThread and we should not modify it + // here. + sBackgroundPRThread.compareExchange(PR_GetCurrentThread(), nullptr); + }))); + + MOZ_ALWAYS_SUCCEEDS(thread->Shutdown()); + } +} + +// static +void ParentImpl::ShutdownTimerCallback(nsITimer* aTimer, void* aClosure) { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(sShutdownHasStarted); + MOZ_ASSERT(sLiveActorCount); + + auto closure = static_cast<TimerCallbackClosure*>(aClosure); + MOZ_ASSERT(closure); + + // Don't let the stack unwind until the ForceCloseBackgroundActorsRunnable has + // finished. + sLiveActorCount++; + + InvokeAsync( + closure->mThread, __func__, + [liveActors = closure->mLiveActors]() { + MOZ_ASSERT(liveActors); + + if (!liveActors->IsEmpty()) { + // Copy the array since calling Close() could mutate the + // actual array. + nsTArray<IToplevelProtocol*> actorsToClose(liveActors->Clone()); + for (IToplevelProtocol* actor : actorsToClose) { + actor->Close(); + } + } + return GenericPromise::CreateAndResolve(true, __func__); + }) + ->Then(GetCurrentSerialEventTarget(), __func__, []() { + MOZ_ASSERT(sLiveActorCount); + sLiveActorCount--; + }); +} + +void ParentImpl::Destroy() { + // May be called on any thread! + + AssertIsInMainProcess(); + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( + NewNonOwningRunnableMethod("ParentImpl::MainThreadActorDestroy", this, + &ParentImpl::MainThreadActorDestroy))); +} + +void ParentImpl::MainThreadActorDestroy() { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT_IF(!mIsOtherProcessActor, !mContent); + + MOZ_ASSERT(sLiveActorCount); + sLiveActorCount--; + + // This may be the last reference! + Release(); +} + +void ParentImpl::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mActorDestroyed); + MOZ_ASSERT_IF(mIsOtherProcessActor, mLiveActorArray); + + BackgroundParentImpl::ActorDestroy(aWhy); + + mActorDestroyed = true; + + if (mLiveActorArray) { + MOZ_ALWAYS_TRUE(mLiveActorArray->RemoveElement(this)); + mLiveActorArray = nullptr; + } + + // This is tricky. We should be able to call Destroy() here directly because + // we're not going to touch 'this' or our MessageChannel any longer on this + // thread. Destroy() dispatches the MainThreadActorDestroy runnable and when + // it runs it will destroy 'this' and our associated MessageChannel. However, + // IPDL is about to call MessageChannel::Clear() on this thread! To avoid + // racing with the main thread we must ensure that the MessageChannel lives + // long enough to be cleared in this call stack. + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(NewNonOwningRunnableMethod( + "ParentImpl::Destroy", this, &ParentImpl::Destroy))); +} + +NS_IMPL_ISUPPORTS(ParentImpl::ShutdownObserver, nsIObserver) + +NS_IMETHODIMP +ParentImpl::ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(!sShutdownHasStarted); + MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)); + + sShutdownHasStarted = true; + + // Do this first before calling (and spinning the event loop in) + // ShutdownBackgroundThread(). + ChildImpl::Shutdown(); + + ShutdownBackgroundThread(); + + return NS_OK; +} + +BackgroundStarterParent::BackgroundStarterParent( + ThreadsafeContentParentHandle* aContent, bool aCrossProcess) + : mCrossProcess(aCrossProcess), mContent(aContent) { + AssertIsOnMainThread(); + AssertIsInMainProcess(); + MOZ_ASSERT_IF(!mCrossProcess, !mContent); + MOZ_ASSERT_IF(!mCrossProcess, XRE_IsParentProcess()); +} + +void BackgroundStarterParent::SetLiveActorArray( + nsTArray<IToplevelProtocol*>* aLiveActorArray) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aLiveActorArray); + MOZ_ASSERT(!aLiveActorArray->Contains(this)); + MOZ_ASSERT(!mLiveActorArray); + MOZ_ASSERT_IF(!mCrossProcess, OtherPid() == base::GetCurrentProcId()); + + mLiveActorArray = aLiveActorArray; + mLiveActorArray->AppendElement(this); +} + +IPCResult BackgroundStarterParent::RecvInitBackground( + Endpoint<PBackgroundParent>&& aEndpoint) { + AssertIsOnBackgroundThread(); + + if (!aEndpoint.IsValid()) { + return IPC_FAIL(this, + "Cannot initialize PBackground with invalid endpoint"); + } + + ParentImpl* actor = new ParentImpl(mContent, mCrossProcess); + + // Take a reference on this thread. If Open() fails then we will release this + // reference in Destroy. + NS_ADDREF(actor); + + ParentImpl::sLiveActorCount++; + + if (!aEndpoint.Bind(actor)) { + actor->Destroy(); + return IPC_OK(); + } + + if (mCrossProcess) { + actor->SetLiveActorArray(mLiveActorArray); + } + return IPC_OK(); +} + +void BackgroundStarterParent::ActorDestroy(ActorDestroyReason aReason) { + AssertIsOnBackgroundThread(); + + if (mLiveActorArray) { + MOZ_ALWAYS_TRUE(mLiveActorArray->RemoveElement(this)); + mLiveActorArray = nullptr; + } + + // Make sure to decrement `sLiveActorCount` on the main thread. + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( + NS_NewRunnableFunction("BackgroundStarterParent::MainThreadDestroy", + [] { ParentImpl::sLiveActorCount--; }))); +} + +// ----------------------------------------------------------------------------- +// ChildImpl Implementation +// ----------------------------------------------------------------------------- + +// static +void ChildImpl::Startup() { + // This happens on the main thread but before XPCOM has started so we can't + // assert that we're being called on the main thread here. + + sParentAndContentProcessThreadInfo.Startup(); + + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + MOZ_RELEASE_ASSERT(observerService); + + nsCOMPtr<nsIObserver> observer = new ShutdownObserver(); + + nsresult rv = observerService->AddObserver( + observer, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + + // Initialize a starter actor to allow starting PBackground within the parent + // process. + if (XRE_IsParentProcess()) { + Endpoint<PBackgroundStarterParent> parent; + Endpoint<PBackgroundStarterChild> child; + MOZ_ALWAYS_SUCCEEDS(PBackgroundStarter::CreateEndpoints( + base::GetCurrentProcId(), base::GetCurrentProcId(), &parent, &child)); + + MOZ_ALWAYS_TRUE(ParentImpl::AllocStarter(nullptr, std::move(parent), + /* aCrossProcess */ false)); + sParentAndContentProcessThreadInfo.InitStarter(std::move(child)); + } +} + +// static +void ChildImpl::Shutdown() { + AssertIsOnMainThread(); + + sParentAndContentProcessThreadInfo.Shutdown(); + + sShutdownHasStarted = true; +} + +// static +PBackgroundChild* ChildImpl::GetForCurrentThread() { + MOZ_ASSERT(sParentAndContentProcessThreadInfo.mThreadLocalIndex != + kBadThreadLocalIndex); + + auto threadLocalInfo = + NS_IsMainThread() + ? sParentAndContentProcessThreadInfo.mMainThreadInfo + : static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate( + sParentAndContentProcessThreadInfo.mThreadLocalIndex)); + + if (!threadLocalInfo) { + return nullptr; + } + + return threadLocalInfo->mActor; +} + +/* static */ +PBackgroundChild* ChildImpl::GetOrCreateForCurrentThread() { + return sParentAndContentProcessThreadInfo.GetOrCreateForCurrentThread(); +} + +// static +void ChildImpl::CloseForCurrentThread() { + MOZ_ASSERT(!NS_IsMainThread(), + "PBackground for the main thread should be shut down via " + "ChildImpl::Shutdown()."); + + sParentAndContentProcessThreadInfo.CloseForCurrentThread(); +} + +// static +BackgroundChildImpl::ThreadLocal* ChildImpl::GetThreadLocalForCurrentThread() { + MOZ_ASSERT(sParentAndContentProcessThreadInfo.mThreadLocalIndex != + kBadThreadLocalIndex, + "BackgroundChild::Startup() was never called!"); + + auto threadLocalInfo = + NS_IsMainThread() + ? sParentAndContentProcessThreadInfo.mMainThreadInfo + : static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate( + sParentAndContentProcessThreadInfo.mThreadLocalIndex)); + + if (!threadLocalInfo) { + return nullptr; + } + + if (!threadLocalInfo->mConsumerThreadLocal) { + threadLocalInfo->mConsumerThreadLocal = + MakeUnique<BackgroundChildImpl::ThreadLocal>(); + } + + return threadLocalInfo->mConsumerThreadLocal.get(); +} + +// static +void ChildImpl::InitContentStarter(mozilla::dom::ContentChild* aContent) { + sParentAndContentProcessThreadInfo.InitStarter(aContent); +} + +// static +void ChildImpl::ThreadLocalDestructor(void* aThreadLocal) { + auto threadLocalInfo = static_cast<ThreadLocalInfo*>(aThreadLocal); + + if (threadLocalInfo) { + MOZ_ASSERT(threadLocalInfo->mClosed); + + if (threadLocalInfo->mActor) { + threadLocalInfo->mActor->Close(); + threadLocalInfo->mActor->AssertActorDestroyed(); + } + + delete threadLocalInfo; + } +} + +void ChildImpl::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnOwningThread(); + +#ifdef DEBUG + MOZ_ASSERT(!mActorDestroyed); + mActorDestroyed = true; +#endif + + BackgroundChildImpl::ActorDestroy(aWhy); +} + +NS_IMPL_ISUPPORTS(ChildImpl::ShutdownObserver, nsIObserver) + +NS_IMETHODIMP +ChildImpl::ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + AssertIsOnMainThread(); + MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID)); + + ChildImpl::Shutdown(); + + return NS_OK; +} diff --git a/ipc/glue/BackgroundParent.h b/ipc/glue/BackgroundParent.h new file mode 100644 index 0000000000..6afad501af --- /dev/null +++ b/ipc/glue/BackgroundParent.h @@ -0,0 +1,112 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_backgroundparent_h__ +#define mozilla_ipc_backgroundparent_h__ + +#include "base/process.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/ContentParent.h" +#include "nsStringFwd.h" +#include "nsTArrayForwardDeclare.h" + +#ifdef DEBUG +# include "nsXULAppAPI.h" +#endif + +template <class> +struct already_AddRefed; + +namespace mozilla { + +namespace net { + +class SocketProcessBridgeParent; +class SocketProcessParent; + +} // namespace net + +namespace dom { + +class BlobImpl; +class ContentParent; + +} // namespace dom + +namespace ipc { + +class BackgroundStarterParent; +class PBackgroundParent; +class PBackgroundStarterParent; + +template <class PFooSide> +class Endpoint; + +// This class is not designed for public consumption beyond the few static +// member functions. +class BackgroundParent final { + friend class mozilla::ipc::BackgroundStarterParent; + friend class mozilla::dom::ContentParent; + friend class mozilla::net::SocketProcessBridgeParent; + friend class mozilla::net::SocketProcessParent; + + using ProcessId = base::ProcessId; + using BlobImpl = mozilla::dom::BlobImpl; + using ContentParent = mozilla::dom::ContentParent; + using ThreadsafeContentParentHandle = + mozilla::dom::ThreadsafeContentParentHandle; + + public: + // This function allows the caller to determine if the given parent actor + // corresponds to a child actor from another process or a child actor from a + // different thread in the same process. + // This function may only be called on the background thread. + static bool IsOtherProcessActor(PBackgroundParent* aBackgroundActor); + + // This function returns a handle to the ContentParent associated with the + // parent actor if the parent actor corresponds to a child actor from another + // content process. If the parent actor corresponds to a child actor from a + // different thread in the same process then this function returns null. + // + // This function may only be called on the background thread. + static ThreadsafeContentParentHandle* GetContentParentHandle( + PBackgroundParent* aBackgroundActor); + + static uint64_t GetChildID(PBackgroundParent* aBackgroundActor); + + static void KillHardAsync(PBackgroundParent* aBackgroundActor, + const nsACString& aReason); + + private: + // Only called by ContentParent for cross-process actors. + static bool AllocStarter(ContentParent* aContent, + Endpoint<PBackgroundStarterParent>&& aEndpoint); + + // Called by SocketProcessBridgeParent and SocketProcessParent for + // cross-process actors. + static bool AllocStarter(Endpoint<PBackgroundStarterParent>&& aEndpoint); +}; + +// Implemented in BackgroundImpl.cpp. +bool IsOnBackgroundThread(); + +#ifdef DEBUG + +// Implemented in BackgroundImpl.cpp. +void AssertIsOnBackgroundThread(); + +#else + +inline void AssertIsOnBackgroundThread() {} + +#endif // DEBUG + +inline void AssertIsInMainProcess() { MOZ_ASSERT(XRE_IsParentProcess()); } + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_backgroundparent_h__ diff --git a/ipc/glue/BackgroundParentImpl.cpp b/ipc/glue/BackgroundParentImpl.cpp new file mode 100644 index 0000000000..e9d0adbb94 --- /dev/null +++ b/ipc/glue/BackgroundParentImpl.cpp @@ -0,0 +1,1397 @@ +/* -*- 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 "BackgroundParentImpl.h" + +#include "BroadcastChannelParent.h" +#ifdef MOZ_WEBRTC +# include "CamerasParent.h" +#endif +#include "mozilla/Assertions.h" +#include "mozilla/RDDProcessManager.h" +#include "mozilla/ipc/UtilityProcessManager.h" +#include "mozilla/RemoteDecodeUtils.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/BackgroundSessionStorageServiceParent.h" +#include "mozilla/dom/ClientManagerActors.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/DOMTypes.h" +#include "mozilla/dom/EndpointForReportParent.h" +#include "mozilla/dom/FetchParent.h" +#include "mozilla/dom/FileCreatorParent.h" +#include "mozilla/dom/FileSystemManagerParentFactory.h" +#include "mozilla/dom/FileSystemRequestParent.h" +#include "mozilla/dom/GamepadEventChannelParent.h" +#include "mozilla/dom/GamepadTestChannelParent.h" +#include "mozilla/dom/MIDIManagerParent.h" +#include "mozilla/dom/MIDIPlatformService.h" +#include "mozilla/dom/MIDIPortParent.h" +#include "mozilla/dom/MessagePortParent.h" +#include "mozilla/dom/PGamepadEventChannelParent.h" +#include "mozilla/dom/PGamepadTestChannelParent.h" +#include "mozilla/dom/RemoteWorkerControllerParent.h" +#include "mozilla/dom/RemoteWorkerServiceParent.h" +#include "mozilla/dom/ReportingHeader.h" +#include "mozilla/dom/ServiceWorkerActors.h" +#include "mozilla/dom/ServiceWorkerContainerParent.h" +#include "mozilla/dom/ServiceWorkerManagerParent.h" +#include "mozilla/dom/ServiceWorkerParent.h" +#include "mozilla/dom/ServiceWorkerRegistrar.h" +#include "mozilla/dom/ServiceWorkerRegistrationParent.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/dom/SharedWorkerParent.h" +#include "mozilla/dom/StorageActivityService.h" +#include "mozilla/dom/StorageIPC.h" +#include "mozilla/dom/TemporaryIPCBlobParent.h" +#include "mozilla/dom/WebAuthnTransactionParent.h" +#include "mozilla/dom/WebTransportParent.h" +#include "mozilla/dom/cache/ActorUtils.h" +#include "mozilla/dom/indexedDB/ActorsParent.h" +#include "mozilla/dom/locks/LockManagerParent.h" +#include "mozilla/dom/localstorage/ActorsParent.h" +#include "mozilla/dom/network/UDPSocketParent.h" +#include "mozilla/dom/quota/ActorsParent.h" +#include "mozilla/dom/quota/QuotaParent.h" +#include "mozilla/dom/simpledb/ActorsParent.h" +#include "mozilla/dom/VsyncParent.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/IdleSchedulerParent.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/ipc/PBackgroundTestParent.h" +#include "mozilla/net/BackgroundDataBridgeParent.h" +#include "mozilla/net/HttpBackgroundChannelParent.h" +#include "nsIHttpChannelInternal.h" +#include "nsIPrincipal.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +using mozilla::AssertIsOnMainThread; +using mozilla::dom::FileSystemRequestParent; +using mozilla::dom::MessagePortParent; +using mozilla::dom::MIDIManagerParent; +using mozilla::dom::MIDIPlatformService; +using mozilla::dom::MIDIPortParent; +using mozilla::dom::PMessagePortParent; +using mozilla::dom::PMIDIManagerParent; +using mozilla::dom::PMIDIPortParent; +using mozilla::dom::PServiceWorkerContainerParent; +using mozilla::dom::PServiceWorkerParent; +using mozilla::dom::PServiceWorkerRegistrationParent; +using mozilla::dom::ServiceWorkerParent; +using mozilla::dom::UDPSocketParent; +using mozilla::dom::WebAuthnTransactionParent; +using mozilla::dom::cache::PCacheParent; +using mozilla::dom::cache::PCacheStorageParent; +using mozilla::dom::cache::PCacheStreamControlParent; +using mozilla::ipc::AssertIsOnBackgroundThread; + +namespace { + +class TestParent final : public mozilla::ipc::PBackgroundTestParent { + friend class mozilla::ipc::BackgroundParentImpl; + + MOZ_COUNTED_DEFAULT_CTOR(TestParent) + + protected: + ~TestParent() override { MOZ_COUNT_DTOR(TestParent); } + + public: + void ActorDestroy(ActorDestroyReason aWhy) override; +}; + +} // namespace + +namespace mozilla::ipc { + +using mozilla::dom::BroadcastChannelParent; +using mozilla::dom::ContentParent; +using mozilla::dom::ThreadsafeContentParentHandle; + +BackgroundParentImpl::BackgroundParentImpl() { + AssertIsInMainProcess(); + + MOZ_COUNT_CTOR(mozilla::ipc::BackgroundParentImpl); +} + +BackgroundParentImpl::~BackgroundParentImpl() { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + + MOZ_COUNT_DTOR(mozilla::ipc::BackgroundParentImpl); +} + +void BackgroundParentImpl::ProcessingError(Result aCode, const char* aReason) { + if (MsgDropped == aCode) { + return; + } + + // XXX Remove this cut-out once bug 1858621 is fixed. Some parent actors + // currently return nullptr in actor allocation methods for non fatal errors. + // We don't want to crash the parent process or child processes in that case. + if (MsgValueError == aCode) { + return; + } + + // Other errors are big deals. + nsDependentCString reason(aReason); + if (BackgroundParent::IsOtherProcessActor(this)) { +#ifndef FUZZING + BackgroundParent::KillHardAsync(this, reason); +#endif + if (CanSend()) { + GetIPCChannel()->InduceConnectionError(); + } + } else { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ipc_channel_error, reason); + + MOZ_CRASH("in-process BackgroundParent abort due to IPC error"); + } +} + +void BackgroundParentImpl::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); +} + +BackgroundParentImpl::PBackgroundTestParent* +BackgroundParentImpl::AllocPBackgroundTestParent(const nsACString& aTestArg) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return new TestParent(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBackgroundTestConstructor( + PBackgroundTestParent* aActor, const nsACString& aTestArg) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!PBackgroundTestParent::Send__delete__(aActor, aTestArg)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPBackgroundTestParent( + PBackgroundTestParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + delete static_cast<TestParent*>(aActor); + return true; +} + +auto BackgroundParentImpl::AllocPBackgroundIDBFactoryParent( + const LoggingInfo& aLoggingInfo) + -> already_AddRefed<PBackgroundIDBFactoryParent> { + using mozilla::dom::indexedDB::AllocPBackgroundIDBFactoryParent; + + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return AllocPBackgroundIDBFactoryParent(aLoggingInfo); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundIDBFactoryConstructor( + PBackgroundIDBFactoryParent* aActor, const LoggingInfo& aLoggingInfo) { + using mozilla::dom::indexedDB::RecvPBackgroundIDBFactoryConstructor; + + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!RecvPBackgroundIDBFactoryConstructor(aActor, aLoggingInfo)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +auto BackgroundParentImpl::AllocPBackgroundIndexedDBUtilsParent() + -> PBackgroundIndexedDBUtilsParent* { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::indexedDB::AllocPBackgroundIndexedDBUtilsParent(); +} + +bool BackgroundParentImpl::DeallocPBackgroundIndexedDBUtilsParent( + PBackgroundIndexedDBUtilsParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::indexedDB::DeallocPBackgroundIndexedDBUtilsParent( + aActor); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvFlushPendingFileDeletions() { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (!mozilla::dom::indexedDB::RecvFlushPendingFileDeletions()) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +already_AddRefed<BackgroundParentImpl::PBackgroundSDBConnectionParent> +BackgroundParentImpl::AllocPBackgroundSDBConnectionParent( + const PersistenceType& aPersistenceType, + const PrincipalInfo& aPrincipalInfo) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundSDBConnectionParent(aPersistenceType, + aPrincipalInfo); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundSDBConnectionConstructor( + PBackgroundSDBConnectionParent* aActor, + const PersistenceType& aPersistenceType, + const PrincipalInfo& aPrincipalInfo) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!mozilla::dom::RecvPBackgroundSDBConnectionConstructor( + aActor, aPersistenceType, aPrincipalInfo)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +already_AddRefed<BackgroundParentImpl::PBackgroundLSDatabaseParent> +BackgroundParentImpl::AllocPBackgroundLSDatabaseParent( + const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId, + const uint64_t& aDatastoreId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundLSDatabaseParent( + aPrincipalInfo, aPrivateBrowsingId, aDatastoreId); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundLSDatabaseConstructor( + PBackgroundLSDatabaseParent* aActor, const PrincipalInfo& aPrincipalInfo, + const uint32_t& aPrivateBrowsingId, const uint64_t& aDatastoreId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!mozilla::dom::RecvPBackgroundLSDatabaseConstructor( + aActor, aPrincipalInfo, aPrivateBrowsingId, aDatastoreId)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +BackgroundParentImpl::PBackgroundLSObserverParent* +BackgroundParentImpl::AllocPBackgroundLSObserverParent( + const uint64_t& aObserverId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundLSObserverParent(aObserverId); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundLSObserverConstructor( + PBackgroundLSObserverParent* aActor, const uint64_t& aObserverId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!mozilla::dom::RecvPBackgroundLSObserverConstructor(aActor, + aObserverId)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPBackgroundLSObserverParent( + PBackgroundLSObserverParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPBackgroundLSObserverParent(aActor); +} + +BackgroundParentImpl::PBackgroundLSRequestParent* +BackgroundParentImpl::AllocPBackgroundLSRequestParent( + const LSRequestParams& aParams) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundLSRequestParent(this, aParams); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundLSRequestConstructor( + PBackgroundLSRequestParent* aActor, const LSRequestParams& aParams) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!mozilla::dom::RecvPBackgroundLSRequestConstructor(aActor, aParams)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPBackgroundLSRequestParent( + PBackgroundLSRequestParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPBackgroundLSRequestParent(aActor); +} + +BackgroundParentImpl::PBackgroundLSSimpleRequestParent* +BackgroundParentImpl::AllocPBackgroundLSSimpleRequestParent( + const LSSimpleRequestParams& aParams) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundLSSimpleRequestParent(this, aParams); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundLSSimpleRequestConstructor( + PBackgroundLSSimpleRequestParent* aActor, + const LSSimpleRequestParams& aParams) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!mozilla::dom::RecvPBackgroundLSSimpleRequestConstructor(aActor, + aParams)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPBackgroundLSSimpleRequestParent( + PBackgroundLSSimpleRequestParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPBackgroundLSSimpleRequestParent(aActor); +} + +BackgroundParentImpl::PBackgroundLocalStorageCacheParent* +BackgroundParentImpl::AllocPBackgroundLocalStorageCacheParent( + const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey, + const uint32_t& aPrivateBrowsingId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundLocalStorageCacheParent( + aPrincipalInfo, aOriginKey, aPrivateBrowsingId); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundLocalStorageCacheConstructor( + PBackgroundLocalStorageCacheParent* aActor, + const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey, + const uint32_t& aPrivateBrowsingId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::RecvPBackgroundLocalStorageCacheConstructor( + this, aActor, aPrincipalInfo, aOriginKey, aPrivateBrowsingId); +} + +bool BackgroundParentImpl::DeallocPBackgroundLocalStorageCacheParent( + PBackgroundLocalStorageCacheParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPBackgroundLocalStorageCacheParent(aActor); +} + +auto BackgroundParentImpl::AllocPBackgroundStorageParent( + const nsAString& aProfilePath, const uint32_t& aPrivateBrowsingId) + -> PBackgroundStorageParent* { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundStorageParent(aProfilePath, + aPrivateBrowsingId); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBackgroundStorageConstructor( + PBackgroundStorageParent* aActor, const nsAString& aProfilePath, + const uint32_t& aPrivateBrowsingId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::RecvPBackgroundStorageConstructor(aActor, aProfilePath, + aPrivateBrowsingId); +} + +bool BackgroundParentImpl::DeallocPBackgroundStorageParent( + PBackgroundStorageParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPBackgroundStorageParent(aActor); +} + +already_AddRefed<BackgroundParentImpl::PBackgroundSessionStorageManagerParent> +BackgroundParentImpl::AllocPBackgroundSessionStorageManagerParent( + const uint64_t& aTopContextId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return dom::AllocPBackgroundSessionStorageManagerParent(aTopContextId); +} + +already_AddRefed<mozilla::dom::PBackgroundSessionStorageServiceParent> +BackgroundParentImpl::AllocPBackgroundSessionStorageServiceParent() { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return MakeAndAddRef<mozilla::dom::BackgroundSessionStorageServiceParent>(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvCreateFileSystemManagerParent( + const PrincipalInfo& aPrincipalInfo, + Endpoint<PFileSystemManagerParent>&& aParentEndpoint, + CreateFileSystemManagerParentResolver&& aResolver) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::CreateFileSystemManagerParent( + aPrincipalInfo, std::move(aParentEndpoint), std::move(aResolver)); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvCreateWebTransportParent( + const nsAString& aURL, nsIPrincipal* aPrincipal, + const mozilla::Maybe<IPCClientInfo>& aClientInfo, const bool& aDedicated, + const bool& aRequireUnreliable, const uint32_t& aCongestionControl, + nsTArray<WebTransportHash>&& aServerCertHashes, + Endpoint<PWebTransportParent>&& aParentEndpoint, + CreateWebTransportParentResolver&& aResolver) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<mozilla::dom::WebTransportParent> webt = + new mozilla::dom::WebTransportParent(); + webt->Create(aURL, aPrincipal, aClientInfo, aDedicated, aRequireUnreliable, + aCongestionControl, std::move(aServerCertHashes), + std::move(aParentEndpoint), std::move(aResolver)); + return IPC_OK(); +} + +already_AddRefed<PIdleSchedulerParent> +BackgroundParentImpl::AllocPIdleSchedulerParent() { + AssertIsOnBackgroundThread(); + RefPtr<IdleSchedulerParent> actor = new IdleSchedulerParent(); + return actor.forget(); +} + +already_AddRefed<dom::PRemoteWorkerControllerParent> +BackgroundParentImpl::AllocPRemoteWorkerControllerParent( + const dom::RemoteWorkerData& aRemoteWorkerData) { + RefPtr<dom::RemoteWorkerControllerParent> actor = + new dom::RemoteWorkerControllerParent(aRemoteWorkerData); + return actor.forget(); +} + +IPCResult BackgroundParentImpl::RecvPRemoteWorkerControllerConstructor( + dom::PRemoteWorkerControllerParent* aActor, + const dom::RemoteWorkerData& aRemoteWorkerData) { + MOZ_ASSERT(aActor); + + return IPC_OK(); +} + +already_AddRefed<dom::PRemoteWorkerServiceParent> +BackgroundParentImpl::AllocPRemoteWorkerServiceParent() { + return MakeAndAddRef<dom::RemoteWorkerServiceParent>(); +} + +IPCResult BackgroundParentImpl::RecvPRemoteWorkerServiceConstructor( + PRemoteWorkerServiceParent* aActor) { + mozilla::dom::RemoteWorkerServiceParent* actor = + static_cast<mozilla::dom::RemoteWorkerServiceParent*>(aActor); + + RefPtr<ThreadsafeContentParentHandle> parent = + BackgroundParent::GetContentParentHandle(this); + // If the ContentParent is null we are dealing with a same-process actor. + if (!parent) { + actor->Initialize(NOT_REMOTE_TYPE); + } else { + actor->Initialize(parent->GetRemoteType()); + } + return IPC_OK(); +} + +mozilla::dom::PSharedWorkerParent* +BackgroundParentImpl::AllocPSharedWorkerParent( + const mozilla::dom::RemoteWorkerData& aData, const uint64_t& aWindowID, + const mozilla::dom::MessagePortIdentifier& aPortIdentifier) { + RefPtr<dom::SharedWorkerParent> agent = + new mozilla::dom::SharedWorkerParent(); + return agent.forget().take(); +} + +IPCResult BackgroundParentImpl::RecvPSharedWorkerConstructor( + PSharedWorkerParent* aActor, const mozilla::dom::RemoteWorkerData& aData, + const uint64_t& aWindowID, + const mozilla::dom::MessagePortIdentifier& aPortIdentifier) { + mozilla::dom::SharedWorkerParent* actor = + static_cast<mozilla::dom::SharedWorkerParent*>(aActor); + actor->Initialize(aData, aWindowID, aPortIdentifier); + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPSharedWorkerParent( + mozilla::dom::PSharedWorkerParent* aActor) { + RefPtr<mozilla::dom::SharedWorkerParent> actor = + dont_AddRef(static_cast<mozilla::dom::SharedWorkerParent*>(aActor)); + return true; +} + +dom::PFileCreatorParent* BackgroundParentImpl::AllocPFileCreatorParent( + const nsAString& aFullPath, const nsAString& aType, const nsAString& aName, + const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck, + const bool& aIsFromNsIFile) { + RefPtr<dom::FileCreatorParent> actor = new dom::FileCreatorParent(); + return actor.forget().take(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPFileCreatorConstructor( + dom::PFileCreatorParent* aActor, const nsAString& aFullPath, + const nsAString& aType, const nsAString& aName, + const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck, + const bool& aIsFromNsIFile) { + bool isFileRemoteType = false; + + // If the ContentParentHandle is null we are dealing with a same-process + // actor. + RefPtr<ThreadsafeContentParentHandle> parent = + BackgroundParent::GetContentParentHandle(this); + if (!parent) { + isFileRemoteType = true; + } else { + isFileRemoteType = parent->GetRemoteType() == FILE_REMOTE_TYPE; + } + + dom::FileCreatorParent* actor = static_cast<dom::FileCreatorParent*>(aActor); + + // We allow the creation of File via this IPC call only for the 'file' process + // or for testing. + if (!isFileRemoteType && !StaticPrefs::dom_file_createInChild()) { + Unused << dom::FileCreatorParent::Send__delete__( + actor, dom::FileCreationErrorResult(NS_ERROR_DOM_INVALID_STATE_ERR)); + return IPC_OK(); + } + + return actor->CreateAndShareFile(aFullPath, aType, aName, aLastModified, + aExistenceCheck, aIsFromNsIFile); +} + +bool BackgroundParentImpl::DeallocPFileCreatorParent( + dom::PFileCreatorParent* aActor) { + RefPtr<dom::FileCreatorParent> actor = + dont_AddRef(static_cast<dom::FileCreatorParent*>(aActor)); + return true; +} + +dom::PTemporaryIPCBlobParent* +BackgroundParentImpl::AllocPTemporaryIPCBlobParent() { + return new dom::TemporaryIPCBlobParent(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPTemporaryIPCBlobConstructor( + dom::PTemporaryIPCBlobParent* aActor) { + dom::TemporaryIPCBlobParent* actor = + static_cast<dom::TemporaryIPCBlobParent*>(aActor); + return actor->CreateAndShareFile(); +} + +bool BackgroundParentImpl::DeallocPTemporaryIPCBlobParent( + dom::PTemporaryIPCBlobParent* aActor) { + delete aActor; + return true; +} + +already_AddRefed<BackgroundParentImpl::PVsyncParent> +BackgroundParentImpl::AllocPVsyncParent() { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<mozilla::dom::VsyncParent> actor = new mozilla::dom::VsyncParent(); + + RefPtr<mozilla::VsyncDispatcher> vsyncDispatcher = + gfxPlatform::GetPlatform()->GetGlobalVsyncDispatcher(); + actor->UpdateVsyncDispatcher(vsyncDispatcher); + return actor.forget(); +} + +camera::PCamerasParent* BackgroundParentImpl::AllocPCamerasParent() { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + +#ifdef MOZ_WEBRTC + RefPtr<mozilla::camera::CamerasParent> actor = + mozilla::camera::CamerasParent::Create(); + return actor.forget().take(); +#else + return nullptr; +#endif +} + +#ifdef MOZ_WEBRTC +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPCamerasConstructor( + camera::PCamerasParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + return static_cast<camera::CamerasParent*>(aActor)->RecvPCamerasConstructor(); +} +#endif + +bool BackgroundParentImpl::DeallocPCamerasParent( + camera::PCamerasParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + +#ifdef MOZ_WEBRTC + RefPtr<mozilla::camera::CamerasParent> actor = + dont_AddRef(static_cast<mozilla::camera::CamerasParent*>(aActor)); +#endif + return true; +} + +auto BackgroundParentImpl::AllocPUDPSocketParent( + const Maybe<PrincipalInfo>& /* unused */, const nsACString& /* unused */) + -> PUDPSocketParent* { + RefPtr<UDPSocketParent> p = new UDPSocketParent(this); + + return p.forget().take(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPUDPSocketConstructor( + PUDPSocketParent* aActor, const Maybe<PrincipalInfo>& aOptionalPrincipal, + const nsACString& aFilter) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (aOptionalPrincipal.isSome()) { + // Support for checking principals (for non-mtransport use) will be handled + // in bug 1167039 + return IPC_FAIL_NO_REASON(this); + } + // No principal - This must be from mtransport (WebRTC/ICE) - We'd want + // to DispatchToMainThread() here, but if we do we must block RecvBind() + // until Init() gets run. Since we don't have a principal, and we verify + // we have a filter, we can safely skip the Dispatch and just invoke Init() + // to install the filter. + + // For mtransport, this will always be "stun", which doesn't allow outbound + // packets if they aren't STUN packets until a STUN response is seen. + if (!aFilter.EqualsASCII(NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX)) { + return IPC_FAIL_NO_REASON(this); + } + + if (!static_cast<UDPSocketParent*>(aActor)->Init(nullptr, aFilter)) { + MOZ_CRASH("UDPSocketCallback - failed init"); + } + + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPUDPSocketParent(PUDPSocketParent* actor) { + UDPSocketParent* p = static_cast<UDPSocketParent*>(actor); + p->Release(); + return true; +} + +mozilla::dom::PBroadcastChannelParent* +BackgroundParentImpl::AllocPBroadcastChannelParent( + const PrincipalInfo& aPrincipalInfo, const nsACString& aOrigin, + const nsAString& aChannel) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + nsString originChannelKey; + + // The format of originChannelKey is: + // <channelName>|<origin+OriginAttributes> + + originChannelKey.Assign(aChannel); + + originChannelKey.AppendLiteral("|"); + + originChannelKey.Append(NS_ConvertUTF8toUTF16(aOrigin)); + + return new BroadcastChannelParent(originChannelKey); +} + +namespace { + +class CheckPrincipalRunnable final : public Runnable { + public: + CheckPrincipalRunnable( + already_AddRefed<ThreadsafeContentParentHandle> aParent, + const PrincipalInfo& aPrincipalInfo, const nsACString& aOrigin) + : Runnable("ipc::CheckPrincipalRunnable"), + mContentParent(aParent), + mPrincipalInfo(aPrincipalInfo), + mOrigin(aOrigin) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + MOZ_ASSERT(mContentParent); + } + + NS_IMETHOD Run() override { + AssertIsOnMainThread(); + RefPtr<ContentParent> contentParent = mContentParent->GetContentParent(); + if (!contentParent) { + return NS_OK; + } + + auto principalOrErr = PrincipalInfoToPrincipal(mPrincipalInfo); + if (NS_WARN_IF(principalOrErr.isErr())) { + contentParent->KillHard( + "BroadcastChannel killed: PrincipalInfoToPrincipal failed."); + return NS_OK; + } + + nsAutoCString origin; + nsresult rv = principalOrErr.unwrap()->GetOrigin(origin); + if (NS_FAILED(rv)) { + contentParent->KillHard( + "BroadcastChannel killed: principal::GetOrigin failed."); + return NS_OK; + } + + if (NS_WARN_IF(!mOrigin.Equals(origin))) { + contentParent->KillHard("BroadcastChannel killed: origins do not match."); + return NS_OK; + } + + return NS_OK; + } + + private: + RefPtr<ThreadsafeContentParentHandle> mContentParent; + PrincipalInfo mPrincipalInfo; + nsCString mOrigin; +}; + +} // namespace + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBroadcastChannelConstructor( + PBroadcastChannelParent* actor, const PrincipalInfo& aPrincipalInfo, + const nsACString& aOrigin, const nsAString& aChannel) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<ThreadsafeContentParentHandle> parent = + BackgroundParent::GetContentParentHandle(this); + + // If the ContentParent is null we are dealing with a same-process actor. + if (!parent) { + return IPC_OK(); + } + + // XXX The principal can be checked right here on the PBackground thread + // since BackgroundParentImpl now overrides the ProcessingError method and + // kills invalid child processes (IPC_FAIL triggers a processing error). + + RefPtr<CheckPrincipalRunnable> runnable = + new CheckPrincipalRunnable(parent.forget(), aPrincipalInfo, aOrigin); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable)); + + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPBroadcastChannelParent( + PBroadcastChannelParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + delete static_cast<BroadcastChannelParent*>(aActor); + return true; +} + +mozilla::dom::PServiceWorkerManagerParent* +BackgroundParentImpl::AllocPServiceWorkerManagerParent() { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<dom::ServiceWorkerManagerParent> agent = + new dom::ServiceWorkerManagerParent(); + return agent.forget().take(); +} + +bool BackgroundParentImpl::DeallocPServiceWorkerManagerParent( + PServiceWorkerManagerParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + RefPtr<dom::ServiceWorkerManagerParent> parent = + dont_AddRef(static_cast<dom::ServiceWorkerManagerParent*>(aActor)); + MOZ_ASSERT(parent); + return true; +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvShutdownServiceWorkerRegistrar() { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (BackgroundParent::IsOtherProcessActor(this)) { + return IPC_FAIL_NO_REASON(this); + } + + RefPtr<dom::ServiceWorkerRegistrar> service = + dom::ServiceWorkerRegistrar::Get(); + MOZ_ASSERT(service); + + service->Shutdown(); + return IPC_OK(); +} + +already_AddRefed<PCacheStorageParent> +BackgroundParentImpl::AllocPCacheStorageParent( + const Namespace& aNamespace, const PrincipalInfo& aPrincipalInfo) { + return dom::cache::AllocPCacheStorageParent(this, aNamespace, aPrincipalInfo); +} + +PMessagePortParent* BackgroundParentImpl::AllocPMessagePortParent( + const nsID& aUUID, const nsID& aDestinationUUID, + const uint32_t& aSequenceID) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return new MessagePortParent(aUUID); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPMessagePortConstructor( + PMessagePortParent* aActor, const nsID& aUUID, const nsID& aDestinationUUID, + const uint32_t& aSequenceID) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + MessagePortParent* mp = static_cast<MessagePortParent*>(aActor); + if (!mp->Entangle(aDestinationUUID, aSequenceID)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPMessagePortParent( + PMessagePortParent* aActor) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + delete static_cast<MessagePortParent*>(aActor); + return true; +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvMessagePortForceClose( + const nsID& aUUID, const nsID& aDestinationUUID, + const uint32_t& aSequenceID) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (!MessagePortParent::ForceClose(aUUID, aDestinationUUID, aSequenceID)) { + return IPC_FAIL(this, "MessagePortParent::ForceClose failed."); + } + + return IPC_OK(); +} + +already_AddRefed<BackgroundParentImpl::PQuotaParent> +BackgroundParentImpl::AllocPQuotaParent() { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::quota::AllocPQuotaParent(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvShutdownQuotaManager() { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (BackgroundParent::IsOtherProcessActor(this)) { + return IPC_FAIL_NO_REASON(this); + } + + if (!mozilla::dom::quota::RecvShutdownQuotaManager()) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvShutdownBackgroundSessionStorageManagers() { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (BackgroundParent::IsOtherProcessActor(this)) { + return IPC_FAIL_NO_REASON(this); + } + + if (!mozilla::dom::RecvShutdownBackgroundSessionStorageManagers()) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPropagateBackgroundSessionStorageManager( + const uint64_t& aCurrentTopContextId, const uint64_t& aTargetTopContextId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (BackgroundParent::IsOtherProcessActor(this)) { + return IPC_FAIL(this, "Wrong actor"); + } + + mozilla::dom::RecvPropagateBackgroundSessionStorageManager( + aCurrentTopContextId, aTargetTopContextId); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvRemoveBackgroundSessionStorageManager( + const uint64_t& aTopContextId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (BackgroundParent::IsOtherProcessActor(this)) { + return IPC_FAIL_NO_REASON(this); + } + + if (!mozilla::dom::RecvRemoveBackgroundSessionStorageManager(aTopContextId)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvGetSessionStorageManagerData( + const uint64_t& aTopContextId, const uint32_t& aSizeLimit, + const bool& aCancelSessionStoreTimer, + GetSessionStorageManagerDataResolver&& aResolver) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (BackgroundParent::IsOtherProcessActor(this)) { + return IPC_FAIL(this, "Wrong actor"); + } + + if (!mozilla::dom::RecvGetSessionStorageData(aTopContextId, aSizeLimit, + aCancelSessionStoreTimer, + std::move(aResolver))) { + return IPC_FAIL(this, "Couldn't get session storage data"); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvLoadSessionStorageManagerData( + const uint64_t& aTopContextId, + nsTArray<mozilla::dom::SSCacheCopy>&& aOriginCacheCopy) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (BackgroundParent::IsOtherProcessActor(this)) { + return IPC_FAIL(this, "Wrong actor"); + } + + if (!mozilla::dom::RecvLoadSessionStorageData(aTopContextId, + std::move(aOriginCacheCopy))) { + return IPC_FAIL_NO_REASON(this); + } + + return IPC_OK(); +} + +already_AddRefed<dom::PFileSystemRequestParent> +BackgroundParentImpl::AllocPFileSystemRequestParent( + const FileSystemParams& aParams) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<FileSystemRequestParent> result = new FileSystemRequestParent(); + + if (NS_WARN_IF(!result->Initialize(aParams))) { + return nullptr; + } + + return result.forget(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPFileSystemRequestConstructor( + PFileSystemRequestParent* aActor, const FileSystemParams& params) { + static_cast<FileSystemRequestParent*>(aActor)->Start(); + return IPC_OK(); +} + +// Gamepad API Background IPC +already_AddRefed<dom::PGamepadEventChannelParent> +BackgroundParentImpl::AllocPGamepadEventChannelParent() { + return dom::GamepadEventChannelParent::Create(); +} + +already_AddRefed<dom::PGamepadTestChannelParent> +BackgroundParentImpl::AllocPGamepadTestChannelParent() { + return dom::GamepadTestChannelParent::Create(); +} + +dom::PWebAuthnTransactionParent* +BackgroundParentImpl::AllocPWebAuthnTransactionParent() { + return new dom::WebAuthnTransactionParent(); +} + +bool BackgroundParentImpl::DeallocPWebAuthnTransactionParent( + dom::PWebAuthnTransactionParent* aActor) { + MOZ_ASSERT(aActor); + delete aActor; + return true; +} + +already_AddRefed<net::PHttpBackgroundChannelParent> +BackgroundParentImpl::AllocPHttpBackgroundChannelParent( + const uint64_t& aChannelId) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<net::HttpBackgroundChannelParent> actor = + new net::HttpBackgroundChannelParent(); + return actor.forget(); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPHttpBackgroundChannelConstructor( + net::PHttpBackgroundChannelParent* aActor, const uint64_t& aChannelId) { + MOZ_ASSERT(aActor); + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + net::HttpBackgroundChannelParent* aParent = + static_cast<net::HttpBackgroundChannelParent*>(aActor); + + if (NS_WARN_IF(NS_FAILED(aParent->Init(aChannelId)))) { + return IPC_FAIL_NO_REASON(this); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvCreateMIDIPort( + Endpoint<PMIDIPortParent>&& aEndpoint, const MIDIPortInfo& aPortInfo, + const bool& aSysexEnabled) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (!aEndpoint.IsValid()) { + return IPC_FAIL(this, "invalid endpoint for MIDIPort"); + } + + MIDIPlatformService::OwnerThread()->Dispatch(NS_NewRunnableFunction( + "CreateMIDIPortRunnable", [=, endpoint = std::move(aEndpoint)]() mutable { + RefPtr<MIDIPortParent> result = + new MIDIPortParent(aPortInfo, aSysexEnabled); + endpoint.Bind(result); + })); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvCreateMIDIManager( + Endpoint<PMIDIManagerParent>&& aEndpoint) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + if (!aEndpoint.IsValid()) { + return IPC_FAIL(this, "invalid endpoint for MIDIManager"); + } + + MIDIPlatformService::OwnerThread()->Dispatch(NS_NewRunnableFunction( + "CreateMIDIManagerRunnable", + [=, endpoint = std::move(aEndpoint)]() mutable { + RefPtr<MIDIManagerParent> result = new MIDIManagerParent(); + endpoint.Bind(result); + MIDIPlatformService::Get()->AddManager(result); + })); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvHasMIDIDevice( + HasMIDIDeviceResolver&& aResolver) { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + InvokeAsync(MIDIPlatformService::OwnerThread(), __func__, + []() { + bool hasDevice = MIDIPlatformService::Get()->HasDevice(); + return BoolPromise::CreateAndResolve(hasDevice, __func__); + }) + ->Then(GetCurrentSerialEventTarget(), __func__, + [resolver = std::move(aResolver)]( + const BoolPromise::ResolveOrRejectValue& r) { + resolver(r.IsResolve() && r.ResolveValue()); + }); + + return IPC_OK(); +} + +already_AddRefed<mozilla::dom::PClientManagerParent> +BackgroundParentImpl::AllocPClientManagerParent() { + return mozilla::dom::AllocClientManagerParent(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPClientManagerConstructor( + mozilla::dom::PClientManagerParent* aActor) { + mozilla::dom::InitClientManagerParent(aActor); + return IPC_OK(); +} + +IPCResult BackgroundParentImpl::RecvStorageActivity( + const PrincipalInfo& aPrincipalInfo) { + dom::StorageActivityService::SendActivity(aPrincipalInfo); + return IPC_OK(); +} + +IPCResult BackgroundParentImpl::RecvPServiceWorkerManagerConstructor( + PServiceWorkerManagerParent* const aActor) { + // Only the parent process is allowed to construct this actor. + if (BackgroundParent::IsOtherProcessActor(this)) { + return IPC_FAIL_NO_REASON(aActor); + } + return IPC_OK(); +} + +already_AddRefed<PServiceWorkerParent> +BackgroundParentImpl::AllocPServiceWorkerParent( + const IPCServiceWorkerDescriptor&) { + return MakeAndAddRef<ServiceWorkerParent>(); +} + +IPCResult BackgroundParentImpl::RecvPServiceWorkerConstructor( + PServiceWorkerParent* aActor, + const IPCServiceWorkerDescriptor& aDescriptor) { + dom::InitServiceWorkerParent(aActor, aDescriptor); + return IPC_OK(); +} + +already_AddRefed<PServiceWorkerContainerParent> +BackgroundParentImpl::AllocPServiceWorkerContainerParent() { + return MakeAndAddRef<mozilla::dom::ServiceWorkerContainerParent>(); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPServiceWorkerContainerConstructor( + PServiceWorkerContainerParent* aActor) { + dom::InitServiceWorkerContainerParent(aActor); + return IPC_OK(); +} + +already_AddRefed<PServiceWorkerRegistrationParent> +BackgroundParentImpl::AllocPServiceWorkerRegistrationParent( + const IPCServiceWorkerRegistrationDescriptor&) { + return MakeAndAddRef<mozilla::dom::ServiceWorkerRegistrationParent>(); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPServiceWorkerRegistrationConstructor( + PServiceWorkerRegistrationParent* aActor, + const IPCServiceWorkerRegistrationDescriptor& aDescriptor) { + dom::InitServiceWorkerRegistrationParent(aActor, aDescriptor); + return IPC_OK(); +} + +dom::PEndpointForReportParent* +BackgroundParentImpl::AllocPEndpointForReportParent( + const nsAString& aGroupName, const PrincipalInfo& aPrincipalInfo) { + RefPtr<dom::EndpointForReportParent> actor = + new dom::EndpointForReportParent(); + return actor.forget().take(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPEndpointForReportConstructor( + PEndpointForReportParent* aActor, const nsAString& aGroupName, + const PrincipalInfo& aPrincipalInfo) { + static_cast<dom::EndpointForReportParent*>(aActor)->Run(aGroupName, + aPrincipalInfo); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvEnsureRDDProcessAndCreateBridge( + EnsureRDDProcessAndCreateBridgeResolver&& aResolver) { + using Type = std::tuple<const nsresult&, + Endpoint<mozilla::PRemoteDecoderManagerChild>&&>; + + RefPtr<ThreadsafeContentParentHandle> parent = + BackgroundParent::GetContentParentHandle(this); + if (NS_WARN_IF(!parent)) { + aResolver( + Type(NS_ERROR_NOT_AVAILABLE, Endpoint<PRemoteDecoderManagerChild>())); + return IPC_OK(); + } + + RDDProcessManager* rdd = RDDProcessManager::Get(); + if (!rdd) { + aResolver( + Type(NS_ERROR_NOT_AVAILABLE, Endpoint<PRemoteDecoderManagerChild>())); + return IPC_OK(); + } + + rdd->EnsureRDDProcessAndCreateBridge(OtherPid(), parent->ChildID()) + ->Then(GetCurrentSerialEventTarget(), __func__, + [resolver = std::move(aResolver)]( + mozilla::RDDProcessManager::EnsureRDDPromise:: + ResolveOrRejectValue&& aValue) mutable { + if (aValue.IsReject()) { + resolver(Type(aValue.RejectValue(), + Endpoint<PRemoteDecoderManagerChild>())); + return; + } + resolver(Type(NS_OK, std::move(aValue.ResolveValue()))); + }); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvEnsureUtilityProcessAndCreateBridge( + const RemoteDecodeIn& aLocation, + EnsureUtilityProcessAndCreateBridgeResolver&& aResolver) { + base::ProcessId otherPid = OtherPid(); + RefPtr<ThreadsafeContentParentHandle> parent = + BackgroundParent::GetContentParentHandle(this); + if (NS_WARN_IF(!parent)) { + return IPC_FAIL_NO_REASON(this); + } + dom::ContentParentId childId = parent->ChildID(); + nsCOMPtr<nsISerialEventTarget> managerThread = GetCurrentSerialEventTarget(); + if (!managerThread) { + return IPC_FAIL_NO_REASON(this); + } + NS_DispatchToMainThread(NS_NewRunnableFunction( + "BackgroundParentImpl::RecvEnsureUtilityProcessAndCreateBridge()", + [aResolver, managerThread, otherPid, childId, aLocation]() { + RefPtr<UtilityProcessManager> upm = + UtilityProcessManager::GetSingleton(); + using Type = + std::tuple<const nsresult&, + Endpoint<mozilla::PRemoteDecoderManagerChild>&&>; + if (!upm) { + managerThread->Dispatch(NS_NewRunnableFunction( + "BackgroundParentImpl::RecvEnsureUtilityProcessAndCreateBridge::" + "Failure", + [aResolver]() { + aResolver(Type(NS_ERROR_NOT_AVAILABLE, + Endpoint<PRemoteDecoderManagerChild>())); + })); + } else { + SandboxingKind sbKind = GetSandboxingKindFromLocation(aLocation); + upm->StartProcessForRemoteMediaDecoding(otherPid, childId, sbKind) + ->Then(managerThread, __func__, + [resolver = aResolver]( + mozilla::ipc::UtilityProcessManager:: + StartRemoteDecodingUtilityPromise:: + ResolveOrRejectValue&& aValue) mutable { + if (aValue.IsReject()) { + resolver(Type(aValue.RejectValue(), + Endpoint<PRemoteDecoderManagerChild>())); + return; + } + resolver(Type(NS_OK, std::move(aValue.ResolveValue()))); + }); + } + })); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvRequestCameraAccess( + const bool& aAllowPermissionRequest, + RequestCameraAccessResolver&& aResolver) { +#ifdef MOZ_WEBRTC + mozilla::camera::CamerasParent::RequestCameraAccess(aAllowPermissionRequest) + ->Then(GetCurrentSerialEventTarget(), __func__, + [resolver = std::move(aResolver)]( + const mozilla::camera::CamerasParent:: + CameraAccessRequestPromise::ResolveOrRejectValue& aValue) { + if (aValue.IsResolve()) { + resolver(aValue.ResolveValue()); + } else { + resolver(CamerasAccessStatus::Error); + } + }); +#else + aResolver(CamerasAccessStatus::Error); +#endif + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPEndpointForReportParent( + PEndpointForReportParent* aActor) { + RefPtr<dom::EndpointForReportParent> actor = + dont_AddRef(static_cast<dom::EndpointForReportParent*>(aActor)); + return true; +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvRemoveEndpoint( + const nsAString& aGroupName, const nsACString& aEndpointURL, + const PrincipalInfo& aPrincipalInfo) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "BackgroundParentImpl::RecvRemoveEndpoint(", + [aGroupName = nsString(aGroupName), + aEndpointURL = nsCString(aEndpointURL), aPrincipalInfo]() { + dom::ReportingHeader::RemoveEndpoint(aGroupName, aEndpointURL, + aPrincipalInfo); + })); + + return IPC_OK(); +} + +already_AddRefed<dom::locks::PLockManagerParent> +BackgroundParentImpl::AllocPLockManagerParent(NotNull<nsIPrincipal*> aPrincipal, + const nsID& aClientId) { + return MakeAndAddRef<mozilla::dom::locks::LockManagerParent>(aPrincipal, + aClientId); +} + +already_AddRefed<dom::PFetchParent> BackgroundParentImpl::AllocPFetchParent() { + return MakeAndAddRef<dom::FetchParent>(); +} + +} // namespace mozilla::ipc + +void TestParent::ActorDestroy(ActorDestroyReason aWhy) { + mozilla::ipc::AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); +} diff --git a/ipc/glue/BackgroundParentImpl.h b/ipc/glue/BackgroundParentImpl.h new file mode 100644 index 0000000000..42f40a1a2b --- /dev/null +++ b/ipc/glue/BackgroundParentImpl.h @@ -0,0 +1,363 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_backgroundparentimpl_h__ +#define mozilla_ipc_backgroundparentimpl_h__ + +#include "mozilla/ipc/PBackgroundParent.h" + +namespace mozilla::ipc { + +// Instances of this class should never be created directly. This class is meant +// to be inherited in BackgroundImpl. +class BackgroundParentImpl : public PBackgroundParent { + protected: + BackgroundParentImpl(); + virtual ~BackgroundParentImpl(); + + void ProcessingError(Result aCode, const char* aReason) override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + + PBackgroundTestParent* AllocPBackgroundTestParent( + const nsACString& aTestArg) override; + + mozilla::ipc::IPCResult RecvPBackgroundTestConstructor( + PBackgroundTestParent* aActor, const nsACString& aTestArg) override; + + bool DeallocPBackgroundTestParent(PBackgroundTestParent* aActor) override; + + already_AddRefed<PBackgroundIDBFactoryParent> + AllocPBackgroundIDBFactoryParent(const LoggingInfo& aLoggingInfo) override; + + mozilla::ipc::IPCResult RecvPBackgroundIDBFactoryConstructor( + PBackgroundIDBFactoryParent* aActor, + const LoggingInfo& aLoggingInfo) override; + + PBackgroundIndexedDBUtilsParent* AllocPBackgroundIndexedDBUtilsParent() + override; + + bool DeallocPBackgroundIndexedDBUtilsParent( + PBackgroundIndexedDBUtilsParent* aActor) override; + + mozilla::ipc::IPCResult RecvFlushPendingFileDeletions() override; + + already_AddRefed<PBackgroundSDBConnectionParent> + AllocPBackgroundSDBConnectionParent( + const PersistenceType& aPersistenceType, + const PrincipalInfo& aPrincipalInfo) override; + + mozilla::ipc::IPCResult RecvPBackgroundSDBConnectionConstructor( + PBackgroundSDBConnectionParent* aActor, + const PersistenceType& aPersistenceType, + const PrincipalInfo& aPrincipalInfo) override; + + already_AddRefed<PBackgroundLSDatabaseParent> + AllocPBackgroundLSDatabaseParent(const PrincipalInfo& aPrincipalInfo, + const uint32_t& aPrivateBrowsingId, + const uint64_t& aDatastoreId) override; + + mozilla::ipc::IPCResult RecvPBackgroundLSDatabaseConstructor( + PBackgroundLSDatabaseParent* aActor, const PrincipalInfo& aPrincipalInfo, + const uint32_t& aPrivateBrowsingId, + const uint64_t& aDatastoreId) override; + + PBackgroundLSObserverParent* AllocPBackgroundLSObserverParent( + const uint64_t& aObserverId) override; + + mozilla::ipc::IPCResult RecvPBackgroundLSObserverConstructor( + PBackgroundLSObserverParent* aActor, + const uint64_t& aObserverId) override; + + bool DeallocPBackgroundLSObserverParent( + PBackgroundLSObserverParent* aActor) override; + + PBackgroundLSRequestParent* AllocPBackgroundLSRequestParent( + const LSRequestParams& aParams) override; + + mozilla::ipc::IPCResult RecvPBackgroundLSRequestConstructor( + PBackgroundLSRequestParent* aActor, + const LSRequestParams& aParams) override; + + bool DeallocPBackgroundLSRequestParent( + PBackgroundLSRequestParent* aActor) override; + + PBackgroundLSSimpleRequestParent* AllocPBackgroundLSSimpleRequestParent( + const LSSimpleRequestParams& aParams) override; + + mozilla::ipc::IPCResult RecvPBackgroundLSSimpleRequestConstructor( + PBackgroundLSSimpleRequestParent* aActor, + const LSSimpleRequestParams& aParams) override; + + bool DeallocPBackgroundLSSimpleRequestParent( + PBackgroundLSSimpleRequestParent* aActor) override; + + PBackgroundLocalStorageCacheParent* AllocPBackgroundLocalStorageCacheParent( + const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey, + const uint32_t& aPrivateBrowsingId) override; + + mozilla::ipc::IPCResult RecvPBackgroundLocalStorageCacheConstructor( + PBackgroundLocalStorageCacheParent* aActor, + const PrincipalInfo& aPrincipalInfo, const nsACString& aOriginKey, + const uint32_t& aPrivateBrowsingId) override; + + bool DeallocPBackgroundLocalStorageCacheParent( + PBackgroundLocalStorageCacheParent* aActor) override; + + PBackgroundStorageParent* AllocPBackgroundStorageParent( + const nsAString& aProfilePath, + const uint32_t& aPrivateBrowsingId) override; + + mozilla::ipc::IPCResult RecvPBackgroundStorageConstructor( + PBackgroundStorageParent* aActor, const nsAString& aProfilePath, + const uint32_t& aPrivateBrowsingId) override; + + bool DeallocPBackgroundStorageParent( + PBackgroundStorageParent* aActor) override; + + already_AddRefed<PBackgroundSessionStorageManagerParent> + AllocPBackgroundSessionStorageManagerParent( + const uint64_t& aTopContextId) override; + + already_AddRefed<PBackgroundSessionStorageServiceParent> + AllocPBackgroundSessionStorageServiceParent() override; + + mozilla::ipc::IPCResult RecvCreateFileSystemManagerParent( + const PrincipalInfo& aPrincipalInfo, + Endpoint<mozilla::dom::PFileSystemManagerParent>&& aParentEndpoint, + CreateFileSystemManagerParentResolver&& aResolver) override; + + mozilla::ipc::IPCResult RecvCreateWebTransportParent( + const nsAString& aURL, nsIPrincipal* aPrincipal, + const mozilla::Maybe<IPCClientInfo>& aClientInfo, const bool& aDedicated, + const bool& aRequireUnreliable, const uint32_t& aCongestionControl, + nsTArray<WebTransportHash>&& aServerCertHashes, + Endpoint<PWebTransportParent>&& aParentEndpoint, + CreateWebTransportParentResolver&& aResolver) override; + + already_AddRefed<PIdleSchedulerParent> AllocPIdleSchedulerParent() override; + + PTemporaryIPCBlobParent* AllocPTemporaryIPCBlobParent() override; + + mozilla::ipc::IPCResult RecvPTemporaryIPCBlobConstructor( + PTemporaryIPCBlobParent* actor) override; + + bool DeallocPTemporaryIPCBlobParent(PTemporaryIPCBlobParent* aActor) override; + + PFileCreatorParent* AllocPFileCreatorParent( + const nsAString& aFullPath, const nsAString& aType, + const nsAString& aName, const Maybe<int64_t>& aLastModified, + const bool& aExistenceCheck, const bool& aIsFromNsIFile) override; + + mozilla::ipc::IPCResult RecvPFileCreatorConstructor( + PFileCreatorParent* actor, const nsAString& aFullPath, + const nsAString& aType, const nsAString& aName, + const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck, + const bool& aIsFromNsIFile) override; + + bool DeallocPFileCreatorParent(PFileCreatorParent* aActor) override; + + already_AddRefed<mozilla::dom::PRemoteWorkerControllerParent> + AllocPRemoteWorkerControllerParent( + const mozilla::dom::RemoteWorkerData& aRemoteWorkerData) override; + + mozilla::ipc::IPCResult RecvPRemoteWorkerControllerConstructor( + mozilla::dom::PRemoteWorkerControllerParent* aActor, + const mozilla::dom::RemoteWorkerData& aRemoteWorkerData) override; + + already_AddRefed<PRemoteWorkerServiceParent> AllocPRemoteWorkerServiceParent() + override; + + mozilla::ipc::IPCResult RecvPRemoteWorkerServiceConstructor( + PRemoteWorkerServiceParent* aActor) override; + + mozilla::dom::PSharedWorkerParent* AllocPSharedWorkerParent( + const mozilla::dom::RemoteWorkerData& aData, const uint64_t& aWindowID, + const mozilla::dom::MessagePortIdentifier& aPortIdentifier) override; + + mozilla::ipc::IPCResult RecvPSharedWorkerConstructor( + PSharedWorkerParent* aActor, const mozilla::dom::RemoteWorkerData& aData, + const uint64_t& aWindowID, + const mozilla::dom::MessagePortIdentifier& aPortIdentifier) override; + + bool DeallocPSharedWorkerParent(PSharedWorkerParent* aActor) override; + + already_AddRefed<PVsyncParent> AllocPVsyncParent() override; + + PBroadcastChannelParent* AllocPBroadcastChannelParent( + const PrincipalInfo& aPrincipalInfo, const nsACString& aOrigin, + const nsAString& aChannel) override; + + mozilla::ipc::IPCResult RecvPBroadcastChannelConstructor( + PBroadcastChannelParent* actor, const PrincipalInfo& aPrincipalInfo, + const nsACString& origin, const nsAString& channel) override; + + bool DeallocPBroadcastChannelParent(PBroadcastChannelParent* aActor) override; + + PServiceWorkerManagerParent* AllocPServiceWorkerManagerParent() override; + + bool DeallocPServiceWorkerManagerParent( + PServiceWorkerManagerParent* aActor) override; + + PCamerasParent* AllocPCamerasParent() override; +#ifdef MOZ_WEBRTC + mozilla::ipc::IPCResult RecvPCamerasConstructor( + PCamerasParent* aActor) override; +#endif + bool DeallocPCamerasParent(PCamerasParent* aActor) override; + + mozilla::ipc::IPCResult RecvShutdownServiceWorkerRegistrar() override; + + already_AddRefed<dom::cache::PCacheStorageParent> AllocPCacheStorageParent( + const dom::cache::Namespace& aNamespace, + const PrincipalInfo& aPrincipalInfo) override; + + PUDPSocketParent* AllocPUDPSocketParent(const Maybe<PrincipalInfo>& pInfo, + const nsACString& aFilter) override; + mozilla::ipc::IPCResult RecvPUDPSocketConstructor( + PUDPSocketParent*, const Maybe<PrincipalInfo>& aPrincipalInfo, + const nsACString& aFilter) override; + bool DeallocPUDPSocketParent(PUDPSocketParent*) override; + + PMessagePortParent* AllocPMessagePortParent( + const nsID& aUUID, const nsID& aDestinationUUID, + const uint32_t& aSequenceID) override; + + mozilla::ipc::IPCResult RecvPMessagePortConstructor( + PMessagePortParent* aActor, const nsID& aUUID, + const nsID& aDestinationUUID, const uint32_t& aSequenceID) override; + + bool DeallocPMessagePortParent(PMessagePortParent* aActor) override; + + mozilla::ipc::IPCResult RecvMessagePortForceClose( + const nsID& aUUID, const nsID& aDestinationUUID, + const uint32_t& aSequenceID) override; + + already_AddRefed<PQuotaParent> AllocPQuotaParent() override; + + mozilla::ipc::IPCResult RecvShutdownQuotaManager() override; + + mozilla::ipc::IPCResult RecvShutdownBackgroundSessionStorageManagers() + override; + + mozilla::ipc::IPCResult RecvPropagateBackgroundSessionStorageManager( + const uint64_t& aCurrentTopContextId, + const uint64_t& aTargetTopContextId) override; + + mozilla::ipc::IPCResult RecvRemoveBackgroundSessionStorageManager( + const uint64_t& aTopContextId) override; + + mozilla::ipc::IPCResult RecvLoadSessionStorageManagerData( + const uint64_t& aTopContextId, + nsTArray<mozilla::dom::SSCacheCopy>&& aOriginCacheCopy) override; + + mozilla::ipc::IPCResult RecvGetSessionStorageManagerData( + const uint64_t& aTopContextId, const uint32_t& aSizeLimit, + const bool& aCancelSessionStoreTimer, + GetSessionStorageManagerDataResolver&& aResolver) override; + + already_AddRefed<PFileSystemRequestParent> AllocPFileSystemRequestParent( + const FileSystemParams&) override; + + mozilla::ipc::IPCResult RecvPFileSystemRequestConstructor( + PFileSystemRequestParent* actor, const FileSystemParams& params) override; + + // Gamepad API Background IPC + already_AddRefed<PGamepadEventChannelParent> AllocPGamepadEventChannelParent() + override; + + already_AddRefed<PGamepadTestChannelParent> AllocPGamepadTestChannelParent() + override; + + PWebAuthnTransactionParent* AllocPWebAuthnTransactionParent() override; + + bool DeallocPWebAuthnTransactionParent( + PWebAuthnTransactionParent* aActor) override; + + already_AddRefed<PHttpBackgroundChannelParent> + AllocPHttpBackgroundChannelParent(const uint64_t& aChannelId) override; + + mozilla::ipc::IPCResult RecvPHttpBackgroundChannelConstructor( + PHttpBackgroundChannelParent* aActor, + const uint64_t& aChannelId) override; + + already_AddRefed<PClientManagerParent> AllocPClientManagerParent() override; + + mozilla::ipc::IPCResult RecvPClientManagerConstructor( + PClientManagerParent* aActor) override; + + mozilla::ipc::IPCResult RecvCreateMIDIPort( + Endpoint<PMIDIPortParent>&& aEndpoint, const MIDIPortInfo& aPortInfo, + const bool& aSysexEnabled) override; + + mozilla::ipc::IPCResult RecvCreateMIDIManager( + Endpoint<PMIDIManagerParent>&& aEndpoint) override; + + mozilla::ipc::IPCResult RecvHasMIDIDevice( + HasMIDIDeviceResolver&& aResolver) override; + + mozilla::ipc::IPCResult RecvStorageActivity( + const PrincipalInfo& aPrincipalInfo) override; + + already_AddRefed<PServiceWorkerParent> AllocPServiceWorkerParent( + const IPCServiceWorkerDescriptor&) final; + + mozilla::ipc::IPCResult RecvPServiceWorkerManagerConstructor( + PServiceWorkerManagerParent* aActor) override; + + mozilla::ipc::IPCResult RecvPServiceWorkerConstructor( + PServiceWorkerParent* aActor, + const IPCServiceWorkerDescriptor& aDescriptor) override; + + already_AddRefed<PServiceWorkerContainerParent> + AllocPServiceWorkerContainerParent() final; + + mozilla::ipc::IPCResult RecvPServiceWorkerContainerConstructor( + PServiceWorkerContainerParent* aActor) override; + + already_AddRefed<PServiceWorkerRegistrationParent> + AllocPServiceWorkerRegistrationParent( + const IPCServiceWorkerRegistrationDescriptor&) final; + + mozilla::ipc::IPCResult RecvPServiceWorkerRegistrationConstructor( + PServiceWorkerRegistrationParent* aActor, + const IPCServiceWorkerRegistrationDescriptor& aDescriptor) override; + + PEndpointForReportParent* AllocPEndpointForReportParent( + const nsAString& aGroupName, + const PrincipalInfo& aPrincipalInfo) override; + + mozilla::ipc::IPCResult RecvPEndpointForReportConstructor( + PEndpointForReportParent* actor, const nsAString& aGroupName, + const PrincipalInfo& aPrincipalInfo) override; + + mozilla::ipc::IPCResult RecvEnsureRDDProcessAndCreateBridge( + EnsureRDDProcessAndCreateBridgeResolver&& aResolver) override; + + mozilla::ipc::IPCResult RecvEnsureUtilityProcessAndCreateBridge( + const RemoteDecodeIn& aLocation, + EnsureUtilityProcessAndCreateBridgeResolver&& aResolver) override; + + mozilla::ipc::IPCResult RecvRequestCameraAccess( + const bool& aAllowPermissionRequest, + RequestCameraAccessResolver&& aResolver) override; + + bool DeallocPEndpointForReportParent( + PEndpointForReportParent* aActor) override; + + mozilla::ipc::IPCResult RecvRemoveEndpoint( + const nsAString& aGroupName, const nsACString& aEndpointURL, + const PrincipalInfo& aPrincipalInfo) override; + + already_AddRefed<PLockManagerParent> AllocPLockManagerParent( + NotNull<nsIPrincipal*> aPrincipal, const nsID& aClientId) final; + + already_AddRefed<PFetchParent> AllocPFetchParent() override; +}; + +} // namespace mozilla::ipc + +#endif // mozilla_ipc_backgroundparentimpl_h__ diff --git a/ipc/glue/BackgroundStarterChild.h b/ipc/glue/BackgroundStarterChild.h new file mode 100644 index 0000000000..1a0b5386f8 --- /dev/null +++ b/ipc/glue/BackgroundStarterChild.h @@ -0,0 +1,35 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_BackgroundStarterChild_h +#define mozilla_ipc_BackgroundStarterChild_h + +#include "mozilla/ipc/PBackgroundStarterChild.h" +#include "mozilla/dom/ContentChild.h" + +namespace mozilla::ipc { + +class BackgroundStarterChild final : public PBackgroundStarterChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BackgroundStarterChild, override) + + BackgroundStarterChild(base::ProcessId aOtherPid, + nsISerialEventTarget* aTaskQueue) + : mOtherPid(aOtherPid), mTaskQueue(aTaskQueue) {} + + // Unlike the methods on `IToplevelProtocol`, may be accessed on any thread + // and will not be modified after construction. + const base::ProcessId mOtherPid; + const nsCOMPtr<nsISerialEventTarget> mTaskQueue; + + private: + friend class PBackgroundStarterChild; + ~BackgroundStarterChild() = default; +}; + +} // namespace mozilla::ipc + +#endif // mozilla_ipc_BackgroundStarterChild_h diff --git a/ipc/glue/BackgroundStarterParent.h b/ipc/glue/BackgroundStarterParent.h new file mode 100644 index 0000000000..4312b9ab2a --- /dev/null +++ b/ipc/glue/BackgroundStarterParent.h @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_BackgroundStarterParent_h +#define mozilla_ipc_BackgroundStarterParent_h + +#include "mozilla/ipc/PBackgroundStarterParent.h" +#include "mozilla/dom/ContentParent.h" +#include "nsISupportsImpl.h" + +namespace mozilla::ipc { + +class BackgroundStarterParent final : public PBackgroundStarterParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( + BackgroundStarterParent, override) + + // Implemented in BackgroundImpl.cpp + BackgroundStarterParent(mozilla::dom::ThreadsafeContentParentHandle* aContent, + bool aCrossProcess); + + void SetLiveActorArray(nsTArray<IToplevelProtocol*>* aLiveActorArray); + + private: + friend class PBackgroundStarterParent; + ~BackgroundStarterParent() = default; + + // Implemented in BackgroundImpl.cpp + void ActorDestroy(ActorDestroyReason aReason) override; + + // Implemented in BackgroundImpl.cpp + IPCResult RecvInitBackground(Endpoint<PBackgroundParent>&& aEndpoint); + + const bool mCrossProcess; + + const RefPtr<mozilla::dom::ThreadsafeContentParentHandle> mContent; + + // Set when the actor is opened successfully and used to handle shutdown + // hangs. Only touched on the background thread. + nsTArray<IToplevelProtocol*>* mLiveActorArray = nullptr; +}; + +} // namespace mozilla::ipc + +#endif // mozilla_ipc_BackgroundStarterParent_h diff --git a/ipc/glue/BackgroundUtils.cpp b/ipc/glue/BackgroundUtils.cpp new file mode 100644 index 0000000000..435e29f635 --- /dev/null +++ b/ipc/glue/BackgroundUtils.cpp @@ -0,0 +1,1158 @@ +/* -*- 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 "BackgroundUtils.h" + +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/ContentPrincipal.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/SystemPrincipal.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/net/InterceptionInfo.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "ExpandedPrincipal.h" +#include "nsIScriptSecurityManager.h" +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "mozilla/LoadInfo.h" +#include "nsContentUtils.h" +#include "nsString.h" +#include "nsTArray.h" +#include "mozilla/nsRedirectHistoryEntry.h" +#include "mozilla/dom/nsCSPUtils.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/LoadInfo.h" + +using namespace mozilla::dom; +using namespace mozilla::net; + +namespace mozilla { + +namespace ipc { + +Result<nsCOMPtr<nsIPrincipal>, nsresult> PrincipalInfoToPrincipal( + const PrincipalInfo& aPrincipalInfo) { + MOZ_ASSERT(aPrincipalInfo.type() != PrincipalInfo::T__None); + + nsCOMPtr<nsIPrincipal> principal; + nsresult rv; + + switch (aPrincipalInfo.type()) { + case PrincipalInfo::TSystemPrincipalInfo: { + principal = SystemPrincipal::Get(); + if (NS_WARN_IF(!principal)) { + return Err(NS_ERROR_NOT_INITIALIZED); + } + + return principal; + } + + case PrincipalInfo::TNullPrincipalInfo: { + const NullPrincipalInfo& info = aPrincipalInfo.get_NullPrincipalInfo(); + + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), info.spec()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Err(rv); + } + + if (!uri->SchemeIs(NS_NULLPRINCIPAL_SCHEME)) { + return Err(NS_ERROR_ILLEGAL_VALUE); + } + + principal = NullPrincipal::Create(info.attrs(), uri); + return principal; + } + + case PrincipalInfo::TContentPrincipalInfo: { + const ContentPrincipalInfo& info = + aPrincipalInfo.get_ContentPrincipalInfo(); + + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), info.spec()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Err(rv); + } + + nsCOMPtr<nsIURI> domain; + if (info.domain()) { + rv = NS_NewURI(getter_AddRefs(domain), *info.domain()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Err(rv); + } + } + + principal = + BasePrincipal::CreateContentPrincipal(uri, info.attrs(), domain); + if (NS_WARN_IF(!principal)) { + return Err(NS_ERROR_NULL_POINTER); + } + + // Origin must match what the_new_principal.getOrigin returns. + nsAutoCString originNoSuffix; + rv = principal->GetOriginNoSuffix(originNoSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Err(rv); + } + + if (NS_WARN_IF(!info.originNoSuffix().Equals(originNoSuffix))) { + return Err(NS_ERROR_FAILURE); + } + + if (!info.baseDomain().IsVoid()) { + nsAutoCString baseDomain; + rv = principal->GetBaseDomain(baseDomain); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Err(rv); + } + + if (NS_WARN_IF(!info.baseDomain().Equals(baseDomain))) { + return Err(NS_ERROR_FAILURE); + } + } + return principal; + } + + case PrincipalInfo::TExpandedPrincipalInfo: { + const ExpandedPrincipalInfo& info = + aPrincipalInfo.get_ExpandedPrincipalInfo(); + + nsTArray<nsCOMPtr<nsIPrincipal>> allowlist; + nsCOMPtr<nsIPrincipal> alPrincipal; + + for (uint32_t i = 0; i < info.allowlist().Length(); i++) { + auto principalOrErr = PrincipalInfoToPrincipal(info.allowlist()[i]); + if (NS_WARN_IF(principalOrErr.isErr())) { + nsresult ret = principalOrErr.unwrapErr(); + return Err(ret); + } + // append that principal to the allowlist + allowlist.AppendElement(principalOrErr.unwrap()); + } + + RefPtr<ExpandedPrincipal> expandedPrincipal = + ExpandedPrincipal::Create(allowlist, info.attrs()); + if (!expandedPrincipal) { + return Err(NS_ERROR_FAILURE); + } + + principal = expandedPrincipal; + return principal; + } + + default: + return Err(NS_ERROR_FAILURE); + } +} + +bool StorageKeysEqual(const PrincipalInfo& aLeft, const PrincipalInfo& aRight) { + MOZ_RELEASE_ASSERT(aLeft.type() == PrincipalInfo::TContentPrincipalInfo || + aLeft.type() == PrincipalInfo::TSystemPrincipalInfo); + MOZ_RELEASE_ASSERT(aRight.type() == PrincipalInfo::TContentPrincipalInfo || + aRight.type() == PrincipalInfo::TSystemPrincipalInfo); + + if (aLeft.type() != aRight.type()) { + return false; + } + + if (aLeft.type() == PrincipalInfo::TContentPrincipalInfo) { + const ContentPrincipalInfo& leftContent = aLeft.get_ContentPrincipalInfo(); + const ContentPrincipalInfo& rightContent = + aRight.get_ContentPrincipalInfo(); + + return leftContent.attrs() == rightContent.attrs() && + leftContent.originNoSuffix() == rightContent.originNoSuffix(); + } + + // Storage keys for the System principal always equal. + return true; +} + +already_AddRefed<nsIContentSecurityPolicy> CSPInfoToCSP( + const CSPInfo& aCSPInfo, Document* aRequestingDoc, + nsresult* aOptionalResult) { + MOZ_ASSERT(NS_IsMainThread()); + + nsresult stackResult; + nsresult& rv = aOptionalResult ? *aOptionalResult : stackResult; + + RefPtr<nsCSPContext> csp = new nsCSPContext(); + + if (aRequestingDoc) { + rv = csp->SetRequestContextWithDocument(aRequestingDoc); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + } else { + auto principalOrErr = + PrincipalInfoToPrincipal(aCSPInfo.requestPrincipalInfo()); + if (NS_WARN_IF(principalOrErr.isErr())) { + return nullptr; + } + + nsCOMPtr<nsIURI> selfURI; + if (!aCSPInfo.selfURISpec().IsEmpty()) { + rv = NS_NewURI(getter_AddRefs(selfURI), aCSPInfo.selfURISpec()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + } + + nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap(); + + rv = csp->SetRequestContextWithPrincipal( + principal, selfURI, aCSPInfo.referrer(), aCSPInfo.innerWindowID()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + } + csp->SetSkipAllowInlineStyleCheck(aCSPInfo.skipAllowInlineStyleCheck()); + + for (uint32_t i = 0; i < aCSPInfo.policyInfos().Length(); i++) { + csp->AddIPCPolicy(aCSPInfo.policyInfos()[i]); + } + return csp.forget(); +} + +nsresult CSPToCSPInfo(nsIContentSecurityPolicy* aCSP, CSPInfo* aCSPInfo) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aCSP); + MOZ_ASSERT(aCSPInfo); + + if (!aCSP || !aCSPInfo) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPrincipal> requestPrincipal = aCSP->GetRequestPrincipal(); + + PrincipalInfo requestingPrincipalInfo; + nsresult rv = + PrincipalToPrincipalInfo(requestPrincipal, &requestingPrincipalInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIURI> selfURI = aCSP->GetSelfURI(); + nsAutoCString selfURISpec; + if (selfURI) { + selfURI->GetSpec(selfURISpec); + } + + nsAutoString referrer; + aCSP->GetReferrer(referrer); + + uint64_t windowID = aCSP->GetInnerWindowID(); + bool skipAllowInlineStyleCheck = aCSP->GetSkipAllowInlineStyleCheck(); + + nsTArray<ContentSecurityPolicy> policies; + static_cast<nsCSPContext*>(aCSP)->SerializePolicies(policies); + + *aCSPInfo = CSPInfo(std::move(policies), requestingPrincipalInfo, selfURISpec, + referrer, windowID, skipAllowInlineStyleCheck); + return NS_OK; +} + +nsresult PrincipalToPrincipalInfo(nsIPrincipal* aPrincipal, + PrincipalInfo* aPrincipalInfo, + bool aSkipBaseDomain) { + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aPrincipalInfo); + + nsresult rv; + if (aPrincipal->GetIsNullPrincipal()) { + nsAutoCString spec; + rv = aPrincipal->GetAsciiSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + *aPrincipalInfo = + NullPrincipalInfo(aPrincipal->OriginAttributesRef(), spec); + return NS_OK; + } + + if (aPrincipal->IsSystemPrincipal()) { + *aPrincipalInfo = SystemPrincipalInfo(); + return NS_OK; + } + + // might be an expanded principal + auto* basePrin = BasePrincipal::Cast(aPrincipal); + if (basePrin->Is<ExpandedPrincipal>()) { + auto* expanded = basePrin->As<ExpandedPrincipal>(); + + nsTArray<PrincipalInfo> allowlistInfo; + PrincipalInfo info; + + for (auto& prin : expanded->AllowList()) { + rv = PrincipalToPrincipalInfo(prin, &info, aSkipBaseDomain); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + // append that spec to the allowlist + allowlistInfo.AppendElement(info); + } + + *aPrincipalInfo = ExpandedPrincipalInfo(aPrincipal->OriginAttributesRef(), + std::move(allowlistInfo)); + return NS_OK; + } + + nsAutoCString spec; + rv = aPrincipal->GetAsciiSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString originNoSuffix; + rv = aPrincipal->GetOriginNoSuffix(originNoSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIURI> domainUri; + rv = aPrincipal->GetDomain(getter_AddRefs(domainUri)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + Maybe<nsCString> domain; + if (domainUri) { + domain.emplace(); + rv = domainUri->GetSpec(domain.ref()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // This attribute is not crucial. + nsCString baseDomain; + if (aSkipBaseDomain) { + baseDomain.SetIsVoid(true); + } else { + if (NS_FAILED(aPrincipal->GetBaseDomain(baseDomain))) { + // No warning here. Some principal URLs do not have a base-domain. + baseDomain.SetIsVoid(true); + } + } + + *aPrincipalInfo = + ContentPrincipalInfo(aPrincipal->OriginAttributesRef(), originNoSuffix, + spec, domain, baseDomain); + return NS_OK; +} + +bool IsPrincipalInfoPrivate(const PrincipalInfo& aPrincipalInfo) { + if (aPrincipalInfo.type() != ipc::PrincipalInfo::TContentPrincipalInfo) { + return false; + } + + const ContentPrincipalInfo& info = aPrincipalInfo.get_ContentPrincipalInfo(); + return !!info.attrs().mPrivateBrowsingId; +} + +already_AddRefed<nsIRedirectHistoryEntry> RHEntryInfoToRHEntry( + const RedirectHistoryEntryInfo& aRHEntryInfo) { + auto principalOrErr = PrincipalInfoToPrincipal(aRHEntryInfo.principalInfo()); + if (NS_WARN_IF(principalOrErr.isErr())) { + return nullptr; + } + + nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap(); + nsCOMPtr<nsIURI> referrerUri = DeserializeURI(aRHEntryInfo.referrerUri()); + + nsCOMPtr<nsIRedirectHistoryEntry> entry = new nsRedirectHistoryEntry( + principal, referrerUri, aRHEntryInfo.remoteAddress()); + + return entry.forget(); +} + +nsresult RHEntryToRHEntryInfo(nsIRedirectHistoryEntry* aRHEntry, + RedirectHistoryEntryInfo* aRHEntryInfo) { + MOZ_ASSERT(aRHEntry); + MOZ_ASSERT(aRHEntryInfo); + + nsresult rv; + aRHEntry->GetRemoteAddress(aRHEntryInfo->remoteAddress()); + + nsCOMPtr<nsIURI> referrerUri; + rv = aRHEntry->GetReferrerURI(getter_AddRefs(referrerUri)); + NS_ENSURE_SUCCESS(rv, rv); + SerializeURI(referrerUri, aRHEntryInfo->referrerUri()); + + nsCOMPtr<nsIPrincipal> principal; + rv = aRHEntry->GetPrincipal(getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + + return PrincipalToPrincipalInfo(principal, &aRHEntryInfo->principalInfo()); +} + +nsresult LoadInfoToLoadInfoArgs(nsILoadInfo* aLoadInfo, + LoadInfoArgs* outLoadInfoArgs) { + nsresult rv = NS_OK; + Maybe<PrincipalInfo> loadingPrincipalInfo; + if (nsIPrincipal* loadingPrin = aLoadInfo->GetLoadingPrincipal()) { + loadingPrincipalInfo.emplace(); + rv = PrincipalToPrincipalInfo(loadingPrin, loadingPrincipalInfo.ptr()); + NS_ENSURE_SUCCESS(rv, rv); + } + + PrincipalInfo triggeringPrincipalInfo; + rv = PrincipalToPrincipalInfo(aLoadInfo->TriggeringPrincipal(), + &triggeringPrincipalInfo); + NS_ENSURE_SUCCESS(rv, rv); + + Maybe<PrincipalInfo> principalToInheritInfo; + if (nsIPrincipal* principalToInherit = aLoadInfo->PrincipalToInherit()) { + principalToInheritInfo.emplace(); + rv = PrincipalToPrincipalInfo(principalToInherit, + principalToInheritInfo.ptr()); + NS_ENSURE_SUCCESS(rv, rv); + } + + Maybe<PrincipalInfo> topLevelPrincipalInfo; + if (nsIPrincipal* topLevenPrin = aLoadInfo->GetTopLevelPrincipal()) { + topLevelPrincipalInfo.emplace(); + rv = PrincipalToPrincipalInfo(topLevenPrin, topLevelPrincipalInfo.ptr()); + NS_ENSURE_SUCCESS(rv, rv); + } + + Maybe<URIParams> optionalResultPrincipalURI; + nsCOMPtr<nsIURI> resultPrincipalURI; + Unused << aLoadInfo->GetResultPrincipalURI( + getter_AddRefs(resultPrincipalURI)); + if (resultPrincipalURI) { + SerializeURI(resultPrincipalURI, optionalResultPrincipalURI); + } + + nsCString triggeringRemoteType; + rv = aLoadInfo->GetTriggeringRemoteType(triggeringRemoteType); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<RedirectHistoryEntryInfo> redirectChainIncludingInternalRedirects; + for (const nsCOMPtr<nsIRedirectHistoryEntry>& redirectEntry : + aLoadInfo->RedirectChainIncludingInternalRedirects()) { + RedirectHistoryEntryInfo* entry = + redirectChainIncludingInternalRedirects.AppendElement(); + rv = RHEntryToRHEntryInfo(redirectEntry, entry); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsTArray<RedirectHistoryEntryInfo> redirectChain; + for (const nsCOMPtr<nsIRedirectHistoryEntry>& redirectEntry : + aLoadInfo->RedirectChain()) { + RedirectHistoryEntryInfo* entry = redirectChain.AppendElement(); + rv = RHEntryToRHEntryInfo(redirectEntry, entry); + NS_ENSURE_SUCCESS(rv, rv); + } + + Maybe<IPCClientInfo> ipcClientInfo; + const Maybe<ClientInfo>& clientInfo = aLoadInfo->GetClientInfo(); + if (clientInfo.isSome()) { + ipcClientInfo.emplace(clientInfo.ref().ToIPC()); + } + + Maybe<IPCClientInfo> ipcReservedClientInfo; + const Maybe<ClientInfo>& reservedClientInfo = + aLoadInfo->GetReservedClientInfo(); + if (reservedClientInfo.isSome()) { + ipcReservedClientInfo.emplace(reservedClientInfo.ref().ToIPC()); + } + + Maybe<IPCClientInfo> ipcInitialClientInfo; + const Maybe<ClientInfo>& initialClientInfo = + aLoadInfo->GetInitialClientInfo(); + if (initialClientInfo.isSome()) { + ipcInitialClientInfo.emplace(initialClientInfo.ref().ToIPC()); + } + + Maybe<IPCServiceWorkerDescriptor> ipcController; + const Maybe<ServiceWorkerDescriptor>& controller = aLoadInfo->GetController(); + if (controller.isSome()) { + ipcController.emplace(controller.ref().ToIPC()); + } + + nsAutoString cspNonce; + Unused << NS_WARN_IF(NS_FAILED(aLoadInfo->GetCspNonce(cspNonce))); + + nsAutoString integrityMetadata; + Unused << NS_WARN_IF( + NS_FAILED(aLoadInfo->GetIntegrityMetadata(integrityMetadata))); + + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + rv = aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); + NS_ENSURE_SUCCESS(rv, rv); + + CookieJarSettingsArgs cookieJarSettingsArgs; + static_cast<CookieJarSettings*>(cookieJarSettings.get()) + ->Serialize(cookieJarSettingsArgs); + + Maybe<CSPInfo> maybeCspToInheritInfo; + nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = + aLoadInfo->GetCspToInherit(); + if (cspToInherit) { + CSPInfo cspToInheritInfo; + Unused << NS_WARN_IF( + NS_FAILED(CSPToCSPInfo(cspToInherit, &cspToInheritInfo))); + maybeCspToInheritInfo.emplace(cspToInheritInfo); + } + + nsCOMPtr<nsIURI> unstrippedURI; + Unused << aLoadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI)); + + Maybe<bool> isThirdPartyContextToTopWindow; + if (static_cast<LoadInfo*>(aLoadInfo) + ->HasIsThirdPartyContextToTopWindowSet()) { + isThirdPartyContextToTopWindow.emplace( + aLoadInfo->GetIsThirdPartyContextToTopWindow()); + } + + Maybe<InterceptionInfoArg> interceptionInfoArg; + nsIInterceptionInfo* interceptionInfo = aLoadInfo->InterceptionInfo(); + if (interceptionInfo) { + Maybe<PrincipalInfo> triggeringPrincipalInfo; + if (interceptionInfo->TriggeringPrincipal()) { + triggeringPrincipalInfo.emplace(); + rv = PrincipalToPrincipalInfo(interceptionInfo->TriggeringPrincipal(), + triggeringPrincipalInfo.ptr()); + } + + nsTArray<RedirectHistoryEntryInfo> redirectChain; + for (const nsCOMPtr<nsIRedirectHistoryEntry>& redirectEntry : + interceptionInfo->RedirectChain()) { + RedirectHistoryEntryInfo* entry = redirectChain.AppendElement(); + rv = RHEntryToRHEntryInfo(redirectEntry, entry); + NS_ENSURE_SUCCESS(rv, rv); + } + + interceptionInfoArg = Some(InterceptionInfoArg( + triggeringPrincipalInfo, interceptionInfo->ContentPolicyType(), + redirectChain, interceptionInfo->FromThirdParty())); + } + + Maybe<uint64_t> overriddenFingerprintingSettingsArg; + Maybe<RFPTarget> overriddenFingerprintingSettings = + aLoadInfo->GetOverriddenFingerprintingSettings(); + + if (overriddenFingerprintingSettings) { + overriddenFingerprintingSettingsArg = + Some(uint64_t(overriddenFingerprintingSettings.ref())); + } + + *outLoadInfoArgs = LoadInfoArgs( + loadingPrincipalInfo, triggeringPrincipalInfo, principalToInheritInfo, + topLevelPrincipalInfo, optionalResultPrincipalURI, triggeringRemoteType, + aLoadInfo->GetSandboxedNullPrincipalID(), aLoadInfo->GetSecurityFlags(), + aLoadInfo->GetSandboxFlags(), aLoadInfo->GetTriggeringSandboxFlags(), + aLoadInfo->GetTriggeringWindowId(), + aLoadInfo->GetTriggeringStorageAccess(), + aLoadInfo->InternalContentPolicyType(), + static_cast<uint32_t>(aLoadInfo->GetTainting()), + aLoadInfo->GetBlockAllMixedContent(), + aLoadInfo->GetUpgradeInsecureRequests(), + aLoadInfo->GetBrowserUpgradeInsecureRequests(), + aLoadInfo->GetBrowserDidUpgradeInsecureRequests(), + aLoadInfo->GetBrowserWouldUpgradeInsecureRequests(), + aLoadInfo->GetForceAllowDataURI(), + aLoadInfo->GetAllowInsecureRedirectToDataURI(), + aLoadInfo->GetSkipContentPolicyCheckForWebRequest(), + aLoadInfo->GetOriginalFrameSrcLoad(), + aLoadInfo->GetForceInheritPrincipalDropped(), + aLoadInfo->GetInnerWindowID(), aLoadInfo->GetBrowsingContextID(), + aLoadInfo->GetFrameBrowsingContextID(), + aLoadInfo->GetInitialSecurityCheckDone(), + aLoadInfo->GetIsInThirdPartyContext(), isThirdPartyContextToTopWindow, + aLoadInfo->GetIsFormSubmission(), aLoadInfo->GetSendCSPViolationEvents(), + aLoadInfo->GetOriginAttributes(), redirectChainIncludingInternalRedirects, + redirectChain, aLoadInfo->GetHasInjectedCookieForCookieBannerHandling(), + aLoadInfo->GetWasSchemelessInput(), ipcClientInfo, ipcReservedClientInfo, + ipcInitialClientInfo, ipcController, aLoadInfo->CorsUnsafeHeaders(), + aLoadInfo->GetForcePreflight(), aLoadInfo->GetIsPreflight(), + aLoadInfo->GetLoadTriggeredFromExternal(), + aLoadInfo->GetServiceWorkerTaintingSynthesized(), + aLoadInfo->GetDocumentHasUserInteracted(), + aLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain(), + aLoadInfo->GetNeedForCheckingAntiTrackingHeuristic(), cspNonce, + integrityMetadata, aLoadInfo->GetSkipContentSniffing(), + aLoadInfo->GetHttpsOnlyStatus(), aLoadInfo->GetHstsStatus(), + aLoadInfo->GetHasValidUserGestureActivation(), + aLoadInfo->GetAllowDeprecatedSystemRequests(), + aLoadInfo->GetIsInDevToolsContext(), aLoadInfo->GetParserCreatedScript(), + aLoadInfo->GetIsFromProcessingFrameAttributes(), + aLoadInfo->GetIsMediaRequest(), aLoadInfo->GetIsMediaInitialRequest(), + aLoadInfo->GetIsFromObjectOrEmbed(), cookieJarSettingsArgs, + aLoadInfo->GetRequestBlockingReason(), maybeCspToInheritInfo, + aLoadInfo->GetStoragePermission(), overriddenFingerprintingSettingsArg, + aLoadInfo->GetIsMetaRefresh(), aLoadInfo->GetLoadingEmbedderPolicy(), + aLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(), + unstrippedURI, interceptionInfoArg); + + return NS_OK; +} + +nsresult LoadInfoArgsToLoadInfo(const LoadInfoArgs& aLoadInfoArgs, + const nsACString& aOriginRemoteType, + nsILoadInfo** outLoadInfo) { + return LoadInfoArgsToLoadInfo(aLoadInfoArgs, aOriginRemoteType, nullptr, + outLoadInfo); +} +nsresult LoadInfoArgsToLoadInfo(const LoadInfoArgs& aLoadInfoArgs, + const nsACString& aOriginRemoteType, + nsINode* aCspToInheritLoadingContext, + nsILoadInfo** outLoadInfo) { + RefPtr<LoadInfo> loadInfo; + nsresult rv = LoadInfoArgsToLoadInfo(aLoadInfoArgs, aOriginRemoteType, + aCspToInheritLoadingContext, + getter_AddRefs(loadInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + loadInfo.forget(outLoadInfo); + return NS_OK; +} + +nsresult LoadInfoArgsToLoadInfo(const LoadInfoArgs& aLoadInfoArgs, + const nsACString& aOriginRemoteType, + LoadInfo** outLoadInfo) { + return LoadInfoArgsToLoadInfo(aLoadInfoArgs, aOriginRemoteType, nullptr, + outLoadInfo); +} +nsresult LoadInfoArgsToLoadInfo(const LoadInfoArgs& loadInfoArgs, + const nsACString& aOriginRemoteType, + nsINode* aCspToInheritLoadingContext, + LoadInfo** outLoadInfo) { + nsCOMPtr<nsIPrincipal> loadingPrincipal; + if (loadInfoArgs.requestingPrincipalInfo().isSome()) { + auto loadingPrincipalOrErr = + PrincipalInfoToPrincipal(loadInfoArgs.requestingPrincipalInfo().ref()); + if (NS_WARN_IF(loadingPrincipalOrErr.isErr())) { + return loadingPrincipalOrErr.unwrapErr(); + } + loadingPrincipal = loadingPrincipalOrErr.unwrap(); + } + + auto triggeringPrincipalOrErr = + PrincipalInfoToPrincipal(loadInfoArgs.triggeringPrincipalInfo()); + if (NS_WARN_IF(triggeringPrincipalOrErr.isErr())) { + return triggeringPrincipalOrErr.unwrapErr(); + } + nsCOMPtr<nsIPrincipal> triggeringPrincipal = + triggeringPrincipalOrErr.unwrap(); + + nsCOMPtr<nsIPrincipal> principalToInherit; + nsCOMPtr<nsIPrincipal> flattenedPrincipalToInherit; + if (loadInfoArgs.principalToInheritInfo().isSome()) { + auto principalToInheritOrErr = + PrincipalInfoToPrincipal(loadInfoArgs.principalToInheritInfo().ref()); + if (NS_WARN_IF(principalToInheritOrErr.isErr())) { + return principalToInheritOrErr.unwrapErr(); + } + flattenedPrincipalToInherit = principalToInheritOrErr.unwrap(); + } + + if (XRE_IsContentProcess()) { + auto targetBrowsingContextId = loadInfoArgs.frameBrowsingContextID() + ? loadInfoArgs.frameBrowsingContextID() + : loadInfoArgs.browsingContextID(); + if (RefPtr<BrowsingContext> bc = + BrowsingContext::Get(targetBrowsingContextId)) { + auto [originalTriggeringPrincipal, originalPrincipalToInherit] = + bc->GetTriggeringAndInheritPrincipalsForCurrentLoad(); + + if (originalTriggeringPrincipal && + originalTriggeringPrincipal->Equals(triggeringPrincipal)) { + triggeringPrincipal = originalTriggeringPrincipal; + } + if (originalPrincipalToInherit && + (loadInfoArgs.securityFlags() & + nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL) && + originalPrincipalToInherit->Equals(flattenedPrincipalToInherit)) { + principalToInherit = originalPrincipalToInherit; + } + } + } + if (!principalToInherit && loadInfoArgs.principalToInheritInfo().isSome()) { + principalToInherit = flattenedPrincipalToInherit; + } + + nsCOMPtr<nsIPrincipal> topLevelPrincipal; + if (loadInfoArgs.topLevelPrincipalInfo().isSome()) { + auto topLevelPrincipalOrErr = + PrincipalInfoToPrincipal(loadInfoArgs.topLevelPrincipalInfo().ref()); + if (NS_WARN_IF(topLevelPrincipalOrErr.isErr())) { + return topLevelPrincipalOrErr.unwrapErr(); + } + topLevelPrincipal = topLevelPrincipalOrErr.unwrap(); + } + + nsCOMPtr<nsIURI> resultPrincipalURI; + if (loadInfoArgs.resultPrincipalURI().isSome()) { + resultPrincipalURI = DeserializeURI(loadInfoArgs.resultPrincipalURI()); + NS_ENSURE_TRUE(resultPrincipalURI, NS_ERROR_UNEXPECTED); + } + + // If we received this message from a content process, reset + // triggeringRemoteType to the process which sent us the message. If the + // parent sent us the message, we trust it to provide the correct triggering + // remote type. + // + // This means that the triggering remote type will be reset if a LoadInfo is + // bounced through a content process, as the LoadInfo can no longer be + // validated to be coming from the originally specified remote type. + nsCString triggeringRemoteType = loadInfoArgs.triggeringRemoteType(); + if (aOriginRemoteType != NOT_REMOTE_TYPE && + aOriginRemoteType != triggeringRemoteType) { + triggeringRemoteType = aOriginRemoteType; + } + + RedirectHistoryArray redirectChainIncludingInternalRedirects; + for (const RedirectHistoryEntryInfo& entryInfo : + loadInfoArgs.redirectChainIncludingInternalRedirects()) { + nsCOMPtr<nsIRedirectHistoryEntry> redirectHistoryEntry = + RHEntryInfoToRHEntry(entryInfo); + NS_ENSURE_TRUE(redirectHistoryEntry, NS_ERROR_UNEXPECTED); + redirectChainIncludingInternalRedirects.AppendElement( + redirectHistoryEntry.forget()); + } + + RedirectHistoryArray redirectChain; + for (const RedirectHistoryEntryInfo& entryInfo : + loadInfoArgs.redirectChain()) { + nsCOMPtr<nsIRedirectHistoryEntry> redirectHistoryEntry = + RHEntryInfoToRHEntry(entryInfo); + NS_ENSURE_TRUE(redirectHistoryEntry, NS_ERROR_UNEXPECTED); + redirectChain.AppendElement(redirectHistoryEntry.forget()); + } + nsTArray<nsCOMPtr<nsIPrincipal>> ancestorPrincipals; + nsTArray<uint64_t> ancestorBrowsingContextIDs; + if (XRE_IsParentProcess() && + (nsContentUtils::InternalContentPolicyTypeToExternal( + loadInfoArgs.contentPolicyType()) != + ExtContentPolicy::TYPE_DOCUMENT)) { + // Only fill out ancestor principals and browsing context IDs when we + // are deserializing LoadInfoArgs to be LoadInfo for a subresource + RefPtr<BrowsingContext> parentBC = + BrowsingContext::Get(loadInfoArgs.browsingContextID()); + if (parentBC) { + LoadInfo::ComputeAncestors(parentBC->Canonical(), ancestorPrincipals, + ancestorBrowsingContextIDs); + } + } + + Maybe<ClientInfo> clientInfo; + if (loadInfoArgs.clientInfo().isSome()) { + clientInfo.emplace(ClientInfo(loadInfoArgs.clientInfo().ref())); + } + + Maybe<ClientInfo> reservedClientInfo; + if (loadInfoArgs.reservedClientInfo().isSome()) { + reservedClientInfo.emplace( + ClientInfo(loadInfoArgs.reservedClientInfo().ref())); + } + + Maybe<ClientInfo> initialClientInfo; + if (loadInfoArgs.initialClientInfo().isSome()) { + initialClientInfo.emplace( + ClientInfo(loadInfoArgs.initialClientInfo().ref())); + } + + // We can have an initial client info or a reserved client info, but not both. + MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() || + initialClientInfo.isNothing()); + NS_ENSURE_TRUE( + reservedClientInfo.isNothing() || initialClientInfo.isNothing(), + NS_ERROR_UNEXPECTED); + + Maybe<ServiceWorkerDescriptor> controller; + if (loadInfoArgs.controller().isSome()) { + controller.emplace( + ServiceWorkerDescriptor(loadInfoArgs.controller().ref())); + } + + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + CookieJarSettings::Deserialize(loadInfoArgs.cookieJarSettings(), + getter_AddRefs(cookieJarSettings)); + + Maybe<RFPTarget> overriddenFingerprintingSettings; + if (loadInfoArgs.overriddenFingerprintingSettings().isSome()) { + overriddenFingerprintingSettings.emplace( + RFPTarget(loadInfoArgs.overriddenFingerprintingSettings().ref())); + } + + nsCOMPtr<nsIContentSecurityPolicy> cspToInherit; + Maybe<mozilla::ipc::CSPInfo> cspToInheritInfo = + loadInfoArgs.cspToInheritInfo(); + if (cspToInheritInfo.isSome()) { + nsCOMPtr<Document> doc = do_QueryInterface(aCspToInheritLoadingContext); + cspToInherit = CSPInfoToCSP(cspToInheritInfo.ref(), doc); + } + + // Restore the loadingContext for frames using the BrowsingContext's + // embedder element. Note that this only works if the embedder is + // same-process, so won't be fission compatible. + nsCOMPtr<nsINode> loadingContext; + RefPtr<BrowsingContext> frameBrowsingContext = + BrowsingContext::Get(loadInfoArgs.frameBrowsingContextID()); + if (frameBrowsingContext) { + loadingContext = frameBrowsingContext->GetEmbedderElement(); + } + + Maybe<bool> isThirdPartyContextToTopWindow; + if (loadInfoArgs.isThirdPartyContextToTopWindow().isSome()) { + isThirdPartyContextToTopWindow.emplace( + loadInfoArgs.isThirdPartyContextToTopWindow().ref()); + } + + nsCOMPtr<nsIInterceptionInfo> interceptionInfo; + if (loadInfoArgs.interceptionInfo().isSome()) { + const InterceptionInfoArg& interceptionInfoArg = + loadInfoArgs.interceptionInfo().ref(); + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + if (interceptionInfoArg.triggeringPrincipalInfo().isSome()) { + auto triggeringPrincipalOrErr = PrincipalInfoToPrincipal( + interceptionInfoArg.triggeringPrincipalInfo().ref()); + if (NS_WARN_IF(triggeringPrincipalOrErr.isErr())) { + return triggeringPrincipalOrErr.unwrapErr(); + } + triggeringPrincipal = triggeringPrincipalOrErr.unwrap(); + } + + RedirectHistoryArray redirectChain; + for (const RedirectHistoryEntryInfo& entryInfo : + interceptionInfoArg.redirectChain()) { + nsCOMPtr<nsIRedirectHistoryEntry> redirectHistoryEntry = + RHEntryInfoToRHEntry(entryInfo); + NS_ENSURE_TRUE(redirectHistoryEntry, NS_ERROR_UNEXPECTED); + redirectChain.AppendElement(redirectHistoryEntry.forget()); + } + + interceptionInfo = new InterceptionInfo( + triggeringPrincipal, interceptionInfoArg.contentPolicyType(), + redirectChain, interceptionInfoArg.fromThirdParty()); + } + + RefPtr<mozilla::net::LoadInfo> loadInfo = new mozilla::net::LoadInfo( + loadingPrincipal, triggeringPrincipal, principalToInherit, + topLevelPrincipal, resultPrincipalURI, cookieJarSettings, cspToInherit, + triggeringRemoteType, loadInfoArgs.sandboxedNullPrincipalID(), clientInfo, + reservedClientInfo, initialClientInfo, controller, + loadInfoArgs.securityFlags(), loadInfoArgs.sandboxFlags(), + loadInfoArgs.triggeringSandboxFlags(), loadInfoArgs.triggeringWindowId(), + loadInfoArgs.triggeringStorageAccess(), loadInfoArgs.contentPolicyType(), + static_cast<LoadTainting>(loadInfoArgs.tainting()), + loadInfoArgs.blockAllMixedContent(), + loadInfoArgs.upgradeInsecureRequests(), + loadInfoArgs.browserUpgradeInsecureRequests(), + loadInfoArgs.browserDidUpgradeInsecureRequests(), + loadInfoArgs.browserWouldUpgradeInsecureRequests(), + loadInfoArgs.forceAllowDataURI(), + loadInfoArgs.allowInsecureRedirectToDataURI(), + loadInfoArgs.skipContentPolicyCheckForWebRequest(), + loadInfoArgs.originalFrameSrcLoad(), + loadInfoArgs.forceInheritPrincipalDropped(), loadInfoArgs.innerWindowID(), + loadInfoArgs.browsingContextID(), loadInfoArgs.frameBrowsingContextID(), + loadInfoArgs.initialSecurityCheckDone(), + loadInfoArgs.isInThirdPartyContext(), isThirdPartyContextToTopWindow, + loadInfoArgs.isFormSubmission(), loadInfoArgs.sendCSPViolationEvents(), + loadInfoArgs.originAttributes(), + std::move(redirectChainIncludingInternalRedirects), + std::move(redirectChain), std::move(ancestorPrincipals), + ancestorBrowsingContextIDs, loadInfoArgs.corsUnsafeHeaders(), + loadInfoArgs.forcePreflight(), loadInfoArgs.isPreflight(), + loadInfoArgs.loadTriggeredFromExternal(), + loadInfoArgs.serviceWorkerTaintingSynthesized(), + loadInfoArgs.documentHasUserInteracted(), + loadInfoArgs.allowListFutureDocumentsCreatedFromThisRedirectChain(), + loadInfoArgs.needForCheckingAntiTrackingHeuristic(), + loadInfoArgs.cspNonce(), loadInfoArgs.integrityMetadata(), + loadInfoArgs.skipContentSniffing(), loadInfoArgs.httpsOnlyStatus(), + loadInfoArgs.hstsStatus(), loadInfoArgs.hasValidUserGestureActivation(), + loadInfoArgs.allowDeprecatedSystemRequests(), + loadInfoArgs.isInDevToolsContext(), loadInfoArgs.parserCreatedScript(), + loadInfoArgs.storagePermission(), overriddenFingerprintingSettings, + loadInfoArgs.isMetaRefresh(), loadInfoArgs.requestBlockingReason(), + loadingContext, loadInfoArgs.loadingEmbedderPolicy(), + loadInfoArgs.originTrialCoepCredentiallessEnabledForTopLevel(), + loadInfoArgs.unstrippedURI(), interceptionInfo, + loadInfoArgs.hasInjectedCookieForCookieBannerHandling(), + loadInfoArgs.wasSchemelessInput()); + + if (loadInfoArgs.isFromProcessingFrameAttributes()) { + loadInfo->SetIsFromProcessingFrameAttributes(); + } + + if (loadInfoArgs.isMediaRequest()) { + loadInfo->SetIsMediaRequest(true); + + if (loadInfoArgs.isMediaInitialRequest()) { + loadInfo->SetIsMediaInitialRequest(true); + } + } + + if (loadInfoArgs.isFromObjectOrEmbed()) { + loadInfo->SetIsFromObjectOrEmbed(true); + } + + loadInfo.forget(outLoadInfo); + return NS_OK; +} + +void LoadInfoToParentLoadInfoForwarder( + nsILoadInfo* aLoadInfo, ParentLoadInfoForwarderArgs* aForwarderArgsOut) { + Maybe<IPCServiceWorkerDescriptor> ipcController; + Maybe<ServiceWorkerDescriptor> controller(aLoadInfo->GetController()); + if (controller.isSome()) { + ipcController.emplace(controller.ref().ToIPC()); + } + + uint32_t tainting = nsILoadInfo::TAINTING_BASIC; + Unused << aLoadInfo->GetTainting(&tainting); + + Maybe<CookieJarSettingsArgs> cookieJarSettingsArgs; + + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + nsresult rv = + aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); + CookieJarSettings* cs = + static_cast<CookieJarSettings*>(cookieJarSettings.get()); + if (NS_SUCCEEDED(rv) && cookieJarSettings && cs->HasBeenChanged()) { + CookieJarSettingsArgs args; + cs->Serialize(args); + cookieJarSettingsArgs = Some(args); + } + + nsCOMPtr<nsIURI> unstrippedURI; + Unused << aLoadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI)); + + Maybe<bool> isThirdPartyContextToTopWindow; + if (static_cast<LoadInfo*>(aLoadInfo) + ->HasIsThirdPartyContextToTopWindowSet()) { + isThirdPartyContextToTopWindow.emplace( + aLoadInfo->GetIsThirdPartyContextToTopWindow()); + } + + Maybe<uint64_t> overriddenFingerprintingSettingsArg; + Maybe<RFPTarget> overriddenFingerprintingSettings = + aLoadInfo->GetOverriddenFingerprintingSettings(); + + if (overriddenFingerprintingSettings) { + overriddenFingerprintingSettingsArg = + Some(uint64_t(overriddenFingerprintingSettings.ref())); + } + + *aForwarderArgsOut = ParentLoadInfoForwarderArgs( + aLoadInfo->GetAllowInsecureRedirectToDataURI(), ipcController, tainting, + aLoadInfo->GetSkipContentSniffing(), aLoadInfo->GetHttpsOnlyStatus(), + aLoadInfo->GetHstsStatus(), aLoadInfo->GetHasValidUserGestureActivation(), + aLoadInfo->GetAllowDeprecatedSystemRequests(), + aLoadInfo->GetIsInDevToolsContext(), aLoadInfo->GetParserCreatedScript(), + aLoadInfo->GetTriggeringSandboxFlags(), + aLoadInfo->GetTriggeringWindowId(), + aLoadInfo->GetTriggeringStorageAccess(), + aLoadInfo->GetServiceWorkerTaintingSynthesized(), + aLoadInfo->GetDocumentHasUserInteracted(), + aLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain(), + cookieJarSettingsArgs, aLoadInfo->GetRequestBlockingReason(), + aLoadInfo->GetStoragePermission(), overriddenFingerprintingSettingsArg, + aLoadInfo->GetIsMetaRefresh(), isThirdPartyContextToTopWindow, + aLoadInfo->GetIsInThirdPartyContext(), unstrippedURI); +} + +nsresult MergeParentLoadInfoForwarder( + ParentLoadInfoForwarderArgs const& aForwarderArgs, nsILoadInfo* aLoadInfo) { + nsresult rv; + + rv = aLoadInfo->SetAllowInsecureRedirectToDataURI( + aForwarderArgs.allowInsecureRedirectToDataURI()); + NS_ENSURE_SUCCESS(rv, rv); + + aLoadInfo->ClearController(); + auto& controller = aForwarderArgs.controller(); + if (controller.isSome()) { + aLoadInfo->SetController(ServiceWorkerDescriptor(controller.ref())); + } + + if (aForwarderArgs.serviceWorkerTaintingSynthesized()) { + aLoadInfo->SynthesizeServiceWorkerTainting( + static_cast<LoadTainting>(aForwarderArgs.tainting())); + } else { + aLoadInfo->MaybeIncreaseTainting(aForwarderArgs.tainting()); + } + + rv = aLoadInfo->SetSkipContentSniffing(aForwarderArgs.skipContentSniffing()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetHttpsOnlyStatus(aForwarderArgs.httpsOnlyStatus()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetHstsStatus(aForwarderArgs.hstsStatus()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetTriggeringSandboxFlags( + aForwarderArgs.triggeringSandboxFlags()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetTriggeringWindowId(aForwarderArgs.triggeringWindowId()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetTriggeringStorageAccess( + aForwarderArgs.triggeringStorageAccess()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetHasValidUserGestureActivation( + aForwarderArgs.hasValidUserGestureActivation()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetAllowDeprecatedSystemRequests( + aForwarderArgs.allowDeprecatedSystemRequests()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetIsInDevToolsContext(aForwarderArgs.isInDevToolsContext()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetParserCreatedScript(aForwarderArgs.parserCreatedScript()); + NS_ENSURE_SUCCESS(rv, rv); + + MOZ_ALWAYS_SUCCEEDS(aLoadInfo->SetDocumentHasUserInteracted( + aForwarderArgs.documentHasUserInteracted())); + MOZ_ALWAYS_SUCCEEDS( + aLoadInfo->SetAllowListFutureDocumentsCreatedFromThisRedirectChain( + aForwarderArgs + .allowListFutureDocumentsCreatedFromThisRedirectChain())); + MOZ_ALWAYS_SUCCEEDS(aLoadInfo->SetRequestBlockingReason( + aForwarderArgs.requestBlockingReason())); + + const Maybe<CookieJarSettingsArgs>& cookieJarSettingsArgs = + aForwarderArgs.cookieJarSettings(); + if (cookieJarSettingsArgs.isSome()) { + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + nsresult rv = + aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); + if (NS_SUCCEEDED(rv) && cookieJarSettings) { + static_cast<CookieJarSettings*>(cookieJarSettings.get()) + ->Merge(cookieJarSettingsArgs.ref()); + } + } + + rv = aLoadInfo->SetStoragePermission(aForwarderArgs.storagePermission()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetIsMetaRefresh(aForwarderArgs.isMetaRefresh()); + NS_ENSURE_SUCCESS(rv, rv); + + const Maybe<uint64_t> overriddenFingerprintingSettings = + aForwarderArgs.overriddenFingerprintingSettings(); + if (overriddenFingerprintingSettings.isSome()) { + aLoadInfo->SetOverriddenFingerprintingSettings( + RFPTarget(overriddenFingerprintingSettings.ref())); + } + + static_cast<LoadInfo*>(aLoadInfo)->ClearIsThirdPartyContextToTopWindow(); + if (aForwarderArgs.isThirdPartyContextToTopWindow().isSome()) { + rv = aLoadInfo->SetIsThirdPartyContextToTopWindow( + aForwarderArgs.isThirdPartyContextToTopWindow().ref()); + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetIsInThirdPartyContext( + aForwarderArgs.isInThirdPartyContext()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetUnstrippedURI(aForwarderArgs.unstrippedURI()); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +void LoadInfoToChildLoadInfoForwarder( + nsILoadInfo* aLoadInfo, ChildLoadInfoForwarderArgs* aForwarderArgsOut) { + Maybe<IPCClientInfo> ipcReserved; + Maybe<ClientInfo> reserved(aLoadInfo->GetReservedClientInfo()); + if (reserved.isSome()) { + ipcReserved.emplace(reserved.ref().ToIPC()); + } + + Maybe<IPCClientInfo> ipcInitial; + Maybe<ClientInfo> initial(aLoadInfo->GetInitialClientInfo()); + if (initial.isSome()) { + ipcInitial.emplace(initial.ref().ToIPC()); + } + + Maybe<IPCServiceWorkerDescriptor> ipcController; + Maybe<ServiceWorkerDescriptor> controller(aLoadInfo->GetController()); + if (controller.isSome()) { + ipcController.emplace(controller.ref().ToIPC()); + } + + *aForwarderArgsOut = + ChildLoadInfoForwarderArgs(ipcReserved, ipcInitial, ipcController, + aLoadInfo->GetRequestBlockingReason()); +} + +nsresult MergeChildLoadInfoForwarder( + const ChildLoadInfoForwarderArgs& aForwarderArgs, nsILoadInfo* aLoadInfo) { + Maybe<ClientInfo> reservedClientInfo; + auto& ipcReserved = aForwarderArgs.reservedClientInfo(); + if (ipcReserved.isSome()) { + reservedClientInfo.emplace(ClientInfo(ipcReserved.ref())); + } + + Maybe<ClientInfo> initialClientInfo; + auto& ipcInitial = aForwarderArgs.initialClientInfo(); + if (ipcInitial.isSome()) { + initialClientInfo.emplace(ClientInfo(ipcInitial.ref())); + } + + // There should only be at most one reserved or initial ClientInfo. + if (NS_WARN_IF(reservedClientInfo.isSome() && initialClientInfo.isSome())) { + return NS_ERROR_FAILURE; + } + + // If we received no reserved or initial ClientInfo, then we must not + // already have one set. There are no use cases where this should + // happen and we don't have a way to clear the current value. + if (NS_WARN_IF(reservedClientInfo.isNothing() && + initialClientInfo.isNothing() && + (aLoadInfo->GetReservedClientInfo().isSome() || + aLoadInfo->GetInitialClientInfo().isSome()))) { + return NS_ERROR_FAILURE; + } + + if (reservedClientInfo.isSome()) { + // We need to override here instead of simply set the value. This + // allows us to change the reserved client. This is necessary when + // the ClientChannelHelper created a new reserved client in the + // child-side of the redirect. + aLoadInfo->OverrideReservedClientInfoInParent(reservedClientInfo.ref()); + } else if (initialClientInfo.isSome()) { + aLoadInfo->SetInitialClientInfo(initialClientInfo.ref()); + } + + aLoadInfo->ClearController(); + auto& controller = aForwarderArgs.controller(); + if (controller.isSome()) { + aLoadInfo->SetController(ServiceWorkerDescriptor(controller.ref())); + } + + uint32_t blockingReason = aForwarderArgs.requestBlockingReason(); + if (blockingReason) { + // We only want to override when non-null, so that any earlier set non-null + // value is not reverted to 0. + aLoadInfo->SetRequestBlockingReason(blockingReason); + } + + return NS_OK; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/BackgroundUtils.h b/ipc/glue/BackgroundUtils.h new file mode 100644 index 0000000000..5d8f80e935 --- /dev/null +++ b/ipc/glue/BackgroundUtils.h @@ -0,0 +1,194 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_backgroundutils_h__ +#define mozilla_ipc_backgroundutils_h__ + +#include "ipc/IPCMessageUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/OriginAttributes.h" +#include "nsCOMPtr.h" +#include "nscore.h" + +class nsIContentSecurityPolicy; +class nsILoadInfo; +class nsINode; +class nsIPrincipal; +class nsIRedirectHistoryEntry; + +namespace IPC { + +namespace detail { +template <class ParamType> +struct OriginAttributesParamTraits { + typedef ParamType paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + nsAutoCString suffix; + aParam.CreateSuffix(suffix); + WriteParam(aWriter, suffix); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + nsAutoCString suffix; + return ReadParam(aReader, &suffix) && aResult->PopulateFromSuffix(suffix); + } +}; +} // namespace detail + +template <> +struct ParamTraits<mozilla::OriginAttributes> + : public detail::OriginAttributesParamTraits<mozilla::OriginAttributes> {}; + +} // namespace IPC + +namespace mozilla { + +namespace dom { +class Document; +} + +namespace net { +class ChildLoadInfoForwarderArgs; +class LoadInfoArgs; +class LoadInfo; +class ParentLoadInfoForwarderArgs; +class RedirectHistoryEntryInfo; +} // namespace net + +namespace ipc { + +class ContentSecurityPolicy; +class CSPInfo; +class PrincipalInfo; + +/** + * Convert a PrincipalInfo to an nsIPrincipal. + * + * MUST be called on the main thread. + */ +Result<nsCOMPtr<nsIPrincipal>, nsresult> PrincipalInfoToPrincipal( + const PrincipalInfo& aPrincipalInfo); + +/** + * Convert an nsIPrincipal to a PrincipalInfo. + * + * MUST be called on the main thread only. + */ +nsresult PrincipalToPrincipalInfo(nsIPrincipal* aPrincipal, + PrincipalInfo* aPrincipalInfo, + bool aSkipBaseDomain = false); + +/** + * Compare storage keys for equivalence. + * + * Only use with storage keys retrieved from nsIGlobalObject::GetStorageKey! + * Bug 1776271 tracks enhancing this into a proper type. + */ +bool StorageKeysEqual(const PrincipalInfo& aLeft, const PrincipalInfo& aRight); + +/** + * Convert a CSPInfo to an nsIContentSecurityPolicy. + * + * MUST be called on the main thread only. + * + * If possible, provide a requesting doc, so policy violation events can + * be dispatched correctly. If aRequestingDoc is null, then the CSPInfo holds + * the necessary fallback information, like a serialized requestPrincipal, + * to generate a valid nsIContentSecurityPolicy. + */ +already_AddRefed<nsIContentSecurityPolicy> CSPInfoToCSP( + const CSPInfo& aCSPInfo, mozilla::dom::Document* aRequestingDoc, + nsresult* aOptionalResult = nullptr); + +/** + * Convert an nsIContentSecurityPolicy to a CSPInfo. + * + * MUST be called on the main thread only. + */ +nsresult CSPToCSPInfo(nsIContentSecurityPolicy* aCSP, CSPInfo* aCSPInfo); + +/** + * Return true if this PrincipalInfo is a content principal and it has + * a privateBrowsing id in its OriginAttributes + */ +bool IsPrincipalInfoPrivate(const PrincipalInfo& aPrincipalInfo); + +/** + * Convert an RedirectHistoryEntryInfo to a nsIRedirectHistoryEntry. + */ + +already_AddRefed<nsIRedirectHistoryEntry> RHEntryInfoToRHEntry( + const mozilla::net::RedirectHistoryEntryInfo& aRHEntryInfo); + +/** + * Convert an nsIRedirectHistoryEntry to a RedirectHistoryEntryInfo. + */ + +nsresult RHEntryToRHEntryInfo( + nsIRedirectHistoryEntry* aRHEntry, + mozilla::net::RedirectHistoryEntryInfo* aRHEntryInfo); + +/** + * Convert a LoadInfo to LoadInfoArgs struct. + */ +nsresult LoadInfoToLoadInfoArgs(nsILoadInfo* aLoadInfo, + mozilla::net::LoadInfoArgs* outLoadInfoArgs); + +/** + * Convert LoadInfoArgs to a LoadInfo. + */ +nsresult LoadInfoArgsToLoadInfo(const mozilla::net::LoadInfoArgs& aLoadInfoArgs, + const nsACString& aOriginRemoteType, + nsILoadInfo** outLoadInfo); +nsresult LoadInfoArgsToLoadInfo(const mozilla::net::LoadInfoArgs& aLoadInfoArgs, + const nsACString& aOriginRemoteType, + nsINode* aCspToInheritLoadingContext, + nsILoadInfo** outLoadInfo); +nsresult LoadInfoArgsToLoadInfo(const net::LoadInfoArgs& aLoadInfoArgs, + const nsACString& aOriginRemoteType, + mozilla::net::LoadInfo** outLoadInfo); +nsresult LoadInfoArgsToLoadInfo(const net::LoadInfoArgs& aLoadInfoArgs, + const nsACString& aOriginRemoteType, + nsINode* aCspToInheritLoadingContext, + mozilla::net::LoadInfo** outLoadInfo); + +/** + * Fills ParentLoadInfoForwarderArgs with properties we want to carry to child + * processes. + */ +void LoadInfoToParentLoadInfoForwarder( + nsILoadInfo* aLoadInfo, + mozilla::net::ParentLoadInfoForwarderArgs* aForwarderArgsOut); + +/** + * Merges (replaces) properties of an existing LoadInfo on a child process + * with properties carried down through ParentLoadInfoForwarderArgs. + */ +nsresult MergeParentLoadInfoForwarder( + mozilla::net::ParentLoadInfoForwarderArgs const& aForwarderArgs, + nsILoadInfo* aLoadInfo); + +/** + * Fills ChildLoadInfoForwarderArgs with properties we want to carry to the + * parent process after the initial channel creation. + */ +void LoadInfoToChildLoadInfoForwarder( + nsILoadInfo* aLoadInfo, + mozilla::net::ChildLoadInfoForwarderArgs* aForwarderArgsOut); + +/** + * Merges (replaces) properties of an existing LoadInfo on the parent process + * with properties contained in a ChildLoadInfoForwarderArgs. + */ +nsresult MergeChildLoadInfoForwarder( + const mozilla::net::ChildLoadInfoForwarderArgs& aForwardArgs, + nsILoadInfo* aLoadInfo); + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_backgroundutils_h__ diff --git a/ipc/glue/BigBuffer.cpp b/ipc/glue/BigBuffer.cpp new file mode 100644 index 0000000000..822ffe20fe --- /dev/null +++ b/ipc/glue/BigBuffer.cpp @@ -0,0 +1,105 @@ +/* -*- 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 "mozilla/ipc/BigBuffer.h" + +#include "mozilla/ipc/SharedMemoryBasic.h" +#include "nsDebug.h" + +namespace mozilla::ipc { + +BigBuffer::BigBuffer(Adopt, SharedMemory* aSharedMemory, size_t aSize) + : mSize(aSize), mData(AsVariant(RefPtr{aSharedMemory})) { + MOZ_RELEASE_ASSERT(aSharedMemory && aSharedMemory->memory(), + "shared memory must be non-null and mapped"); + MOZ_RELEASE_ASSERT(mSize <= aSharedMemory->Size(), + "shared memory region isn't large enough"); +} + +BigBuffer::BigBuffer(Adopt, uint8_t* aData, size_t aSize) + : mSize(aSize), mData(AsVariant(UniqueFreePtr<uint8_t[]>{aData})) {} + +uint8_t* BigBuffer::Data() { + return mData.is<0>() ? mData.as<0>().get() + : reinterpret_cast<uint8_t*>(mData.as<1>()->memory()); +} +const uint8_t* BigBuffer::Data() const { + return mData.is<0>() + ? mData.as<0>().get() + : reinterpret_cast<const uint8_t*>(mData.as<1>()->memory()); +} + +auto BigBuffer::TryAllocBuffer(size_t aSize) -> Maybe<Storage> { + if (aSize <= kShmemThreshold) { + auto mem = UniqueFreePtr<uint8_t[]>{ + reinterpret_cast<uint8_t*>(malloc(aSize))}; // Fallible! + if (!mem) return {}; + return Some(AsVariant(std::move(mem))); + } + + RefPtr<SharedMemory> shmem = new SharedMemoryBasic(); + size_t capacity = SharedMemory::PageAlignedSize(aSize); + if (!shmem->Create(capacity) || !shmem->Map(capacity)) { + return {}; + } + return Some(AsVariant(shmem)); +} + +} // namespace mozilla::ipc + +void IPC::ParamTraits<mozilla::ipc::BigBuffer>::Write(MessageWriter* aWriter, + paramType&& aParam) { + using namespace mozilla::ipc; + size_t size = std::exchange(aParam.mSize, 0); + auto data = std::exchange(aParam.mData, BigBuffer::NoData()); + + WriteParam(aWriter, size); + bool isShmem = data.is<1>(); + WriteParam(aWriter, isShmem); + + if (isShmem) { + if (!data.as<1>()->WriteHandle(aWriter)) { + aWriter->FatalError("Failed to write data shmem"); + } + } else { + aWriter->WriteBytes(data.as<0>().get(), size); + } +} + +bool IPC::ParamTraits<mozilla::ipc::BigBuffer>::Read(MessageReader* aReader, + paramType* aResult) { + using namespace mozilla::ipc; + size_t size = 0; + bool isShmem = false; + if (!ReadParam(aReader, &size) || !ReadParam(aReader, &isShmem)) { + aReader->FatalError("Failed to read data size and format"); + return false; + } + + if (isShmem) { + RefPtr<SharedMemory> shmem = new SharedMemoryBasic(); + size_t capacity = SharedMemory::PageAlignedSize(size); + if (!shmem->ReadHandle(aReader) || !shmem->Map(capacity)) { + aReader->FatalError("Failed to read data shmem"); + return false; + } + *aResult = BigBuffer(BigBuffer::Adopt{}, shmem, size); + return true; + } + + mozilla::UniqueFreePtr<uint8_t[]> buf{ + reinterpret_cast<uint8_t*>(malloc(size))}; + if (!buf) { + aReader->FatalError("Failed to allocate data buffer"); + return false; + } + if (!aReader->ReadBytesInto(buf.get(), size)) { + aReader->FatalError("Failed to read data"); + return false; + } + *aResult = BigBuffer(BigBuffer::Adopt{}, buf.release(), size); + return true; +} diff --git a/ipc/glue/BigBuffer.h b/ipc/glue/BigBuffer.h new file mode 100644 index 0000000000..7136b6c03c --- /dev/null +++ b/ipc/glue/BigBuffer.h @@ -0,0 +1,131 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_BigBuffer_h +#define mozilla_ipc_BigBuffer_h + +#include <stdlib.h> +#include <inttypes.h> +#include "mozilla/Span.h" +#include "mozilla/Variant.h" +#include "mozilla/ipc/SharedMemory.h" + +namespace mozilla::ipc { + +class BigBuffer { + public: + static constexpr size_t kShmemThreshold = 64 * 1024; + + static BigBuffer TryAlloc(const size_t aSize) { + auto ret = BigBuffer{}; + auto data = TryAllocBuffer(aSize); + if (data) { + ret.mSize = aSize; + ret.mData = std::move(data.ref()); + } + return ret; + } + + // Return a new BigBuffer which wraps no data. + BigBuffer() : mSize(0), mData(NoData()) {} + + BigBuffer(const BigBuffer&) = delete; + BigBuffer& operator=(const BigBuffer&) = delete; + + BigBuffer(BigBuffer&& aOther) noexcept + : mSize(std::exchange(aOther.mSize, 0)), + mData(std::exchange(aOther.mData, NoData())) {} + + BigBuffer& operator=(BigBuffer&& aOther) noexcept { + mSize = std::exchange(aOther.mSize, 0); + mData = std::exchange(aOther.mData, NoData()); + return *this; + } + + // Create a new BigBuffer with the given size. + // The buffer will be created uninitialized and must be fully initialized + // before sending over IPC to avoid leaking uninitialized memory to another + // process. + explicit BigBuffer(size_t aSize) : mSize(aSize), mData(AllocBuffer(aSize)) {} + + // Create a new BigBuffer containing the data from the provided byte slice. + explicit BigBuffer(Span<const uint8_t> aData) : BigBuffer(aData.Length()) { + memcpy(Data(), aData.Elements(), aData.Length()); + } + + // Marker to indicate that a particular constructor of BigBuffer adopts + // ownership of the provided data. + struct Adopt {}; + + // Create a new BigBuffer from an existing shared memory region, taking + // ownership of that shared memory region. The shared memory region must be + // non-null, mapped, and large enough to fit aSize bytes. + BigBuffer(Adopt, SharedMemory* aSharedMemory, size_t aSize); + + // Create a new BigBuffer from an existing memory buffer, taking ownership of + // that memory region. The region will be freed using `free()` when it is no + // longer needed. + BigBuffer(Adopt, uint8_t* aData, size_t aSize); + + ~BigBuffer() = default; + + // Returns a pointer to the data stored by this BigBuffer, regardless of + // backing storage type. + uint8_t* Data(); + const uint8_t* Data() const; + + // Returns the size of the data stored by this BigBuffer, regardless of + // backing storage type. + size_t Size() const { return mSize; } + + // Get a view of the BigBuffer's data as a span. + Span<uint8_t> AsSpan() { return Span{Data(), Size()}; } + Span<const uint8_t> AsSpan() const { return Span{Data(), Size()}; } + + // If the BigBuffer is backed by shared memory, returns a pointer to the + // backing SharedMemory region. + SharedMemory* GetSharedMemory() const { + return mData.is<1>() ? mData.as<1>().get() : nullptr; + } + + private: + friend struct IPC::ParamTraits<mozilla::ipc::BigBuffer>; + + using Storage = Variant<UniqueFreePtr<uint8_t[]>, RefPtr<SharedMemory>>; + + // Empty storage which holds no data. + static Storage NoData() { return AsVariant(UniqueFreePtr<uint8_t[]>{}); } + + // Fallibly allocate a new storage of the given size. + static Maybe<Storage> TryAllocBuffer(size_t aSize); + + // Infallibly allocate a new storage of the given size. + static Storage AllocBuffer(size_t aSize) { + auto ret = TryAllocBuffer(aSize); + if (!ret) { + NS_ABORT_OOM(aSize); + } + return std::move(ret.ref()); + } + + size_t mSize; + Storage mData; +}; + +} // namespace mozilla::ipc + +namespace IPC { + +template <> +struct ParamTraits<mozilla::ipc::BigBuffer> { + using paramType = mozilla::ipc::BigBuffer; + static void Write(MessageWriter* aWriter, paramType&& aParam); + static bool Read(MessageReader* aReader, paramType* aResult); +}; + +} // namespace IPC + +#endif // mozilla_BigBuffer_h diff --git a/ipc/glue/BrowserProcessSubThread.cpp b/ipc/glue/BrowserProcessSubThread.cpp new file mode 100644 index 0000000000..6c2ba6a48c --- /dev/null +++ b/ipc/glue/BrowserProcessSubThread.cpp @@ -0,0 +1,83 @@ +/* -*- 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 "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/ipc/NodeController.h" + +#if defined(XP_WIN) +# include <objbase.h> +#endif + +namespace mozilla { +namespace ipc { + +// +// BrowserProcessSubThread +// + +// Friendly names for the well-known threads. +static const char* kBrowserThreadNames[BrowserProcessSubThread::ID_COUNT] = { + "IPC I/O Parent", // IO +}; + +/* static */ +StaticMutex BrowserProcessSubThread::sLock; +BrowserProcessSubThread* BrowserProcessSubThread::sBrowserThreads[ID_COUNT] = { + nullptr, // IO +}; + +BrowserProcessSubThread::BrowserProcessSubThread(ID aId) + : base::Thread(kBrowserThreadNames[aId]), mIdentifier(aId) { + StaticMutexAutoLock lock(sLock); + DCHECK(aId >= 0 && aId < ID_COUNT); + DCHECK(sBrowserThreads[aId] == nullptr); + sBrowserThreads[aId] = this; +} + +BrowserProcessSubThread::~BrowserProcessSubThread() { + Stop(); + { + StaticMutexAutoLock lock(sLock); + sBrowserThreads[mIdentifier] = nullptr; + } +} + +void BrowserProcessSubThread::Init() { +#if defined(XP_WIN) + // Initializes the COM library on the current thread. + CoInitialize(nullptr); +#endif + + // Initialize the ports library in the current thread. + if (mIdentifier == IO) { + NodeController::InitBrokerProcess(); + } +} + +void BrowserProcessSubThread::CleanUp() { + if (mIdentifier == IO) { + NodeController::CleanUp(); + } + +#if defined(XP_WIN) + // Closes the COM library on the current thread. CoInitialize must + // be balanced by a corresponding call to CoUninitialize. + CoUninitialize(); +#endif +} + +// static +MessageLoop* BrowserProcessSubThread::GetMessageLoop(ID aId) { + StaticMutexAutoLock lock(sLock); + DCHECK(aId >= 0 && aId < ID_COUNT); + + if (sBrowserThreads[aId]) return sBrowserThreads[aId]->message_loop(); + + return nullptr; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/BrowserProcessSubThread.h b/ipc/glue/BrowserProcessSubThread.h new file mode 100644 index 0000000000..9be8d1ec3d --- /dev/null +++ b/ipc/glue/BrowserProcessSubThread.h @@ -0,0 +1,66 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_BrowserProcessSubThread_h +#define mozilla_ipc_BrowserProcessSubThread_h + +#include "base/thread.h" +#include "mozilla/StaticMutex.h" + +#include "nsDebug.h" + +namespace mozilla { +namespace ipc { + +// Copied from browser_process_impl.cc, modified slightly. +class BrowserProcessSubThread : public base::Thread { + public: + // An enumeration of the well-known threads. + enum ID { + IO, + + // This identifier does not represent a thread. Instead it counts + // the number of well-known threads. Insert new well-known + // threads before this identifier. + ID_COUNT + }; + + explicit BrowserProcessSubThread(ID aId); + ~BrowserProcessSubThread(); + + static MessageLoop* GetMessageLoop(ID identifier); + + protected: + virtual void Init() override; + virtual void CleanUp() override; + + private: + // The identifier of this thread. Only one thread can exist with a given + // identifier at a given time. + ID mIdentifier; + + // This lock protects |browser_threads_|. Do not read or modify that array + // without holding this lock. Do not block while holding this lock. + + static StaticMutex sLock; + + // An array of the ChromeThread objects. This array is protected by |lock_|. + // The threads are not owned by this array. Typically, the threads are owned + // on the UI thread by the g_browser_process object. ChromeThreads remove + // themselves from this array upon destruction. + static BrowserProcessSubThread* sBrowserThreads[ID_COUNT] MOZ_GUARDED_BY( + sLock); +}; + +inline void AssertIOThread() { + NS_ASSERTION(MessageLoop::TYPE_IO == MessageLoop::current()->type(), + "should be on the IO thread!"); +} + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_BrowserProcessSubThread_h diff --git a/ipc/glue/ByteBuf.h b/ipc/glue/ByteBuf.h new file mode 100644 index 0000000000..3f3a798b89 --- /dev/null +++ b/ipc/glue/ByteBuf.h @@ -0,0 +1,71 @@ +/* -*- 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/. */ + +/* A type that can be sent without needing to make a copy during + * serialization. In addition the receiver can take ownership of the + * data to avoid having to make an additional copy. */ + +#ifndef mozilla_ipc_ByteBuf_h +#define mozilla_ipc_ByteBuf_h + +#include "mozilla/Assertions.h" + +namespace IPC { +template <typename T> +struct ParamTraits; +} + +namespace mozilla { + +namespace ipc { + +class ByteBuf final { + friend struct IPC::ParamTraits<mozilla::ipc::ByteBuf>; + + public: + bool Allocate(size_t aLength) { + MOZ_ASSERT(mData == nullptr); + mData = (uint8_t*)malloc(aLength); + if (!mData) { + return false; + } + mLen = aLength; + mCapacity = aLength; + return true; + } + + ByteBuf() : mData(nullptr), mLen(0), mCapacity(0) {} + + ByteBuf(uint8_t* aData, size_t aLen, size_t aCapacity) + : mData(aData), mLen(aLen), mCapacity(aCapacity) {} + + ByteBuf(const ByteBuf& aFrom) = delete; + + ByteBuf(ByteBuf&& aFrom) + : mData(aFrom.mData), mLen(aFrom.mLen), mCapacity(aFrom.mCapacity) { + aFrom.mData = nullptr; + aFrom.mLen = 0; + aFrom.mCapacity = 0; + } + + ByteBuf& operator=(ByteBuf&& aFrom) { + std::swap(mData, aFrom.mData); + std::swap(mLen, aFrom.mLen); + std::swap(mCapacity, aFrom.mCapacity); + return *this; + } + + ~ByteBuf() { free(mData); } + + uint8_t* mData; + size_t mLen; + size_t mCapacity; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef mozilla_ipc_ByteBuf_h diff --git a/ipc/glue/ByteBufUtils.h b/ipc/glue/ByteBufUtils.h new file mode 100644 index 0000000000..ba19864214 --- /dev/null +++ b/ipc/glue/ByteBufUtils.h @@ -0,0 +1,56 @@ +/* -*- 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/. */ + +/* A type that can be sent without needing to make a copy during + * serialization. In addition the receiver can take ownership of the + * data to avoid having to make an additional copy. */ + +#ifndef mozilla_ipc_ByteBufUtils_h +#define mozilla_ipc_ByteBufUtils_h + +#include "mozilla/ipc/ByteBuf.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/mozalloc_oom.h" +#include "ipc/IPCMessageUtils.h" + +namespace IPC { + +template <> +struct ParamTraits<mozilla::ipc::ByteBuf> { + typedef mozilla::ipc::ByteBuf paramType; + + // this is where we transfer the memory from the ByteBuf to IPDL, avoiding a + // copy + static void Write(MessageWriter* aWriter, paramType&& aParam) { + // We need to send the length as a 32-bit value, not a size_t, because on + // ARM64 Windows we end up with a 32-bit GMP process sending a ByteBuf to + // a 64-bit parent process. WriteBytesZeroCopy takes a uint32_t as an + // argument, so it would end up getting truncated anyways. See bug 1757534. + mozilla::CheckedInt<uint32_t> length = aParam.mLen; + MOZ_RELEASE_ASSERT(length.isValid()); + WriteParam(aWriter, length.value()); + // hand over ownership of the buffer to the Message + aWriter->WriteBytesZeroCopy(aParam.mData, length.value(), aParam.mCapacity); + aParam.mData = nullptr; + aParam.mCapacity = 0; + aParam.mLen = 0; + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + // We make a copy from the BufferList so that we get a contigous result. + uint32_t length; + if (!ReadParam(aReader, &length)) return false; + if (!aResult->Allocate(length)) { + mozalloc_handle_oom(length); + return false; + } + return aReader->ReadBytesInto(aResult->mData, length); + } +}; + +} // namespace IPC + +#endif // ifndef mozilla_ipc_ByteBufUtils_h diff --git a/ipc/glue/CrashReporterClient.cpp b/ipc/glue/CrashReporterClient.cpp new file mode 100644 index 0000000000..7e0eabe6ca --- /dev/null +++ b/ipc/glue/CrashReporterClient.cpp @@ -0,0 +1,47 @@ +/* -*- 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 "CrashReporterClient.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace ipc { + +StaticMutex CrashReporterClient::sLock; +StaticRefPtr<CrashReporterClient> CrashReporterClient::sClientSingleton; + +CrashReporterClient::CrashReporterClient() { + MOZ_COUNT_CTOR(CrashReporterClient); +} + +CrashReporterClient::~CrashReporterClient() { + MOZ_COUNT_DTOR(CrashReporterClient); +} + +/* static */ +void CrashReporterClient::InitSingleton() { + { + StaticMutexAutoLock lock(sLock); + + MOZ_ASSERT(!sClientSingleton); + sClientSingleton = new CrashReporterClient(); + } +} + +/* static */ +void CrashReporterClient::DestroySingleton() { + StaticMutexAutoLock lock(sLock); + sClientSingleton = nullptr; +} + +/* static */ +RefPtr<CrashReporterClient> CrashReporterClient::GetSingleton() { + StaticMutexAutoLock lock(sLock); + return sClientSingleton; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/CrashReporterClient.h b/ipc/glue/CrashReporterClient.h new file mode 100644 index 0000000000..26ada18a03 --- /dev/null +++ b/ipc/glue/CrashReporterClient.h @@ -0,0 +1,51 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_CrashReporterClient_h +#define mozilla_ipc_CrashReporterClient_h + +#include "mozilla/Assertions.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.h" +#include "nsExceptionHandler.h" + +namespace mozilla { +namespace ipc { + +class CrashReporterClient { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CrashReporterClient); + + // |aTopLevelProtocol| must have a child-to-parent message: + // + // async InitCrashReporter(NativeThreadId threadId); + template <typename T> + static void InitSingleton(T* aToplevelProtocol) { + InitSingleton(); + Unused << aToplevelProtocol->SendInitCrashReporter( + CrashReporter::CurrentThreadId()); + } + + static void InitSingleton(); + + static void DestroySingleton(); + static RefPtr<CrashReporterClient> GetSingleton(); + + private: + explicit CrashReporterClient(); + ~CrashReporterClient(); + + private: + static StaticMutex sLock; + static StaticRefPtr<CrashReporterClient> sClientSingleton + MOZ_GUARDED_BY(sLock); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_CrashReporterClient_h diff --git a/ipc/glue/CrashReporterHelper.h b/ipc/glue/CrashReporterHelper.h new file mode 100644 index 0000000000..c13901c2cc --- /dev/null +++ b/ipc/glue/CrashReporterHelper.h @@ -0,0 +1,94 @@ +/* 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_ipc_CrashReporterHelper_h +#define mozilla_ipc_CrashReporterHelper_h + +#include "CrashReporterHost.h" +#include "mozilla/UniquePtr.h" +#include "nsIAppStartup.h" +#include "nsExceptionHandler.h" +#include "nsICrashService.h" +#include "nsPrintfCString.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { +namespace ipc { + +/** + * This class encapsulates the common elements of crash report handling for + * toplevel protocols representing processes. To use this class, you should: + * + * 1. Declare a method to initialize the crash reporter in your IPDL: + * `async InitCrashReporter(NativeThreadId threadId)` + * + * 2. Inherit from this class, providing the appropriate `GeckoProcessType` + * enum value for the template parameter PT. + * + * 3. When your protocol actor is destroyed with a reason of `AbnormalShutdown`, + * you should call `GenerateCrashReport(OtherPid())`. If you need the crash + * report ID it will be copied in the second optional parameter upon + * successful crash report generation. + */ +template <GeckoProcessType PT> +class CrashReporterHelper { + public: + CrashReporterHelper() : mCrashReporter(nullptr) {} + IPCResult RecvInitCrashReporter(const CrashReporter::ThreadId& aThreadId) { + mCrashReporter = MakeUnique<ipc::CrashReporterHost>(PT, aThreadId); + return IPC_OK(); + } + + protected: + void GenerateCrashReport(base::ProcessId aPid, + nsString* aMinidumpId = nullptr) { + nsAutoString minidumpId; + if (!mCrashReporter) { + HandleOrphanedMinidump(aPid, minidumpId); + } else if (mCrashReporter->GenerateCrashReport(aPid)) { + minidumpId = mCrashReporter->MinidumpID(); + } + + if (aMinidumpId) { + *aMinidumpId = minidumpId; + } + + mCrashReporter = nullptr; + } + + void MaybeTerminateProcess() { + if (PR_GetEnv("MOZ_CRASHREPORTER_SHUTDOWN")) { + NS_WARNING(nsPrintfCString("Shutting down due to %s process crash.", + XRE_GetProcessTypeString()) + .get()); + nsCOMPtr<nsIAppStartup> appService = + do_GetService("@mozilla.org/toolkit/app-startup;1"); + if (appService) { + bool userAllowedQuit = true; + appService->Quit(nsIAppStartup::eForceQuit, 1, &userAllowedQuit); + } + } + } + + private: + void HandleOrphanedMinidump(base::ProcessId aPid, nsString& aMinidumpId) { + if (CrashReporter::FinalizeOrphanedMinidump(aPid, PT, &aMinidumpId)) { + CrashReporterHost::RecordCrash(PT, nsICrashService::CRASH_TYPE_CRASH, + aMinidumpId); + } else { + NS_WARNING(nsPrintfCString("child process pid = %" PRIPID + " crashed without leaving a minidump behind", + aPid) + .get()); + } + } + + protected: + UniquePtr<ipc::CrashReporterHost> mCrashReporter; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_CrashReporterHelper_h diff --git a/ipc/glue/CrashReporterHost.cpp b/ipc/glue/CrashReporterHost.cpp new file mode 100644 index 0000000000..d4f0c0f968 --- /dev/null +++ b/ipc/glue/CrashReporterHost.cpp @@ -0,0 +1,181 @@ +/* -*- 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 "CrashReporterHost.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/Sprintf.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/Telemetry.h" +#include "nsServiceManagerUtils.h" +#include "nsICrashService.h" +#include "nsXULAppAPI.h" +#include "nsIFile.h" + +namespace mozilla { +namespace ipc { + +CrashReporterHost::CrashReporterHost(GeckoProcessType aProcessType, + CrashReporter::ThreadId aThreadId) + : mProcessType(aProcessType), + mThreadId(aThreadId), + mStartTime(::time(nullptr)), + mFinalized(false) {} + +bool CrashReporterHost::GenerateCrashReport(base::ProcessId aPid) { + if (!TakeCrashedChildMinidump(aPid, nullptr)) { + return false; + } + + FinalizeCrashReport(); + RecordCrash(mProcessType, nsICrashService::CRASH_TYPE_CRASH, mDumpID); + return true; +} + +RefPtr<nsIFile> CrashReporterHost::TakeCrashedChildMinidump( + base::ProcessId aPid, uint32_t* aOutSequence) { + CrashReporter::AnnotationTable annotations; + MOZ_ASSERT(!HasMinidump()); + + RefPtr<nsIFile> crashDump; + if (!CrashReporter::TakeMinidumpForChild(aPid, getter_AddRefs(crashDump), + annotations, aOutSequence)) { + return nullptr; + } + if (!AdoptMinidump(crashDump, annotations)) { + return nullptr; + } + return crashDump; +} + +bool CrashReporterHost::AdoptMinidump(nsIFile* aFile, + const AnnotationTable& aAnnotations) { + if (!CrashReporter::GetIDFromMinidump(aFile, mDumpID)) { + return false; + } + + MergeCrashAnnotations(mExtraAnnotations, aAnnotations); + return true; +} + +void CrashReporterHost::FinalizeCrashReport() { + MOZ_ASSERT(!mFinalized); + MOZ_ASSERT(HasMinidump()); + + mExtraAnnotations[CrashReporter::Annotation::ProcessType] = + XRE_ChildProcessTypeToAnnotation(mProcessType); + + char startTime[32]; + SprintfLiteral(startTime, "%lld", static_cast<long long>(mStartTime)); + mExtraAnnotations[CrashReporter::Annotation::StartupTime] = + nsDependentCString(startTime); + + CrashReporter::WriteExtraFile(mDumpID, mExtraAnnotations); + mFinalized = true; +} + +void CrashReporterHost::DeleteCrashReport() { + if (mFinalized && HasMinidump()) { + CrashReporter::DeleteMinidumpFilesForID(mDumpID, Some(u"browser"_ns)); + } +} + +/* static */ +void CrashReporterHost::RecordCrash(GeckoProcessType aProcessType, + int32_t aCrashType, + const nsString& aChildDumpID) { + if (!NS_IsMainThread()) { + RefPtr<Runnable> runnable = NS_NewRunnableFunction( + "ipc::CrashReporterHost::RecordCrash", [&]() -> void { + CrashReporterHost::RecordCrash(aProcessType, aCrashType, + aChildDumpID); + }); + RefPtr<nsIThread> mainThread = do_GetMainThread(); + SyncRunnable::DispatchToThread(mainThread, runnable); + return; + } + + RecordCrashWithTelemetry(aProcessType, aCrashType); + NotifyCrashService(aProcessType, aCrashType, aChildDumpID); +} + +/* static */ +void CrashReporterHost::RecordCrashWithTelemetry(GeckoProcessType aProcessType, + int32_t aCrashType) { + nsCString key; + + switch (aProcessType) { +#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \ + process_bin_type, procinfo_typename, \ + webidl_typename, allcaps_name) \ + case GeckoProcessType_##enum_name: \ + key.AssignLiteral(string_name); \ + break; +#include "mozilla/GeckoProcessTypes.h" +#undef GECKO_PROCESS_TYPE + // We can't really hit this, thanks to the above switch, but having it + // here will placate the compiler. + default: + MOZ_ASSERT_UNREACHABLE("unknown process type"); + } + + Telemetry::Accumulate(Telemetry::SUBPROCESS_CRASHES_WITH_DUMP, key, 1); +} + +/* static */ +void CrashReporterHost::NotifyCrashService(GeckoProcessType aProcessType, + int32_t aCrashType, + const nsString& aChildDumpID) { + MOZ_ASSERT(!aChildDumpID.IsEmpty()); + + nsCOMPtr<nsICrashService> crashService = + do_GetService("@mozilla.org/crashservice;1"); + if (!crashService) { + return; + } + + int32_t processType; + + switch (aProcessType) { + case GeckoProcessType_IPDLUnitTest: + case GeckoProcessType_Default: + NS_ERROR("unknown process type"); + return; + default: + processType = (int)aProcessType; + break; + } + + RefPtr<dom::Promise> promise; + crashService->AddCrash(processType, aCrashType, aChildDumpID, + getter_AddRefs(promise)); +} + +void CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey, + bool aValue) { + mExtraAnnotations[aKey] = aValue ? "1"_ns : "0"_ns; +} + +void CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey, + int aValue) { + nsAutoCString valueString; + valueString.AppendInt(aValue); + mExtraAnnotations[aKey] = valueString; +} + +void CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey, + unsigned int aValue) { + nsAutoCString valueString; + valueString.AppendInt(aValue); + mExtraAnnotations[aKey] = valueString; +} + +void CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey, + const nsACString& aValue) { + mExtraAnnotations[aKey] = aValue; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/CrashReporterHost.h b/ipc/glue/CrashReporterHost.h new file mode 100644 index 0000000000..7b71b64cdf --- /dev/null +++ b/ipc/glue/CrashReporterHost.h @@ -0,0 +1,130 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_CrashReporterHost_h +#define mozilla_ipc_CrashReporterHost_h + +#include <functional> + +#include "mozilla/UniquePtr.h" +#include "base/process.h" +#include "nsExceptionHandler.h" +#include "nsThreadUtils.h" +#include "mozilla/ipc/ProtocolUtils.h" + +namespace mozilla { +namespace ipc { + +// This is the newer replacement for CrashReporterParent. It is created in +// response to a InitCrashReporter message on a top-level actor. When the +// process terminates abnormally, the top-level should call GenerateCrashReport +// to automatically integrate metadata. +class CrashReporterHost { + typedef CrashReporter::AnnotationTable AnnotationTable; + + public: + CrashReporterHost(GeckoProcessType aProcessType, + CrashReporter::ThreadId aThreadId); + + // Helper function for generating a crash report for a process that probably + // crashed (i.e., had an AbnormalShutdown in ActorDestroy). Returns true if + // the process has a minidump attached and we were able to generate a report. + bool GenerateCrashReport(base::ProcessId aPid); + + // Given an existing minidump for a crashed child process, take ownership of + // it from IPDL. After this, FinalizeCrashReport may be called. + RefPtr<nsIFile> TakeCrashedChildMinidump(base::ProcessId aPid, + uint32_t* aOutSequence); + + // Replace the stored minidump with a new one. After this, + // FinalizeCrashReport may be called. + bool AdoptMinidump(nsIFile* aFile, const AnnotationTable& aAnnotations); + + // If a minidump was already captured (e.g. via the hang reporter), this + // finalizes the existing report by attaching metadata, writing out the + // .extra file and notifying the crash service. + void FinalizeCrashReport(); + + // Delete any crash report we might have generated. + void DeleteCrashReport(); + + // Generate a paired minidump. This does not take the crash report, as + // GenerateCrashReport does. After this, FinalizeCrashReport may be called. + // + // This calls TakeCrashedChildMinidump and FinalizeCrashReport. + template <typename Toplevel> + bool GenerateMinidumpAndPair(Toplevel* aToplevelProtocol, + const nsACString& aPairName) { + auto childHandle = base::kInvalidProcessHandle; + const auto cleanup = MakeScopeExit([&]() { + if (childHandle && childHandle != base::kInvalidProcessHandle) { + base::CloseProcessHandle(childHandle); + } + }); +#ifdef XP_MACOSX + childHandle = aToplevelProtocol->Process()->GetChildTask(); +#else + if (!base::OpenPrivilegedProcessHandle(aToplevelProtocol->OtherPid(), + &childHandle)) { + NS_WARNING("Failed to open child process handle."); + return false; + } +#endif + + nsCOMPtr<nsIFile> targetDump; + if (!CrashReporter::CreateMinidumpsAndPair(childHandle, mThreadId, + aPairName, mExtraAnnotations, + getter_AddRefs(targetDump))) { + return false; + } + + return CrashReporter::GetIDFromMinidump(targetDump, mDumpID); + } + + void AddAnnotation(CrashReporter::Annotation aKey, bool aValue); + void AddAnnotation(CrashReporter::Annotation aKey, int aValue); + void AddAnnotation(CrashReporter::Annotation aKey, unsigned int aValue); + void AddAnnotation(CrashReporter::Annotation aKey, const nsACString& aValue); + + bool HasMinidump() const { return !mDumpID.IsEmpty(); } + const nsString& MinidumpID() const { + MOZ_ASSERT(HasMinidump()); + return mDumpID; + } + const nsCString& AdditionalMinidumps() const { + return mExtraAnnotations[CrashReporter::Annotation::additional_minidumps]; + } + + // This is a static helper function to notify the crash service that a + // crash has occurred and record the crash with telemetry. This can be called + // from any thread, and if not called from the main thread, will post a + // synchronous message to the main thread. + static void RecordCrash(GeckoProcessType aProcessType, int32_t aCrashType, + const nsString& aChildDumpID); + + private: + // Get the nsICrashService crash type to use for an impending crash. + int32_t GetCrashType(); + + static void RecordCrashWithTelemetry(GeckoProcessType aProcessType, + int32_t aCrashType); + static void NotifyCrashService(GeckoProcessType aProcessType, + int32_t aCrashType, + const nsString& aChildDumpID); + + private: + GeckoProcessType mProcessType; + CrashReporter::ThreadId mThreadId; + time_t mStartTime; + AnnotationTable mExtraAnnotations; + nsString mDumpID; + bool mFinalized; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_CrashReporterHost_h diff --git a/ipc/glue/CrossProcessMutex.h b/ipc/glue/CrossProcessMutex.h new file mode 100644 index 0000000000..3e16166c4b --- /dev/null +++ b/ipc/glue/CrossProcessMutex.h @@ -0,0 +1,118 @@ +/* -*- 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/. */ + +#ifndef mozilla_CrossProcessMutex_h +#define mozilla_CrossProcessMutex_h + +#include "base/process.h" +#include "mozilla/Mutex.h" + +#if defined(XP_WIN) +# include "mozilla/UniquePtrExtensions.h" +#endif +#if !defined(XP_WIN) && !defined(XP_NETBSD) && !defined(XP_OPENBSD) +# include <pthread.h> +# include "mozilla/ipc/SharedMemoryBasic.h" +# include "mozilla/Atomics.h" +#endif + +namespace IPC { +template <typename T> +struct ParamTraits; +} // namespace IPC + +// +// Provides: +// +// - CrossProcessMutex, a non-recursive mutex that can be shared across +// processes +// - CrossProcessMutexAutoLock, an RAII class for ensuring that Mutexes are +// properly locked and unlocked +// +// Using CrossProcessMutexAutoLock/CrossProcessMutexAutoUnlock is MUCH +// preferred to making bare calls to CrossProcessMutex.Lock and Unlock. +// +namespace mozilla { +#if defined(XP_WIN) +typedef mozilla::UniqueFileHandle CrossProcessMutexHandle; +#elif !defined(XP_NETBSD) && !defined(XP_OPENBSD) +typedef mozilla::ipc::SharedMemoryBasic::Handle CrossProcessMutexHandle; +#else +// Stub for other platforms. We can't use uintptr_t here since different +// processes could disagree on its size. +typedef uintptr_t CrossProcessMutexHandle; +#endif + +class CrossProcessMutex { + public: + /** + * CrossProcessMutex + * @param name A name which can reference this lock (currently unused) + **/ + explicit CrossProcessMutex(const char* aName); + /** + * CrossProcessMutex + * @param handle A handle of an existing cross process mutex that can be + * opened. + */ + explicit CrossProcessMutex(CrossProcessMutexHandle aHandle); + + /** + * ~CrossProcessMutex + **/ + ~CrossProcessMutex(); + + /** + * Lock + * This will lock the mutex. Any other thread in any other process that + * has access to this mutex calling lock will block execution until the + * initial caller of lock has made a call to Unlock. + * + * If the owning process is terminated unexpectedly the mutex will be + * released. + **/ + void Lock(); + + /** + * Unlock + * This will unlock the mutex. A single thread currently waiting on a lock + * call will resume execution and aquire ownership of the lock. No + * guarantees are made as to the order in which waiting threads will resume + * execution. + **/ + void Unlock(); + + /** + * CloneHandle + * This function is called to generate a serializable structure that can + * be sent to the specified process and opened on the other side. + * + * @returns A handle that can be shared to another process + */ + CrossProcessMutexHandle CloneHandle(); + + private: + friend struct IPC::ParamTraits<CrossProcessMutex>; + + CrossProcessMutex(); + CrossProcessMutex(const CrossProcessMutex&); + CrossProcessMutex& operator=(const CrossProcessMutex&); + +#if defined(XP_WIN) + HANDLE mMutex; +#elif !defined(XP_NETBSD) && !defined(XP_OPENBSD) + RefPtr<mozilla::ipc::SharedMemoryBasic> mSharedBuffer; + pthread_mutex_t* mMutex; + mozilla::Atomic<int32_t>* mCount; +#endif +}; + +typedef detail::BaseAutoLock<CrossProcessMutex&> CrossProcessMutexAutoLock; +typedef detail::BaseAutoUnlock<CrossProcessMutex&> CrossProcessMutexAutoUnlock; + +} // namespace mozilla + +#endif diff --git a/ipc/glue/CrossProcessMutex_posix.cpp b/ipc/glue/CrossProcessMutex_posix.cpp new file mode 100644 index 0000000000..c37fcb797d --- /dev/null +++ b/ipc/glue/CrossProcessMutex_posix.cpp @@ -0,0 +1,140 @@ +/* -*- 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 "CrossProcessMutex.h" +#include "mozilla/Unused.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" + +namespace { + +struct MutexData { + pthread_mutex_t mMutex; + mozilla::Atomic<int32_t> mCount; +}; + +} // namespace + +namespace mozilla { + +static void InitMutex(pthread_mutex_t* mMutex) { + pthread_mutexattr_t mutexAttributes; + pthread_mutexattr_init(&mutexAttributes); + // Make the mutex reentrant so it behaves the same as a win32 mutex + if (pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE)) { + MOZ_CRASH(); + } + if (pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED)) { + MOZ_CRASH(); + } + + if (pthread_mutex_init(mMutex, &mutexAttributes)) { + MOZ_CRASH(); + } +} + +CrossProcessMutex::CrossProcessMutex(const char*) + : mMutex(nullptr), mCount(nullptr) { +#if defined(MOZ_SANDBOX) + // POSIX mutexes in shared memory aren't guaranteed to be safe - and + // they specifically are not on Linux. + MOZ_RELEASE_ASSERT(false); +#endif + mSharedBuffer = new ipc::SharedMemoryBasic; + if (!mSharedBuffer->Create(sizeof(MutexData))) { + MOZ_CRASH(); + } + + if (!mSharedBuffer->Map(sizeof(MutexData))) { + MOZ_CRASH(); + } + + MutexData* data = static_cast<MutexData*>(mSharedBuffer->memory()); + + if (!data) { + MOZ_CRASH(); + } + + mMutex = &(data->mMutex); + mCount = &(data->mCount); + + *mCount = 1; + InitMutex(mMutex); + + MOZ_COUNT_CTOR(CrossProcessMutex); +} + +CrossProcessMutex::CrossProcessMutex(CrossProcessMutexHandle aHandle) + : mMutex(nullptr), mCount(nullptr) { + mSharedBuffer = new ipc::SharedMemoryBasic; + + if (!mSharedBuffer->IsHandleValid(aHandle)) { + MOZ_CRASH(); + } + + if (!mSharedBuffer->SetHandle(std::move(aHandle), + ipc::SharedMemory::RightsReadWrite)) { + MOZ_CRASH(); + } + + if (!mSharedBuffer->Map(sizeof(MutexData))) { + MOZ_CRASH(); + } + + MutexData* data = static_cast<MutexData*>(mSharedBuffer->memory()); + + if (!data) { + MOZ_CRASH(); + } + + mMutex = &(data->mMutex); + mCount = &(data->mCount); + int32_t count = (*mCount)++; + + if (count == 0) { + // The other side has already let go of their CrossProcessMutex, so now + // mMutex is garbage. We need to re-initialize it. + InitMutex(mMutex); + } + + MOZ_COUNT_CTOR(CrossProcessMutex); +} + +CrossProcessMutex::~CrossProcessMutex() { + int32_t count = --(*mCount); + + if (count == 0) { + // Nothing can be done if the destroy fails so ignore return code. + Unused << pthread_mutex_destroy(mMutex); + } + + MOZ_COUNT_DTOR(CrossProcessMutex); +} + +void CrossProcessMutex::Lock() { + MOZ_ASSERT(*mCount > 0, "Attempting to lock mutex with zero ref count"); + pthread_mutex_lock(mMutex); +} + +void CrossProcessMutex::Unlock() { + MOZ_ASSERT(*mCount > 0, "Attempting to unlock mutex with zero ref count"); + pthread_mutex_unlock(mMutex); +} + +CrossProcessMutexHandle CrossProcessMutex::CloneHandle() { + CrossProcessMutexHandle result = ipc::SharedMemoryBasic::NULLHandle(); + + if (mSharedBuffer) { + result = mSharedBuffer->CloneHandle(); + if (!result) { + MOZ_CRASH(); + } + } + + return result; +} + +} // namespace mozilla diff --git a/ipc/glue/CrossProcessMutex_unimplemented.cpp b/ipc/glue/CrossProcessMutex_unimplemented.cpp new file mode 100644 index 0000000000..70b63d7ea9 --- /dev/null +++ b/ipc/glue/CrossProcessMutex_unimplemented.cpp @@ -0,0 +1,46 @@ +/* -*- 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 "CrossProcessMutex.h" + +#include "nsDebug.h" + +namespace mozilla { + +CrossProcessMutex::CrossProcessMutex(const char*) { + MOZ_CRASH("Cross-process mutices not allowed on this platform."); +} + +CrossProcessMutex::CrossProcessMutex(CrossProcessMutexHandle) { + MOZ_CRASH("Cross-process mutices not allowed on this platform."); +} + +CrossProcessMutex::~CrossProcessMutex() { + MOZ_CRASH( + "Cross-process mutices not allowed on this platform - woah! We should've " + "aborted by now!"); +} + +void CrossProcessMutex::Lock() { + MOZ_CRASH( + "Cross-process mutices not allowed on this platform - woah! We should've " + "aborted by now!"); +} + +void CrossProcessMutex::Unlock() { + MOZ_CRASH( + "Cross-process mutices not allowed on this platform - woah! We should've " + "aborted by now!"); +} + +CrossProcessMutexHandle CrossProcessMutex::CloneHandle() { + MOZ_CRASH( + "Cross-process mutices not allowed on this platform - woah! We should've " + "aborted by now!"); + return 0; +} + +} // namespace mozilla diff --git a/ipc/glue/CrossProcessMutex_windows.cpp b/ipc/glue/CrossProcessMutex_windows.cpp new file mode 100644 index 0000000000..d4bbe2f2ba --- /dev/null +++ b/ipc/glue/CrossProcessMutex_windows.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 <windows.h> + +#include "base/process_util.h" +#include "CrossProcessMutex.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include "ProtocolUtils.h" + +using base::GetCurrentProcessHandle; +using base::ProcessHandle; + +namespace mozilla { + +CrossProcessMutex::CrossProcessMutex(const char*) { + // We explicitly share this using DuplicateHandle, we do -not- want this to + // be inherited by child processes by default! So no security attributes are + // given. + mMutex = ::CreateMutexA(nullptr, FALSE, nullptr); + if (!mMutex) { + MOZ_CRASH("This shouldn't happen - failed to create mutex!"); + } + MOZ_COUNT_CTOR(CrossProcessMutex); +} + +CrossProcessMutex::CrossProcessMutex(CrossProcessMutexHandle aHandle) { + DWORD flags; + if (!::GetHandleInformation(aHandle.get(), &flags)) { + MOZ_CRASH("Attempt to construct a mutex from an invalid handle!"); + } + mMutex = aHandle.release(); + MOZ_COUNT_CTOR(CrossProcessMutex); +} + +CrossProcessMutex::~CrossProcessMutex() { + NS_ASSERTION(mMutex, "Improper construction of mutex or double free."); + ::CloseHandle(mMutex); + MOZ_COUNT_DTOR(CrossProcessMutex); +} + +void CrossProcessMutex::Lock() { + NS_ASSERTION(mMutex, "Improper construction of mutex."); + ::WaitForSingleObject(mMutex, INFINITE); +} + +void CrossProcessMutex::Unlock() { + NS_ASSERTION(mMutex, "Improper construction of mutex."); + ::ReleaseMutex(mMutex); +} + +CrossProcessMutexHandle CrossProcessMutex::CloneHandle() { + HANDLE newHandle; + if (!::DuplicateHandle(GetCurrentProcess(), mMutex, GetCurrentProcess(), + &newHandle, 0, false, DUPLICATE_SAME_ACCESS)) { + return nullptr; + } + return mozilla::UniqueFileHandle(newHandle); +} + +} // namespace mozilla diff --git a/ipc/glue/CrossProcessSemaphore.h b/ipc/glue/CrossProcessSemaphore.h new file mode 100644 index 0000000000..466f88f8c3 --- /dev/null +++ b/ipc/glue/CrossProcessSemaphore.h @@ -0,0 +1,119 @@ +/* -*- 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/. */ + +#ifndef mozilla_CrossProcessSemaphore_h +#define mozilla_CrossProcessSemaphore_h + +#include "base/process.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Maybe.h" + +#if defined(XP_WIN) || defined(XP_DARWIN) +# include "mozilla/UniquePtrExtensions.h" +#else +# include <pthread.h> +# include <semaphore.h> +# include "mozilla/ipc/SharedMemoryBasic.h" +# include "mozilla/Atomics.h" +#endif + +namespace IPC { +template <typename T> +struct ParamTraits; +} // namespace IPC + +// +// Provides: +// +// - CrossProcessSemaphore, a semaphore that can be shared across processes +namespace mozilla { + +template <typename T> +inline bool IsHandleValid(const T& handle) { + return bool(handle); +} + +#if defined(XP_WIN) +typedef mozilla::UniqueFileHandle CrossProcessSemaphoreHandle; +#elif defined(XP_DARWIN) +typedef mozilla::UniqueMachSendRight CrossProcessSemaphoreHandle; +#else +typedef mozilla::ipc::SharedMemoryBasic::Handle CrossProcessSemaphoreHandle; + +template <> +inline bool IsHandleValid<CrossProcessSemaphoreHandle>( + const CrossProcessSemaphoreHandle& handle) { + return !(handle == mozilla::ipc::SharedMemoryBasic::NULLHandle()); +} +#endif + +class CrossProcessSemaphore { + public: + /** + * CrossProcessSemaphore + * @param name A name which can reference this lock (currently unused) + **/ + static CrossProcessSemaphore* Create(const char* aName, + uint32_t aInitialValue); + + /** + * CrossProcessSemaphore + * @param handle A handle of an existing cross process semaphore that can be + * opened. + */ + static CrossProcessSemaphore* Create(CrossProcessSemaphoreHandle aHandle); + + ~CrossProcessSemaphore(); + + /** + * Decrements the current value of the semaphore. This will block if the + * current value of the semaphore is 0, up to a maximum of aWaitTime (if + * specified). + * Returns true if the semaphore was succesfully decremented, false otherwise. + **/ + bool Wait(const Maybe<TimeDuration>& aWaitTime = Nothing()); + + /** + * Increments the current value of the semaphore. + **/ + void Signal(); + + /** + * CloneHandle + * This function is called to generate a serializable structure that can + * be sent to the specified process and opened on the other side. + * + * @returns A handle that can be shared to another process + */ + CrossProcessSemaphoreHandle CloneHandle(); + + void CloseHandle(); + + private: + friend struct IPC::ParamTraits<CrossProcessSemaphore>; + + CrossProcessSemaphore(); + CrossProcessSemaphore(const CrossProcessSemaphore&); + CrossProcessSemaphore& operator=(const CrossProcessSemaphore&); + +#if defined(XP_WIN) + explicit CrossProcessSemaphore(HANDLE aSemaphore); + + HANDLE mSemaphore; +#elif defined(XP_DARWIN) + explicit CrossProcessSemaphore(CrossProcessSemaphoreHandle aSemaphore); + + CrossProcessSemaphoreHandle mSemaphore; +#else + RefPtr<mozilla::ipc::SharedMemoryBasic> mSharedBuffer; + sem_t* mSemaphore; + mozilla::Atomic<int32_t>* mRefCount; +#endif +}; + +} // namespace mozilla + +#endif diff --git a/ipc/glue/CrossProcessSemaphore_mach.cpp b/ipc/glue/CrossProcessSemaphore_mach.cpp new file mode 100644 index 0000000000..d7cccee2a0 --- /dev/null +++ b/ipc/glue/CrossProcessSemaphore_mach.cpp @@ -0,0 +1,92 @@ +/* -*- 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 "CrossProcessSemaphore.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include <mach/mach_time.h> + +static const uint64_t kNsPerUs = 1000; +static const uint64_t kNsPerSec = 1000000000; + +namespace mozilla { + +/* static */ +CrossProcessSemaphore* CrossProcessSemaphore::Create(const char*, + uint32_t aInitialValue) { + semaphore_t sem = SEMAPHORE_NULL; + if (semaphore_create(mach_task_self(), &sem, SYNC_POLICY_FIFO, + aInitialValue) == KERN_SUCCESS && + sem != SEMAPHORE_NULL) { + return new CrossProcessSemaphore(CrossProcessSemaphoreHandle(sem)); + } + return nullptr; +} + +/* static */ +CrossProcessSemaphore* CrossProcessSemaphore::Create( + CrossProcessSemaphoreHandle aHandle) { + if (!aHandle) { + return nullptr; + } + return new CrossProcessSemaphore(std::move(aHandle)); +} + +CrossProcessSemaphore::CrossProcessSemaphore( + CrossProcessSemaphoreHandle aSemaphore) + : mSemaphore(std::move(aSemaphore)) { + MOZ_COUNT_CTOR(CrossProcessSemaphore); +} + +CrossProcessSemaphore::~CrossProcessSemaphore() { + MOZ_ASSERT(mSemaphore, "Improper construction of semaphore or double free."); + MOZ_COUNT_DTOR(CrossProcessSemaphore); +} + +bool CrossProcessSemaphore::Wait(const Maybe<TimeDuration>& aWaitTime) { + MOZ_ASSERT(mSemaphore, "Improper construction of semaphore."); + int kr = KERN_OPERATION_TIMED_OUT; + // semaphore_(timed)wait may be interrupted by KERN_ABORTED. Carefully restart + // the wait until it either succeeds or times out. + if (aWaitTime.isNothing()) { + do { + kr = semaphore_wait(mSemaphore.get()); + } while (kr == KERN_ABORTED); + } else { + mach_timebase_info_data_t tb; + if (mach_timebase_info(&tb) != KERN_SUCCESS) { + return false; + } + uint64_t now = (mach_absolute_time() * tb.numer) / tb.denom; + uint64_t deadline = now + uint64_t(kNsPerUs * aWaitTime->ToMicroseconds()); + while (now <= deadline) { + uint64_t ns = deadline - now; + mach_timespec_t ts; + ts.tv_sec = ns / kNsPerSec; + ts.tv_nsec = ns % kNsPerSec; + kr = semaphore_timedwait(mSemaphore.get(), ts); + if (kr != KERN_ABORTED) { + break; + } + now = (mach_absolute_time() * tb.numer) / tb.denom; + } + } + return kr == KERN_SUCCESS; +} + +void CrossProcessSemaphore::Signal() { + MOZ_ASSERT(mSemaphore, "Improper construction of semaphore."); + semaphore_signal(mSemaphore.get()); +} + +CrossProcessSemaphoreHandle CrossProcessSemaphore::CloneHandle() { + // Transfer the mach port backing the semaphore. + return mozilla::RetainMachSendRight(mSemaphore.get()); +} + +void CrossProcessSemaphore::CloseHandle() {} + +} // namespace mozilla diff --git a/ipc/glue/CrossProcessSemaphore_posix.cpp b/ipc/glue/CrossProcessSemaphore_posix.cpp new file mode 100644 index 0000000000..3b32a897ee --- /dev/null +++ b/ipc/glue/CrossProcessSemaphore_posix.cpp @@ -0,0 +1,164 @@ +/* -*- 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 "CrossProcessSemaphore.h" +#include "mozilla/Unused.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include <errno.h> + +static const uint64_t kNsPerMs = 1000000; +static const uint64_t kNsPerSec = 1000000000; + +namespace { + +struct SemaphoreData { + sem_t mSemaphore; + mozilla::Atomic<int32_t> mRefCount; + uint32_t mInitialValue; +}; + +} // namespace + +namespace mozilla { + +/* static */ +CrossProcessSemaphore* CrossProcessSemaphore::Create(const char*, + uint32_t aInitialValue) { + RefPtr<ipc::SharedMemoryBasic> sharedBuffer = new ipc::SharedMemoryBasic; + if (!sharedBuffer->Create(sizeof(SemaphoreData))) { + return nullptr; + } + + if (!sharedBuffer->Map(sizeof(SemaphoreData))) { + return nullptr; + } + + SemaphoreData* data = static_cast<SemaphoreData*>(sharedBuffer->memory()); + + if (!data) { + return nullptr; + } + + if (sem_init(&data->mSemaphore, 1, aInitialValue)) { + return nullptr; + } + + CrossProcessSemaphore* sem = new CrossProcessSemaphore; + sem->mSharedBuffer = sharedBuffer; + sem->mSemaphore = &data->mSemaphore; + sem->mRefCount = &data->mRefCount; + *sem->mRefCount = 1; + + data->mInitialValue = aInitialValue; + + return sem; +} + +/* static */ +CrossProcessSemaphore* CrossProcessSemaphore::Create( + CrossProcessSemaphoreHandle aHandle) { + RefPtr<ipc::SharedMemoryBasic> sharedBuffer = new ipc::SharedMemoryBasic; + + if (!sharedBuffer->IsHandleValid(aHandle)) { + return nullptr; + } + + if (!sharedBuffer->SetHandle(std::move(aHandle), + ipc::SharedMemory::RightsReadWrite)) { + return nullptr; + } + + if (!sharedBuffer->Map(sizeof(SemaphoreData))) { + return nullptr; + } + + sharedBuffer->CloseHandle(); + + SemaphoreData* data = static_cast<SemaphoreData*>(sharedBuffer->memory()); + + if (!data) { + return nullptr; + } + + int32_t oldCount = data->mRefCount++; + if (oldCount == 0) { + // The other side has already let go of their CrossProcessSemaphore, so now + // mSemaphore is garbage. We need to re-initialize it. + if (sem_init(&data->mSemaphore, 1, data->mInitialValue)) { + data->mRefCount--; + return nullptr; + } + } + + CrossProcessSemaphore* sem = new CrossProcessSemaphore; + sem->mSharedBuffer = sharedBuffer; + sem->mSemaphore = &data->mSemaphore; + sem->mRefCount = &data->mRefCount; + return sem; +} + +CrossProcessSemaphore::CrossProcessSemaphore() + : mSemaphore(nullptr), mRefCount(nullptr) { + MOZ_COUNT_CTOR(CrossProcessSemaphore); +} + +CrossProcessSemaphore::~CrossProcessSemaphore() { + int32_t oldCount = --(*mRefCount); + + if (oldCount == 0) { + // Nothing can be done if the destroy fails so ignore return code. + Unused << sem_destroy(mSemaphore); + } + + MOZ_COUNT_DTOR(CrossProcessSemaphore); +} + +bool CrossProcessSemaphore::Wait(const Maybe<TimeDuration>& aWaitTime) { + MOZ_ASSERT(*mRefCount > 0, + "Attempting to wait on a semaphore with zero ref count"); + int ret; + if (aWaitTime.isSome()) { + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { + return false; + } + + uint64_t ns = uint64_t(kNsPerMs * aWaitTime->ToMilliseconds()) + ts.tv_nsec; + ts.tv_sec += ns / kNsPerSec; + ts.tv_nsec = ns % kNsPerSec; + + while ((ret = sem_timedwait(mSemaphore, &ts)) == -1 && errno == EINTR) { + } + } else { + while ((ret = sem_wait(mSemaphore)) == -1 && errno == EINTR) { + } + } + return ret == 0; +} + +void CrossProcessSemaphore::Signal() { + MOZ_ASSERT(*mRefCount > 0, + "Attempting to signal a semaphore with zero ref count"); + sem_post(mSemaphore); +} + +CrossProcessSemaphoreHandle CrossProcessSemaphore::CloneHandle() { + CrossProcessSemaphoreHandle result = ipc::SharedMemoryBasic::NULLHandle(); + + if (mSharedBuffer) { + result = mSharedBuffer->CloneHandle(); + if (!result) { + MOZ_CRASH(); + } + } + + return result; +} + +void CrossProcessSemaphore::CloseHandle() { mSharedBuffer->CloseHandle(); } + +} // namespace mozilla diff --git a/ipc/glue/CrossProcessSemaphore_unimplemented.cpp b/ipc/glue/CrossProcessSemaphore_unimplemented.cpp new file mode 100644 index 0000000000..822aeb76eb --- /dev/null +++ b/ipc/glue/CrossProcessSemaphore_unimplemented.cpp @@ -0,0 +1,64 @@ +/* -*- 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 "CrossProcessSemaphore.h" + +#include "nsDebug.h" + +namespace mozilla { + +/* static */ +CrossProcessSemaphore* CrossProcessSemaphore::Create(const char*, uint32_t) { + MOZ_CRASH("Cross-process semaphores not allowed on this platform."); + return nullptr; +} + +/* static */ +CrossProcessSemaphore* CrossProcessSemaphore::Create( + CrossProcessSemaphoreHandle) { + MOZ_CRASH("Cross-process semaphores not allowed on this platform."); + return nullptr; +} + +CrossProcessSemaphore::CrossProcessSemaphore() { + MOZ_CRASH( + "Cross-process semaphores not allowed on this platform - woah! We " + "should've aborted by now!"); +} + +CrossProcessSemaphore::~CrossProcessSemaphore() { + MOZ_CRASH( + "Cross-process semaphores not allowed on this platform - woah! We " + "should've aborted by now!"); +} + +bool CrossProcessSemaphore::Wait(const Maybe<TimeDuration>& aWaitTime) { + MOZ_CRASH( + "Cross-process semaphores not allowed on this platform - woah! We " + "should've aborted by now!"); + return false; +} + +void CrossProcessSemaphore::Signal() { + MOZ_CRASH( + "Cross-process semaphores not allowed on this platform - woah! We " + "should've aborted by now!"); +} + +CrossProcessSemaphoreHandle CrossProcessSemaphore::CloneHandle() { + MOZ_CRASH( + "Cross-process semaphores not allowed on this platform - woah! We " + "should've aborted by now!"); + return 0; +} + +void CrossProcessSemaphore::CloseHandle() { + MOZ_CRASH( + "Cross-process semaphores not allowed on this platform - woah! We " + "should've aborted by now!"); +} + +} // namespace mozilla diff --git a/ipc/glue/CrossProcessSemaphore_windows.cpp b/ipc/glue/CrossProcessSemaphore_windows.cpp new file mode 100644 index 0000000000..70644b7d51 --- /dev/null +++ b/ipc/glue/CrossProcessSemaphore_windows.cpp @@ -0,0 +1,83 @@ +/* -*- 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 <windows.h> + +#include "base/process_util.h" +#include "CrossProcessSemaphore.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include "ProtocolUtils.h" + +using base::GetCurrentProcessHandle; +using base::ProcessHandle; + +namespace mozilla { + +/* static */ +CrossProcessSemaphore* CrossProcessSemaphore::Create(const char*, + uint32_t aInitialValue) { + // We explicitly share this using DuplicateHandle, we do -not- want this to + // be inherited by child processes by default! So no security attributes are + // given. + HANDLE semaphore = + ::CreateSemaphoreA(nullptr, aInitialValue, 0x7fffffff, nullptr); + if (!semaphore) { + return nullptr; + } + return new CrossProcessSemaphore(semaphore); +} + +/* static */ +CrossProcessSemaphore* CrossProcessSemaphore::Create( + CrossProcessSemaphoreHandle aHandle) { + DWORD flags; + if (!::GetHandleInformation(aHandle.get(), &flags)) { + return nullptr; + } + + return new CrossProcessSemaphore(aHandle.release()); +} + +CrossProcessSemaphore::CrossProcessSemaphore(HANDLE aSemaphore) + : mSemaphore(aSemaphore) { + MOZ_COUNT_CTOR(CrossProcessSemaphore); +} + +CrossProcessSemaphore::~CrossProcessSemaphore() { + MOZ_ASSERT(mSemaphore, "Improper construction of semaphore or double free."); + ::CloseHandle(mSemaphore); + MOZ_COUNT_DTOR(CrossProcessSemaphore); +} + +bool CrossProcessSemaphore::Wait(const Maybe<TimeDuration>& aWaitTime) { + MOZ_ASSERT(mSemaphore, "Improper construction of semaphore."); + HRESULT hr = ::WaitForSingleObject( + mSemaphore, aWaitTime.isSome() ? aWaitTime->ToMilliseconds() : INFINITE); + return hr == WAIT_OBJECT_0; +} + +void CrossProcessSemaphore::Signal() { + MOZ_ASSERT(mSemaphore, "Improper construction of semaphore."); + ::ReleaseSemaphore(mSemaphore, 1, nullptr); +} + +CrossProcessSemaphoreHandle CrossProcessSemaphore::CloneHandle() { + HANDLE newHandle; + bool succeeded = + ::DuplicateHandle(GetCurrentProcess(), mSemaphore, GetCurrentProcess(), + &newHandle, 0, false, DUPLICATE_SAME_ACCESS); + + if (!succeeded) { + return nullptr; + } + + return UniqueFileHandle(newHandle); +} + +void CrossProcessSemaphore::CloseHandle() {} + +} // namespace mozilla diff --git a/ipc/glue/DataPipe.cpp b/ipc/glue/DataPipe.cpp new file mode 100644 index 0000000000..bc1af11515 --- /dev/null +++ b/ipc/glue/DataPipe.cpp @@ -0,0 +1,767 @@ +/* -*- 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 "DataPipe.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Logging.h" +#include "mozilla/MoveOnlyFunction.h" +#include "mozilla/ipc/InputStreamParams.h" +#include "nsIAsyncInputStream.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace ipc { + +LazyLogModule gDataPipeLog("DataPipe"); + +namespace data_pipe_detail { + +// Helper for queueing up actions to be run once the mutex has been unlocked. +// Actions will be run in-order. +class MOZ_SCOPED_CAPABILITY DataPipeAutoLock { + public: + explicit DataPipeAutoLock(Mutex& aMutex) MOZ_CAPABILITY_ACQUIRE(aMutex) + : mMutex(aMutex) { + mMutex.Lock(); + } + DataPipeAutoLock(const DataPipeAutoLock&) = delete; + DataPipeAutoLock& operator=(const DataPipeAutoLock&) = delete; + + template <typename F> + void AddUnlockAction(F aAction) { + mActions.AppendElement(std::move(aAction)); + } + + ~DataPipeAutoLock() MOZ_CAPABILITY_RELEASE() { + mMutex.Unlock(); + for (auto& action : mActions) { + action(); + } + } + + private: + Mutex& mMutex; + AutoTArray<MoveOnlyFunction<void()>, 4> mActions; +}; + +static void DoNotifyOnUnlock(DataPipeAutoLock& aLock, + already_AddRefed<nsIRunnable> aCallback, + already_AddRefed<nsIEventTarget> aTarget) { + nsCOMPtr<nsIRunnable> callback{std::move(aCallback)}; + nsCOMPtr<nsIEventTarget> target{std::move(aTarget)}; + if (callback) { + aLock.AddUnlockAction( + [callback = std::move(callback), target = std::move(target)]() mutable { + if (target) { + target->Dispatch(callback.forget()); + } else { + NS_DispatchBackgroundTask(callback.forget()); + } + }); + } +} + +class DataPipeLink : public NodeController::PortObserver { + public: + DataPipeLink(bool aReceiverSide, std::shared_ptr<Mutex> aMutex, + ScopedPort aPort, SharedMemoryBasic::Handle aShmemHandle, + SharedMemory* aShmem, uint32_t aCapacity, nsresult aPeerStatus, + uint32_t aOffset, uint32_t aAvailable) + : mMutex(std::move(aMutex)), + mPort(std::move(aPort)), + mShmemHandle(std::move(aShmemHandle)), + mShmem(aShmem), + mCapacity(aCapacity), + mReceiverSide(aReceiverSide), + mPeerStatus(aPeerStatus), + mOffset(aOffset), + mAvailable(aAvailable) {} + + void Init() MOZ_EXCLUDES(*mMutex) { + { + DataPipeAutoLock lock(*mMutex); + if (NS_FAILED(mPeerStatus)) { + return; + } + MOZ_ASSERT(mPort.IsValid()); + mPort.Controller()->SetPortObserver(mPort.Port(), this); + } + OnPortStatusChanged(); + } + + void OnPortStatusChanged() final MOZ_EXCLUDES(*mMutex); + + // Add a task to notify the callback after `aLock` is unlocked. + // + // This method is safe to call multiple times, as after the first time it is + // called, `mCallback` will be cleared. + void NotifyOnUnlock(DataPipeAutoLock& aLock) MOZ_REQUIRES(*mMutex) { + DoNotifyOnUnlock(aLock, mCallback.forget(), mCallbackTarget.forget()); + } + + void SendBytesConsumedOnUnlock(DataPipeAutoLock& aLock, uint32_t aBytes) + MOZ_REQUIRES(*mMutex) { + MOZ_LOG(gDataPipeLog, LogLevel::Verbose, + ("SendOnUnlock CONSUMED(%u) %s", aBytes, Describe(aLock).get())); + if (NS_FAILED(mPeerStatus)) { + return; + } + + // `mPort` may be destroyed by `SetPeerError` after the DataPipe is unlocked + // but before we send the message. The strong controller and port references + // will allow us to try to send the message anyway, and it will be safely + // dropped if the port has already been closed. CONSUMED messages are safe + // to deliver out-of-order, so we don't need to worry about ordering here. + aLock.AddUnlockAction([controller = RefPtr{mPort.Controller()}, + port = mPort.Port(), aBytes]() mutable { + auto message = MakeUnique<IPC::Message>( + MSG_ROUTING_NONE, DATA_PIPE_BYTES_CONSUMED_MESSAGE_TYPE); + IPC::MessageWriter writer(*message); + WriteParam(&writer, aBytes); + controller->SendUserMessage(port, std::move(message)); + }); + } + + void SetPeerError(DataPipeAutoLock& aLock, nsresult aStatus, + bool aSendClosed = false) MOZ_REQUIRES(*mMutex) { + MOZ_LOG(gDataPipeLog, LogLevel::Debug, + ("SetPeerError(%s%s) %s", GetStaticErrorName(aStatus), + aSendClosed ? ", send" : "", Describe(aLock).get())); + // The pipe was closed or errored. Clear the observer reference back + // to this type from the port layer, and ensure we notify waiters. + MOZ_ASSERT(NS_SUCCEEDED(mPeerStatus)); + mPeerStatus = NS_SUCCEEDED(aStatus) ? NS_BASE_STREAM_CLOSED : aStatus; + aLock.AddUnlockAction([port = std::move(mPort), aStatus, aSendClosed] { + if (aSendClosed) { + auto message = MakeUnique<IPC::Message>(MSG_ROUTING_NONE, + DATA_PIPE_CLOSED_MESSAGE_TYPE); + IPC::MessageWriter writer(*message); + WriteParam(&writer, aStatus); + port.Controller()->SendUserMessage(port.Port(), std::move(message)); + } + // The `ScopedPort` being destroyed with this action will close it, + // clearing the observer reference from the ports layer. + }); + NotifyOnUnlock(aLock); + } + + nsCString Describe(DataPipeAutoLock& aLock) const MOZ_REQUIRES(*mMutex) { + return nsPrintfCString( + "[%s(%p) c=%u e=%s o=%u a=%u, cb=%s]", + mReceiverSide ? "Receiver" : "Sender", this, mCapacity, + GetStaticErrorName(mPeerStatus), mOffset, mAvailable, + mCallback ? (mCallbackClosureOnly ? "clo" : "yes") : "no"); + } + + // This mutex is shared with the `DataPipeBase` which owns this + // `DataPipeLink`. + std::shared_ptr<Mutex> mMutex; + + ScopedPort mPort MOZ_GUARDED_BY(*mMutex); + SharedMemoryBasic::Handle mShmemHandle MOZ_GUARDED_BY(*mMutex); + const RefPtr<SharedMemory> mShmem; + const uint32_t mCapacity; + const bool mReceiverSide; + + bool mProcessingSegment MOZ_GUARDED_BY(*mMutex) = false; + + nsresult mPeerStatus MOZ_GUARDED_BY(*mMutex) = NS_OK; + uint32_t mOffset MOZ_GUARDED_BY(*mMutex) = 0; + uint32_t mAvailable MOZ_GUARDED_BY(*mMutex) = 0; + + bool mCallbackClosureOnly MOZ_GUARDED_BY(*mMutex) = false; + nsCOMPtr<nsIRunnable> mCallback MOZ_GUARDED_BY(*mMutex); + nsCOMPtr<nsIEventTarget> mCallbackTarget MOZ_GUARDED_BY(*mMutex); +}; + +void DataPipeLink::OnPortStatusChanged() { + DataPipeAutoLock lock(*mMutex); + + while (NS_SUCCEEDED(mPeerStatus)) { + UniquePtr<IPC::Message> message; + if (!mPort.Controller()->GetMessage(mPort.Port(), &message)) { + SetPeerError(lock, NS_BASE_STREAM_CLOSED); + return; + } + if (!message) { + return; // no more messages + } + + IPC::MessageReader reader(*message); + switch (message->type()) { + case DATA_PIPE_CLOSED_MESSAGE_TYPE: { + nsresult status = NS_OK; + if (!ReadParam(&reader, &status)) { + NS_WARNING("Unable to parse nsresult error from peer"); + status = NS_ERROR_UNEXPECTED; + } + MOZ_LOG(gDataPipeLog, LogLevel::Debug, + ("Got CLOSED(%s) %s", GetStaticErrorName(status), + Describe(lock).get())); + SetPeerError(lock, status); + return; + } + case DATA_PIPE_BYTES_CONSUMED_MESSAGE_TYPE: { + uint32_t consumed = 0; + if (!ReadParam(&reader, &consumed)) { + NS_WARNING("Unable to parse bytes consumed from peer"); + SetPeerError(lock, NS_ERROR_UNEXPECTED); + return; + } + + MOZ_LOG(gDataPipeLog, LogLevel::Verbose, + ("Got CONSUMED(%u) %s", consumed, Describe(lock).get())); + auto newAvailable = CheckedUint32{mAvailable} + consumed; + if (!newAvailable.isValid() || newAvailable.value() > mCapacity) { + NS_WARNING("Illegal bytes consumed message received from peer"); + SetPeerError(lock, NS_ERROR_UNEXPECTED); + return; + } + mAvailable = newAvailable.value(); + if (!mCallbackClosureOnly) { + NotifyOnUnlock(lock); + } + break; + } + default: { + NS_WARNING("Illegal message type received from peer"); + SetPeerError(lock, NS_ERROR_UNEXPECTED); + return; + } + } + } +} + +DataPipeBase::DataPipeBase(bool aReceiverSide, nsresult aError) + : mMutex(std::make_shared<Mutex>(aReceiverSide ? "DataPipeReceiver" + : "DataPipeSender")), + mStatus(NS_SUCCEEDED(aError) ? NS_BASE_STREAM_CLOSED : aError) {} + +DataPipeBase::DataPipeBase(bool aReceiverSide, ScopedPort aPort, + SharedMemoryBasic::Handle aShmemHandle, + SharedMemory* aShmem, uint32_t aCapacity, + nsresult aPeerStatus, uint32_t aOffset, + uint32_t aAvailable) + : mMutex(std::make_shared<Mutex>(aReceiverSide ? "DataPipeReceiver" + : "DataPipeSender")), + mStatus(NS_OK), + mLink(new DataPipeLink(aReceiverSide, mMutex, std::move(aPort), + std::move(aShmemHandle), aShmem, aCapacity, + aPeerStatus, aOffset, aAvailable)) { + mLink->Init(); +} + +DataPipeBase::~DataPipeBase() { + DataPipeAutoLock lock(*mMutex); + CloseInternal(lock, NS_BASE_STREAM_CLOSED); +} + +void DataPipeBase::CloseInternal(DataPipeAutoLock& aLock, nsresult aStatus) { + if (NS_FAILED(mStatus)) { + return; + } + + MOZ_LOG( + gDataPipeLog, LogLevel::Debug, + ("Closing(%s) %s", GetStaticErrorName(aStatus), Describe(aLock).get())); + + // Set our status to an errored status. + mStatus = NS_SUCCEEDED(aStatus) ? NS_BASE_STREAM_CLOSED : aStatus; + RefPtr<DataPipeLink> link = mLink.forget(); + AssertSameMutex(link->mMutex); + link->NotifyOnUnlock(aLock); + + // If our peer hasn't disappeared yet, clean up our connection to it. + if (NS_SUCCEEDED(link->mPeerStatus)) { + link->SetPeerError(aLock, mStatus, /* aSendClosed */ true); + } +} + +nsresult DataPipeBase::ProcessSegmentsInternal( + uint32_t aCount, ProcessSegmentFun aProcessSegment, + uint32_t* aProcessedCount) { + *aProcessedCount = 0; + + while (*aProcessedCount < aCount) { + DataPipeAutoLock lock(*mMutex); + mMutex->AssertCurrentThreadOwns(); + + MOZ_LOG(gDataPipeLog, LogLevel::Verbose, + ("ProcessSegments(%u of %u) %s", *aProcessedCount, aCount, + Describe(lock).get())); + + nsresult status = CheckStatus(lock); + if (NS_FAILED(status)) { + if (*aProcessedCount > 0) { + return NS_OK; + } + return status == NS_BASE_STREAM_CLOSED ? NS_OK : status; + } + + RefPtr<DataPipeLink> link = mLink; + AssertSameMutex(link->mMutex); + if (!link->mAvailable) { + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(link->mPeerStatus), + "CheckStatus will have returned an error"); + return *aProcessedCount > 0 ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + MOZ_RELEASE_ASSERT(!link->mProcessingSegment, + "Only one thread may be processing a segment at a time"); + + // Extract an iterator over the next contiguous region of the shared memory + // buffer which will be used . + char* start = static_cast<char*>(link->mShmem->memory()) + link->mOffset; + char* iter = start; + char* end = start + std::min({aCount - *aProcessedCount, link->mAvailable, + link->mCapacity - link->mOffset}); + + // Record the consumed region from our segment when exiting this scope, + // telling our peer how many bytes were consumed. Hold on to `mLink` to keep + // the shmem mapped and make sure we can clean up even if we're closed while + // processing the shmem region. + link->mProcessingSegment = true; + auto scopeExit = MakeScopeExit([&] { + mMutex->AssertCurrentThreadOwns(); // should still be held + AssertSameMutex(link->mMutex); + + MOZ_RELEASE_ASSERT(link->mProcessingSegment); + link->mProcessingSegment = false; + uint32_t totalProcessed = iter - start; + if (totalProcessed > 0) { + link->mOffset += totalProcessed; + MOZ_RELEASE_ASSERT(link->mOffset <= link->mCapacity); + if (link->mOffset == link->mCapacity) { + link->mOffset = 0; + } + link->mAvailable -= totalProcessed; + link->SendBytesConsumedOnUnlock(lock, totalProcessed); + } + MOZ_LOG(gDataPipeLog, LogLevel::Verbose, + ("Processed Segment(%u of %zu) %s", totalProcessed, end - start, + Describe(lock).get())); + }); + + { + MutexAutoUnlock unlock(*mMutex); + while (iter < end) { + uint32_t processed = 0; + Span segment{iter, end}; + nsresult rv = aProcessSegment(segment, *aProcessedCount, &processed); + if (NS_FAILED(rv) || processed == 0) { + return NS_OK; + } + + MOZ_RELEASE_ASSERT(processed <= segment.Length()); + iter += processed; + *aProcessedCount += processed; + } + } + } + MOZ_DIAGNOSTIC_ASSERT(*aProcessedCount == aCount, + "Must have processed exactly aCount"); + return NS_OK; +} + +void DataPipeBase::AsyncWaitInternal(already_AddRefed<nsIRunnable> aCallback, + already_AddRefed<nsIEventTarget> aTarget, + bool aClosureOnly) { + RefPtr<nsIRunnable> callback = std::move(aCallback); + RefPtr<nsIEventTarget> target = std::move(aTarget); + + DataPipeAutoLock lock(*mMutex); + MOZ_LOG(gDataPipeLog, LogLevel::Debug, + ("AsyncWait %s %p %s", aClosureOnly ? "(closure)" : "(ready)", + callback.get(), Describe(lock).get())); + + if (NS_FAILED(CheckStatus(lock))) { +#ifdef DEBUG + if (mLink) { + AssertSameMutex(mLink->mMutex); + MOZ_ASSERT(!mLink->mCallback); + } +#endif + DoNotifyOnUnlock(lock, callback.forget(), target.forget()); + return; + } + + AssertSameMutex(mLink->mMutex); + + // NOTE: After this point, `mLink` may have previously had a callback which is + // now being cancelled, make sure we clear `mCallback` even if we're going to + // call `aCallback` immediately. + mLink->mCallback = callback.forget(); + mLink->mCallbackTarget = target.forget(); + mLink->mCallbackClosureOnly = aClosureOnly; + if (!aClosureOnly && mLink->mAvailable) { + mLink->NotifyOnUnlock(lock); + } +} + +nsresult DataPipeBase::CheckStatus(DataPipeAutoLock& aLock) { + // If our peer has closed or errored, we may need to close our local side to + // reflect the error code our peer provided. If we're a sender, we want to + // become closed immediately, whereas if we're a receiver we want to wait + // until our available buffer has been exhausted. + // + // NOTE: There may still be 2-stage writes/reads ongoing at this point, which + // will continue due to `mLink` being kept alive by the + // `ProcessSegmentsInternal` function. + if (NS_FAILED(mStatus)) { + return mStatus; + } + AssertSameMutex(mLink->mMutex); + if (NS_FAILED(mLink->mPeerStatus) && + (!mLink->mReceiverSide || !mLink->mAvailable)) { + CloseInternal(aLock, mLink->mPeerStatus); + } + return mStatus; +} + +nsCString DataPipeBase::Describe(DataPipeAutoLock& aLock) { + if (mLink) { + AssertSameMutex(mLink->mMutex); + return mLink->Describe(aLock); + } + return nsPrintfCString("[status=%s]", GetStaticErrorName(mStatus)); +} + +template <typename T> +void DataPipeWrite(IPC::MessageWriter* aWriter, T* aParam) { + DataPipeAutoLock lock(*aParam->mMutex); + MOZ_LOG(gDataPipeLog, LogLevel::Debug, + ("IPC Write: %s", aParam->Describe(lock).get())); + + WriteParam(aWriter, aParam->mStatus); + if (NS_FAILED(aParam->mStatus)) { + return; + } + + aParam->AssertSameMutex(aParam->mLink->mMutex); + MOZ_RELEASE_ASSERT(!aParam->mLink->mProcessingSegment, + "cannot transfer while processing a segment"); + + // Serialize relevant parameters to our peer. + WriteParam(aWriter, std::move(aParam->mLink->mPort)); + WriteParam(aWriter, std::move(aParam->mLink->mShmemHandle)); + WriteParam(aWriter, aParam->mLink->mCapacity); + WriteParam(aWriter, aParam->mLink->mPeerStatus); + WriteParam(aWriter, aParam->mLink->mOffset); + WriteParam(aWriter, aParam->mLink->mAvailable); + + // Mark our peer as closed so we don't try to send to it when closing. + aParam->mLink->mPeerStatus = NS_ERROR_NOT_INITIALIZED; + aParam->CloseInternal(lock, NS_ERROR_NOT_INITIALIZED); +} + +template <typename T> +bool DataPipeRead(IPC::MessageReader* aReader, RefPtr<T>* aResult) { + nsresult rv = NS_OK; + if (!ReadParam(aReader, &rv)) { + aReader->FatalError("failed to read DataPipe status"); + return false; + } + if (NS_FAILED(rv)) { + *aResult = new T(rv); + MOZ_LOG(gDataPipeLog, LogLevel::Debug, + ("IPC Read: [status=%s]", GetStaticErrorName(rv))); + return true; + } + + ScopedPort port; + if (!ReadParam(aReader, &port)) { + aReader->FatalError("failed to read DataPipe port"); + return false; + } + SharedMemoryBasic::Handle shmemHandle; + if (!ReadParam(aReader, &shmemHandle)) { + aReader->FatalError("failed to read DataPipe shmem"); + return false; + } + // Due to the awkward shared memory API provided by SharedMemoryBasic, we need + // to transfer ownership into the `shmem` here, then steal it back later in + // the function. Bug 1797039 tracks potential changes to the RawShmem API + // which could improve this situation. + RefPtr shmem = new SharedMemoryBasic(); + if (!shmem->SetHandle(std::move(shmemHandle), + SharedMemory::RightsReadWrite)) { + aReader->FatalError("failed to create DataPipe shmem from handle"); + return false; + } + uint32_t capacity = 0; + nsresult peerStatus = NS_OK; + uint32_t offset = 0; + uint32_t available = 0; + if (!ReadParam(aReader, &capacity) || !ReadParam(aReader, &peerStatus) || + !ReadParam(aReader, &offset) || !ReadParam(aReader, &available)) { + aReader->FatalError("failed to read DataPipe fields"); + return false; + } + if (!capacity || offset >= capacity || available > capacity) { + aReader->FatalError("received DataPipe state values are inconsistent"); + return false; + } + if (!shmem->Map(SharedMemory::PageAlignedSize(capacity))) { + aReader->FatalError("failed to map DataPipe shared memory region"); + return false; + } + + *aResult = new T(std::move(port), shmem->TakeHandle(), shmem, capacity, + peerStatus, offset, available); + if (MOZ_LOG_TEST(gDataPipeLog, LogLevel::Debug)) { + DataPipeAutoLock lock(*(*aResult)->mMutex); + MOZ_LOG(gDataPipeLog, LogLevel::Debug, + ("IPC Read: %s", (*aResult)->Describe(lock).get())); + } + return true; +} + +} // namespace data_pipe_detail + +//----------------------------------------------------------------------------- +// DataPipeSender +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(DataPipeSender, nsIOutputStream, nsIAsyncOutputStream, + DataPipeSender) + +// nsIOutputStream + +NS_IMETHODIMP DataPipeSender::Close() { + return CloseWithStatus(NS_BASE_STREAM_CLOSED); +} + +NS_IMETHODIMP DataPipeSender::Flush() { return NS_OK; } + +NS_IMETHODIMP DataPipeSender::StreamStatus() { + data_pipe_detail::DataPipeAutoLock lock(*mMutex); + return CheckStatus(lock); +} + +NS_IMETHODIMP DataPipeSender::Write(const char* aBuf, uint32_t aCount, + uint32_t* aWriteCount) { + return WriteSegments(NS_CopyBufferToSegment, (void*)aBuf, aCount, + aWriteCount); +} + +NS_IMETHODIMP DataPipeSender::WriteFrom(nsIInputStream* aFromStream, + uint32_t aCount, + uint32_t* aWriteCount) { + return WriteSegments(NS_CopyStreamToSegment, aFromStream, aCount, + aWriteCount); +} + +NS_IMETHODIMP DataPipeSender::WriteSegments(nsReadSegmentFun aReader, + void* aClosure, uint32_t aCount, + uint32_t* aWriteCount) { + auto processSegment = [&](Span<char> aSpan, uint32_t aToOffset, + uint32_t* aReadCount) -> nsresult { + return aReader(this, aClosure, aSpan.data(), aToOffset, aSpan.Length(), + aReadCount); + }; + return ProcessSegmentsInternal(aCount, processSegment, aWriteCount); +} + +NS_IMETHODIMP DataPipeSender::IsNonBlocking(bool* _retval) { + *_retval = true; + return NS_OK; +} + +// nsIAsyncOutputStream + +NS_IMETHODIMP DataPipeSender::CloseWithStatus(nsresult reason) { + data_pipe_detail::DataPipeAutoLock lock(*mMutex); + CloseInternal(lock, reason); + return NS_OK; +} + +NS_IMETHODIMP DataPipeSender::AsyncWait(nsIOutputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) { + AsyncWaitInternal( + aCallback ? NS_NewCancelableRunnableFunction( + "DataPipeReceiver::AsyncWait", + [self = RefPtr{this}, callback = RefPtr{aCallback}] { + MOZ_LOG(gDataPipeLog, LogLevel::Debug, + ("Calling OnOutputStreamReady(%p, %p)", + callback.get(), self.get())); + callback->OnOutputStreamReady(self); + }) + : nullptr, + do_AddRef(aTarget), aFlags & WAIT_CLOSURE_ONLY); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// DataPipeReceiver +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(DataPipeReceiver, nsIInputStream, nsIAsyncInputStream, + nsIIPCSerializableInputStream, DataPipeReceiver) + +// nsIInputStream + +NS_IMETHODIMP DataPipeReceiver::Close() { + return CloseWithStatus(NS_BASE_STREAM_CLOSED); +} + +NS_IMETHODIMP DataPipeReceiver::Available(uint64_t* _retval) { + data_pipe_detail::DataPipeAutoLock lock(*mMutex); + nsresult rv = CheckStatus(lock); + if (NS_FAILED(rv)) { + return rv; + } + AssertSameMutex(mLink->mMutex); + *_retval = mLink->mAvailable; + return NS_OK; +} + +NS_IMETHODIMP DataPipeReceiver::StreamStatus() { + data_pipe_detail::DataPipeAutoLock lock(*mMutex); + return CheckStatus(lock); +} + +NS_IMETHODIMP DataPipeReceiver::Read(char* aBuf, uint32_t aCount, + uint32_t* aReadCount) { + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount); +} + +NS_IMETHODIMP DataPipeReceiver::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* aReadCount) { + auto processSegment = [&](Span<char> aSpan, uint32_t aToOffset, + uint32_t* aWriteCount) -> nsresult { + return aWriter(this, aClosure, aSpan.data(), aToOffset, aSpan.Length(), + aWriteCount); + }; + return ProcessSegmentsInternal(aCount, processSegment, aReadCount); +} + +NS_IMETHODIMP DataPipeReceiver::IsNonBlocking(bool* _retval) { + *_retval = true; + return NS_OK; +} + +// nsIAsyncInputStream + +NS_IMETHODIMP DataPipeReceiver::CloseWithStatus(nsresult aStatus) { + data_pipe_detail::DataPipeAutoLock lock(*mMutex); + CloseInternal(lock, aStatus); + return NS_OK; +} + +NS_IMETHODIMP DataPipeReceiver::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) { + AsyncWaitInternal( + aCallback ? NS_NewCancelableRunnableFunction( + "DataPipeReceiver::AsyncWait", + [self = RefPtr{this}, callback = RefPtr{aCallback}] { + MOZ_LOG(gDataPipeLog, LogLevel::Debug, + ("Calling OnInputStreamReady(%p, %p)", + callback.get(), self.get())); + callback->OnInputStreamReady(self); + }) + : nullptr, + do_AddRef(aTarget), aFlags & WAIT_CLOSURE_ONLY); + return NS_OK; +} + +// nsIIPCSerializableInputStream + +void DataPipeReceiver::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + // We report DataPipeReceiver as taking one transferrable to serialize, rather + // than one pipe, as we aren't starting a new pipe for this purpose, and are + // instead transferring an existing pipe. + *aTransferables = 1; +} + +void DataPipeReceiver::Serialize(InputStreamParams& aParams, uint32_t aMaxSize, + uint32_t* aSizeUsed) { + *aSizeUsed = 0; + aParams = DataPipeReceiverStreamParams(WrapNotNull(this)); +} + +bool DataPipeReceiver::Deserialize(const InputStreamParams& aParams) { + MOZ_CRASH("Handled directly in `DeserializeInputStream`"); +} + +//----------------------------------------------------------------------------- +// NewDataPipe +//----------------------------------------------------------------------------- + +nsresult NewDataPipe(uint32_t aCapacity, DataPipeSender** aSender, + DataPipeReceiver** aReceiver) { + if (!aCapacity) { + aCapacity = kDefaultDataPipeCapacity; + } + + RefPtr<NodeController> controller = NodeController::GetSingleton(); + if (!controller) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + // Allocate a pair of ports for messaging between the sender & receiver. + auto [senderPort, receiverPort] = controller->CreatePortPair(); + + // Create and allocate the shared memory region. + auto shmem = MakeRefPtr<SharedMemoryBasic>(); + size_t alignedCapacity = SharedMemory::PageAlignedSize(aCapacity); + if (!shmem->Create(alignedCapacity) || !shmem->Map(alignedCapacity)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // We'll first clone then take the handle from the region so that the sender & + // receiver each have a handle. This avoids the need to duplicate the handle + // when serializing, when errors are non-recoverable. + SharedMemoryBasic::Handle senderShmemHandle = shmem->CloneHandle(); + SharedMemoryBasic::Handle receiverShmemHandle = shmem->TakeHandle(); + if (!senderShmemHandle || !receiverShmemHandle) { + return NS_ERROR_OUT_OF_MEMORY; + } + + RefPtr sender = + new DataPipeSender(std::move(senderPort), std::move(senderShmemHandle), + shmem, aCapacity, NS_OK, 0, aCapacity); + RefPtr receiver = new DataPipeReceiver(std::move(receiverPort), + std::move(receiverShmemHandle), shmem, + aCapacity, NS_OK, 0, 0); + sender.forget(aSender); + receiver.forget(aReceiver); + return NS_OK; +} + +} // namespace ipc +} // namespace mozilla + +void IPC::ParamTraits<mozilla::ipc::DataPipeSender*>::Write( + MessageWriter* aWriter, mozilla::ipc::DataPipeSender* aParam) { + mozilla::ipc::data_pipe_detail::DataPipeWrite(aWriter, aParam); +} + +bool IPC::ParamTraits<mozilla::ipc::DataPipeSender*>::Read( + MessageReader* aReader, RefPtr<mozilla::ipc::DataPipeSender>* aResult) { + return mozilla::ipc::data_pipe_detail::DataPipeRead(aReader, aResult); +} + +void IPC::ParamTraits<mozilla::ipc::DataPipeReceiver*>::Write( + MessageWriter* aWriter, mozilla::ipc::DataPipeReceiver* aParam) { + mozilla::ipc::data_pipe_detail::DataPipeWrite(aWriter, aParam); +} + +bool IPC::ParamTraits<mozilla::ipc::DataPipeReceiver*>::Read( + MessageReader* aReader, RefPtr<mozilla::ipc::DataPipeReceiver>* aResult) { + return mozilla::ipc::data_pipe_detail::DataPipeRead(aReader, aResult); +} diff --git a/ipc/glue/DataPipe.h b/ipc/glue/DataPipe.h new file mode 100644 index 0000000000..f339b1c72d --- /dev/null +++ b/ipc/glue/DataPipe.h @@ -0,0 +1,192 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_DataPipe_h +#define mozilla_ipc_DataPipe_h + +#include "mozilla/ipc/SharedMemoryBasic.h" +#include "mozilla/ipc/NodeController.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISupports.h" + +namespace mozilla { +namespace ipc { + +namespace data_pipe_detail { + +class DataPipeAutoLock; +class DataPipeLink; + +class DataPipeBase { + public: + DataPipeBase(const DataPipeBase&) = delete; + DataPipeBase& operator=(const DataPipeBase&) = delete; + + protected: + explicit DataPipeBase(bool aReceiverSide, nsresult aError); + DataPipeBase(bool aReceiverSide, ScopedPort aPort, + SharedMemoryBasic::Handle aShmemHandle, SharedMemory* aShmem, + uint32_t aCapacity, nsresult aPeerStatus, uint32_t aOffset, + uint32_t aAvailable); + + void CloseInternal(DataPipeAutoLock&, nsresult aStatus) MOZ_REQUIRES(*mMutex); + + void AsyncWaitInternal(already_AddRefed<nsIRunnable> aCallback, + already_AddRefed<nsIEventTarget> aTarget, + bool aClosureOnly) MOZ_EXCLUDES(*mMutex); + + // Like `nsWriteSegmentFun` or `nsReadSegmentFun`. + using ProcessSegmentFun = + FunctionRef<nsresult(Span<char> aSpan, uint32_t aProcessedThisCall, + uint32_t* aProcessedCount)>; + nsresult ProcessSegmentsInternal(uint32_t aCount, + ProcessSegmentFun aProcessSegment, + uint32_t* aProcessedCount) + MOZ_EXCLUDES(*mMutex); + + nsresult CheckStatus(DataPipeAutoLock&) MOZ_REQUIRES(*mMutex); + + nsCString Describe(DataPipeAutoLock&) MOZ_REQUIRES(*mMutex); + + // Thread safety helper to tell the analysis that `mLink->mMutex` is held when + // `mMutex` is held. + void AssertSameMutex(const std::shared_ptr<Mutex>& aMutex) + MOZ_REQUIRES(*mMutex) MOZ_ASSERT_CAPABILITY(*aMutex) { + MOZ_ASSERT(mMutex == aMutex); + } + + virtual ~DataPipeBase(); + + const std::shared_ptr<Mutex> mMutex; + nsresult mStatus MOZ_GUARDED_BY(*mMutex) = NS_OK; + RefPtr<DataPipeLink> mLink MOZ_GUARDED_BY(*mMutex); +}; + +template <typename T> +void DataPipeWrite(IPC::MessageWriter* aWriter, T* aParam); + +template <typename T> +bool DataPipeRead(IPC::MessageReader* aReader, RefPtr<T>* aResult); + +} // namespace data_pipe_detail + +class DataPipeSender; +class DataPipeReceiver; + +#define NS_DATAPIPESENDER_IID \ + { \ + 0x6698ed77, 0x9fff, 0x425d, { \ + 0xb0, 0xa6, 0x1d, 0x30, 0x66, 0xee, 0xb8, 0x16 \ + } \ + } + +// Helper class for streaming data to another process. +class DataPipeSender final : public nsIAsyncOutputStream, + public data_pipe_detail::DataPipeBase { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_DATAPIPESENDER_IID) + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSIASYNCOUTPUTSTREAM + + private: + friend nsresult NewDataPipe(uint32_t, DataPipeSender**, DataPipeReceiver**); + friend void data_pipe_detail::DataPipeWrite<DataPipeSender>( + IPC::MessageWriter* aWriter, DataPipeSender* aParam); + friend bool data_pipe_detail::DataPipeRead<DataPipeSender>( + IPC::MessageReader* aReader, RefPtr<DataPipeSender>* aResult); + + explicit DataPipeSender(nsresult aError) + : data_pipe_detail::DataPipeBase(/* aReceiverSide */ false, aError) {} + DataPipeSender(ScopedPort aPort, SharedMemoryBasic::Handle aShmemHandle, + SharedMemory* aShmem, uint32_t aCapacity, nsresult aPeerStatus, + uint32_t aOffset, uint32_t aAvailable) + : data_pipe_detail::DataPipeBase( + /* aReceiverSide */ false, std::move(aPort), + std::move(aShmemHandle), aShmem, aCapacity, aPeerStatus, aOffset, + aAvailable) {} + + ~DataPipeSender() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(DataPipeSender, NS_DATAPIPESENDER_IID) + +#define NS_DATAPIPERECEIVER_IID \ + { \ + 0x0a185f83, 0x499e, 0x450c, { \ + 0x95, 0x82, 0x27, 0x67, 0xad, 0x6d, 0x64, 0xb5 \ + } \ + } + +// Helper class for streaming data from another process. +class DataPipeReceiver final : public nsIAsyncInputStream, + public nsIIPCSerializableInputStream, + public data_pipe_detail::DataPipeBase { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_DATAPIPERECEIVER_IID) + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + + private: + friend nsresult NewDataPipe(uint32_t, DataPipeSender**, DataPipeReceiver**); + friend void data_pipe_detail::DataPipeWrite<DataPipeReceiver>( + IPC::MessageWriter* aWriter, DataPipeReceiver* aParam); + friend bool data_pipe_detail::DataPipeRead<DataPipeReceiver>( + IPC::MessageReader* aReader, RefPtr<DataPipeReceiver>* aResult); + + explicit DataPipeReceiver(nsresult aError) + : data_pipe_detail::DataPipeBase(/* aReceiverSide */ true, aError) {} + DataPipeReceiver(ScopedPort aPort, SharedMemoryBasic::Handle aShmemHandle, + SharedMemory* aShmem, uint32_t aCapacity, + nsresult aPeerStatus, uint32_t aOffset, uint32_t aAvailable) + : data_pipe_detail::DataPipeBase( + /* aReceiverSide */ true, std::move(aPort), std::move(aShmemHandle), + aShmem, aCapacity, aPeerStatus, aOffset, aAvailable) {} + + ~DataPipeReceiver() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(DataPipeReceiver, NS_DATAPIPERECEIVER_IID) + +constexpr uint32_t kDefaultDataPipeCapacity = 64 * 1024; + +/** + * Create a new DataPipe pair. The sender and receiver ends of the pipe may be + * used to transfer data between processes. |aCapacity| is the capacity of the + * underlying ring buffer. If `0` is passed, `kDefaultDataPipeCapacity` will be + * used. + */ +nsresult NewDataPipe(uint32_t aCapacity, DataPipeSender** aSender, + DataPipeReceiver** aReceiver); + +} // namespace ipc +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::ipc::DataPipeSender*> { + static void Write(MessageWriter* aWriter, + mozilla::ipc::DataPipeSender* aParam); + static bool Read(MessageReader* aReader, + RefPtr<mozilla::ipc::DataPipeSender>* aResult); +}; + +template <> +struct ParamTraits<mozilla::ipc::DataPipeReceiver*> { + static void Write(MessageWriter* aWriter, + mozilla::ipc::DataPipeReceiver* aParam); + static bool Read(MessageReader* aReader, + RefPtr<mozilla::ipc::DataPipeReceiver>* aResult); +}; + +} // namespace IPC + +#endif // mozilla_ipc_DataPipe_h diff --git a/ipc/glue/Endpoint.cpp b/ipc/glue/Endpoint.cpp new file mode 100644 index 0000000000..3391f8b359 --- /dev/null +++ b/ipc/glue/Endpoint.cpp @@ -0,0 +1,167 @@ +/* -*- 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 "mozilla/ipc/Endpoint.h" +#include "chrome/common/ipc_message.h" +#include "mozilla/ipc/IPDLParamTraits.h" +#include "nsThreadUtils.h" +#include "mozilla/ipc/ProtocolMessageUtils.h" + +namespace mozilla::ipc { + +UntypedManagedEndpoint::UntypedManagedEndpoint(IProtocol* aActor) + : mInner(Some(Inner{ + /* mOtherSide */ aActor->GetWeakLifecycleProxy(), + /* mToplevel */ nullptr, + aActor->Id(), + aActor->GetProtocolId(), + aActor->Manager()->Id(), + aActor->Manager()->GetProtocolId(), + })) {} + +UntypedManagedEndpoint::~UntypedManagedEndpoint() { + if (!IsValid()) { + return; + } + + if (mInner->mOtherSide) { + // If this ManagedEndpoint was never sent over IPC, deliver a fake + // MANAGED_ENDPOINT_DROPPED_MESSAGE_TYPE message directly to the other side + // actor. + mInner->mOtherSide->ActorEventTarget()->Dispatch(NS_NewRunnableFunction( + "~ManagedEndpoint (Local)", + [otherSide = mInner->mOtherSide, id = mInner->mId] { + if (IProtocol* actor = otherSide->Get(); actor && actor->CanRecv()) { + MOZ_DIAGNOSTIC_ASSERT(actor->Id() == id, "Wrong Actor?"); + RefPtr<ActorLifecycleProxy> strongProxy(actor->GetLifecycleProxy()); + strongProxy->Get()->OnMessageReceived( + IPC::Message(id, MANAGED_ENDPOINT_DROPPED_MESSAGE_TYPE)); + } + })); + } else if (mInner->mToplevel) { + // If it was sent over IPC, we'll need to send the message to the sending + // side. Let's send the message async. + mInner->mToplevel->ActorEventTarget()->Dispatch(NS_NewRunnableFunction( + "~ManagedEndpoint (Remote)", + [toplevel = mInner->mToplevel, id = mInner->mId] { + if (IProtocol* actor = toplevel->Get(); + actor && actor->CanSend() && actor->GetIPCChannel()) { + actor->GetIPCChannel()->Send(MakeUnique<IPC::Message>( + id, MANAGED_ENDPOINT_DROPPED_MESSAGE_TYPE)); + } + })); + } +} + +bool UntypedManagedEndpoint::BindCommon(IProtocol* aActor, + IProtocol* aManager) { + MOZ_ASSERT(aManager); + if (!mInner) { + NS_WARNING("Cannot bind to invalid endpoint"); + return false; + } + + // Perform thread assertions. + if (mInner->mToplevel) { + MOZ_DIAGNOSTIC_ASSERT( + mInner->mToplevel->ActorEventTarget()->IsOnCurrentThread()); + MOZ_DIAGNOSTIC_ASSERT(aManager->ToplevelProtocol() == + mInner->mToplevel->Get()); + } + + if (NS_WARN_IF(aManager->Id() != mInner->mManagerId) || + NS_WARN_IF(aManager->GetProtocolId() != mInner->mManagerType) || + NS_WARN_IF(aActor->GetProtocolId() != mInner->mType)) { + MOZ_ASSERT_UNREACHABLE("Actor and manager do not match Endpoint"); + return false; + } + + if (!aManager->CanSend() || !aManager->GetIPCChannel()) { + NS_WARNING("Manager cannot send"); + return false; + } + + int32_t id = mInner->mId; + mInner.reset(); + + // Our typed caller will insert the actor into the managed container. + aActor->SetManagerAndRegister(aManager, id); + + aManager->GetIPCChannel()->Send( + MakeUnique<IPC::Message>(id, MANAGED_ENDPOINT_BOUND_MESSAGE_TYPE)); + return true; +} + +/* static */ +void IPDLParamTraits<UntypedManagedEndpoint>::Write(IPC::MessageWriter* aWriter, + IProtocol* aActor, + paramType&& aParam) { + bool isValid = aParam.mInner.isSome(); + WriteIPDLParam(aWriter, aActor, isValid); + if (!isValid) { + return; + } + + auto inner = std::move(*aParam.mInner); + aParam.mInner.reset(); + + MOZ_RELEASE_ASSERT(inner.mOtherSide, "Has not been sent over IPC yet"); + MOZ_RELEASE_ASSERT(inner.mOtherSide->ActorEventTarget()->IsOnCurrentThread(), + "Must be being sent from the correct thread"); + MOZ_RELEASE_ASSERT( + inner.mOtherSide->Get() && inner.mOtherSide->Get()->ToplevelProtocol() == + aActor->ToplevelProtocol(), + "Must be being sent over the same toplevel protocol"); + + WriteIPDLParam(aWriter, aActor, inner.mId); + WriteIPDLParam(aWriter, aActor, inner.mType); + WriteIPDLParam(aWriter, aActor, inner.mManagerId); + WriteIPDLParam(aWriter, aActor, inner.mManagerType); +} + +/* static */ +bool IPDLParamTraits<UntypedManagedEndpoint>::Read(IPC::MessageReader* aReader, + IProtocol* aActor, + paramType* aResult) { + *aResult = UntypedManagedEndpoint{}; + bool isValid = false; + if (!aActor || !ReadIPDLParam(aReader, aActor, &isValid)) { + return false; + } + if (!isValid) { + return true; + } + + aResult->mInner.emplace(); + auto& inner = *aResult->mInner; + inner.mToplevel = aActor->ToplevelProtocol()->GetWeakLifecycleProxy(); + return ReadIPDLParam(aReader, aActor, &inner.mId) && + ReadIPDLParam(aReader, aActor, &inner.mType) && + ReadIPDLParam(aReader, aActor, &inner.mManagerId) && + ReadIPDLParam(aReader, aActor, &inner.mManagerType); +} + +} // namespace mozilla::ipc + +namespace IPC { + +void ParamTraits<mozilla::ipc::UntypedEndpoint>::Write(MessageWriter* aWriter, + paramType&& aParam) { + IPC::WriteParam(aWriter, std::move(aParam.mPort)); + IPC::WriteParam(aWriter, aParam.mMessageChannelId); + IPC::WriteParam(aWriter, aParam.mMyPid); + IPC::WriteParam(aWriter, aParam.mOtherPid); +} + +bool ParamTraits<mozilla::ipc::UntypedEndpoint>::Read(MessageReader* aReader, + paramType* aResult) { + return IPC::ReadParam(aReader, &aResult->mPort) && + IPC::ReadParam(aReader, &aResult->mMessageChannelId) && + IPC::ReadParam(aReader, &aResult->mMyPid) && + IPC::ReadParam(aReader, &aResult->mOtherPid); +} + +} // namespace IPC diff --git a/ipc/glue/Endpoint.h b/ipc/glue/Endpoint.h new file mode 100644 index 0000000000..d7eea94dfc --- /dev/null +++ b/ipc/glue/Endpoint.h @@ -0,0 +1,288 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef IPC_GLUE_ENDPOINT_H_ +#define IPC_GLUE_ENDPOINT_H_ + +#include <utility> +#include "CrashAnnotations.h" +#include "base/process.h" +#include "base/process_util.h" +#include "mozilla/Assertions.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/ipc/MessageLink.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/ipc/NodeController.h" +#include "mozilla/ipc/ScopedPort.h" +#include "nsXULAppAPI.h" +#include "nscore.h" + +namespace IPC { +template <class P> +struct ParamTraits; +} + +namespace mozilla { +namespace ipc { + +namespace endpoint_detail { + +template <class T> +static auto ActorNeedsOtherPidHelper(int) + -> decltype(std::declval<T>().OtherPid(), std::true_type{}); +template <class> +static auto ActorNeedsOtherPidHelper(long) -> std::false_type; + +template <typename T> +constexpr bool ActorNeedsOtherPid = + decltype(ActorNeedsOtherPidHelper<T>(0))::value; + +} // namespace endpoint_detail + +struct PrivateIPDLInterface {}; + +class UntypedEndpoint { + public: + using ProcessId = base::ProcessId; + + UntypedEndpoint() = default; + + UntypedEndpoint(const PrivateIPDLInterface&, ScopedPort aPort, + const nsID& aMessageChannelId, + ProcessId aMyPid = base::kInvalidProcessId, + ProcessId aOtherPid = base::kInvalidProcessId) + : mPort(std::move(aPort)), + mMessageChannelId(aMessageChannelId), + mMyPid(aMyPid), + mOtherPid(aOtherPid) {} + + UntypedEndpoint(const UntypedEndpoint&) = delete; + UntypedEndpoint(UntypedEndpoint&& aOther) = default; + + UntypedEndpoint& operator=(const UntypedEndpoint&) = delete; + UntypedEndpoint& operator=(UntypedEndpoint&& aOther) = default; + + // This method binds aActor to this endpoint. After this call, the actor can + // be used to send and receive messages. The endpoint becomes invalid. + // + // If specified, aEventTarget is the target the actor will be bound to, and + // must be on the current thread. Otherwise, GetCurrentSerialEventTarget() is + // used. + bool Bind(IToplevelProtocol* aActor, + nsISerialEventTarget* aEventTarget = nullptr) { + MOZ_RELEASE_ASSERT(IsValid()); + MOZ_RELEASE_ASSERT(mMyPid == base::kInvalidProcessId || + mMyPid == base::GetCurrentProcId()); + MOZ_RELEASE_ASSERT(!aEventTarget || aEventTarget->IsOnCurrentThread()); + return aActor->Open(std::move(mPort), mMessageChannelId, mOtherPid, + aEventTarget); + } + + bool IsValid() const { return mPort.IsValid(); } + + protected: + friend struct IPC::ParamTraits<UntypedEndpoint>; + + ScopedPort mPort; + nsID mMessageChannelId{}; + ProcessId mMyPid = base::kInvalidProcessId; + ProcessId mOtherPid = base::kInvalidProcessId; +}; + +/** + * An endpoint represents one end of a partially initialized IPDL channel. To + * set up a new top-level protocol: + * + * Endpoint<PFooParent> parentEp; + * Endpoint<PFooChild> childEp; + * nsresult rv; + * rv = PFoo::CreateEndpoints(&parentEp, &childEp); + * + * Endpoints can be passed in IPDL messages or sent to other threads using + * PostTask. Once an Endpoint has arrived at its destination process and thread, + * you need to create the top-level actor and bind it to the endpoint: + * + * FooParent* parent = new FooParent(); + * bool rv1 = parentEp.Bind(parent, processActor); + * bool rv2 = parent->SendBar(...); + * + * (See Bind below for an explanation of processActor.) Once the actor is bound + * to the endpoint, it can send and receive messages. + * + * If creating endpoints for a [NeedsOtherPid] actor, you're required to also + * pass in parentPid and childPid, which are the pids of the processes in which + * the parent and child endpoints will be used. + */ +template <class PFooSide> +class Endpoint final : public UntypedEndpoint { + public: + using UntypedEndpoint::IsValid; + using UntypedEndpoint::UntypedEndpoint; + + base::ProcessId OtherPid() const { + static_assert( + endpoint_detail::ActorNeedsOtherPid<PFooSide>, + "OtherPid may only be called on Endpoints for actors which are " + "[NeedsOtherPid]"); + MOZ_RELEASE_ASSERT(mOtherPid != base::kInvalidProcessId); + return mOtherPid; + } + + // This method binds aActor to this endpoint. After this call, the actor can + // be used to send and receive messages. The endpoint becomes invalid. + // + // If specified, aEventTarget is the target the actor will be bound to, and + // must be on the current thread. Otherwise, GetCurrentSerialEventTarget() is + // used. + bool Bind(PFooSide* aActor, nsISerialEventTarget* aEventTarget = nullptr) { + return UntypedEndpoint::Bind(aActor, aEventTarget); + } +}; + +#if defined(XP_MACOSX) +void AnnotateCrashReportWithErrno(CrashReporter::Annotation tag, int error); +#else +inline void AnnotateCrashReportWithErrno(CrashReporter::Annotation tag, + int error) {} +#endif + +// This function is used internally to create a pair of Endpoints. See the +// comment above Endpoint for a description of how it might be used. +template <class PFooParent, class PFooChild> +nsresult CreateEndpoints(const PrivateIPDLInterface& aPrivate, + Endpoint<PFooParent>* aParentEndpoint, + Endpoint<PFooChild>* aChildEndpoint) { + static_assert( + !endpoint_detail::ActorNeedsOtherPid<PFooParent> && + !endpoint_detail::ActorNeedsOtherPid<PFooChild>, + "Pids are required when creating endpoints for [NeedsOtherPid] actors"); + + auto [parentPort, childPort] = + NodeController::GetSingleton()->CreatePortPair(); + nsID channelId = nsID::GenerateUUID(); + *aParentEndpoint = + Endpoint<PFooParent>(aPrivate, std::move(parentPort), channelId); + *aChildEndpoint = + Endpoint<PFooChild>(aPrivate, std::move(childPort), channelId); + return NS_OK; +} + +template <class PFooParent, class PFooChild> +nsresult CreateEndpoints(const PrivateIPDLInterface& aPrivate, + base::ProcessId aParentDestPid, + base::ProcessId aChildDestPid, + Endpoint<PFooParent>* aParentEndpoint, + Endpoint<PFooChild>* aChildEndpoint) { + MOZ_RELEASE_ASSERT(aParentDestPid != base::kInvalidProcessId); + MOZ_RELEASE_ASSERT(aChildDestPid != base::kInvalidProcessId); + + auto [parentPort, childPort] = + NodeController::GetSingleton()->CreatePortPair(); + nsID channelId = nsID::GenerateUUID(); + *aParentEndpoint = + Endpoint<PFooParent>(aPrivate, std::move(parentPort), channelId, + aParentDestPid, aChildDestPid); + *aChildEndpoint = Endpoint<PFooChild>( + aPrivate, std::move(childPort), channelId, aChildDestPid, aParentDestPid); + return NS_OK; +} + +class UntypedManagedEndpoint { + public: + bool IsValid() const { return mInner.isSome(); } + + UntypedManagedEndpoint(const UntypedManagedEndpoint&) = delete; + UntypedManagedEndpoint& operator=(const UntypedManagedEndpoint&) = delete; + + protected: + UntypedManagedEndpoint() = default; + explicit UntypedManagedEndpoint(IProtocol* aActor); + + UntypedManagedEndpoint(UntypedManagedEndpoint&& aOther) noexcept + : mInner(std::move(aOther.mInner)) { + aOther.mInner = Nothing(); + } + UntypedManagedEndpoint& operator=(UntypedManagedEndpoint&& aOther) noexcept { + this->~UntypedManagedEndpoint(); + new (this) UntypedManagedEndpoint(std::move(aOther)); + return *this; + } + + ~UntypedManagedEndpoint() noexcept; + + bool BindCommon(IProtocol* aActor, IProtocol* aManager); + + private: + friend struct IPDLParamTraits<UntypedManagedEndpoint>; + + struct Inner { + // Pointers to the toplevel actor which will manage this connection. When + // created, only `mOtherSide` will be set, and will reference the + // toplevel actor which the other side is managed by. After being sent over + // IPC, only `mToplevel` will be set, and will be the toplevel actor for the + // channel which received the IPC message. + RefPtr<WeakActorLifecycleProxy> mOtherSide; + RefPtr<WeakActorLifecycleProxy> mToplevel; + + int32_t mId = 0; + ProtocolId mType = LastMsgIndex; + int32_t mManagerId = 0; + ProtocolId mManagerType = LastMsgIndex; + }; + Maybe<Inner> mInner; +}; + +/** + * A managed endpoint represents one end of a partially initialized managed + * IPDL actor. It is used for situations where the usual IPDL Constructor + * methods do not give sufficient control over the construction of actors, such + * as when constructing actors within replies, or constructing multiple related + * actors simultaneously. + * + * FooParent* parent = new FooParent(); + * ManagedEndpoint<PFooChild> childEp = parentMgr->OpenPFooEndpoint(parent); + * + * ManagedEndpoints should be sent using IPDL messages or other mechanisms to + * the other side of the manager channel. Once the ManagedEndpoint has arrived + * at its destination, you can create the actor, and bind it to the endpoint. + * + * FooChild* child = new FooChild(); + * childMgr->BindPFooEndpoint(childEp, child); + * + * WARNING: If the remote side of an endpoint has not been bound before it + * begins to receive messages, an IPC routing error will occur, likely causing + * a browser crash. + */ +template <class PFooSide> +class ManagedEndpoint : public UntypedManagedEndpoint { + public: + ManagedEndpoint() = default; + ManagedEndpoint(ManagedEndpoint&&) noexcept = default; + ManagedEndpoint& operator=(ManagedEndpoint&&) noexcept = default; + + ManagedEndpoint(const PrivateIPDLInterface&, IProtocol* aActor) + : UntypedManagedEndpoint(aActor) {} + + bool Bind(const PrivateIPDLInterface&, PFooSide* aActor, IProtocol* aManager, + ManagedContainer<PFooSide>& aContainer) { + if (!BindCommon(aActor, aManager)) { + return false; + } + aContainer.Insert(aActor); + return true; + } + + // Only invalid ManagedEndpoints can be equal, as valid endpoints are unique. + bool operator==(const ManagedEndpoint& _o) const { + return !IsValid() && !_o.IsValid(); + } +}; + +} // namespace ipc +} // namespace mozilla + +#endif // IPC_GLUE_ENDPOINT_H_ diff --git a/ipc/glue/EnumSerializer.h b/ipc/glue/EnumSerializer.h new file mode 100644 index 0000000000..13c1de3841 --- /dev/null +++ b/ipc/glue/EnumSerializer.h @@ -0,0 +1,187 @@ +/* -*- 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/. */ + +#ifndef __IPC_GLUE_ENUMSERIALIZER_H__ +#define __IPC_GLUE_ENUMSERIALIZER_H__ + +#include "CrashAnnotations.h" +#include "chrome/common/ipc_message_utils.h" +#include "mozilla/Assertions.h" +#include "mozilla/IntegerTypeTraits.h" +#include "nsExceptionHandler.h" +#include "nsLiteralString.h" +#include "nsString.h" +#include "nsTLiteralString.h" + +class PickleIterator; + +namespace IPC { +class Message; +class MessageReader; +class MessageWriter; +} // namespace IPC + +#ifdef _MSC_VER +# pragma warning(disable : 4800) +#endif + +namespace IPC { + +/** + * Generic enum serializer. + * + * Consider using the specializations below, such as ContiguousEnumSerializer. + * + * This is a generic serializer for any enum type used in IPDL. + * Programmers can define ParamTraits<E> for enum type E by deriving + * EnumSerializer<E, MyEnumValidator> where MyEnumValidator is a struct + * that has to define a static IsLegalValue function returning whether + * a given value is a legal value of the enum type at hand. + * + * \sa https://developer.mozilla.org/en/IPDL/Type_Serialization + */ +template <typename E, typename EnumValidator> +struct EnumSerializer { + typedef E paramType; + + // XXX(Bug 1690343) Should this be changed to + // std::make_unsigned_t<std::underlying_type_t<paramType>>, to make this more + // consistent with the type used for validating values? + typedef typename mozilla::UnsignedStdintTypeForSize<sizeof(paramType)>::Type + uintParamType; + + static void Write(MessageWriter* aWriter, const paramType& aValue) { + // XXX This assertion is somewhat meaningless at least for E that don't have + // a fixed underlying type: if aValue weren't a legal value, we would + // already have UB where this function is called. + MOZ_RELEASE_ASSERT(EnumValidator::IsLegalValue( + static_cast<std::underlying_type_t<paramType>>(aValue))); + WriteParam(aWriter, uintParamType(aValue)); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + uintParamType value; + if (!ReadParam(aReader, &value)) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCReadErrorReason, "Bad iter"_ns); + return false; + } else if (!EnumValidator::IsLegalValue(value)) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCReadErrorReason, "Illegal value"_ns); + return false; + } + *aResult = paramType(value); + return true; + } +}; + +template <typename E, E MinLegal, E HighBound> +class ContiguousEnumValidator { + // Silence overzealous -Wtype-limits bug in GCC fixed in GCC 4.8: + // "comparison of unsigned expression >= 0 is always true" + // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11856 + template <typename T> + static bool IsLessThanOrEqual(T a, T b) { + return a <= b; + } + + public: + using IntegralType = std::underlying_type_t<E>; + static constexpr auto kMinLegalIntegral = static_cast<IntegralType>(MinLegal); + static constexpr auto kHighBoundIntegral = + static_cast<IntegralType>(HighBound); + + static bool IsLegalValue(const IntegralType e) { + return IsLessThanOrEqual(kMinLegalIntegral, e) && e < kHighBoundIntegral; + } +}; + +template <typename E, E MinLegal, E MaxLegal> +class ContiguousEnumValidatorInclusive { + // Silence overzealous -Wtype-limits bug in GCC fixed in GCC 4.8: + // "comparison of unsigned expression >= 0 is always true" + // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11856 + template <typename T> + static bool IsLessThanOrEqual(T a, T b) { + return a <= b; + } + + public: + using IntegralType = std::underlying_type_t<E>; + static constexpr auto kMinLegalIntegral = static_cast<IntegralType>(MinLegal); + static constexpr auto kMaxLegalIntegral = static_cast<IntegralType>(MaxLegal); + + static bool IsLegalValue(const IntegralType e) { + return IsLessThanOrEqual(kMinLegalIntegral, e) && e <= kMaxLegalIntegral; + } +}; + +template <typename E, E AllBits> +struct BitFlagsEnumValidator { + static bool IsLegalValue(const std::underlying_type_t<E> e) { + return (e & static_cast<std::underlying_type_t<E>>(AllBits)) == e; + } +}; + +/** + * Specialization of EnumSerializer for enums with contiguous enum values. + * + * Provide two values: MinLegal, HighBound. An enum value x will be + * considered legal if MinLegal <= x < HighBound. + * + * For example, following is definition of serializer for enum type FOO. + * \code + * enum FOO { FOO_FIRST, FOO_SECOND, FOO_LAST, NUM_FOO }; + * + * template <> + * struct ParamTraits<FOO>: + * public ContiguousEnumSerializer<FOO, FOO_FIRST, NUM_FOO> {}; + * \endcode + * FOO_FIRST, FOO_SECOND, and FOO_LAST are valid value. + */ +template <typename E, E MinLegal, E HighBound> +struct ContiguousEnumSerializer + : EnumSerializer<E, ContiguousEnumValidator<E, MinLegal, HighBound>> {}; + +/** + * This is similar to ContiguousEnumSerializer, but the last template + * parameter is expected to be the highest legal value, rather than a + * sentinel value. This is intended to support enumerations that don't + * have sentinel values. + */ +template <typename E, E MinLegal, E MaxLegal> +struct ContiguousEnumSerializerInclusive + : EnumSerializer<E, + ContiguousEnumValidatorInclusive<E, MinLegal, MaxLegal>> { +}; + +/** + * Specialization of EnumSerializer for enums representing bit flags. + * + * Provide one value: AllBits. An enum value x will be + * considered legal if (x & AllBits) == x; + * + * Example: + * \code + * enum FOO { + * FOO_FIRST = 1 << 0, + * FOO_SECOND = 1 << 1, + * FOO_LAST = 1 << 2, + * ALL_BITS = (1 << 3) - 1 + * }; + * + * template <> + * struct ParamTraits<FOO>: + * public BitFlagsEnumSerializer<FOO, FOO::ALL_BITS> {}; + * \endcode + */ +template <typename E, E AllBits> +struct BitFlagsEnumSerializer + : EnumSerializer<E, BitFlagsEnumValidator<E, AllBits>> {}; + +} /* namespace IPC */ + +#endif /* __IPC_GLUE_ENUMSERIALIZER_H__ */ diff --git a/ipc/glue/EnvironmentMap.h b/ipc/glue/EnvironmentMap.h new file mode 100644 index 0000000000..61dcc1b7d2 --- /dev/null +++ b/ipc/glue/EnvironmentMap.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOXING_COMMON_ENVIRONMENTMAP_H_ +#define SANDBOXING_COMMON_ENVIRONMENTMAP_H_ + +#include <map> +#include <memory> +#include <string> + +namespace base { + +#if defined(XP_WIN) + +typedef std::wstring NativeEnvironmentString; +typedef std::map<NativeEnvironmentString, NativeEnvironmentString> + EnvironmentMap; + +# define ENVIRONMENT_LITERAL(x) L##x +# define ENVIRONMENT_STRING(x) \ + ((std::wstring)(NS_ConvertUTF8toUTF16((x)).get())) + +// Returns a modified environment vector constructed from the given environment +// and the list of changes given in |changes|. Each key in the environment is +// matched against the first element of the pairs. In the event of a match, the +// value is replaced by the second of the pair, unless the second is empty, in +// which case the key-value is removed. +// +// This Windows version takes and returns a Windows-style environment block +// which is a concatenated list of null-terminated 16-bit strings. The end is +// marked by a double-null terminator. The size of the returned string will +// include the terminators. +NativeEnvironmentString AlterEnvironment(const wchar_t* env, + const EnvironmentMap& changes); + +#else + +typedef std::string NativeEnvironmentString; +typedef std::map<NativeEnvironmentString, NativeEnvironmentString> + EnvironmentMap; + +# define ENVIRONMENT_LITERAL(x) x +# define ENVIRONMENT_STRING(x) x + +// See general comments for the Windows version above. +// +// This Posix version takes and returns a Posix-style environment block, which +// is a null-terminated list of pointers to null-terminated strings. The +// returned array will have appended to it the storage for the array itself so +// there is only one pointer to manage, but this means that you can't copy the +// array without keeping the original around. +std::unique_ptr<char*[]> AlterEnvironment(const char* const* env, + const EnvironmentMap& changes); + +#endif + +} // namespace base + +#endif // SANDBOXING_COMMON_ENVIRONMENTMAP_H_ diff --git a/ipc/glue/FileDescriptor.cpp b/ipc/glue/FileDescriptor.cpp new file mode 100644 index 0000000000..697de4cc63 --- /dev/null +++ b/ipc/glue/FileDescriptor.cpp @@ -0,0 +1,117 @@ +/* -*- 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 "FileDescriptor.h" + +#include "mozilla/Assertions.h" +#include "mozilla/ipc/IPDLParamTraits.h" +#include "mozilla/ipc/ProtocolMessageUtils.h" +#include "nsDebug.h" + +#ifdef XP_WIN +# include <windows.h> +# include "ProtocolUtils.h" +#else // XP_WIN +# include <unistd.h> +#endif // XP_WIN + +namespace mozilla { +namespace ipc { + +FileDescriptor::FileDescriptor() = default; + +FileDescriptor::FileDescriptor(const FileDescriptor& aOther) + : mHandle(Clone(aOther.mHandle.get())) {} + +FileDescriptor::FileDescriptor(FileDescriptor&& aOther) + : mHandle(std::move(aOther.mHandle)) {} + +FileDescriptor::FileDescriptor(PlatformHandleType aHandle) + : mHandle(Clone(aHandle)) {} + +FileDescriptor::FileDescriptor(UniquePlatformHandle&& aHandle) + : mHandle(std::move(aHandle)) {} + +FileDescriptor::~FileDescriptor() = default; + +FileDescriptor& FileDescriptor::operator=(const FileDescriptor& aOther) { + if (this != &aOther) { + mHandle = Clone(aOther.mHandle.get()); + } + return *this; +} + +FileDescriptor& FileDescriptor::operator=(FileDescriptor&& aOther) { + if (this != &aOther) { + mHandle = std::move(aOther.mHandle); + } + return *this; +} + +bool FileDescriptor::IsValid() const { return mHandle != nullptr; } + +FileDescriptor::UniquePlatformHandle FileDescriptor::ClonePlatformHandle() + const { + return Clone(mHandle.get()); +} + +FileDescriptor::UniquePlatformHandle FileDescriptor::TakePlatformHandle() { + return UniquePlatformHandle(mHandle.release()); +} + +bool FileDescriptor::operator==(const FileDescriptor& aOther) const { + return mHandle == aOther.mHandle; +} + +// static +FileDescriptor::UniquePlatformHandle FileDescriptor::Clone( + PlatformHandleType aHandle) { + FileDescriptor::PlatformHandleType newHandle; + +#ifdef XP_WIN + if (aHandle == INVALID_HANDLE_VALUE || aHandle == nullptr) { + return UniqueFileHandle(); + } + if (::DuplicateHandle(GetCurrentProcess(), aHandle, GetCurrentProcess(), + &newHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) { + return UniqueFileHandle(newHandle); + } +#else // XP_WIN + if (aHandle < 0) { + return UniqueFileHandle(); + } + newHandle = dup(aHandle); + if (newHandle >= 0) { + return UniqueFileHandle(newHandle); + } +#endif + NS_WARNING("Failed to duplicate file handle for current process!"); + return UniqueFileHandle(); +} + +void IPDLParamTraits<FileDescriptor>::Write(IPC::MessageWriter* aWriter, + IProtocol* aActor, + const FileDescriptor& aParam) { + WriteIPDLParam(aWriter, aActor, aParam.ClonePlatformHandle()); +} + +bool IPDLParamTraits<FileDescriptor>::Read(IPC::MessageReader* aReader, + IProtocol* aActor, + FileDescriptor* aResult) { + UniqueFileHandle handle; + if (!ReadIPDLParam(aReader, aActor, &handle)) { + return false; + } + + *aResult = FileDescriptor(std::move(handle)); + if (!aResult->IsValid()) { + printf_stderr("IPDL protocol Error: Received an invalid file descriptor\n"); + } + return true; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/FileDescriptor.h b/ipc/glue/FileDescriptor.h new file mode 100644 index 0000000000..15160624da --- /dev/null +++ b/ipc/glue/FileDescriptor.h @@ -0,0 +1,79 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_FileDescriptor_h +#define mozilla_ipc_FileDescriptor_h + +#include "base/basictypes.h" +#include "base/process.h" +#include "mozilla/UniquePtrExtensions.h" + +namespace mozilla { +namespace ipc { + +// This class is used by IPDL to share file descriptors across processes. When +// sending a FileDescriptor, IPDL will transfer a duplicate of the handle into +// the remote process. +// +// To use this class add 'FileDescriptor' as an argument in the IPDL protocol +// and then pass a file descriptor from C++ to the Send method. The Recv method +// will receive a FileDescriptor& on which PlatformHandle() can be called to +// return the platform file handle. +class FileDescriptor { + public: + typedef base::ProcessId ProcessId; + + using UniquePlatformHandle = mozilla::UniqueFileHandle; + using PlatformHandleType = UniquePlatformHandle::ElementType; + + // This should only ever be created by IPDL. + struct IPDLPrivate {}; + + // Represents an invalid handle. + FileDescriptor(); + + // Copy constructor will duplicate a new handle. + FileDescriptor(const FileDescriptor& aOther); + + FileDescriptor(FileDescriptor&& aOther); + + // This constructor will duplicate a new handle. + // The caller still have to close aHandle. + explicit FileDescriptor(PlatformHandleType aHandle); + + explicit FileDescriptor(UniquePlatformHandle&& aHandle); + + ~FileDescriptor(); + + FileDescriptor& operator=(const FileDescriptor& aOther); + + FileDescriptor& operator=(FileDescriptor&& aOther); + + // Tests mHandle against a well-known invalid platform-specific file handle + // (e.g. -1 on POSIX, INVALID_HANDLE_VALUE on Windows). + bool IsValid() const; + + // Returns a duplicated handle, it is caller's responsibility to close the + // handle. + UniquePlatformHandle ClonePlatformHandle() const; + + // Extracts the underlying handle and makes this object an invalid handle. + // (Compare UniquePtr::release.) + UniquePlatformHandle TakePlatformHandle(); + + // Only used in nsTArray. + bool operator==(const FileDescriptor& aOther) const; + + private: + static UniqueFileHandle Clone(PlatformHandleType aHandle); + + UniquePlatformHandle mHandle; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_FileDescriptor_h diff --git a/ipc/glue/FileDescriptorShuffle.cpp b/ipc/glue/FileDescriptorShuffle.cpp new file mode 100644 index 0000000000..7ce89e1b16 --- /dev/null +++ b/ipc/glue/FileDescriptorShuffle.cpp @@ -0,0 +1,113 @@ +/* -*- 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 "FileDescriptorShuffle.h" + +#include "base/eintr_wrapper.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" + +#include <algorithm> +#include <unistd.h> +#include <fcntl.h> + +namespace mozilla { +namespace ipc { + +// F_DUPFD_CLOEXEC is like F_DUPFD (see below) but atomically makes +// the new fd close-on-exec. This is useful to prevent accidental fd +// leaks into processes created by plain fork/exec, but IPC uses +// CloseSuperfluousFds so it's not essential. +// +// F_DUPFD_CLOEXEC is in POSIX 2008, but as of 2018 there are still +// some OSes that don't support it. (Specifically: Solaris 10 doesn't +// have it, and Android should have kernel support but doesn't define +// the constant until API 21 (Lollipop). We also don't use this for +// IPC child launching on Android, so there's no point in hard-coding +// the definitions like we do for Android in some other cases.) +#ifdef F_DUPFD_CLOEXEC +static const int kDupFdCmd = F_DUPFD_CLOEXEC; +#else +static const int kDupFdCmd = F_DUPFD; +#endif + +// This implementation ensures that the *ranges* of the source and +// destination fds don't overlap, which is unnecessary but sufficient +// to avoid conflicts or identity mappings. +// +// In practice, the source fds will usually be large and the +// destination fds will all be relatively small, so there mostly won't +// be temporary fds. This approach has the advantage of being simple +// (and linear-time, but hopefully there aren't enough fds for that to +// matter). +bool FileDescriptorShuffle::Init(MappingRef aMapping) { + MOZ_ASSERT(mMapping.IsEmpty()); + + // Find the maximum destination fd; any source fds not greater than + // this will be duplicated. + int maxDst = STDERR_FILENO; + for (const auto& elem : aMapping) { + maxDst = std::max(maxDst, elem.second); + } + mMaxDst = maxDst; + +#ifdef DEBUG + // Increase the limit to make sure the F_DUPFD case gets test coverage. + if (!aMapping.IsEmpty()) { + // Try to find a value that will create a nontrivial partition. + int fd0 = aMapping[0].first; + int fdn = aMapping.rbegin()->first; + maxDst = std::max(maxDst, (fd0 + fdn) / 2); + } +#endif + + for (const auto& elem : aMapping) { + int src = elem.first; + // F_DUPFD is like dup() but allows placing a lower bound + // on the new fd, which is exactly what we want. + if (src <= maxDst) { + src = fcntl(src, kDupFdCmd, maxDst + 1); + if (src < 0) { + return false; + } + mTempFds.AppendElement(src); + } + MOZ_ASSERT(src > maxDst); +#ifdef DEBUG + // Check for accidentally mapping two different fds to the same + // destination. (This is O(n^2) time, but it shouldn't matter.) + for (const auto& otherElem : mMapping) { + MOZ_ASSERT(elem.second != otherElem.second); + } +#endif + mMapping.AppendElement(std::make_pair(src, elem.second)); + } + return true; +} + +bool FileDescriptorShuffle::MapsTo(int aFd) const { + // Prune fds that are too large to be a destination, rather than + // searching; this should be the common case. + if (aFd > mMaxDst) { + return false; + } + for (const auto& elem : mMapping) { + if (elem.second == aFd) { + return true; + } + } + return false; +} + +FileDescriptorShuffle::~FileDescriptorShuffle() { + for (const auto& fd : mTempFds) { + mozilla::DebugOnly<int> rv = IGNORE_EINTR(close(fd)); + MOZ_ASSERT(rv == 0); + } +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/FileDescriptorShuffle.h b/ipc/glue/FileDescriptorShuffle.h new file mode 100644 index 0000000000..4afa8e0474 --- /dev/null +++ b/ipc/glue/FileDescriptorShuffle.h @@ -0,0 +1,68 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_FileDescriptorShuffle_h +#define mozilla_ipc_FileDescriptorShuffle_h + +#include "mozilla/Span.h" +#include "nsTArray.h" + +#include <functional> +#include <utility> + +// This class converts a set of file descriptor mapping, which may +// contain conflicts (like {a->b, b->c} or {a->b, b->a}) into a +// sequence of dup2() operations that can be performed between fork +// and exec, or with posix_spawn_file_actions_adddup2. It may create +// temporary duplicates of fds to use as the source of a dup2; they +// are closed on destruction. +// +// The dup2 sequence is guaranteed to not contain dup2(x, x) for any +// x; if such an element is present in the input, it will be dup2()ed +// from a temporary fd to ensure that the close-on-exec bit is cleared. +// +// In general, this is *not* guaranteed to minimize the use of +// temporary fds. + +namespace mozilla { +namespace ipc { + +class FileDescriptorShuffle { + public: + FileDescriptorShuffle() = default; + ~FileDescriptorShuffle(); + + using MappingRef = mozilla::Span<const std::pair<int, int>>; + + // Translate the given mapping, creating temporary fds as needed. + // Can fail (return false) on failure to duplicate fds. + bool Init(MappingRef aMapping); + + // Accessor for the dup2() sequence. Do not use the returned value + // or the fds contained in it after this object is destroyed. + MappingRef Dup2Sequence() const { return mMapping; } + + // Tests whether the given fd is used as a destination in this mapping. + // Can be used to close other fds after performing the dup2()s. + bool MapsTo(int aFd) const; + + // Forget the information, so that it's destructor will not try to + // delete FDs duped by itself. + void Forget() { mTempFds.Clear(); } + + private: + nsTArray<std::pair<int, int>> mMapping; + nsTArray<int> mTempFds; + int mMaxDst; + + FileDescriptorShuffle(const FileDescriptorShuffle&) = delete; + void operator=(const FileDescriptorShuffle&) = delete; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_FileDescriptorShuffle_h diff --git a/ipc/glue/FileDescriptorUtils.cpp b/ipc/glue/FileDescriptorUtils.cpp new file mode 100644 index 0000000000..e4af78e79e --- /dev/null +++ b/ipc/glue/FileDescriptorUtils.cpp @@ -0,0 +1,109 @@ +/* -*- 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 "FileDescriptorUtils.h" + +#include "nsIEventTarget.h" + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "prio.h" +#include "private/pprio.h" + +#include <errno.h> +#ifdef XP_WIN +# include <io.h> +#else +# include <unistd.h> +#endif + +using mozilla::ipc::CloseFileRunnable; + +CloseFileRunnable::CloseFileRunnable(const FileDescriptor& aFileDescriptor) + : Runnable("CloseFileRunnable"), mFileDescriptor(aFileDescriptor) { + MOZ_ASSERT(aFileDescriptor.IsValid()); +} + +CloseFileRunnable::~CloseFileRunnable() { + if (mFileDescriptor.IsValid()) { + // It's probably safer to take the main thread IO hit here rather than leak + // the file descriptor. + CloseFile(); + } +} + +NS_IMPL_ISUPPORTS_INHERITED0(CloseFileRunnable, Runnable) + +void CloseFileRunnable::Dispatch() { + nsCOMPtr<nsIEventTarget> eventTarget = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + NS_ENSURE_TRUE_VOID(eventTarget); + + nsresult rv = eventTarget->Dispatch(this, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS_VOID(rv); +} + +void CloseFileRunnable::CloseFile() { + // It's possible for this to happen on the main thread if the dispatch to the + // stream service fails so we can't assert the thread on which we're running. + mFileDescriptor = FileDescriptor(); +} + +NS_IMETHODIMP +CloseFileRunnable::Run() { + MOZ_ASSERT(!NS_IsMainThread()); + + CloseFile(); + return NS_OK; +} + +namespace mozilla { +namespace ipc { + +FILE* FileDescriptorToFILE(const FileDescriptor& aDesc, const char* aOpenMode) { + if (!aDesc.IsValid()) { + errno = EBADF; + return nullptr; + } + auto handle = aDesc.ClonePlatformHandle(); +#ifdef XP_WIN + int fd = _open_osfhandle(static_cast<intptr_t>(handle.get()), 0); + if (fd == -1) { + return nullptr; + } + Unused << handle.release(); +#else + int fd = handle.release(); +#endif + FILE* file = fdopen(fd, aOpenMode); + if (!file) { + int saved_errno = errno; + close(fd); + errno = saved_errno; + } + return file; +} + +FileDescriptor FILEToFileDescriptor(FILE* aStream) { + if (!aStream) { + errno = EBADF; + return FileDescriptor(); + } +#ifdef XP_WIN + int fd = _fileno(aStream); + if (fd == -1) { + return FileDescriptor(); + } + return FileDescriptor(reinterpret_cast<HANDLE>(_get_osfhandle(fd))); +#else + return FileDescriptor(fileno(aStream)); +#endif +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/FileDescriptorUtils.h b/ipc/glue/FileDescriptorUtils.h new file mode 100644 index 0000000000..e8aab0639e --- /dev/null +++ b/ipc/glue/FileDescriptorUtils.h @@ -0,0 +1,53 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_FileDescriptorUtils_h +#define mozilla_ipc_FileDescriptorUtils_h + +#include "mozilla/Attributes.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "nsThreadUtils.h" +#include <stdio.h> + +namespace mozilla { +namespace ipc { + +// When Dispatch() is called (from main thread) this class arranges to close the +// provided FileDescriptor on one of the socket transport service threads (to +// avoid main thread I/O). +class CloseFileRunnable final : public Runnable { + typedef mozilla::ipc::FileDescriptor FileDescriptor; + + FileDescriptor mFileDescriptor; + + public: + explicit CloseFileRunnable(const FileDescriptor& aFileDescriptor); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + + void Dispatch(); + + private: + ~CloseFileRunnable(); + + void CloseFile(); +}; + +// On failure, FileDescriptorToFILE returns nullptr; on success, +// returns duplicated FILE*. +// This is meant for use with FileDescriptors received over IPC. +FILE* FileDescriptorToFILE(const FileDescriptor& aDesc, const char* aOpenMode); + +// FILEToFileDescriptor does not consume the given FILE*; it must be +// fclose()d as normal, and this does not invalidate the returned +// FileDescriptor. +FileDescriptor FILEToFileDescriptor(FILE* aStream); + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_FileDescriptorUtils_h diff --git a/ipc/glue/ForkServer.cpp b/ipc/glue/ForkServer.cpp new file mode 100644 index 0000000000..b70fdbf2c0 --- /dev/null +++ b/ipc/glue/ForkServer.cpp @@ -0,0 +1,316 @@ +/* -*- 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 "mozilla/ipc/ForkServer.h" + +#include "chrome/common/chrome_switches.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/Logging.h" +#include "mozilla/Omnijar.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/ipc/IPDLParamTraits.h" +#include "mozilla/ipc/ProtocolMessageUtils.h" +#include "mozilla/ipc/SetProcessTitle.h" +#include "nsTraceRefcnt.h" + +#include <fcntl.h> +#include <string.h> +#include <unistd.h> + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxLaunch.h" +#endif + +#include <algorithm> + +namespace mozilla { +namespace ipc { + +LazyLogModule gForkServiceLog("ForkService"); + +ForkServer::ForkServer() {} + +/** + * Prepare an environment for running a fork server. + */ +void ForkServer::InitProcess(int* aArgc, char*** aArgv) { + base::InitForkServerProcess(); + + mTcver = MakeUnique<MiniTransceiver>(kClientPipeFd, + DataBufferClear::AfterReceiving); +} + +/** + * Preload any resources that the forked child processes might need, + * and which might change incompatibly or become unavailable by the + * time they're started. For example: the omnijar files, or certain + * shared libraries. + */ +static void ForkServerPreload(int& aArgc, char** aArgv) { + Omnijar::ChildProcessInit(aArgc, aArgv); +} + +/** + * Start providing the service at the IPC channel. + */ +bool ForkServer::HandleMessages() { + while (true) { + UniquePtr<IPC::Message> msg; + if (!mTcver->Recv(msg)) { + break; + } + + OnMessageReceived(std::move(msg)); + + if (mAppProcBuilder) { + // New process - child + return false; + } + } + // Stop the server + return true; +} + +inline void CleanCString(nsCString& str) { + char* data; + int sz = str.GetMutableData(&data); + + memset(data, ' ', sz); +} + +inline void CleanString(std::string& str) { + const char deadbeef[] = + "\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef" + "\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef"; + int pos = 0; + size_t sz = str.size(); + while (sz > 0) { + int toclean = std::min(sz, sizeof(deadbeef) - 1); + str.replace(pos, toclean, deadbeef); + sz -= toclean; + pos += toclean; + } +} + +inline void PrepareArguments(std::vector<std::string>& aArgv, + nsTArray<nsCString>& aArgvArray) { + for (auto& elt : aArgvArray) { + aArgv.push_back(elt.get()); + CleanCString(elt); + } +} + +// Prepare aOptions->env_map +inline void PrepareEnv(base::LaunchOptions* aOptions, + nsTArray<EnvVar>& aEnvMap) { + for (auto& elt : aEnvMap) { + nsCString& var = std::get<0>(elt); + nsCString& val = std::get<1>(elt); + aOptions->env_map[var.get()] = val.get(); + CleanCString(var); + CleanCString(val); + } +} + +// Prepare aOptions->fds_to_remap +inline void PrepareFdsRemap(base::LaunchOptions* aOptions, + nsTArray<FdMapping>& aFdsRemap) { + MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("fds mapping:")); + for (auto& elt : aFdsRemap) { + // FDs are duplicated here. + int fd = std::get<0>(elt).ClonePlatformHandle().release(); + std::pair<int, int> fdmap(fd, std::get<1>(elt)); + aOptions->fds_to_remap.push_back(fdmap); + MOZ_LOG(gForkServiceLog, LogLevel::Verbose, + ("\t%d => %d", fdmap.first, fdmap.second)); + } +} + +template <class P> +static void ReadParamInfallible(IPC::MessageReader* aReader, P* aResult, + const char* aCrashMessage) { + if (!IPC::ReadParam(aReader, aResult)) { + MOZ_CRASH_UNSAFE(aCrashMessage); + } +} + +/** + * Parse a Message to get a list of arguments and fill a LaunchOptions. + */ +inline bool ParseForkNewSubprocess(IPC::Message& aMsg, + std::vector<std::string>& aArgv, + base::LaunchOptions* aOptions) { + if (aMsg.type() != Msg_ForkNewSubprocess__ID) { + MOZ_LOG(gForkServiceLog, LogLevel::Verbose, + ("unknown message type %d\n", aMsg.type())); + return false; + } + + IPC::MessageReader reader(aMsg); + nsTArray<nsCString> argv_array; + nsTArray<EnvVar> env_map; + nsTArray<FdMapping> fds_remap; + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + ReadParamInfallible(&reader, &aOptions->fork_flags, + "Error deserializing 'int'"); + ReadParamInfallible(&reader, &aOptions->sandbox_chroot, + "Error deserializing 'bool'"); +#endif + ReadParamInfallible(&reader, &argv_array, + "Error deserializing 'nsCString[]'"); + ReadParamInfallible(&reader, &env_map, "Error deserializing 'EnvVar[]'"); + ReadParamInfallible(&reader, &fds_remap, "Error deserializing 'FdMapping[]'"); + reader.EndRead(); + + PrepareArguments(aArgv, argv_array); + PrepareEnv(aOptions, env_map); + PrepareFdsRemap(aOptions, fds_remap); + + return true; +} + +inline void SanitizeBuffers(IPC::Message& aMsg, std::vector<std::string>& aArgv, + base::LaunchOptions& aOptions) { + // Clean all buffers in the message to make sure content processes + // not peeking others. + auto& blist = aMsg.Buffers(); + for (auto itr = blist.Iter(); !itr.Done(); + itr.Advance(blist, itr.RemainingInSegment())) { + memset(itr.Data(), 0, itr.RemainingInSegment()); + } + + // clean all data string made from the message. + for (auto& var : aOptions.env_map) { + // Do it anyway since it is not going to be used anymore. + CleanString(*const_cast<std::string*>(&var.first)); + CleanString(var.second); + } + for (auto& arg : aArgv) { + CleanString(arg); + } +} + +/** + * Extract parameters from the |Message| to create a + * |base::AppProcessBuilder| as |mAppProcBuilder|. + * + * It will return in both the fork server process and the new content + * process. |mAppProcBuilder| is null for the fork server. + */ +void ForkServer::OnMessageReceived(UniquePtr<IPC::Message> message) { + std::vector<std::string> argv; + base::LaunchOptions options; + if (!ParseForkNewSubprocess(*message, argv, &options)) { + return; + } + + base::ProcessHandle child_pid = -1; + mAppProcBuilder = MakeUnique<base::AppProcessBuilder>(); + if (!mAppProcBuilder->ForkProcess(argv, std::move(options), &child_pid)) { + MOZ_CRASH("fail to fork"); + } + MOZ_ASSERT(child_pid >= 0); + + if (child_pid == 0) { + // Content process + return; + } + + // Fork server process + + mAppProcBuilder = nullptr; + + IPC::Message reply(MSG_ROUTING_CONTROL, Reply_ForkNewSubprocess__ID); + IPC::MessageWriter writer(reply); + WriteIPDLParam(&writer, nullptr, child_pid); + mTcver->SendInfallible(reply, "failed to send a reply message"); + + // Without this, the content processes that is forked later are + // able to read the content of buffers even the buffers have been + // released. + SanitizeBuffers(*message, argv, options); +} + +/** + * Setup and run a fork server at the main thread. + * + * This function returns for two reasons: + * - the fork server is stopped normally, or + * - a new process is forked from the fork server and this function + * returned in the child, the new process. + * + * For the later case, aArgc and aArgv are modified to pass the + * arguments from the chrome process. + */ +bool ForkServer::RunForkServer(int* aArgc, char*** aArgv) { +#ifdef DEBUG + if (getenv("MOZ_FORKSERVER_WAIT_GDB")) { + printf( + "Waiting for 30 seconds." + " Attach the fork server with gdb %s %d\n", + (*aArgv)[0], base::GetCurrentProcId()); + sleep(30); + } + bool sleep_newproc = !!getenv("MOZ_FORKSERVER_WAIT_GDB_NEWPROC"); +#endif + + SetProcessTitleInit(*aArgv); + + // Do this before NS_LogInit() to avoid log files taking lower + // FDs. + ForkServer forkserver; + forkserver.InitProcess(aArgc, aArgv); + + XRE_SetProcessType("forkserver"); + NS_LogInit(); + mozilla::LogModule::Init(0, nullptr); + ForkServerPreload(*aArgc, *aArgv); + MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("Start a fork server")); + { + DebugOnly<base::ProcessHandle> forkserver_pid = base::GetCurrentProcId(); + if (forkserver.HandleMessages()) { + // In the fork server process + // The server has stopped. + MOZ_LOG(gForkServiceLog, LogLevel::Verbose, + ("Terminate the fork server")); + Omnijar::CleanUp(); + NS_LogTerm(); + return true; + } + // Now, we are running in a content process just forked from + // the fork server process. + MOZ_ASSERT(base::GetCurrentProcId() != forkserver_pid); + MOZ_LOG(gForkServiceLog, LogLevel::Verbose, ("Fork a new content process")); + } +#ifdef DEBUG + if (sleep_newproc) { + printf( + "Waiting for 30 seconds." + " Attach the new process with gdb %s %d\n", + (*aArgv)[0], base::GetCurrentProcId()); + sleep(30); + } +#endif + NS_LogTerm(); + + MOZ_ASSERT(forkserver.mAppProcBuilder); + // |messageloop| has been destroyed. So, we can intialized the + // process safely. Message loops may allocates some file + // descriptors. If it is destroyed later, it may mess up this + // content process by closing wrong file descriptors. + forkserver.mAppProcBuilder->InitAppProcess(aArgc, aArgv); + forkserver.mAppProcBuilder.reset(); + + // Open log files again with right names and the new PID. + nsTraceRefcnt::ResetLogFiles((*aArgv)[*aArgc - 1]); + + return false; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ForkServer.h b/ipc/glue/ForkServer.h new file mode 100644 index 0000000000..ba4ed9d44e --- /dev/null +++ b/ipc/glue/ForkServer.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 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/. */ +#ifndef __FORKSERVER_H_ +#define __FORKSERVER_H_ + +#include "mozilla/UniquePtr.h" +#include "base/process_util.h" +#include "mozilla/ipc/MiniTransceiver.h" + +namespace mozilla { +namespace ipc { + +class ForkServer { + public: + // NOTE: This can re-use the same ID as the initial IPC::Channel, as the + // initial IPC::Channel will not be used by the fork server. + static constexpr int kClientPipeFd = 3; + + ForkServer(); + ~ForkServer(){}; + + void InitProcess(int* aArgc, char*** aArgv); + bool HandleMessages(); + + // Called when a message is received. + void OnMessageReceived(UniquePtr<IPC::Message> message); + + static bool RunForkServer(int* aArgc, char*** aArgv); + + private: + UniquePtr<MiniTransceiver> mTcver; + UniquePtr<base::AppProcessBuilder> mAppProcBuilder; +}; + +enum { + Msg_ForkNewSubprocess__ID = 0x7f0, // a random picked number + Reply_ForkNewSubprocess__ID, +}; + +} // namespace ipc +} // namespace mozilla + +#endif // __FORKSERVER_H_ diff --git a/ipc/glue/ForkServiceChild.cpp b/ipc/glue/ForkServiceChild.cpp new file mode 100644 index 0000000000..65560dfaea --- /dev/null +++ b/ipc/glue/ForkServiceChild.cpp @@ -0,0 +1,193 @@ +/* -*- 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 "ForkServiceChild.h" +#include "ForkServer.h" +#include "mozilla/ipc/IPDLParamTraits.h" +#include "mozilla/Logging.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/ProtocolMessageUtils.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/Services.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "nsIObserverService.h" + +#include <unistd.h> +#include <fcntl.h> + +namespace mozilla { +namespace ipc { + +extern LazyLogModule gForkServiceLog; + +mozilla::UniquePtr<ForkServiceChild> ForkServiceChild::sForkServiceChild; + +static bool ConfigurePipeFd(int aFd) { + int flags = fcntl(aFd, F_GETFD, 0); + return flags != -1 && fcntl(aFd, F_SETFD, flags | FD_CLOEXEC) != -1; +} + +void ForkServiceChild::StartForkServer() { + // Create the socket to use for communication, and mark both ends as + // FD_CLOEXEC. + int fds[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) { + MOZ_LOG(gForkServiceLog, LogLevel::Error, + ("failed to create fork server socket")); + return; + } + UniqueFileHandle server(fds[0]); + UniqueFileHandle client(fds[1]); + + if (!ConfigurePipeFd(server.get()) || !ConfigurePipeFd(client.get())) { + MOZ_LOG(gForkServiceLog, LogLevel::Error, + ("failed to configure fork server socket")); + return; + } + + GeckoChildProcessHost* subprocess = + new GeckoChildProcessHost(GeckoProcessType_ForkServer, false); + subprocess->AddFdToRemap(client.get(), ForkServer::kClientPipeFd); + if (!subprocess->LaunchAndWaitForProcessHandle(std::vector<std::string>{})) { + MOZ_LOG(gForkServiceLog, LogLevel::Error, ("failed to launch fork server")); + return; + } + + sForkServiceChild = + mozilla::MakeUnique<ForkServiceChild>(server.release(), subprocess); +} + +void ForkServiceChild::StopForkServer() { sForkServiceChild = nullptr; } + +ForkServiceChild::ForkServiceChild(int aFd, GeckoChildProcessHost* aProcess) + : mFailed(false), mProcess(aProcess) { + mTcver = MakeUnique<MiniTransceiver>(aFd); +} + +ForkServiceChild::~ForkServiceChild() { + mProcess->Destroy(); + close(mTcver->GetFD()); +} + +Result<Ok, LaunchError> ForkServiceChild::SendForkNewSubprocess( + const Args& aArgs, pid_t* aPid) { + mRecvPid = -1; + IPC::Message msg(MSG_ROUTING_CONTROL, Msg_ForkNewSubprocess__ID); + + IPC::MessageWriter writer(msg); +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + WriteIPDLParam(&writer, nullptr, aArgs.mForkFlags); + WriteIPDLParam(&writer, nullptr, aArgs.mChroot); +#endif + WriteIPDLParam(&writer, nullptr, aArgs.mArgv); + WriteIPDLParam(&writer, nullptr, aArgs.mEnv); + WriteIPDLParam(&writer, nullptr, aArgs.mFdsRemap); + if (!mTcver->Send(msg)) { + MOZ_LOG(gForkServiceLog, LogLevel::Verbose, + ("the pipe to the fork server is closed or having errors")); + OnError(); + return Err(LaunchError("FSC::SFNS::Send")); + } + + UniquePtr<IPC::Message> reply; + if (!mTcver->Recv(reply)) { + MOZ_LOG(gForkServiceLog, LogLevel::Verbose, + ("the pipe to the fork server is closed or having errors")); + OnError(); + return Err(LaunchError("FSC::SFNS::Recv")); + } + OnMessageReceived(std::move(reply)); + + MOZ_ASSERT(mRecvPid != -1); + *aPid = mRecvPid; + return Ok(); +} + +void ForkServiceChild::OnMessageReceived(UniquePtr<IPC::Message> message) { + if (message->type() != Reply_ForkNewSubprocess__ID) { + MOZ_LOG(gForkServiceLog, LogLevel::Verbose, + ("unknown reply type %d", message->type())); + return; + } + IPC::MessageReader reader(*message); + + if (!ReadIPDLParam(&reader, nullptr, &mRecvPid)) { + MOZ_CRASH("Error deserializing 'pid_t'"); + } + reader.EndRead(); +} + +void ForkServiceChild::OnError() { + mFailed = true; + ForkServerLauncher::RestartForkServer(); +} + +NS_IMPL_ISUPPORTS(ForkServerLauncher, nsIObserver) + +bool ForkServerLauncher::mHaveStartedClient = false; +StaticRefPtr<ForkServerLauncher> ForkServerLauncher::mSingleton; + +ForkServerLauncher::ForkServerLauncher() {} + +ForkServerLauncher::~ForkServerLauncher() {} + +already_AddRefed<ForkServerLauncher> ForkServerLauncher::Create() { + if (mSingleton == nullptr) { + mSingleton = new ForkServerLauncher(); + } + RefPtr<ForkServerLauncher> launcher = mSingleton; + return launcher.forget(); +} + +NS_IMETHODIMP +ForkServerLauncher::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (strcmp(aTopic, NS_XPCOM_STARTUP_CATEGORY) == 0) { + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + MOZ_ASSERT(obsSvc != nullptr); + // preferences are not available until final-ui-startup + obsSvc->AddObserver(this, "final-ui-startup", false); + } else if (!mHaveStartedClient && strcmp(aTopic, "final-ui-startup") == 0) { + if (StaticPrefs::dom_ipc_forkserver_enable_AtStartup()) { + mHaveStartedClient = true; + ForkServiceChild::StartForkServer(); + + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + MOZ_ASSERT(obsSvc != nullptr); + obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + } else { + mSingleton = nullptr; + } + } + + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + if (mHaveStartedClient) { + mHaveStartedClient = false; + ForkServiceChild::StopForkServer(); + } + + // To make leak checker happy! + mSingleton = nullptr; + } + return NS_OK; +} + +void ForkServerLauncher::RestartForkServer() { + // Restart fork server + NS_SUCCEEDED(NS_DispatchToMainThreadQueue( + NS_NewRunnableFunction("OnForkServerError", + [] { + if (mSingleton) { + ForkServiceChild::StopForkServer(); + ForkServiceChild::StartForkServer(); + } + }), + EventQueuePriority::Idle)); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ForkServiceChild.h b/ipc/glue/ForkServiceChild.h new file mode 100644 index 0000000000..73d090d556 --- /dev/null +++ b/ipc/glue/ForkServiceChild.h @@ -0,0 +1,113 @@ +/* -*- 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/. */ +#ifndef __FORKSERVICE_CHILD_H_ +#define __FORKSERVICE_CHILD_H_ + +#include "base/process_util.h" +#include "nsIObserver.h" +#include "nsString.h" +#include "mozilla/ipc/MiniTransceiver.h" +#include "mozilla/ipc/LaunchError.h" +#include "mozilla/Result.h" + +#include <sys/types.h> +#include <poll.h> + +namespace mozilla { +namespace ipc { + +class GeckoChildProcessHost; + +/** + * This is the interface to the fork server. + * + * When the chrome process calls |ForkServiceChild| to create a new + * process, this class send a message to the fork server through a + * pipe and get the PID of the new process from the reply. + */ +class ForkServiceChild { + public: + ForkServiceChild(int aFd, GeckoChildProcessHost* aProcess); + virtual ~ForkServiceChild(); + + struct Args { +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + int mForkFlags = 0; + bool mChroot = false; +#endif + nsTArray<nsCString> mArgv; + nsTArray<EnvVar> mEnv; + nsTArray<FdMapping> mFdsRemap; + }; + + /** + * Ask the fork server to create a new process with given parameters. + * + * The fork server uses |base::LaunchApp()| to create a new + * content process with the following parameters. + * + * \param aArgv assigns |argv| of the content process. + * \param aEnvMap sets |LaunchOptions::env_map|. + * \param aFdsRemap sets |LaunchOptions::fd_to_remap|. + * \param aPid returns the PID of the content process created. + * \return true if success. + */ + Result<Ok, LaunchError> SendForkNewSubprocess(const Args& aArgs, pid_t* aPid); + + /** + * Create a fork server process and the singleton of this class. + * + * This function uses |GeckoChildProcessHost| to launch the fork + * server, getting the fd of a pipe/socket to the fork server from + * it's |IPC::Channel|. + */ + static void StartForkServer(); + static void StopForkServer(); + /** + * Return the singleton. + */ + static ForkServiceChild* Get() { + auto child = sForkServiceChild.get(); + return child == nullptr || child->mFailed ? nullptr : child; + } + + private: + // Called when a message is received. + void OnMessageReceived(UniquePtr<IPC::Message> message); + void OnError(); + + UniquePtr<MiniTransceiver> mTcver; + static UniquePtr<ForkServiceChild> sForkServiceChild; + pid_t mRecvPid; + bool mFailed; // The forkserver has crashed or disconnected. + GeckoChildProcessHost* mProcess; +}; + +/** + * Start a fork server at |xpcom-startup| from the chrome process. + */ +class ForkServerLauncher : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + ForkServerLauncher(); + static already_AddRefed<ForkServerLauncher> Create(); + + private: + friend class ForkServiceChild; + virtual ~ForkServerLauncher(); + + static void RestartForkServer(); + + static bool mHaveStartedClient; + static StaticRefPtr<ForkServerLauncher> mSingleton; +}; + +} // namespace ipc +} // namespace mozilla + +#endif /* __FORKSERVICE_CHILD_H_ */ diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp new file mode 100644 index 0000000000..bde3a9a389 --- /dev/null +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -0,0 +1,1801 @@ +/* -*- 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 "GeckoChildProcessHost.h" + +#include "base/command_line.h" +#include "base/process.h" +#include "base/process_util.h" +#include "base/string_util.h" +#include "base/task.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/process_watcher.h" +#ifdef MOZ_WIDGET_COCOA +# include <bsm/libbsm.h> +# include <mach/mach_traps.h> +# include <servers/bootstrap.h> +# include "SharedMemoryBasic.h" +# include "base/rand_util.h" +# include "chrome/common/mach_ipc_mac.h" +# include "mozilla/StaticPrefs_media.h" +# include "nsILocalFileMac.h" +#endif + +#include "GeckoProfiler.h" +#include "MainThreadUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/Sprintf.h" +#include "nsXPCOMPrivate.h" +#include "prenv.h" +#include "prerror.h" + +#if defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +# include "nsAppDirectoryServiceDefs.h" +#endif + +#include <sys/stat.h> + +#include "ProtocolUtils.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Logging.h" +#include "mozilla/Maybe.h" +#include "mozilla/GeckoArgs.h" +#include "mozilla/Omnijar.h" +#include "mozilla/RDDProcessHost.h" +#include "mozilla/Services.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/ipc/EnvironmentMap.h" +#include "mozilla/ipc/NodeController.h" +#include "mozilla/net/SocketProcessHost.h" +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsExceptionHandler.h" +#include "nsIFile.h" +#include "nsIObserverService.h" +#include "nsPrintfCString.h" + +#ifdef XP_WIN +# include <stdlib.h> + +# include "nsIWinTaskbar.h" +# define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1" + +# if defined(MOZ_SANDBOX) +# include "WinUtils.h" +# include "mozilla/Preferences.h" +# include "mozilla/sandboxing/sandboxLogging.h" +# if defined(_ARM64_) +# include "mozilla/remoteSandboxBroker.h" +# endif +# endif + +# include "mozilla/NativeNt.h" +# include "mozilla/CacheNtDllThunk.h" +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxLaunch.h" +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "GMPProcessParent.h" +# include "nsMacUtilsImpl.h" +#endif + +#include "mozilla/ipc/UtilityProcessHost.h" +#include "mozilla/ipc/UtilityProcessSandboxing.h" + +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsNativeCharsetUtils.h" +#include "nsTArray.h" +#include "nscore.h" // for NS_FREE_PERMANENT_DATA +#include "nsIThread.h" + +using mozilla::MonitorAutoLock; +using mozilla::Preferences; +using mozilla::StaticMutexAutoLock; + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidBridge.h" +# include "mozilla/java/GeckoProcessManagerWrappers.h" +# include "mozilla/java/GeckoProcessTypeWrappers.h" +# include "mozilla/java/GeckoResultWrappers.h" +# include "mozilla/jni/Refs.h" +# include "mozilla/jni/Utils.h" +#endif + +#ifdef MOZ_ENABLE_FORKSERVER +# include "mozilla/ipc/ForkServiceChild.h" +#endif + +static bool ShouldHaveDirectoryService() { + return GeckoProcessType_Default == XRE_GetProcessType(); +} + +namespace mozilla { +namespace ipc { + +struct LaunchResults { + base::ProcessHandle mHandle = 0; +#ifdef XP_MACOSX + task_t mChildTask = MACH_PORT_NULL; +#endif +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + RefPtr<AbstractSandboxBroker> mSandboxBroker; +#endif +}; +typedef mozilla::MozPromise<LaunchResults, LaunchError, true> + ProcessLaunchPromise; + +static Atomic<int32_t> gChildCounter; + +static inline nsISerialEventTarget* IOThread() { + return XRE_GetIOMessageLoop()->SerialEventTarget(); +} + +class BaseProcessLauncher { + public: + BaseProcessLauncher(GeckoChildProcessHost* aHost, + std::vector<std::string>&& aExtraOpts) + : mProcessType(aHost->mProcessType), + mLaunchOptions(std::move(aHost->mLaunchOptions)), + mExtraOpts(std::move(aExtraOpts)), +#ifdef XP_WIN + mGroupId(aHost->mGroupId), +#endif +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + mAllowedFilesRead(aHost->mAllowedFilesRead), + mSandboxLevel(aHost->mSandboxLevel), + mSandbox(aHost->mSandbox), + mIsFileContent(aHost->mIsFileContent), + mEnableSandboxLogging(aHost->mEnableSandboxLogging), +#endif +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + mDisableOSActivityMode(aHost->mDisableOSActivityMode), +#endif + mTmpDirName(aHost->mTmpDirName), + mChildId(++gChildCounter) { + SprintfLiteral(mPidString, "%" PRIPID, base::GetCurrentProcId()); + aHost->mInitialChannelId.ToProvidedString(mInitialChannelIdString); + + // Compute the serial event target we'll use for launching. + nsCOMPtr<nsIEventTarget> threadOrPool = GetIPCLauncher(); + mLaunchThread = + TaskQueue::Create(threadOrPool.forget(), "BaseProcessLauncher"); + + if (ShouldHaveDirectoryService()) { + // "Current process directory" means the app dir, not the current + // working dir or similar. + mozilla::Unused + << nsDirectoryService::gService->GetCurrentProcessDirectory( + getter_AddRefs(mAppDir)); + } + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BaseProcessLauncher); + +#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH + void SetLaunchArchitecture(uint32_t aLaunchArch) { + mLaunchArch = aLaunchArch; + } +#endif + + RefPtr<ProcessLaunchPromise> Launch(GeckoChildProcessHost*); + + protected: + virtual ~BaseProcessLauncher() = default; + + RefPtr<ProcessLaunchPromise> PerformAsyncLaunch(); + RefPtr<ProcessLaunchPromise> FinishLaunch(); + + // Overrideable hooks. If superclass behavior is invoked, it's always at the + // top of the override. + virtual Result<Ok, LaunchError> DoSetup(); + virtual RefPtr<ProcessHandlePromise> DoLaunch() = 0; + virtual Result<Ok, LaunchError> DoFinishLaunch(); + + void MapChildLogging(); + + static BinPathType GetPathToBinary(FilePath&, GeckoProcessType); + + void GetChildLogName(const char* origLogName, nsACString& buffer); + + const char* ChildProcessType() { + return XRE_GeckoProcessTypeToString(mProcessType); + } + + nsCOMPtr<nsISerialEventTarget> mLaunchThread; + GeckoProcessType mProcessType; + UniquePtr<base::LaunchOptions> mLaunchOptions; +#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH + uint32_t mLaunchArch = base::PROCESS_ARCH_INVALID; +#endif + std::vector<std::string> mExtraOpts; +#ifdef XP_WIN + nsString mGroupId; +#endif +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + std::vector<std::wstring> mAllowedFilesRead; + int32_t mSandboxLevel; + SandboxingKind mSandbox; + bool mIsFileContent; + bool mEnableSandboxLogging; +#endif +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + // Controls whether or not the process will be launched with + // environment variable OS_ACTIVITY_MODE set to "disabled". + bool mDisableOSActivityMode; +#endif + nsCString mTmpDirName; + LaunchResults mResults = LaunchResults(); + int32_t mChildId; + TimeStamp mStartTimeStamp = TimeStamp::Now(); + char mPidString[32]; + char mInitialChannelIdString[NSID_LENGTH]; + + // Set during launch. + IPC::Channel::ChannelHandle mClientChannelHandle; + nsCOMPtr<nsIFile> mAppDir; +}; + +#ifdef XP_WIN +class WindowsProcessLauncher : public BaseProcessLauncher { + public: + WindowsProcessLauncher(GeckoChildProcessHost* aHost, + std::vector<std::string>&& aExtraOpts) + : BaseProcessLauncher(aHost, std::move(aExtraOpts)), + mCachedNtdllThunk(GetCachedNtDllThunk()) {} + + protected: + virtual Result<Ok, LaunchError> DoSetup() override; + virtual RefPtr<ProcessHandlePromise> DoLaunch() override; + virtual Result<Ok, LaunchError> DoFinishLaunch() override; + + mozilla::Maybe<CommandLine> mCmdLine; + bool mUseSandbox = false; + + const Buffer<IMAGE_THUNK_DATA>* mCachedNtdllThunk; +}; +typedef WindowsProcessLauncher ProcessLauncher; +#endif // XP_WIN + +#ifdef XP_UNIX +class PosixProcessLauncher : public BaseProcessLauncher { + public: + PosixProcessLauncher(GeckoChildProcessHost* aHost, + std::vector<std::string>&& aExtraOpts) + : BaseProcessLauncher(aHost, std::move(aExtraOpts)), + mProfileDir(aHost->mProfileDir), + mChannelDstFd(IPC::Channel::GetClientChannelHandle()) {} + + protected: + virtual Result<Ok, LaunchError> DoSetup() override; + virtual RefPtr<ProcessHandlePromise> DoLaunch() override; + + nsCOMPtr<nsIFile> mProfileDir; + + std::vector<std::string> mChildArgv; + int mChannelDstFd; +}; + +# if defined(XP_MACOSX) +class MacProcessLauncher : public PosixProcessLauncher { + public: + MacProcessLauncher(GeckoChildProcessHost* aHost, + std::vector<std::string>&& aExtraOpts) + : PosixProcessLauncher(aHost, std::move(aExtraOpts)), + // Put a random number into the channel name, so that + // a compromised renderer can't pretend being the child + // that's forked off. + mMachConnectionName( + StringPrintf("org.mozilla.machname.%d", + base::RandInt(0, std::numeric_limits<int>::max()))) { + MOZ_ASSERT(mMachConnectionName.size() < BOOTSTRAP_MAX_NAME_LEN); + } + + protected: + virtual Result<Ok, LaunchError> DoFinishLaunch() override; + + std::string mMachConnectionName; + // We add a mach port to the command line so the child can communicate its + // 'task_t' back to the parent. + mozilla::UniqueMachReceiveRight mParentRecvPort; + + friend class PosixProcessLauncher; +}; +typedef MacProcessLauncher ProcessLauncher; +# elif defined(MOZ_WIDGET_ANDROID) +class AndroidProcessLauncher : public PosixProcessLauncher { + public: + AndroidProcessLauncher(GeckoChildProcessHost* aHost, + std::vector<std::string>&& aExtraOpts) + : PosixProcessLauncher(aHost, std::move(aExtraOpts)) {} + + protected: + virtual RefPtr<ProcessHandlePromise> DoLaunch() override; + RefPtr<ProcessHandlePromise> LaunchAndroidService( + const GeckoProcessType aType, const std::vector<std::string>& argv, + const base::file_handle_mapping_vector& fds_to_remap); +}; +typedef AndroidProcessLauncher ProcessLauncher; +// NB: Technically Android is linux (i.e. XP_LINUX is defined), but we want +// orthogonal IPC machinery there. Conversely, there are tier-3 non-Linux +// platforms (BSD and Solaris) where we want the "linux" IPC machinery. So +// we use MOZ_WIDGET_* to choose the platform backend. +# elif defined(MOZ_WIDGET_GTK) +class LinuxProcessLauncher : public PosixProcessLauncher { + public: + LinuxProcessLauncher(GeckoChildProcessHost* aHost, + std::vector<std::string>&& aExtraOpts) + : PosixProcessLauncher(aHost, std::move(aExtraOpts)) {} + + protected: + virtual Result<Ok, LaunchError> DoSetup() override; +}; +typedef LinuxProcessLauncher ProcessLauncher; +# elif +# error "Unknown platform" +# endif +#endif // XP_UNIX + +using base::ProcessHandle; +using mozilla::ipc::BaseProcessLauncher; +using mozilla::ipc::ProcessLauncher; + +mozilla::StaticAutoPtr<mozilla::LinkedList<GeckoChildProcessHost>> + GeckoChildProcessHost::sGeckoChildProcessHosts; + +mozilla::StaticMutex GeckoChildProcessHost::sMutex; + +GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType, + bool aIsFileContent) + : mProcessType(aProcessType), + mIsFileContent(aIsFileContent), + mMonitor("mozilla.ipc.GeckoChildProcessHost.mMonitor"), + mLaunchOptions(MakeUnique<base::LaunchOptions>()), + mInitialChannelId(nsID::GenerateUUID()), + mProcessState(CREATING_CHANNEL), +#ifdef XP_WIN + mGroupId(u"-"), +#endif +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + mEnableSandboxLogging(false), + mSandboxLevel(0), +#endif + mHandleLock("mozilla.ipc.GeckoChildProcessHost.mHandleLock"), + mChildProcessHandle(0), +#if defined(MOZ_WIDGET_COCOA) + mChildTask(MACH_PORT_NULL), +#endif +#if defined(MOZ_SANDBOX) && defined(XP_MACOSX) + mDisableOSActivityMode(false), +#endif + mDestroying(false) { + MOZ_COUNT_CTOR(GeckoChildProcessHost); + StaticMutexAutoLock lock(sMutex); + if (!sGeckoChildProcessHosts) { + sGeckoChildProcessHosts = new mozilla::LinkedList<GeckoChildProcessHost>(); + } + sGeckoChildProcessHosts->insertBack(this); +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) + if (aProcessType == GeckoProcessType_Content) { +# if defined(MOZ_CONTENT_TEMP_DIR) + // The content process needs the content temp dir: + nsCOMPtr<nsIFile> contentTempDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR, + getter_AddRefs(contentTempDir)); + if (NS_SUCCEEDED(rv)) { + contentTempDir->GetNativePath(mTmpDirName); + } +# endif + } else if (aProcessType == GeckoProcessType_RDD) { + // The RDD process makes limited use of EGL. If Mesa's shader + // cache is enabled and the directory isn't explicitly set, then + // it will try to getpwuid() the user which can cause problems + // with sandboxing. Because we shouldn't need shader caching in + // this process, we just disable the cache to prevent that. + mLaunchOptions->env_map["MESA_GLSL_CACHE_DISABLE"] = "true"; + mLaunchOptions->env_map["MESA_SHADER_CACHE_DISABLE"] = "true"; + // In case the nvidia driver is also loaded: + mLaunchOptions->env_map["__GL_SHADER_DISK_CACHE"] = "0"; + } +#endif +#if defined(MOZ_ENABLE_FORKSERVER) + if (aProcessType != GeckoProcessType_ForkServer && ForkServiceChild::Get()) { + mLaunchOptions->use_forkserver = true; + } +#endif +} + +GeckoChildProcessHost::~GeckoChildProcessHost() + +{ + AssertIOThread(); + MOZ_RELEASE_ASSERT(mDestroying); + + MOZ_COUNT_DTOR(GeckoChildProcessHost); + + { + mozilla::AutoWriteLock hLock(mHandleLock); +#if defined(MOZ_WIDGET_COCOA) + if (mChildTask != MACH_PORT_NULL) { + mach_port_deallocate(mach_task_self(), mChildTask); + } +#endif + + if (mChildProcessHandle != 0) { + ProcessWatcher::EnsureProcessTerminated( + mChildProcessHandle +#ifdef NS_FREE_PERMANENT_DATA + // If we're doing leak logging, shutdown can be slow. + , + false // don't "force" +#endif + ); + mChildProcessHandle = 0; + } + } + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + if (mSandboxBroker) { + mSandboxBroker->Shutdown(); + mSandboxBroker = nullptr; + } +#endif +} + +base::ProcessHandle GeckoChildProcessHost::GetChildProcessHandle() { + mozilla::AutoReadLock handleLock(mHandleLock); + return mChildProcessHandle; +} + +base::ProcessId GeckoChildProcessHost::GetChildProcessId() { + mozilla::AutoReadLock handleLock(mHandleLock); + if (!mChildProcessHandle) { + return 0; + } + return base::GetProcId(mChildProcessHandle); +} + +#ifdef XP_MACOSX +task_t GeckoChildProcessHost::GetChildTask() { + mozilla::AutoReadLock handleLock(mHandleLock); + return mChildTask; +} +#endif + +void GeckoChildProcessHost::RemoveFromProcessList() { + StaticMutexAutoLock lock(sMutex); + if (!sGeckoChildProcessHosts) { + return; + } + LinkedListElement<GeckoChildProcessHost>::removeFrom( + *sGeckoChildProcessHosts); +} + +void GeckoChildProcessHost::Destroy() { + MOZ_RELEASE_ASSERT(!mDestroying); + // We can remove from the list before it's really destroyed + RemoveFromProcessList(); + RefPtr<ProcessHandlePromise> whenReady = mHandlePromise; + + if (!whenReady) { + // AsyncLaunch not called yet, so dispatch immediately. + whenReady = ProcessHandlePromise::CreateAndReject( + LaunchError("DestroyEarly"), __func__); + } + + using Value = ProcessHandlePromise::ResolveOrRejectValue; + mDestroying = true; + whenReady->Then(XRE_GetIOMessageLoop()->SerialEventTarget(), __func__, + [this](const Value&) { delete this; }); +} + +// static +mozilla::BinPathType BaseProcessLauncher::GetPathToBinary( + FilePath& exePath, GeckoProcessType processType) { + BinPathType pathType = XRE_GetChildProcBinPathType(processType); + + if (pathType == BinPathType::Self) { +#if defined(XP_WIN) + wchar_t exePathBuf[MAXPATHLEN]; + if (!::GetModuleFileNameW(nullptr, exePathBuf, MAXPATHLEN)) { + MOZ_CRASH("GetModuleFileNameW failed (FIXME)"); + } + exePath = FilePath::FromWStringHack(exePathBuf); +#else + exePath = FilePath(CommandLine::ForCurrentProcess()->argv()[0]); +#endif + return pathType; + } + +#ifdef MOZ_WIDGET_COCOA + // The GMP child process runs via the Media Plugin Helper executable + // which is a clone of plugin-container allowing for GMP-specific + // codesigning entitlements. + nsCString bundleName; + std::string executableLeafName; + if (processType == GeckoProcessType_GMPlugin && + mozilla::StaticPrefs::media_plugin_helper_process_enabled()) { + bundleName = MOZ_EME_PROCESS_BUNDLENAME; + executableLeafName = MOZ_EME_PROCESS_NAME_BRANDED; + } else { + bundleName = MOZ_CHILD_PROCESS_BUNDLENAME; + executableLeafName = MOZ_CHILD_PROCESS_NAME; + } +#endif + + if (ShouldHaveDirectoryService()) { + MOZ_ASSERT(gGREBinPath); +#ifdef XP_WIN + exePath = FilePath(char16ptr_t(gGREBinPath)); +#elif MOZ_WIDGET_COCOA + nsCOMPtr<nsIFile> childProcPath; + NS_NewLocalFile(nsDependentString(gGREBinPath), false, + getter_AddRefs(childProcPath)); + + // We need to use an App Bundle on OS X so that we can hide + // the dock icon. See Bug 557225. + childProcPath->AppendNative(bundleName); + childProcPath->AppendNative("Contents"_ns); + childProcPath->AppendNative("MacOS"_ns); + nsCString tempCPath; + childProcPath->GetNativePath(tempCPath); + exePath = FilePath(tempCPath.get()); +#else + nsCString path; + NS_CopyUnicodeToNative(nsDependentString(gGREBinPath), path); + exePath = FilePath(path.get()); +#endif + } + + if (exePath.empty()) { +#ifdef XP_WIN + exePath = + FilePath::FromWStringHack(CommandLine::ForCurrentProcess()->program()); +#else + exePath = FilePath(CommandLine::ForCurrentProcess()->argv()[0]); +#endif + exePath = exePath.DirName(); + } + +#ifdef MOZ_WIDGET_COCOA + exePath = exePath.Append(executableLeafName); +#else + exePath = exePath.AppendASCII(MOZ_CHILD_PROCESS_NAME); +#endif + + return pathType; +} + +#ifdef MOZ_WIDGET_COCOA +class AutoCFTypeObject { + public: + explicit AutoCFTypeObject(CFTypeRef object) { mObject = object; } + ~AutoCFTypeObject() { ::CFRelease(mObject); } + + private: + CFTypeRef mObject; +}; +#endif + +// We start the unique IDs at 1 so that 0 can be used to mean that +// a component has no unique ID assigned to it. +uint32_t GeckoChildProcessHost::sNextUniqueID = 1; + +/* static */ +uint32_t GeckoChildProcessHost::GetUniqueID() { return sNextUniqueID++; } + +/* static */ +void GeckoChildProcessHost::SetEnv(const char* aKey, const char* aValue) { + MOZ_ASSERT(mLaunchOptions); + mLaunchOptions->env_map[ENVIRONMENT_STRING(aKey)] = + ENVIRONMENT_STRING(aValue); +} + +void GeckoChildProcessHost::PrepareLaunch() { + if (CrashReporter::GetEnabled()) { + CrashReporter::OOPInit(); + } + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + SandboxLaunch::Configure(mProcessType, mSandbox, mLaunchOptions.get()); +#endif + +#ifdef XP_WIN + +# if defined(MOZ_SANDBOX) + // We need to get the pref here as the process is launched off main thread. + if (mProcessType == GeckoProcessType_Content) { + // Win32k Lockdown state must be initialized on the main thread. + // This is our last chance to do it before it is read on the IPC Launch + // thread + GetWin32kLockdownState(); + mSandboxLevel = GetEffectiveContentSandboxLevel(); + mEnableSandboxLogging = + Preferences::GetBool("security.sandbox.logging.enabled"); + + // We currently have to whitelist certain paths for tests to work in some + // development configurations. + nsAutoString readPaths; + nsresult rv = Preferences::GetString( + "security.sandbox.content.read_path_whitelist", readPaths); + if (NS_SUCCEEDED(rv)) { + for (const nsAString& readPath : readPaths.Split(',')) { + nsString trimmedPath(readPath); + trimmedPath.Trim(" ", true, true); + std::wstring resolvedPath(trimmedPath.Data()); + // Check if path ends with '\' as this indicates we want to give read + // access to a directory and so it needs a wildcard. + if (resolvedPath.back() == L'\\') { + resolvedPath.append(L"*"); + } + mAllowedFilesRead.push_back(resolvedPath); + } + } + } +# endif + +# if defined(MOZ_SANDBOX) + // For other process types we can't rely on them being launched on main + // thread and they may not have access to prefs in the child process, so allow + // them to turn on logging via an environment variable. + mEnableSandboxLogging = + mEnableSandboxLogging || !!PR_GetEnv("MOZ_SANDBOX_LOGGING"); + +# endif +#elif defined(XP_MACOSX) +# if defined(MOZ_SANDBOX) + if (ShouldHaveDirectoryService() && + mProcessType != GeckoProcessType_GMPlugin) { + mozilla::Unused << NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(mProfileDir)); + } +# endif +#endif +} + +#ifdef XP_WIN +void GeckoChildProcessHost::InitWindowsGroupID() { + // On Win7+, pass the application user model to the child, so it can + // register with it. This insures windows created by the container + // properly group with the parent app on the Win7 taskbar. + nsCOMPtr<nsIWinTaskbar> taskbarInfo = do_GetService(NS_TASKBAR_CONTRACTID); + if (taskbarInfo) { + bool isSupported = false; + taskbarInfo->GetAvailable(&isSupported); + nsAutoString appId; + if (isSupported && NS_SUCCEEDED(taskbarInfo->GetDefaultGroupId(appId))) { + MOZ_ASSERT(mGroupId.EqualsLiteral("-")); + mGroupId.Assign(appId); + } + } +} +#endif + +bool GeckoChildProcessHost::SyncLaunch(std::vector<std::string> aExtraOpts, + int aTimeoutMs) { + if (!AsyncLaunch(std::move(aExtraOpts))) { + return false; + } + return WaitUntilConnected(aTimeoutMs); +} + +// Note: for most process types, we currently call AsyncLaunch, and therefore +// the *ProcessLauncher constructor, on the main thread, while the +// ProcessLauncher methods to actually execute the launch are called on the IO +// or IPC launcher thread. GMP processes are an exception - the GMP code +// invokes GeckoChildProcessHost from non-main-threads, and therefore we cannot +// rely on having access to mainthread-only services (like the directory +// service) from this code if we're launching that type of process. +bool GeckoChildProcessHost::AsyncLaunch(std::vector<std::string> aExtraOpts) { + PrepareLaunch(); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + if (IsMacSandboxLaunchEnabled() && !AppendMacSandboxParams(aExtraOpts)) { + return false; + } +#endif + + RefPtr<BaseProcessLauncher> launcher = + new ProcessLauncher(this, std::move(aExtraOpts)); +#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH + launcher->SetLaunchArchitecture(mLaunchArch); +#endif + + // Note: Destroy() waits on mHandlePromise to delete |this|. As such, we want + // to be sure that all of our post-launch processing on |this| happens before + // mHandlePromise notifies. + MOZ_ASSERT(mHandlePromise == nullptr); + mHandlePromise = + mozilla::InvokeAsync<GeckoChildProcessHost*>( + IOThread(), launcher.get(), __func__, &BaseProcessLauncher::Launch, + this) + ->Then( + IOThread(), __func__, + [this](LaunchResults&& aResults) { + { + { + mozilla::AutoWriteLock handleLock(mHandleLock); + if (!OpenPrivilegedHandle(base::GetProcId(aResults.mHandle)) +#ifdef XP_WIN + // If we failed in opening the process handle, try + // harder by duplicating one. + && !::DuplicateHandle( + ::GetCurrentProcess(), aResults.mHandle, + ::GetCurrentProcess(), &mChildProcessHandle, + PROCESS_DUP_HANDLE | PROCESS_TERMINATE | + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | + SYNCHRONIZE, + FALSE, 0) +#endif // XP_WIN + ) { + MOZ_CRASH("cannot open handle to child process"); + } + // The original handle is no longer needed; it must + // be closed to prevent a resource leak. + base::CloseProcessHandle(aResults.mHandle); + // FIXME (bug 1720523): define a cross-platform + // "safe" invalid value to use in places like this. + aResults.mHandle = 0; + +#ifdef XP_MACOSX + this->mChildTask = aResults.mChildTask; +#endif + + if (mNodeChannel) { + mNodeChannel->SetOtherPid( + base::GetProcId(this->mChildProcessHandle)); +#ifdef XP_MACOSX + mNodeChannel->SetMachTaskPort(this->mChildTask); +#endif + } + } +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + this->mSandboxBroker = std::move(aResults.mSandboxBroker); +#endif + + MonitorAutoLock lock(mMonitor); + // The OnChannel{Connected,Error} may have already advanced + // the state. + if (mProcessState < PROCESS_CREATED) { + mProcessState = PROCESS_CREATED; + } + lock.Notify(); + } + return ProcessHandlePromise::CreateAndResolve( + GetChildProcessHandle(), __func__); + }, + [this](const LaunchError aError) { + // WaitUntilConnected might be waiting for us to signal. + // If something failed let's set the error state and notify. + CHROMIUM_LOG(ERROR) + << "Failed to launch " + << XRE_GeckoProcessTypeToString(mProcessType) + << " subprocess"; + Telemetry::Accumulate( + Telemetry::SUBPROCESS_LAUNCH_FAILURE, + nsDependentCString( + XRE_GeckoProcessTypeToString(mProcessType))); + nsCString telemetryKey = nsPrintfCString( +#if defined(XP_WIN) + "%s,0x%lx,%s", +#else + "%s,%d,%s", +#endif + aError.FunctionName(), aError.ErrorCode(), + XRE_GeckoProcessTypeToString(mProcessType)); + // Max telemetry key is 72 chars + // https://searchfox.org/mozilla-central/rev/c244b16815d1fc827d141472b9faac5610f250e7/toolkit/components/telemetry/core/TelemetryScalar.cpp#105 + if (telemetryKey.Length() > 72) { + NS_WARNING(nsPrintfCString("Truncating telemetry key: %s", + telemetryKey.get()) + .get()); + telemetryKey.Truncate(72); + } + Telemetry::ScalarAdd( + Telemetry::ScalarID:: + DOM_PARENTPROCESS_PROCESS_LAUNCH_ERRORS, + NS_ConvertUTF8toUTF16(telemetryKey), 1); + { + MonitorAutoLock lock(mMonitor); + mProcessState = PROCESS_ERROR; + lock.Notify(); + } + return ProcessHandlePromise::CreateAndReject(aError, __func__); + }); + return true; +} + +bool GeckoChildProcessHost::WaitUntilConnected(int32_t aTimeoutMs) { + AUTO_PROFILER_LABEL("GeckoChildProcessHost::WaitUntilConnected", OTHER); + + // NB: this uses a different mechanism than the chromium parent + // class. + TimeDuration timeout = (aTimeoutMs > 0) + ? TimeDuration::FromMilliseconds(aTimeoutMs) + : TimeDuration::Forever(); + + MonitorAutoLock lock(mMonitor); + TimeStamp waitStart = TimeStamp::Now(); + TimeStamp current; + + // We'll receive several notifications, we need to exit when we + // have either successfully launched or have timed out. + while (mProcessState != PROCESS_CONNECTED) { + // If there was an error then return it, don't wait out the timeout. + if (mProcessState == PROCESS_ERROR) { + break; + } + + CVStatus status = lock.Wait(timeout); + if (status == CVStatus::Timeout) { + break; + } + + if (timeout != TimeDuration::Forever()) { + current = TimeStamp::Now(); + timeout -= current - waitStart; + waitStart = current; + } + } + + return mProcessState == PROCESS_CONNECTED; +} + +bool GeckoChildProcessHost::WaitForProcessHandle() { + MonitorAutoLock lock(mMonitor); + while (mProcessState < PROCESS_CREATED) { + lock.Wait(); + } + MOZ_ASSERT(mProcessState == PROCESS_ERROR || GetChildProcessHandle()); + + return mProcessState < PROCESS_ERROR; +} + +bool GeckoChildProcessHost::LaunchAndWaitForProcessHandle( + StringVector aExtraOpts) { + if (!AsyncLaunch(std::move(aExtraOpts))) { + return false; + } + return WaitForProcessHandle(); +} + +void GeckoChildProcessHost::InitializeChannel( + IPC::Channel::ChannelHandle&& aServerHandle) { + // Create the IPC channel which will be used for communication with this + // process. + mozilla::UniquePtr<IPC::Channel> channel = MakeUnique<IPC::Channel>( + std::move(aServerHandle), IPC::Channel::MODE_SERVER, + base::kInvalidProcessId); +#if defined(XP_WIN) + channel->StartAcceptingHandles(IPC::Channel::MODE_SERVER); +#elif defined(XP_DARWIN) + channel->StartAcceptingMachPorts(IPC::Channel::MODE_SERVER); +#endif + + mNodeController = NodeController::GetSingleton(); + std::tie(mInitialPort, mNodeChannel) = + mNodeController->InviteChildProcess(std::move(channel), this); + + MonitorAutoLock lock(mMonitor); + mProcessState = CHANNEL_INITIALIZED; + lock.Notify(); +} + +void GeckoChildProcessHost::SetAlreadyDead() { + mozilla::AutoWriteLock handleLock(mHandleLock); + if (mChildProcessHandle && + mChildProcessHandle != base::kInvalidProcessHandle) { + base::CloseProcessHandle(mChildProcessHandle); + } + + mChildProcessHandle = 0; +} + +void BaseProcessLauncher::GetChildLogName(const char* origLogName, + nsACString& buffer) { +#ifdef XP_WIN + // On Windows we must expand relative paths because sandboxing rules + // bound only to full paths. fopen fowards to NtCreateFile which checks + // the path against the sanboxing rules as passed to fopen (left relative). + char absPath[MAX_PATH + 2]; + if (_fullpath(absPath, origLogName, sizeof(absPath))) { + buffer.Append(absPath); + } else +#endif + { + buffer.Append(origLogName); + } + + // Remove .moz_log extension to avoid its duplication, it will be added + // automatically by the logging backend + static constexpr auto kMozLogExt = nsLiteralCString{MOZ_LOG_FILE_EXTENSION}; + if (StringEndsWith(buffer, kMozLogExt)) { + buffer.Truncate(buffer.Length() - kMozLogExt.Length()); + } + + // Append child-specific postfix to name + buffer.AppendLiteral(".child-"); + buffer.AppendInt(mChildId); +} + +// Windows needs a single dedicated thread for process launching, +// because of thread-safety restrictions/assertions in the sandbox +// code. +// +// Android also needs a single dedicated thread to simplify thread +// safety in java. +// +// Fork server needs a dedicated thread for accessing +// |ForkServiceChild|. +#if defined(XP_WIN) || defined(MOZ_WIDGET_ANDROID) || \ + defined(MOZ_ENABLE_FORKSERVER) + +static mozilla::StaticMutex gIPCLaunchThreadMutex; +static mozilla::StaticRefPtr<nsIThread> gIPCLaunchThread + MOZ_GUARDED_BY(gIPCLaunchThreadMutex); + +class IPCLaunchThreadObserver final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + protected: + virtual ~IPCLaunchThreadObserver() = default; +}; + +NS_IMPL_ISUPPORTS(IPCLaunchThreadObserver, nsIObserver, nsISupports) + +NS_IMETHODIMP +IPCLaunchThreadObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_RELEASE_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0); + StaticMutexAutoLock lock(gIPCLaunchThreadMutex); + + nsresult rv = NS_OK; + if (gIPCLaunchThread) { + rv = gIPCLaunchThread->Shutdown(); + gIPCLaunchThread = nullptr; + } + mozilla::Unused << NS_WARN_IF(NS_FAILED(rv)); + return rv; +} + +nsCOMPtr<nsIEventTarget> GetIPCLauncher() { + StaticMutexAutoLock lock(gIPCLaunchThreadMutex); + if (!gIPCLaunchThread) { + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_NewNamedThread("IPC Launch"_ns, getter_AddRefs(thread)); + if (!NS_WARN_IF(NS_FAILED(rv))) { + NS_DispatchToMainThread( + NS_NewRunnableFunction("GeckoChildProcessHost::GetIPCLauncher", [] { + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + nsCOMPtr<nsIObserver> obs = new IPCLaunchThreadObserver(); + obsService->AddObserver(obs, "xpcom-shutdown-threads", false); + })); + gIPCLaunchThread = thread.forget(); + } + } + + nsCOMPtr<nsIEventTarget> thread = gIPCLaunchThread.get(); + MOZ_DIAGNOSTIC_ASSERT(thread); + return thread; +} + +#else // defined(XP_WIN) || defined(MOZ_WIDGET_ANDROID) || + // defined(MOZ_ENABLE_FORKSERVER) + +// Other platforms use an on-demand thread pool. + +nsCOMPtr<nsIEventTarget> GetIPCLauncher() { + nsCOMPtr<nsIEventTarget> pool = + mozilla::SharedThreadPool::Get("IPC Launch"_ns); + MOZ_DIAGNOSTIC_ASSERT(pool); + return pool; +} + +#endif // XP_WIN || MOZ_WIDGET_ANDROID || MOZ_ENABLE_FORKSERVER + +void +#if defined(XP_WIN) +AddAppDirToCommandLine(CommandLine& aCmdLine, nsIFile* aAppDir) +#else +AddAppDirToCommandLine(std::vector<std::string>& aCmdLine, nsIFile* aAppDir, + nsIFile* aProfileDir) +#endif +{ + // Content processes need access to application resources, so pass + // the full application directory path to the child process. + if (aAppDir) { +#if defined(XP_WIN) + nsString path; + MOZ_ALWAYS_SUCCEEDS(aAppDir->GetPath(path)); + aCmdLine.AppendLooseValue(UTF8ToWide(geckoargs::sAppDir.Name())); + std::wstring wpath(path.get()); + aCmdLine.AppendLooseValue(wpath); +#else + nsAutoCString path; + MOZ_ALWAYS_SUCCEEDS(aAppDir->GetNativePath(path)); + geckoargs::sAppDir.Put(path.get(), aCmdLine); +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + // Full path to the profile dir + if (aProfileDir) { + // If the profile doesn't exist, normalization will + // fail. But we don't return an error here because some + // tests require startup with a missing profile dir. + // For users, almost universally, the profile will be in + // the home directory and normalization isn't required. + mozilla::Unused << aProfileDir->Normalize(); + nsAutoCString path; + MOZ_ALWAYS_SUCCEEDS(aProfileDir->GetNativePath(path)); + geckoargs::sProfile.Put(path.get(), aCmdLine); + } +#endif + } +} + +#if defined(XP_WIN) && (defined(MOZ_SANDBOX) || defined(_ARM64_)) +static bool Contains(const std::vector<std::string>& aExtraOpts, + const char* aValue) { + return std::any_of(aExtraOpts.begin(), aExtraOpts.end(), + [&](const std::string arg) { + return arg.find(aValue) != std::string::npos; + }); +} +#endif // defined(XP_WIN) && (defined(MOZ_SANDBOX) || defined(_ARM64_)) + +RefPtr<ProcessLaunchPromise> BaseProcessLauncher::PerformAsyncLaunch() { + Result<Ok, LaunchError> aError = DoSetup(); + if (aError.isErr()) { + return ProcessLaunchPromise::CreateAndReject(aError.unwrapErr(), __func__); + } + RefPtr<BaseProcessLauncher> self = this; + return DoLaunch()->Then( + mLaunchThread, __func__, + [self](base::ProcessHandle aHandle) { + self->mResults.mHandle = aHandle; + return self->FinishLaunch(); + }, + [](LaunchError aError) { + return ProcessLaunchPromise::CreateAndReject(aError, __func__); + }); +} + +Result<Ok, LaunchError> BaseProcessLauncher::DoSetup() { + RefPtr<BaseProcessLauncher> self = this; + GetProfilerEnvVarsForChildProcess([self](const char* key, const char* value) { + self->mLaunchOptions->env_map[ENVIRONMENT_STRING(key)] = + ENVIRONMENT_STRING(value); + }); +#ifdef MOZ_MEMORY + if (mProcessType == GeckoProcessType_Content) { + nsAutoCString mallocOpts(PR_GetEnv("MALLOC_OPTIONS")); + // Disable randomization of small arenas in content. + mallocOpts.Append("r"); + self->mLaunchOptions->env_map[ENVIRONMENT_LITERAL("MALLOC_OPTIONS")] = + ENVIRONMENT_STRING(mallocOpts.get()); + } +#endif + + MapChildLogging(); + + return Ok(); +} + +void BaseProcessLauncher::MapChildLogging() { + const char* origNSPRLogName = PR_GetEnv("NSPR_LOG_FILE"); + const char* origMozLogName = PR_GetEnv("MOZ_LOG_FILE"); + + if (origNSPRLogName) { + nsAutoCString nsprLogName; + GetChildLogName(origNSPRLogName, nsprLogName); + mLaunchOptions->env_map[ENVIRONMENT_LITERAL("NSPR_LOG_FILE")] = + ENVIRONMENT_STRING(nsprLogName.get()); + } + if (origMozLogName) { + nsAutoCString mozLogName; + GetChildLogName(origMozLogName, mozLogName); + mLaunchOptions->env_map[ENVIRONMENT_LITERAL("MOZ_LOG_FILE")] = + ENVIRONMENT_STRING(mozLogName.get()); + } + + // `RUST_LOG_CHILD` is meant for logging child processes only. + nsAutoCString childRustLog(PR_GetEnv("RUST_LOG_CHILD")); + if (!childRustLog.IsEmpty()) { + mLaunchOptions->env_map[ENVIRONMENT_LITERAL("RUST_LOG")] = + ENVIRONMENT_STRING(childRustLog.get()); + } +} + +Result<Ok, LaunchError> BaseProcessLauncher::DoFinishLaunch() { + // We're in the parent and the child was launched. Close the child channel + // handle in the parent as soon as possible, which will allow the parent to + // detect when the child closes its handle (either due to normal exit or due + // to crash). + mClientChannelHandle = nullptr; + + return Ok(); +} + +#if defined(MOZ_WIDGET_GTK) +Result<Ok, LaunchError> LinuxProcessLauncher::DoSetup() { + Result<Ok, LaunchError> aError = PosixProcessLauncher::DoSetup(); + if (aError.isErr()) { + return aError; + } + + if (mProcessType == GeckoProcessType_Content) { + // disable IM module to avoid sandbox violation + mLaunchOptions->env_map["GTK_IM_MODULE"] = "gtk-im-context-simple"; + + // Disable ATK accessibility code in content processes because it conflicts + // with the sandbox, and we proxy that information through the main process + // anyway. + mLaunchOptions->env_map["NO_AT_BRIDGE"] = "1"; + } + +# ifdef MOZ_SANDBOX + if (!mTmpDirName.IsEmpty()) { + // Point a bunch of things that might want to write from content to our + // shiny new content-process specific tmpdir + mLaunchOptions->env_map[ENVIRONMENT_LITERAL("TMPDIR")] = + ENVIRONMENT_STRING(mTmpDirName.get()); + // Partial fix for bug 1380051 (not persistent - should be) + mLaunchOptions->env_map[ENVIRONMENT_LITERAL("MESA_GLSL_CACHE_DIR")] = + ENVIRONMENT_STRING(mTmpDirName.get()); + } +# endif // MOZ_SANDBOX + + return Ok(); +} +#endif // MOZ_WIDGET_GTK + +#ifdef XP_UNIX +Result<Ok, LaunchError> PosixProcessLauncher::DoSetup() { + Result<Ok, LaunchError> aError = BaseProcessLauncher::DoSetup(); + if (aError.isErr()) { + return aError; + } + + // XPCOM may not be initialized in some subprocesses. We don't want + // to initialize XPCOM just for the directory service, especially + // since LD_LIBRARY_PATH is already set correctly in subprocesses + // (meaning that we don't need to set that up in the environment). + if (ShouldHaveDirectoryService()) { + MOZ_ASSERT(gGREBinPath); + nsCString path; + NS_CopyUnicodeToNative(nsDependentString(gGREBinPath), path); +# if defined(XP_LINUX) || defined(__DragonFly__) || defined(XP_FREEBSD) || \ + defined(XP_NETBSD) || defined(XP_OPENBSD) + const char* ld_library_path = PR_GetEnv("LD_LIBRARY_PATH"); + nsCString new_ld_lib_path(path.get()); + + if (ld_library_path && *ld_library_path) { + new_ld_lib_path.Append(':'); + new_ld_lib_path.Append(ld_library_path); + } + mLaunchOptions->env_map["LD_LIBRARY_PATH"] = new_ld_lib_path.get(); + +# elif XP_DARWIN + // With signed production Mac builds, the dynamic linker (dyld) will + // ignore dyld environment variables preventing the use of variables + // such as DYLD_LIBRARY_PATH and DYLD_INSERT_LIBRARIES. + + // If we're running with gtests, add the gtest XUL ahead of normal XUL on + // the DYLD_LIBRARY_PATH so that plugin-container.app loads it instead. + nsCString new_dyld_lib_path(path.get()); + if (PR_GetEnv("MOZ_RUN_GTEST")) { + new_dyld_lib_path = path + "/gtest:"_ns + new_dyld_lib_path; + mLaunchOptions->env_map["DYLD_LIBRARY_PATH"] = new_dyld_lib_path.get(); + } + + // DYLD_INSERT_LIBRARIES is currently unused by default but we allow + // it to be set by the external environment. + const char* interpose = PR_GetEnv("DYLD_INSERT_LIBRARIES"); + if (interpose && strlen(interpose) > 0) { + mLaunchOptions->env_map["DYLD_INSERT_LIBRARIES"] = interpose; + } + + // Prevent connection attempts to diagnosticd(8) to save cycles. Log + // messages can trigger these connection attempts, but access to + // diagnosticd is blocked in sandboxed child processes. +# ifdef MOZ_SANDBOX + if (mDisableOSActivityMode) { + mLaunchOptions->env_map["OS_ACTIVITY_MODE"] = "disable"; + } +# endif // defined(MOZ_SANDBOX) +# endif + } + + FilePath exePath; + BinPathType pathType = GetPathToBinary(exePath, mProcessType); + + // remap the IPC socket fd to a well-known int, as the OS does for + // STDOUT_FILENO, for example + // The fork server doesn't use IPC::Channel, so can skip this step. + if (mProcessType != GeckoProcessType_ForkServer) { +# ifdef MOZ_WIDGET_ANDROID + // On Android mChannelDstFd is uninitialised and the launching code uses + // only the first of each pair. + mLaunchOptions->fds_to_remap.push_back( + std::pair<int, int>(mClientChannelHandle.get(), -1)); +# else + MOZ_ASSERT(mChannelDstFd >= 0); + mLaunchOptions->fds_to_remap.push_back( + std::pair<int, int>(mClientChannelHandle.get(), mChannelDstFd)); +# endif + } + + // no need for kProcessChannelID, the child process inherits the + // other end of the socketpair() from us + + mChildArgv.push_back(exePath.value()); + + if (pathType == BinPathType::Self) { + mChildArgv.push_back("-contentproc"); + } + + mChildArgv.insert(mChildArgv.end(), mExtraOpts.begin(), mExtraOpts.end()); + + if ((mProcessType == GeckoProcessType_Content || + mProcessType == GeckoProcessType_ForkServer) && + Omnijar::IsInitialized()) { + // Make sure that child processes can find the omnijar, if they + // use full XPCOM. See Omnijar::ChildProcessInit and its callers. + nsAutoCString path; + nsCOMPtr<nsIFile> greFile = Omnijar::GetPath(Omnijar::GRE); + if (greFile && NS_SUCCEEDED(greFile->GetNativePath(path))) { + geckoargs::sGREOmni.Put(path.get(), mChildArgv); + } + nsCOMPtr<nsIFile> appFile = Omnijar::GetPath(Omnijar::APP); + if (appFile && NS_SUCCEEDED(appFile->GetNativePath(path))) { + geckoargs::sAppOmni.Put(path.get(), mChildArgv); + } + } + + if (mProcessType != GeckoProcessType_GMPlugin) { + // Add the application directory path (-appdir path) +# ifdef XP_MACOSX + AddAppDirToCommandLine(mChildArgv, mAppDir, mProfileDir); +# else + AddAppDirToCommandLine(mChildArgv, mAppDir, nullptr); +# endif + } + + mChildArgv.push_back(mInitialChannelIdString); + + mChildArgv.push_back(mPidString); + + if (!CrashReporter::IsDummy()) { +# if defined(MOZ_WIDGET_COCOA) + mChildArgv.push_back(CrashReporter::GetChildNotificationPipe()); +# elif defined(XP_UNIX) + int childCrashFd, childCrashRemapFd; + if (NS_WARN_IF(!CrashReporter::CreateNotificationPipeForChild( + &childCrashFd, &childCrashRemapFd))) { + return Err(LaunchError("CR::CreateNotificationPipeForChild")); + } + + if (0 <= childCrashFd) { + mLaunchOptions->fds_to_remap.push_back( + std::pair<int, int>(childCrashFd, childCrashRemapFd)); + // "true" == crash reporting enabled + mChildArgv.push_back("true"); + } else { + // "false" == crash reporting disabled + mChildArgv.push_back("false"); + } +# endif + } + +# ifdef MOZ_WIDGET_COCOA + { + auto* thisMac = static_cast<MacProcessLauncher*>(this); + kern_return_t kr = + bootstrap_check_in(bootstrap_port, thisMac->mMachConnectionName.c_str(), + getter_Transfers(thisMac->mParentRecvPort)); + if (kr != KERN_SUCCESS) { + CHROMIUM_LOG(ERROR) << "parent bootstrap_check_in failed: " + << mach_error_string(kr); + return Err(LaunchError("bootstrap_check_in", kr)); + } + mChildArgv.push_back(thisMac->mMachConnectionName.c_str()); + } +# endif // MOZ_WIDGET_COCOA + + mChildArgv.push_back(ChildProcessType()); + return Ok(); +} +#endif // XP_UNIX + +#if defined(MOZ_WIDGET_ANDROID) +RefPtr<ProcessHandlePromise> AndroidProcessLauncher::DoLaunch() { + return LaunchAndroidService(mProcessType, mChildArgv, + mLaunchOptions->fds_to_remap); +} +#endif // MOZ_WIDGET_ANDROID + +#ifdef XP_UNIX +RefPtr<ProcessHandlePromise> PosixProcessLauncher::DoLaunch() { + ProcessHandle handle = 0; + Result<Ok, LaunchError> aError = + base::LaunchApp(mChildArgv, std::move(*mLaunchOptions), &handle); + if (aError.isErr()) { + return ProcessHandlePromise::CreateAndReject(aError.unwrapErr(), __func__); + } + return ProcessHandlePromise::CreateAndResolve(handle, __func__); +} +#endif // XP_UNIX + +#ifdef XP_MACOSX +Result<Ok, LaunchError> MacProcessLauncher::DoFinishLaunch() { + Result<Ok, LaunchError> aError = PosixProcessLauncher::DoFinishLaunch(); + if (aError.isErr()) { + return aError; + } + + MOZ_ASSERT(mParentRecvPort, "should have been configured during DoSetup()"); + + // Wait for the child process to send us its 'task_t' data. + const int kTimeoutMs = 10000; + + mozilla::UniqueMachSendRight child_task; + audit_token_t audit_token{}; + kern_return_t kr = MachReceivePortSendRight( + mParentRecvPort, mozilla::Some(kTimeoutMs), &child_task, &audit_token); + if (kr != KERN_SUCCESS) { + std::string errString = StringPrintf("0x%x %s", kr, mach_error_string(kr)); + CHROMIUM_LOG(ERROR) << "parent MachReceivePortSendRight failed: " + << errString; + return Err(LaunchError("MachReceivePortSendRight", kr)); + } + + // Ensure the message was sent by the newly spawned child process. + if (audit_token_to_pid(audit_token) != base::GetProcId(mResults.mHandle)) { + CHROMIUM_LOG(ERROR) << "task_t was not sent by child process"; + return Err(LaunchError("audit_token_to_pid")); + } + + // Ensure the task_t corresponds to the newly spawned child process. + pid_t task_pid = -1; + kr = pid_for_task(child_task.get(), &task_pid); + if (kr != KERN_SUCCESS) { + CHROMIUM_LOG(ERROR) << "pid_for_task failed: " << mach_error_string(kr); + return Err(LaunchError("pid_for_task", kr)); + } + if (task_pid != base::GetProcId(mResults.mHandle)) { + CHROMIUM_LOG(ERROR) << "task_t is not for child process"; + return Err(LaunchError("task_pid")); + } + + mResults.mChildTask = child_task.release(); + + return Ok(); +} +#endif // XP_MACOSX + +#ifdef XP_WIN +Result<Ok, LaunchError> WindowsProcessLauncher::DoSetup() { + Result<Ok, LaunchError> aError = BaseProcessLauncher::DoSetup(); + if (aError.isErr()) { + return aError; + } + + FilePath exePath; + BinPathType pathType = GetPathToBinary(exePath, mProcessType); + +# if defined(MOZ_SANDBOX) || defined(_ARM64_) + const bool isGMP = mProcessType == GeckoProcessType_GMPlugin; + const bool isWidevine = isGMP && Contains(mExtraOpts, "gmp-widevinecdm"); +# if defined(_ARM64_) + bool useRemoteSandboxBroker = false; + if (mLaunchArch & (base::PROCESS_ARCH_I386 | base::PROCESS_ARCH_X86_64)) { + // On Windows on ARM64 for ClearKey and Widevine, and for the sandbox + // launcher process, we want to run the x86 plugin-container.exe in + // the "i686" subdirectory, instead of the aarch64 plugin-container.exe. + // So insert "i686" into the exePath. + exePath = exePath.DirName().AppendASCII("i686").Append(exePath.BaseName()); + useRemoteSandboxBroker = + mProcessType != GeckoProcessType_RemoteSandboxBroker; + } +# endif // if defined(_ARM64_) +# endif // defined(MOZ_SANDBOX) || defined(_ARM64_) + + mCmdLine.emplace(exePath.ToWStringHack()); + + if (pathType == BinPathType::Self) { + mCmdLine->AppendLooseValue(UTF8ToWide("-contentproc")); + } + +# ifdef HAS_DLL_BLOCKLIST + if (IsDynamicBlocklistDisabled( + gSafeMode, + CommandLine::ForCurrentProcess()->HasSwitch(UTF8ToWide( + mozilla::geckoargs::sDisableDynamicDllBlocklist.sMatch)))) { + mCmdLine->AppendLooseValue( + UTF8ToWide(mozilla::geckoargs::sDisableDynamicDllBlocklist.sMatch)); + } +# endif // HAS_DLL_BLOCKLIST + + // Inherit the initial client channel handle into the child process. + std::wstring processChannelID = + std::to_wstring(uint32_t(uintptr_t(mClientChannelHandle.get()))); + mLaunchOptions->handles_to_inherit.push_back(mClientChannelHandle.get()); + mCmdLine->AppendSwitchWithValue(switches::kProcessChannelID, + processChannelID); + + for (std::vector<std::string>::iterator it = mExtraOpts.begin(); + it != mExtraOpts.end(); ++it) { + mCmdLine->AppendLooseValue(UTF8ToWide(*it)); + } + +# if defined(MOZ_SANDBOX) +# if defined(_ARM64_) + if (useRemoteSandboxBroker) + mResults.mSandboxBroker = new RemoteSandboxBroker(mLaunchArch); + else +# endif // if defined(_ARM64_) + mResults.mSandboxBroker = new SandboxBroker(); + + // XXX: Bug 1124167: We should get rid of the process specific logic for + // sandboxing in this class at some point. Unfortunately it will take a bit + // of reorganizing so I don't think this patch is the right time. + switch (mProcessType) { + case GeckoProcessType_Content: + if (mSandboxLevel > 0) { + // For now we treat every failure as fatal in + // SetSecurityLevelForContentProcess and just crash there right away. + // Should this change in the future then we should also handle the error + // here. + mResults.mSandboxBroker->SetSecurityLevelForContentProcess( + mSandboxLevel, mIsFileContent); + mUseSandbox = true; + } + break; + case GeckoProcessType_IPDLUnitTest: + // XXX: We don't sandbox this process type yet + break; + case GeckoProcessType_GMPlugin: + if (!PR_GetEnv("MOZ_DISABLE_GMP_SANDBOX")) { + // The Widevine CDM on Windows can only load at USER_RESTRICTED, + // not at USER_LOCKDOWN. So look in the command line arguments + // to see if we're loading the path to the Widevine CDM, and if + // so use sandbox level USER_RESTRICTED instead of USER_LOCKDOWN. + auto level = + isWidevine ? SandboxBroker::Restricted : SandboxBroker::LockDown; + if (NS_WARN_IF( + !mResults.mSandboxBroker->SetSecurityLevelForGMPlugin(level))) { + return Err(LaunchError("SetSecurityLevelForGMPlugin")); + } + mUseSandbox = true; + } + break; + case GeckoProcessType_GPU: + if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_GPU_SANDBOX")) { + // For now we treat every failure as fatal in + // SetSecurityLevelForGPUProcess and just crash there right away. Should + // this change in the future then we should also handle the error here. + mResults.mSandboxBroker->SetSecurityLevelForGPUProcess(mSandboxLevel); + mUseSandbox = true; + } + break; + case GeckoProcessType_VR: + if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_VR_SANDBOX")) { + // TODO: Implement sandbox for VR process, Bug 1430043. + } + break; + case GeckoProcessType_RDD: + if (!PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX")) { + if (NS_WARN_IF( + !mResults.mSandboxBroker->SetSecurityLevelForRDDProcess())) { + return Err(LaunchError("SetSecurityLevelForRDDProcess")); + } + mUseSandbox = true; + } + break; + case GeckoProcessType_Socket: + if (!PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS_SANDBOX")) { + if (NS_WARN_IF( + !mResults.mSandboxBroker->SetSecurityLevelForSocketProcess())) { + return Err(LaunchError("SetSecurityLevelForSocketProcess")); + } + mUseSandbox = true; + } + break; + case GeckoProcessType_Utility: + if (IsUtilitySandboxEnabled(mSandbox)) { + if (!mResults.mSandboxBroker->SetSecurityLevelForUtilityProcess( + mSandbox)) { + return Err(LaunchError("SetSecurityLevelForUtilityProcess")); + } + mUseSandbox = true; + } + break; + case GeckoProcessType_RemoteSandboxBroker: + // We don't sandbox the sandbox launcher... + break; + case GeckoProcessType_Default: + default: + MOZ_CRASH("Bad process type in GeckoChildProcessHost"); + break; + }; + + if (mUseSandbox) { + for (auto it = mAllowedFilesRead.begin(); it != mAllowedFilesRead.end(); + ++it) { + mResults.mSandboxBroker->AllowReadFile(it->c_str()); + } + + if (mResults.mSandboxBroker->IsWin32kLockedDown()) { + mCmdLine->AppendLooseValue( + UTF8ToWide(geckoargs::sWin32kLockedDown.Name())); + } + } +# endif // defined(MOZ_SANDBOX) + + // Add the application directory path (-appdir path) + AddAppDirToCommandLine(mCmdLine.ref(), mAppDir); + + // XXX Command line params past this point are expected to be at + // the end of the command line string, and in a specific order. + // See XRE_InitChildProcess in nsEmbedFunction. + + // Win app model id + mCmdLine->AppendLooseValue(mGroupId.get()); + + // Initial MessageChannel id + mCmdLine->AppendLooseValue(UTF8ToWide(mInitialChannelIdString)); + + // Process id + mCmdLine->AppendLooseValue(UTF8ToWide(mPidString)); + + mCmdLine->AppendLooseValue( + UTF8ToWide(CrashReporter::GetChildNotificationPipe())); + + // Process type + mCmdLine->AppendLooseValue(UTF8ToWide(ChildProcessType())); + +# ifdef MOZ_SANDBOX + if (mUseSandbox) { + // Mark the handles to inherit as inheritable. + for (HANDLE h : mLaunchOptions->handles_to_inherit) { + mResults.mSandboxBroker->AddHandleToShare(h); + } + } +# endif // MOZ_SANDBOX + + return Ok(); +} + +RefPtr<ProcessHandlePromise> WindowsProcessLauncher::DoLaunch() { + ProcessHandle handle = 0; +# ifdef MOZ_SANDBOX + if (mUseSandbox) { + const IMAGE_THUNK_DATA* cachedNtdllThunk = + mCachedNtdllThunk ? mCachedNtdllThunk->begin() : nullptr; + Result<Ok, LaunchError> err = mResults.mSandboxBroker->LaunchApp( + mCmdLine->program().c_str(), mCmdLine->command_line_string().c_str(), + mLaunchOptions->env_map, mProcessType, mEnableSandboxLogging, + cachedNtdllThunk, &handle); + if (err.isOk()) { + EnvironmentLog("MOZ_PROCESS_LOG") + .print("==> process %d launched child process %d (%S)\n", + base::GetCurrentProcId(), base::GetProcId(handle), + mCmdLine->command_line_string().c_str()); + return ProcessHandlePromise::CreateAndResolve(handle, __func__); + } + return ProcessHandlePromise::CreateAndReject(err.unwrapErr(), __func__); + } +# endif // defined(MOZ_SANDBOX) + + Result<Ok, LaunchError> launchErr = + base::LaunchApp(mCmdLine.ref(), *mLaunchOptions, &handle); + if (launchErr.isErr()) { + return ProcessHandlePromise::CreateAndReject(launchErr.unwrapErr(), + __func__); + } + return ProcessHandlePromise::CreateAndResolve(handle, __func__); +} + +Result<Ok, LaunchError> WindowsProcessLauncher::DoFinishLaunch() { + Result<Ok, LaunchError> err = BaseProcessLauncher::DoFinishLaunch(); + if (err.isErr()) { + return err; + } + + return Ok(); +} +#endif // XP_WIN + +RefPtr<ProcessLaunchPromise> BaseProcessLauncher::FinishLaunch() { + Result<Ok, LaunchError> aError = DoFinishLaunch(); + if (aError.isErr()) { + return ProcessLaunchPromise::CreateAndReject(aError.unwrapErr(), __func__); + } + + MOZ_DIAGNOSTIC_ASSERT(mResults.mHandle); + + Telemetry::AccumulateTimeDelta(Telemetry::CHILD_PROCESS_LAUNCH_MS, + mStartTimeStamp); + + return ProcessLaunchPromise::CreateAndResolve(mResults, __func__); +} + +bool GeckoChildProcessHost::OpenPrivilegedHandle(base::ProcessId aPid) { + if (mChildProcessHandle) { + MOZ_ASSERT(aPid == base::GetProcId(mChildProcessHandle)); + return true; + } + + return base::OpenPrivilegedProcessHandle(aPid, &mChildProcessHandle); +} + +void GeckoChildProcessHost::OnChannelConnected(base::ProcessId peer_pid) { + { + mozilla::AutoWriteLock hLock(mHandleLock); + if (!OpenPrivilegedHandle(peer_pid)) { + MOZ_CRASH("can't open handle to child process"); + } + } + MonitorAutoLock lock(mMonitor); + mProcessState = PROCESS_CONNECTED; + lock.Notify(); +} + +RefPtr<ProcessHandlePromise> GeckoChildProcessHost::WhenProcessHandleReady() { + MOZ_ASSERT(mHandlePromise != nullptr); + return mHandlePromise; +} + +#ifdef MOZ_WIDGET_ANDROID +RefPtr<ProcessHandlePromise> AndroidProcessLauncher::LaunchAndroidService( + const GeckoProcessType aType, const std::vector<std::string>& argv, + const base::file_handle_mapping_vector& fds_to_remap) { + MOZ_RELEASE_ASSERT((2 <= fds_to_remap.size()) && (fds_to_remap.size() <= 5)); + JNIEnv* const env = mozilla::jni::GetEnvForThread(); + MOZ_ASSERT(env); + + const int argvSize = argv.size(); + jni::ObjectArray::LocalRef jargs = + jni::ObjectArray::New<jni::String>(argvSize); + for (int ix = 0; ix < argvSize; ix++) { + jargs->SetElement(ix, jni::StringParam(argv[ix].c_str(), env)); + } + + // XXX: this processing depends entirely on the internals of + // ContentParent::LaunchSubprocess() + // GeckoChildProcessHost::PerformAsyncLaunch(), and the order in + // which they append to fds_to_remap. There must be a better way to do it. + // See bug 1440207. + int32_t prefsFd = fds_to_remap[0].first; + int32_t prefMapFd = fds_to_remap[1].first; + int32_t ipcFd = fds_to_remap[2].first; + int32_t crashFd = -1; + if (fds_to_remap.size() == 4) { + crashFd = fds_to_remap[3].first; + } + + auto type = java::GeckoProcessType::FromInt(aType); + auto genericResult = java::GeckoProcessManager::Start( + type, jargs, prefsFd, prefMapFd, ipcFd, crashFd); + auto typedResult = java::GeckoResult::LocalRef(std::move(genericResult)); + return ProcessHandlePromise::FromGeckoResult(typedResult); +} +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +bool GeckoChildProcessHost::AppendMacSandboxParams(StringVector& aArgs) { + MacSandboxInfo info; + if (NS_WARN_IF(!FillMacSandboxInfo(info))) { + return false; + } + info.AppendAsParams(aArgs); + return true; +} + +// Fill |aInfo| with the flags needed to launch the utility sandbox +bool GeckoChildProcessHost::FillMacSandboxInfo(MacSandboxInfo& aInfo) { + aInfo.type = GetDefaultMacSandboxType(); + aInfo.shouldLog = Preferences::GetBool("security.sandbox.logging.enabled") || + PR_GetEnv("MOZ_SANDBOX_LOGGING"); + + nsAutoCString appPath; + if (!nsMacUtilsImpl::GetAppPath(appPath)) { + MOZ_CRASH("Failed to get app path"); + } + aInfo.appPath.assign(appPath.get()); + return true; +} + +void GeckoChildProcessHost::DisableOSActivityMode() { + mDisableOSActivityMode = true; +} + +// +// If early sandbox startup is enabled for this process type, map the +// process type to the sandbox type and enable the sandbox. Returns true +// if no errors were encountered or if early sandbox startup is not +// enabled for this process. Returns false if an error was encountered. +// +/* static */ +bool GeckoChildProcessHost::StartMacSandbox(int aArgc, char** aArgv, + std::string& aErrorMessage) { + MacSandboxType sandboxType = MacSandboxType_Invalid; + switch (XRE_GetProcessType()) { + // For now, only support early sandbox startup for content, + // RDD, and GMP processes. Add case statements for the additional + // process types once early sandbox startup is implemented for them. + case GeckoProcessType_Content: + // Content processes don't use GeckoChildProcessHost + // to configure sandboxing so hard code the sandbox type. + sandboxType = MacSandboxType_Content; + break; + case GeckoProcessType_RDD: + sandboxType = RDDProcessHost::GetMacSandboxType(); + break; + case GeckoProcessType_Socket: + sandboxType = net::SocketProcessHost::GetMacSandboxType(); + break; + case GeckoProcessType_GMPlugin: + sandboxType = gmp::GMPProcessParent::GetMacSandboxType(); + break; + case GeckoProcessType_Utility: + sandboxType = ipc::UtilityProcessHost::GetMacSandboxType(); + break; + default: + return true; + } + + return mozilla::StartMacSandboxIfEnabled(sandboxType, aArgc, aArgv, + aErrorMessage); +} + +#endif /* XP_MACOSX && MOZ_SANDBOX */ + +/* static */ +void GeckoChildProcessHost::GetAll(const GeckoProcessCallback& aCallback) { + StaticMutexAutoLock lock(sMutex); + if (!sGeckoChildProcessHosts) { + return; + } + for (GeckoChildProcessHost* gp = sGeckoChildProcessHosts->getFirst(); gp; + gp = static_cast<mozilla::LinkedListElement<GeckoChildProcessHost>*>(gp) + ->getNext()) { + aCallback(gp); + } +} + +RefPtr<ProcessLaunchPromise> BaseProcessLauncher::Launch( + GeckoChildProcessHost* aHost) { + AssertIOThread(); + + // The ForkServer doesn't use IPC::Channel for communication, so we can skip + // initializing it. + if (mProcessType != GeckoProcessType_ForkServer) { + IPC::Channel::ChannelHandle serverHandle; + if (!IPC::Channel::CreateRawPipe(&serverHandle, &mClientChannelHandle)) { + return ProcessLaunchPromise::CreateAndReject(LaunchError("CreateRawPipe"), + __func__); + } + aHost->InitializeChannel(std::move(serverHandle)); + } + + return InvokeAsync(mLaunchThread, this, __func__, + &BaseProcessLauncher::PerformAsyncLaunch); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/GeckoChildProcessHost.h b/ipc/glue/GeckoChildProcessHost.h new file mode 100644 index 0000000000..89ed399fea --- /dev/null +++ b/ipc/glue/GeckoChildProcessHost.h @@ -0,0 +1,316 @@ +/* -*- 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/. */ + +#ifndef __IPC_GLUE_GECKOCHILDPROCESSHOST_H__ +#define __IPC_GLUE_GECKOCHILDPROCESSHOST_H__ + +#include "base/file_path.h" +#include "base/process_util.h" +#include "base/waitable_event.h" +#include "chrome/common/ipc_message.h" +#include "mojo/core/ports/port_ref.h" + +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/ipc/NodeChannel.h" +#include "mozilla/ipc/LaunchError.h" +#include "mozilla/ipc/ScopedPort.h" +#include "mozilla/Atomics.h" +#include "mozilla/Buffer.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Monitor.h" +#include "mozilla/MozPromise.h" +#include "mozilla/RWLock.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" + +#include "nsCOMPtr.h" +#include "nsExceptionHandler.h" +#include "nsXULAppAPI.h" // for GeckoProcessType +#include "nsString.h" + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +# include "sandboxBroker.h" +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.h" +#endif + +#if defined(MOZ_SANDBOX) +# include "mozilla/ipc/UtilityProcessSandboxing.h" +#endif + +#if (defined(XP_WIN) && defined(_ARM64_)) || \ + (defined(XP_MACOSX) && defined(__aarch64__)) +# define ALLOW_GECKO_CHILD_PROCESS_ARCH +#endif + +struct _MacSandboxInfo; +typedef _MacSandboxInfo MacSandboxInfo; + +namespace mozilla { +namespace ipc { + +typedef mozilla::MozPromise<base::ProcessHandle, LaunchError, false> + ProcessHandlePromise; + +class GeckoChildProcessHost : public SupportsWeakPtr, + public LinkedListElement<GeckoChildProcessHost> { + protected: + typedef mozilla::Monitor Monitor; + typedef std::vector<std::string> StringVector; + + public: + using ProcessId = base::ProcessId; + using ProcessHandle = base::ProcessHandle; + + explicit GeckoChildProcessHost(GeckoProcessType aProcessType, + bool aIsFileContent = false); + + // Causes the object to be deleted, on the I/O thread, after any + // pending asynchronous work (like launching) is complete. This + // method can be called from any thread. If called from the I/O + // thread itself, deletion won't happen until the event loop spins; + // otherwise, it could happen immediately. + // + // GeckoChildProcessHost instances must not be deleted except + // through this method. + void Destroy(); + + static uint32_t GetUniqueID(); + + // Call this before launching to set an environment variable for the + // child process. The arguments must be UTF-8. + void SetEnv(const char* aKey, const char* aValue); + + // Does not block. The IPC channel may not be initialized yet, and + // the child process may or may not have been created when this + // method returns. + bool AsyncLaunch(StringVector aExtraOpts = StringVector()); + + virtual bool WaitUntilConnected(int32_t aTimeoutMs = 0); + + // Block until the IPC channel for our subprocess is initialized and + // the OS process is created. The subprocess may or may not have + // connected back to us when this method returns. + // + // NB: on POSIX, this method is relatively cheap, and doesn't + // require disk IO. On win32 however, it requires at least the + // analogue of stat(). This difference induces a semantic + // difference in this method: on POSIX, when we return, we know the + // subprocess has been created, but we don't know whether its + // executable image can be loaded. On win32, we do know that when + // we return. But we don't know if dynamic linking succeeded on + // either platform. + bool LaunchAndWaitForProcessHandle(StringVector aExtraOpts = StringVector()); + bool WaitForProcessHandle(); + + // Block until the child process has been created and it connects to + // the IPC channel, meaning it's fully initialized. (Or until an + // error occurs.) + bool SyncLaunch(StringVector aExtraOpts = StringVector(), + int32_t timeoutMs = 0); + + virtual void OnChannelConnected(base::ProcessId peer_pid); + + // Resolves to the process handle when it's available (see + // LaunchAndWaitForProcessHandle); use with AsyncLaunch. + RefPtr<ProcessHandlePromise> WhenProcessHandleReady(); + + void InitializeChannel(IPC::Channel::ChannelHandle&& aServerHandle); + + virtual bool CanShutdown() { return true; } + + UntypedEndpoint TakeInitialEndpoint() { + return UntypedEndpoint{PrivateIPDLInterface{}, std::move(mInitialPort), + mInitialChannelId, base::GetCurrentProcId(), + GetChildProcessId()}; + } + + // Returns a "borrowed" handle to the child process - the handle returned + // by this function must not be closed by the caller. The handle is also + // not guaranteed to remain valid; if the caller is using it for anything + // more than logging or asserting non-null, it will need to deal with + // synchronization. + // + // Warning: the null value here is 0, not kInvalidProcessHandle. + ProcessHandle GetChildProcessHandle(); + + // Returns the child's process ID; as for GetChildProcessHandle, there is + // no inherent guarantee that it will remain valid or continue to + // reference the same process. + // + // The null value here is also 0; this matches the result of + // GetProcId on a zero or (on Windows) invalid handle. + ProcessId GetChildProcessId(); + + GeckoProcessType GetProcessType() { return mProcessType; } + +#ifdef XP_MACOSX + task_t GetChildTask(); +#endif + +#ifdef XP_WIN + + void AddHandleToShare(HANDLE aHandle) { + mLaunchOptions->handles_to_inherit.push_back(aHandle); + } +#else + void AddFdToRemap(int aSrcFd, int aDstFd) { + mLaunchOptions->fds_to_remap.push_back(std::make_pair(aSrcFd, aDstFd)); + } +#endif + +#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH + void SetLaunchArchitecture(uint32_t aArch) { mLaunchArch = aArch; } +#endif + + // For bug 943174: Skip the EnsureProcessTerminated call in the destructor. + void SetAlreadyDead(); + +#if defined(MOZ_SANDBOX) && defined(XP_MACOSX) + // Start the sandbox from the child process. + static bool StartMacSandbox(int aArgc, char** aArgv, + std::string& aErrorMessage); + + // The sandbox type that will be use when sandboxing is + // enabled in the derived class and FillMacSandboxInfo + // has not been overridden. + static MacSandboxType GetDefaultMacSandboxType() { + return MacSandboxType_Utility; + }; + + // Must be called before the process is launched. Determines if + // child processes will be launched with OS_ACTIVITY_MODE set to + // "disabled" or not. When |mDisableOSActivityMode| is set to true, + // child processes will be launched with OS_ACTIVITY_MODE + // disabled to avoid connection attempts to diagnosticd(8) which are + // blocked in child processes due to sandboxing. + void DisableOSActivityMode(); +#endif // defined(MOZ_SANDBOX) && defined(XP_MACOSX) + typedef std::function<void(GeckoChildProcessHost*)> GeckoProcessCallback; + + // Iterates over all instances and calls aCallback with each one of them. + // This method will lock any addition/removal of new processes + // so you need to make sure the callback is as fast as possible. + // + // To reiterate: the callbacks are executed synchronously. + static void GetAll(const GeckoProcessCallback& aCallback); + + friend class BaseProcessLauncher; + friend class PosixProcessLauncher; + friend class WindowsProcessLauncher; + + protected: + virtual ~GeckoChildProcessHost(); + GeckoProcessType mProcessType; + bool mIsFileContent; + Monitor mMonitor; + FilePath mProcessPath; +#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH + // Used on platforms where we may launch a child process with a different + // architecture than the parent process. + uint32_t mLaunchArch = base::PROCESS_ARCH_INVALID; +#endif + // GeckoChildProcessHost holds the launch options so they can be set + // up on the main thread using main-thread-only APIs like prefs, and + // then used for the actual launch on another thread. This pointer + // is set to null to free the options after the child is launched. + UniquePtr<base::LaunchOptions> mLaunchOptions; + ScopedPort mInitialPort; + nsID mInitialChannelId; + RefPtr<NodeController> mNodeController; + RefPtr<NodeChannel> mNodeChannel; + + // This value must be accessed while holding mMonitor. + enum { + // This object has been constructed, but the OS process has not + // yet. + CREATING_CHANNEL = 0, + // The IPC channel for our subprocess has been created, but the OS + // process has still not been created. + CHANNEL_INITIALIZED, + // The OS process has been created, but it hasn't yet connected to + // our IPC channel. + PROCESS_CREATED, + // The process is launched and connected to our IPC channel. All + // is well. + PROCESS_CONNECTED, + PROCESS_ERROR + } mProcessState MOZ_GUARDED_BY(mMonitor); + + void PrepareLaunch(); + +#ifdef XP_WIN + void InitWindowsGroupID(); + nsString mGroupId; +# ifdef MOZ_SANDBOX + RefPtr<AbstractSandboxBroker> mSandboxBroker; + std::vector<std::wstring> mAllowedFilesRead; + bool mEnableSandboxLogging; + int32_t mSandboxLevel; +# endif +#endif // XP_WIN + +#if defined(MOZ_SANDBOX) + SandboxingKind mSandbox; +#endif + + mozilla::RWLock mHandleLock; + ProcessHandle mChildProcessHandle MOZ_GUARDED_BY(mHandleLock); +#if defined(XP_DARWIN) + task_t mChildTask MOZ_GUARDED_BY(mHandleLock); +#endif + RefPtr<ProcessHandlePromise> mHandlePromise; + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + bool mDisableOSActivityMode; +#endif + + bool OpenPrivilegedHandle(base::ProcessId aPid) MOZ_REQUIRES(mHandleLock); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + // Override this method to return true to launch the child process + // using the Mac utility (by default) sandbox. Override + // FillMacSandboxInfo() to change the sandbox type and settings. + virtual bool IsMacSandboxLaunchEnabled() { return false; } + + // Fill a MacSandboxInfo to configure the sandbox + virtual bool FillMacSandboxInfo(MacSandboxInfo& aInfo); + + // Adds the command line arguments needed to enable + // sandboxing of the child process at startup before + // the child event loop is up. + virtual bool AppendMacSandboxParams(StringVector& aArgs); +#endif + + private: + DISALLOW_EVIL_CONSTRUCTORS(GeckoChildProcessHost); + + // Removes the instance from sGeckoChildProcessHosts + void RemoveFromProcessList(); + + // Linux-Only. Set this up before we're called from a different thread. + nsCString mTmpDirName; + // Mac and Windows. Set this up before we're called from a different thread. + nsCOMPtr<nsIFile> mProfileDir; + + mozilla::Atomic<bool> mDestroying; + + static uint32_t sNextUniqueID; + static StaticAutoPtr<LinkedList<GeckoChildProcessHost>> + sGeckoChildProcessHosts MOZ_GUARDED_BY(sMutex); + static StaticMutex sMutex; +}; + +nsCOMPtr<nsIEventTarget> GetIPCLauncher(); + +} /* namespace ipc */ +} /* namespace mozilla */ + +#endif /* __IPC_GLUE_GECKOCHILDPROCESSHOST_H__ */ diff --git a/ipc/glue/IOThreadChild.h b/ipc/glue/IOThreadChild.h new file mode 100644 index 0000000000..d4e877e67c --- /dev/null +++ b/ipc/glue/IOThreadChild.h @@ -0,0 +1,46 @@ +/* -*- 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/. */ + +#ifndef dom_plugins_IOThreadChild_h +#define dom_plugins_IOThreadChild_h + +#include "chrome/common/child_thread.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/NodeController.h" +#include "mozilla/ipc/ProcessChild.h" + +namespace mozilla { +namespace ipc { +//----------------------------------------------------------------------------- + +// The IOThreadChild class represents a background thread where the +// IPC IO MessageLoop lives. +class IOThreadChild : public ChildThread { + public: + explicit IOThreadChild(base::ProcessId aParentPid) + : ChildThread(base::Thread::Options(MessageLoop::TYPE_IO, + /* stack size */ 0), + aParentPid) {} + + ~IOThreadChild() = default; + + static MessageLoop* message_loop() { + return IOThreadChild::current()->Thread::message_loop(); + } + + protected: + static IOThreadChild* current() { + return static_cast<IOThreadChild*>(ChildThread::current()); + } + + private: + DISALLOW_EVIL_CONSTRUCTORS(IOThreadChild); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef dom_plugins_IOThreadChild_h diff --git a/ipc/glue/IPCCore.h b/ipc/glue/IPCCore.h new file mode 100644 index 0000000000..bb6b725c35 --- /dev/null +++ b/ipc/glue/IPCCore.h @@ -0,0 +1,22 @@ +/* -*- 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/. */ + +#ifndef IPC_GLUE_IPCCORE_H_ +#define IPC_GLUE_IPCCORE_H_ + +namespace mozilla { + +struct void_t { + constexpr bool operator==(const void_t&) const { return true; } +}; + +struct null_t { + constexpr bool operator==(const null_t&) const { return true; } +}; + +} // namespace mozilla + +#endif // IPC_GLUE_IPCCORE_H_ diff --git a/ipc/glue/IPCForwards.h b/ipc/glue/IPCForwards.h new file mode 100644 index 0000000000..8aef885257 --- /dev/null +++ b/ipc/glue/IPCForwards.h @@ -0,0 +1,38 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_IPCForwards_h +#define mozilla_ipc_IPCForwards_h + +// A few helpers to avoid having to include lots of stuff in headers. + +namespace mozilla { +template <typename T> +class Maybe; + +namespace ipc { +class IProtocol; +} +} // namespace mozilla + +namespace IPC { +class Message; +class MessageReader; +class MessageWriter; +template <typename T, bool> +class ReadResult; +} // namespace IPC + +// TODO(bug 1812271): Remove users of this macro. +#define ALLOW_DEPRECATED_READPARAM \ + public: \ + enum { kHasDeprecatedReadParamPrivateConstructor = true }; \ + template <typename, bool> \ + friend class IPC::ReadResult; \ + \ + private: + +#endif diff --git a/ipc/glue/IPCMessageUtils.h b/ipc/glue/IPCMessageUtils.h new file mode 100644 index 0000000000..cdcd3c574a --- /dev/null +++ b/ipc/glue/IPCMessageUtils.h @@ -0,0 +1,237 @@ +/* -*- 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/. */ + +#ifndef __IPC_GLUE_IPCMESSAGEUTILS_H__ +#define __IPC_GLUE_IPCMESSAGEUTILS_H__ + +#include <cstdint> +#include <string> +#include <type_traits> +#include "chrome/common/ipc_message.h" +#include "chrome/common/ipc_message_utils.h" +#include "mozilla/ipc/IPCCore.h" +#include "mozilla/MacroForEach.h" + +class PickleIterator; + +// XXX Things that are not necessary if moving implementations to the cpp file +#include "base/string_util.h" + +#ifdef _MSC_VER +# pragma warning(disable : 4800) +#endif + +#if !defined(XP_UNIX) +// This condition must be kept in sync with the one in +// ipc_message_utils.h, but this dummy definition of +// base::FileDescriptor acts as a static assert that we only get one +// def or the other (or neither, in which case code using +// FileDescriptor fails to build) +namespace base { +struct FileDescriptor {}; +} // namespace base +#endif + +namespace mozilla { +template <typename...> +class Variant; + +namespace detail { +template <typename...> +struct VariantTag; +} +} // namespace mozilla + +namespace IPC { + +/** + * A helper class for serializing plain-old data (POD) structures. + * The memory representation of the structure is written to and read from + * the serialized stream directly, without individual processing of the + * structure's members. + * + * Derive ParamTraits<T> from PlainOldDataSerializer<T> if T is POD. + * + * Note: For POD structures with enumeration fields, this will not do + * validation of the enum values the way serializing the fields + * individually would. Prefer serializing the fields individually + * in such cases. + */ +template <typename T> +struct PlainOldDataSerializer { + static_assert( + std::is_trivially_copyable<T>::value, + "PlainOldDataSerializer can only be used with trivially copyable types!"); + + typedef T paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + aWriter->WriteBytes(&aParam, sizeof(aParam)); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return aReader->ReadBytesInto(aResult, sizeof(paramType)); + } +}; + +/** + * A helper class for serializing empty structs. Since the struct is empty there + * is nothing to write, and a priori we know the result of the read. + */ +template <typename T> +struct EmptyStructSerializer { + typedef T paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) {} + + static bool Read(MessageReader* aReader, paramType* aResult) { + *aResult = {}; + return true; + } +}; + +template <> +struct ParamTraits<int8_t> { + typedef int8_t paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + aWriter->WriteBytes(&aParam, sizeof(aParam)); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return aReader->ReadBytesInto(aResult, sizeof(*aResult)); + } +}; + +template <> +struct ParamTraits<uint8_t> { + typedef uint8_t paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + aWriter->WriteBytes(&aParam, sizeof(aParam)); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return aReader->ReadBytesInto(aResult, sizeof(*aResult)); + } +}; + +#if !defined(XP_UNIX) +// See above re: keeping definitions in sync +template <> +struct ParamTraits<base::FileDescriptor> { + typedef base::FileDescriptor paramType; + static void Write(MessageWriter* aWriter, const paramType& aParam) { + MOZ_CRASH("FileDescriptor isn't meaningful on this platform"); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + MOZ_CRASH("FileDescriptor isn't meaningful on this platform"); + return false; + } +}; +#endif // !defined(XP_UNIX) + +template <> +struct ParamTraits<mozilla::void_t> { + typedef mozilla::void_t paramType; + static void Write(MessageWriter* aWriter, const paramType& aParam) {} + static bool Read(MessageReader* aReader, paramType* aResult) { + *aResult = paramType(); + return true; + } +}; + +template <> +struct ParamTraits<mozilla::null_t> { + typedef mozilla::null_t paramType; + static void Write(MessageWriter* aWriter, const paramType& aParam) {} + static bool Read(MessageReader* aReader, paramType* aResult) { + *aResult = paramType(); + return true; + } +}; + +// Helper class for reading bitfields. +// If T has bitfields members, derive ParamTraits<T> from BitfieldHelper<T>. +template <typename ParamType> +struct BitfieldHelper { + // We need this helper because we can't get the address of a bitfield to + // pass directly to ReadParam. So instead we read it into a temporary bool + // and set the bitfield using a setter function + static bool ReadBoolForBitfield(MessageReader* aReader, ParamType* aResult, + void (ParamType::*aSetter)(bool)) { + bool value; + if (ReadParam(aReader, &value)) { + (aResult->*aSetter)(value); + return true; + } + return false; + } +}; + +// A couple of recursive helper functions, allows syntax like: +// WriteParams(aMsg, aParam.foo, aParam.bar, aParam.baz) +// ReadParams(aMsg, aIter, aParam.foo, aParam.bar, aParam.baz) + +template <typename... Ts> +static void WriteParams(MessageWriter* aWriter, const Ts&... aArgs) { + (WriteParam(aWriter, aArgs), ...); +} + +template <typename... Ts> +static bool ReadParams(MessageReader* aReader, Ts&... aArgs) { + return (ReadParam(aReader, &aArgs) && ...); +} + +// Macros that allow syntax like: +// DEFINE_IPC_SERIALIZER_WITH_FIELDS(SomeType, member1, member2, member3) +// Makes sure that serialize/deserialize code do the same members in the same +// order. +#define ACCESS_PARAM_FIELD(Field) aParam.Field + +#define DEFINE_IPC_SERIALIZER_WITH_FIELDS(Type, ...) \ + template <> \ + struct ParamTraits<Type> { \ + typedef Type paramType; \ + static void Write(MessageWriter* aWriter, const paramType& aParam) { \ + WriteParams(aWriter, MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), \ + (), (__VA_ARGS__))); \ + } \ + \ + static bool Read(MessageReader* aReader, paramType* aResult) { \ + paramType& aParam = *aResult; \ + return ReadParams(aReader, \ + MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \ + (__VA_ARGS__))); \ + } \ + }; + +#define DEFINE_IPC_SERIALIZER_WITHOUT_FIELDS(Type) \ + template <> \ + struct ParamTraits<Type> : public EmptyStructSerializer<Type> {}; + +} /* namespace IPC */ + +#define DEFINE_IPC_SERIALIZER_WITH_SUPER_CLASS_AND_FIELDS(Type, Super, ...) \ + template <> \ + struct ParamTraits<Type> { \ + typedef Type paramType; \ + static void Write(MessageWriter* aWriter, const paramType& aParam) { \ + WriteParam(aWriter, static_cast<const Super&>(aParam)); \ + WriteParams(aWriter, MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), \ + (), (__VA_ARGS__))); \ + } \ + \ + static bool Read(MessageReader* aReader, paramType* aResult) { \ + paramType& aParam = *aResult; \ + return ReadParam(aReader, static_cast<Super*>(aResult)) && \ + ReadParams(aReader, \ + MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \ + (__VA_ARGS__))); \ + } \ + }; + +#endif /* __IPC_GLUE_IPCMESSAGEUTILS_H__ */ diff --git a/ipc/glue/IPCMessageUtilsSpecializations.cpp b/ipc/glue/IPCMessageUtilsSpecializations.cpp new file mode 100644 index 0000000000..410b1e730f --- /dev/null +++ b/ipc/glue/IPCMessageUtilsSpecializations.cpp @@ -0,0 +1,67 @@ +/* -*- 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 "IPCMessageUtilsSpecializations.h" +#include "nsGkAtoms.h" + +namespace IPC { + +static const uint16_t kDynamicAtomToken = 0xffff; +static const uint16_t kAtomsCount = + static_cast<uint16_t>(mozilla::detail::GkAtoms::Atoms::AtomsCount); + +static_assert(static_cast<size_t>( + mozilla::detail::GkAtoms::Atoms::AtomsCount) == kAtomsCount, + "Number of static atoms must fit in a uint16_t"); + +static_assert(kDynamicAtomToken >= kAtomsCount, + "Exceeded supported number of static atoms"); + +/* static */ +void ParamTraits<nsAtom*>::Write(MessageWriter* aWriter, const nsAtom* aParam) { + MOZ_ASSERT(aParam); + + if (aParam->IsStatic()) { + const nsStaticAtom* atom = aParam->AsStatic(); + uint16_t index = static_cast<uint16_t>(nsGkAtoms::IndexOf(atom)); + MOZ_ASSERT(index < kAtomsCount); + WriteParam(aWriter, index); + return; + } + WriteParam(aWriter, kDynamicAtomToken); + nsDependentAtomString atomStr(aParam); + // nsDependentAtomString is serialized as its base, nsString, but we + // can be explicit about it. + nsString& str = atomStr; + WriteParam(aWriter, str); +} + +/* static */ +bool ParamTraits<nsAtom*>::Read(MessageReader* aReader, + RefPtr<nsAtom>* aResult) { + uint16_t token; + if (!ReadParam(aReader, &token)) { + return false; + } + if (token != kDynamicAtomToken) { + if (token >= kAtomsCount) { + return false; + } + *aResult = nsGkAtoms::GetAtomByIndex(token); + return true; + } + + nsAutoString str; + if (!ReadParam(aReader, static_cast<nsString*>(&str))) { + return false; + } + + *aResult = NS_Atomize(str); + MOZ_ASSERT(*aResult); + return true; +} + +} // namespace IPC diff --git a/ipc/glue/IPCMessageUtilsSpecializations.h b/ipc/glue/IPCMessageUtilsSpecializations.h new file mode 100644 index 0000000000..e9742c5c74 --- /dev/null +++ b/ipc/glue/IPCMessageUtilsSpecializations.h @@ -0,0 +1,841 @@ +/* -*- 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/. */ + +#ifndef __IPC_GLUE_IPCMESSAGEUTILSSPECIALIZATIONS_H__ +#define __IPC_GLUE_IPCMESSAGEUTILSSPECIALIZATIONS_H__ + +#include <cstdint> +#include <cstdlib> +#include <limits> +#include <string> +#include <type_traits> +#include <unordered_map> +#include <utility> +#include <vector> +#include "chrome/common/ipc_message.h" +#include "chrome/common/ipc_message_utils.h" +#include "ipc/EnumSerializer.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/BitSet.h" +#include "mozilla/EnumSet.h" +#include "mozilla/EnumTypeTraits.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/Maybe.h" +#include "mozilla/TimeStamp.h" +#ifdef XP_WIN +# include "mozilla/TimeStamp_windows.h" +#endif + +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/Vector.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/dom/UserActivation.h" +#include "nsCSSPropertyID.h" +#include "nsDebug.h" +#include "nsIContentPolicy.h" +#include "nsID.h" +#include "nsILoadInfo.h" +#include "nsIThread.h" +#include "nsLiteralString.h" +#include "nsNetUtil.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsTHashSet.h" + +// XXX Includes that are only required by implementations which could be moved +// to the cpp file. +#include "base/string_util.h" // for StringPrintf +#include "mozilla/ArrayUtils.h" // for ArrayLength +#include "mozilla/CheckedInt.h" + +#ifdef _MSC_VER +# pragma warning(disable : 4800) +#endif + +namespace mozilla { +template <typename... Ts> +class Variant; + +namespace detail { +template <typename... Ts> +struct VariantTag; +} +} // namespace mozilla + +namespace mozilla::dom { +template <typename T> +class Optional; +} + +class nsAtom; + +namespace IPC { + +template <class T> +struct ParamTraits<nsTSubstring<T>> { + typedef nsTSubstring<T> paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + bool isVoid = aParam.IsVoid(); + aWriter->WriteBool(isVoid); + + if (isVoid) { + // represents a nullptr pointer + return; + } + + WriteSequenceParam<const T&>(aWriter, aParam.BeginReading(), + aParam.Length()); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + bool isVoid; + if (!aReader->ReadBool(&isVoid)) { + return false; + } + + if (isVoid) { + aResult->SetIsVoid(true); + return true; + } + + return ReadSequenceParam<T>(aReader, [&](uint32_t aLength) -> T* { + T* data = nullptr; + aResult->GetMutableData(&data, aLength); + return data; + }); + } +}; + +template <class T> +struct ParamTraits<nsTString<T>> : ParamTraits<nsTSubstring<T>> {}; + +template <class T> +struct ParamTraits<nsTLiteralString<T>> : ParamTraits<nsTSubstring<T>> {}; + +template <class T, size_t N> +struct ParamTraits<nsTAutoStringN<T, N>> : ParamTraits<nsTSubstring<T>> {}; + +template <class T> +struct ParamTraits<nsTDependentString<T>> : ParamTraits<nsTSubstring<T>> {}; + +// XXX While this has no special dependencies, it's currently only used in +// GfxMessageUtils and could be moved there, or generalized to potentially work +// with any nsTHashSet. +template <> +struct ParamTraits<nsTHashSet<uint64_t>> { + typedef nsTHashSet<uint64_t> paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + uint32_t count = aParam.Count(); + WriteParam(aWriter, count); + for (const auto& key : aParam) { + WriteParam(aWriter, key); + } + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + uint32_t count; + if (!ReadParam(aReader, &count)) { + return false; + } + paramType table(count); + for (uint32_t i = 0; i < count; ++i) { + uint64_t key; + if (!ReadParam(aReader, &key)) { + return false; + } + table.Insert(key); + } + *aResult = std::move(table); + return true; + } +}; + +template <typename E> +struct ParamTraits<nsTArray<E>> { + typedef nsTArray<E> paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteSequenceParam<const E&>(aWriter, aParam.Elements(), aParam.Length()); + } + + static void Write(MessageWriter* aWriter, paramType&& aParam) { + WriteSequenceParam<E&&>(aWriter, aParam.Elements(), aParam.Length()); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadSequenceParam<E>(aReader, [&](uint32_t aLength) { + if constexpr (std::is_trivially_default_constructible_v<E>) { + return aResult->AppendElements(aLength); + } else { + aResult->SetCapacity(aLength); + return mozilla::Some(MakeBackInserter(*aResult)); + } + }); + } +}; + +template <typename E> +struct ParamTraits<CopyableTArray<E>> : ParamTraits<nsTArray<E>> {}; + +template <typename E> +struct ParamTraits<FallibleTArray<E>> { + typedef FallibleTArray<E> paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteSequenceParam<const E&>(aWriter, aParam.Elements(), aParam.Length()); + } + + static void Write(MessageWriter* aWriter, paramType&& aParam) { + WriteSequenceParam<E&&>(aWriter, aParam.Elements(), aParam.Length()); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadSequenceParam<E>(aReader, [&](uint32_t aLength) { + if constexpr (std::is_trivially_default_constructible_v<E>) { + return aResult->AppendElements(aLength, mozilla::fallible); + } else { + if (!aResult->SetCapacity(aLength, mozilla::fallible)) { + return mozilla::Maybe<BackInserter>{}; + } + return mozilla::Some(BackInserter{.mArray = aResult}); + } + }); + } + + private: + struct BackInserter { + using iterator_category = std::output_iterator_tag; + using value_type = void; + using difference_type = void; + using pointer = void; + using reference = void; + + struct Proxy { + paramType& mArray; + + template <typename U> + void operator=(U&& aValue) { + // This won't fail because we've reserved capacity earlier. + MOZ_ALWAYS_TRUE(mArray.AppendElement(aValue, mozilla::fallible)); + } + }; + Proxy operator*() { return Proxy{.mArray = *mArray}; } + + BackInserter& operator++() { return *this; } + BackInserter& operator++(int) { return *this; } + + paramType* mArray = nullptr; + }; +}; + +template <typename E, size_t N> +struct ParamTraits<AutoTArray<E, N>> : ParamTraits<nsTArray<E>> { + typedef AutoTArray<E, N> paramType; +}; + +template <typename E, size_t N> +struct ParamTraits<CopyableAutoTArray<E, N>> : ParamTraits<AutoTArray<E, N>> {}; + +template <typename T> +struct ParamTraits<mozilla::dom::Sequence<T>> : ParamTraits<FallibleTArray<T>> { +}; + +template <typename E, size_t N, typename AP> +struct ParamTraits<mozilla::Vector<E, N, AP>> { + typedef mozilla::Vector<E, N, AP> paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteSequenceParam<const E&>(aWriter, aParam.Elements(), aParam.Length()); + } + + static void Write(MessageWriter* aWriter, paramType&& aParam) { + WriteSequenceParam<E&&>(aWriter, aParam.Elements(), aParam.Length()); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadSequenceParam<E>(aReader, [&](uint32_t aLength) -> E* { + if (!aResult->resize(aLength)) { + // So that OOM failure shows up as OOM crash instead of IPC FatalError. + NS_ABORT_OOM(aLength * sizeof(E)); + } + return aResult->begin(); + }); + } +}; + +template <typename E> +struct ParamTraits<std::vector<E>> { + typedef std::vector<E> paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteSequenceParam<const E&>(aWriter, aParam.data(), aParam.size()); + } + static void Write(MessageWriter* aWriter, paramType&& aParam) { + WriteSequenceParam<E&&>(aWriter, aParam.data(), aParam.size()); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadSequenceParam<E>(aReader, [&](uint32_t aLength) { + if constexpr (std::is_trivially_default_constructible_v<E>) { + aResult->resize(aLength); + return aResult->data(); + } else { + aResult->reserve(aLength); + return mozilla::Some(std::back_inserter(*aResult)); + } + }); + } +}; + +template <typename K, typename V> +struct ParamTraits<std::unordered_map<K, V>> final { + using T = std::unordered_map<K, V>; + + static void Write(MessageWriter* const writer, const T& in) { + WriteParam(writer, in.size()); + for (const auto& pair : in) { + WriteParam(writer, pair.first); + WriteParam(writer, pair.second); + } + } + + static bool Read(MessageReader* const reader, T* const out) { + size_t size = 0; + if (!ReadParam(reader, &size)) return false; + T map; + map.reserve(size); + for (const auto i : mozilla::IntegerRange(size)) { + std::pair<K, V> pair; + mozilla::Unused << i; + if (!ReadParam(reader, &(pair.first)) || + !ReadParam(reader, &(pair.second))) { + return false; + } + map.insert(std::move(pair)); + } + *out = std::move(map); + return true; + } +}; + +template <> +struct ParamTraits<float> { + typedef float paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + aWriter->WriteBytes(&aParam, sizeof(paramType)); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return aReader->ReadBytesInto(aResult, sizeof(*aResult)); + } +}; + +template <> +struct ParamTraits<nsCSSPropertyID> + : public ContiguousEnumSerializer<nsCSSPropertyID, eCSSProperty_UNKNOWN, + eCSSProperty_COUNT> {}; + +template <> +struct ParamTraits<nsID> { + typedef nsID paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.m0); + WriteParam(aWriter, aParam.m1); + WriteParam(aWriter, aParam.m2); + for (unsigned int i = 0; i < mozilla::ArrayLength(aParam.m3); i++) { + WriteParam(aWriter, aParam.m3[i]); + } + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!ReadParam(aReader, &(aResult->m0)) || + !ReadParam(aReader, &(aResult->m1)) || + !ReadParam(aReader, &(aResult->m2))) + return false; + + for (unsigned int i = 0; i < mozilla::ArrayLength(aResult->m3); i++) + if (!ReadParam(aReader, &(aResult->m3[i]))) return false; + + return true; + } +}; + +template <> +struct ParamTraits<nsContentPolicyType> + : public ContiguousEnumSerializer<nsContentPolicyType, + nsIContentPolicy::TYPE_INVALID, + nsIContentPolicy::TYPE_END> {}; + +template <> +struct ParamTraits<mozilla::TimeDuration> { + typedef mozilla::TimeDuration paramType; + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mValue); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->mValue); + }; +}; + +template <> +struct ParamTraits<mozilla::TimeStamp> { + typedef mozilla::TimeStamp paramType; + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mValue); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->mValue); + }; +}; + +#ifdef XP_WIN + +template <> +struct ParamTraits<mozilla::TimeStampValue> { + typedef mozilla::TimeStampValue paramType; + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mGTC); + WriteParam(aWriter, aParam.mQPC); + WriteParam(aWriter, aParam.mIsNull); + WriteParam(aWriter, aParam.mHasQPC); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + return (ReadParam(aReader, &aResult->mGTC) && + ReadParam(aReader, &aResult->mQPC) && + ReadParam(aReader, &aResult->mIsNull) && + ReadParam(aReader, &aResult->mHasQPC)); + } +}; + +#endif + +template <> +struct ParamTraits<mozilla::dom::ipc::StructuredCloneData> { + typedef mozilla::dom::ipc::StructuredCloneData paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + aParam.WriteIPCParams(aWriter); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return aResult->ReadIPCParams(aReader); + } +}; + +template <class T> +struct ParamTraits<mozilla::Maybe<T>> { + typedef mozilla::Maybe<T> paramType; + + static void Write(MessageWriter* writer, const paramType& param) { + if (param.isSome()) { + WriteParam(writer, true); + WriteParam(writer, param.ref()); + } else { + WriteParam(writer, false); + } + } + + static void Write(MessageWriter* writer, paramType&& param) { + if (param.isSome()) { + WriteParam(writer, true); + WriteParam(writer, std::move(param.ref())); + } else { + WriteParam(writer, false); + } + } + + static bool Read(MessageReader* reader, paramType* result) { + bool isSome; + if (!ReadParam(reader, &isSome)) { + return false; + } + if (isSome) { + mozilla::Maybe<T> tmp = ReadParam<T>(reader).TakeMaybe(); + if (!tmp) { + return false; + } + *result = std::move(tmp); + } else { + *result = mozilla::Nothing(); + } + return true; + } +}; + +template <typename T, typename U> +struct ParamTraits<mozilla::EnumSet<T, U>> { + typedef mozilla::EnumSet<T, U> paramType; + typedef U serializedType; + + static void Write(MessageWriter* writer, const paramType& param) { + MOZ_RELEASE_ASSERT(IsLegalValue(param.serialize())); + WriteParam(writer, param.serialize()); + } + + static bool Read(MessageReader* reader, paramType* result) { + serializedType tmp; + + if (ReadParam(reader, &tmp)) { + if (IsLegalValue(tmp)) { + result->deserialize(tmp); + return true; + } + } + + return false; + } + + static constexpr serializedType AllEnumBits() { + return ~serializedType(0) >> (std::numeric_limits<serializedType>::digits - + (mozilla::MaxEnumValue<T>::value + 1)); + } + + static constexpr bool IsLegalValue(const serializedType value) { + static_assert(mozilla::MaxEnumValue<T>::value < + std::numeric_limits<serializedType>::digits, + "Enum max value is not in the range!"); + static_assert( + std::is_unsigned<decltype(mozilla::MaxEnumValue<T>::value)>::value, + "Type of MaxEnumValue<T>::value specialization should be unsigned!"); + + return (value & AllEnumBits()) == value; + } +}; + +template <class... Ts> +struct ParamTraits<mozilla::Variant<Ts...>> { + typedef mozilla::Variant<Ts...> paramType; + using Tag = typename mozilla::detail::VariantTag<Ts...>::Type; + + static void Write(MessageWriter* writer, const paramType& param) { + WriteParam(writer, param.tag); + param.match([writer](const auto& t) { WriteParam(writer, t); }); + } + + // Because VariantReader is a nested struct, we need the dummy template + // parameter to avoid making VariantReader<0> an explicit specialization, + // which is not allowed for a nested class template + template <size_t N, typename dummy = void> + struct VariantReader { + using Next = VariantReader<N - 1>; + + static bool Read(MessageReader* reader, Tag tag, paramType* result) { + // Since the VariantReader specializations start at N , we need to + // subtract one to look at N - 1, the first valid tag. This means our + // comparisons are off by 1. If we get to N = 0 then we have failed to + // find a match to the tag. + if (tag == N - 1) { + // Recall, even though the template parameter is N, we are + // actually interested in the N - 1 tag. + // Default construct our field within the result outparameter and + // directly deserialize into the variant. Note that this means that + // every type in Ts needs to be default constructible + return ReadParam(reader, &result->template emplace<N - 1>()); + } else { + return Next::Read(reader, tag, result); + } + } + + }; // VariantReader<N> + + // Since we are conditioning on tag = N - 1 in the preceding specialization, + // if we get to `VariantReader<0, dummy>` we have failed to find + // a matching tag. + template <typename dummy> + struct VariantReader<0, dummy> { + static bool Read(MessageReader* reader, Tag tag, paramType* result) { + return false; + } + }; + + static bool Read(MessageReader* reader, paramType* result) { + Tag tag; + if (ReadParam(reader, &tag)) { + return VariantReader<sizeof...(Ts)>::Read(reader, tag, result); + } + return false; + } +}; + +template <typename T> +struct ParamTraits<mozilla::dom::Optional<T>> { + typedef mozilla::dom::Optional<T> paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + if (aParam.WasPassed()) { + WriteParam(aWriter, true); + WriteParam(aWriter, aParam.Value()); + return; + } + + WriteParam(aWriter, false); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + bool wasPassed = false; + + if (!ReadParam(aReader, &wasPassed)) { + return false; + } + + aResult->Reset(); + + if (wasPassed) { + if (!ReadParam(aReader, &aResult->Construct())) { + return false; + } + } + + return true; + } +}; + +template <> +struct ParamTraits<nsAtom*> { + typedef nsAtom paramType; + + static void Write(MessageWriter* aWriter, const paramType* aParam); + static bool Read(MessageReader* aReader, RefPtr<paramType>* aResult); +}; + +struct CrossOriginOpenerPolicyValidator { + using IntegralType = + std::underlying_type_t<nsILoadInfo::CrossOriginOpenerPolicy>; + + static bool IsLegalValue(const IntegralType e) { + return AreIntegralValuesEqual(e, nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) || + AreIntegralValuesEqual(e, nsILoadInfo::OPENER_POLICY_SAME_ORIGIN) || + AreIntegralValuesEqual( + e, nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS) || + AreIntegralValuesEqual( + e, nsILoadInfo:: + OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP); + } + + private: + static bool AreIntegralValuesEqual( + const IntegralType aLhs, + const nsILoadInfo::CrossOriginOpenerPolicy aRhs) { + return aLhs == static_cast<IntegralType>(aRhs); + } +}; + +template <> +struct ParamTraits<nsILoadInfo::CrossOriginOpenerPolicy> + : EnumSerializer<nsILoadInfo::CrossOriginOpenerPolicy, + CrossOriginOpenerPolicyValidator> {}; + +struct CrossOriginEmbedderPolicyValidator { + using IntegralType = + std::underlying_type_t<nsILoadInfo::CrossOriginEmbedderPolicy>; + + static bool IsLegalValue(const IntegralType e) { + return AreIntegralValuesEqual(e, nsILoadInfo::EMBEDDER_POLICY_NULL) || + AreIntegralValuesEqual(e, + nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) || + AreIntegralValuesEqual(e, + nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS); + } + + private: + static bool AreIntegralValuesEqual( + const IntegralType aLhs, + const nsILoadInfo::CrossOriginEmbedderPolicy aRhs) { + return aLhs == static_cast<IntegralType>(aRhs); + } +}; + +template <> +struct ParamTraits<nsILoadInfo::CrossOriginEmbedderPolicy> + : EnumSerializer<nsILoadInfo::CrossOriginEmbedderPolicy, + CrossOriginEmbedderPolicyValidator> {}; + +template <> +struct ParamTraits<nsIThread::QoSPriority> + : public ContiguousEnumSerializerInclusive<nsIThread::QoSPriority, + nsIThread::QOS_PRIORITY_NORMAL, + nsIThread::QOS_PRIORITY_LOW> {}; + +template <size_t N, typename Word> +struct ParamTraits<mozilla::BitSet<N, Word>> { + typedef mozilla::BitSet<N, Word> paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + for (Word word : aParam.Storage()) { + WriteParam(aWriter, word); + } + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + for (Word& word : aResult->Storage()) { + if (!ReadParam(aReader, &word)) { + return false; + } + } + return true; + } +}; + +template <typename T> +struct ParamTraits<mozilla::UniquePtr<T>> { + typedef mozilla::UniquePtr<T> paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + bool isNull = aParam == nullptr; + WriteParam(aWriter, isNull); + + if (!isNull) { + WriteParam(aWriter, *aParam.get()); + } + } + + static bool Read(IPC::MessageReader* aReader, paramType* aResult) { + bool isNull = true; + if (!ReadParam(aReader, &isNull)) { + return false; + } + + if (isNull) { + aResult->reset(); + } else { + *aResult = mozilla::MakeUnique<T>(); + if (!ReadParam(aReader, aResult->get())) { + return false; + } + } + return true; + } +}; + +template <typename... Ts> +struct ParamTraits<std::tuple<Ts...>> { + typedef std::tuple<Ts...> paramType; + + template <typename U> + static void Write(IPC::MessageWriter* aWriter, U&& aParam) { + WriteInternal(aWriter, std::forward<U>(aParam), + std::index_sequence_for<Ts...>{}); + } + + static bool Read(IPC::MessageReader* aReader, std::tuple<Ts...>* aResult) { + return ReadInternal(aReader, *aResult, std::index_sequence_for<Ts...>{}); + } + + private: + template <size_t... Is> + static void WriteInternal(IPC::MessageWriter* aWriter, + const std::tuple<Ts...>& aParam, + std::index_sequence<Is...>) { + WriteParams(aWriter, std::get<Is>(aParam)...); + } + + template <size_t... Is> + static void WriteInternal(IPC::MessageWriter* aWriter, + std::tuple<Ts...>&& aParam, + std::index_sequence<Is...>) { + WriteParams(aWriter, std::move(std::get<Is>(aParam))...); + } + + template <size_t... Is> + static bool ReadInternal(IPC::MessageReader* aReader, + std::tuple<Ts...>& aResult, + std::index_sequence<Is...>) { + return ReadParams(aReader, std::get<Is>(aResult)...); + } +}; + +template <> +struct ParamTraits<mozilla::net::LinkHeader> { + typedef mozilla::net::LinkHeader paramType; + constexpr static int kNumberOfMembers = 14; + constexpr static int kSizeOfEachMember = sizeof(nsString); + constexpr static int kExpectedSizeOfParamType = + kNumberOfMembers * kSizeOfEachMember; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + static_assert(sizeof(paramType) == kExpectedSizeOfParamType, + "All members of should be written below."); + // Bug 1860565: `aParam.mAnchor` is not written. + + WriteParam(aWriter, aParam.mHref); + WriteParam(aWriter, aParam.mRel); + WriteParam(aWriter, aParam.mTitle); + WriteParam(aWriter, aParam.mNonce); + WriteParam(aWriter, aParam.mIntegrity); + WriteParam(aWriter, aParam.mSrcset); + WriteParam(aWriter, aParam.mSizes); + WriteParam(aWriter, aParam.mType); + WriteParam(aWriter, aParam.mMedia); + WriteParam(aWriter, aParam.mCrossOrigin); + WriteParam(aWriter, aParam.mReferrerPolicy); + WriteParam(aWriter, aParam.mAs); + WriteParam(aWriter, aParam.mFetchPriority); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + static_assert(sizeof(paramType) == kExpectedSizeOfParamType, + "All members of should be handled below."); + // Bug 1860565: `aParam.mAnchor` is not handled. + + if (!ReadParam(aReader, &aResult->mHref)) { + return false; + } + if (!ReadParam(aReader, &aResult->mRel)) { + return false; + } + if (!ReadParam(aReader, &aResult->mTitle)) { + return false; + } + if (!ReadParam(aReader, &aResult->mNonce)) { + return false; + } + if (!ReadParam(aReader, &aResult->mIntegrity)) { + return false; + } + if (!ReadParam(aReader, &aResult->mSrcset)) { + return false; + } + if (!ReadParam(aReader, &aResult->mSizes)) { + return false; + } + if (!ReadParam(aReader, &aResult->mType)) { + return false; + } + if (!ReadParam(aReader, &aResult->mMedia)) { + return false; + } + if (!ReadParam(aReader, &aResult->mCrossOrigin)) { + return false; + } + if (!ReadParam(aReader, &aResult->mReferrerPolicy)) { + return false; + } + if (!ReadParam(aReader, &aResult->mAs)) { + return false; + } + return ReadParam(aReader, &aResult->mFetchPriority); + }; +}; + +template <> +struct ParamTraits<mozilla::dom::UserActivation::Modifiers> { + typedef mozilla::dom::UserActivation::Modifiers paramType; + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mModifiers); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->mModifiers); + }; +}; + +} /* namespace IPC */ + +#endif /* __IPC_GLUE_IPCMESSAGEUTILSSPECIALIZATIONS_H__ */ diff --git a/ipc/glue/IPCStream.ipdlh b/ipc/glue/IPCStream.ipdlh new file mode 100644 index 0000000000..abf7ef7714 --- /dev/null +++ b/ipc/glue/IPCStream.ipdlh @@ -0,0 +1,22 @@ +/* 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 InputStreamParams; +include ProtocolTypes; + +namespace mozilla { +namespace ipc { + +// Use IPCStream in your ipdl to represent serialized nsIInputStreams. Then use +// SerializeIPCStream from IPCStreamUtils.h to perform the serialization. +// +// NOTE: If you don't need to handle nsIInputStream serialization failure, +// `nsIInputStream` may be used directly by IPDL protocols. +struct IPCStream +{ + InputStreamParams stream; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/IPCStreamUtils.cpp b/ipc/glue/IPCStreamUtils.cpp new file mode 100644 index 0000000000..b884bf5d5d --- /dev/null +++ b/ipc/glue/IPCStreamUtils.cpp @@ -0,0 +1,191 @@ +/* -*- 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 "IPCStreamUtils.h" + +#include "ipc/IPCMessageUtilsSpecializations.h" + +#include "nsIHttpHeaderVisitor.h" +#include "nsIIPCSerializableInputStream.h" +#include "mozIRemoteLazyInputStream.h" + +#include "mozilla/Assertions.h" +#include "mozilla/dom/File.h" +#include "mozilla/ipc/IPCStream.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/InputStreamLengthHelper.h" +#include "mozilla/RemoteLazyInputStreamParent.h" +#include "mozilla/Unused.h" +#include "nsIMIMEInputStream.h" +#include "nsNetCID.h" + +using namespace mozilla::dom; + +namespace mozilla::ipc { + +namespace { + +class MIMEStreamHeaderVisitor final : public nsIHttpHeaderVisitor { + public: + explicit MIMEStreamHeaderVisitor( + nsTArray<mozilla::ipc::HeaderEntry>& aHeaders) + : mHeaders(aHeaders) {} + + NS_DECL_ISUPPORTS + NS_IMETHOD VisitHeader(const nsACString& aName, + const nsACString& aValue) override { + auto el = mHeaders.AppendElement(); + el->name() = aName; + el->value() = aValue; + return NS_OK; + } + + private: + ~MIMEStreamHeaderVisitor() = default; + + nsTArray<mozilla::ipc::HeaderEntry>& mHeaders; +}; + +NS_IMPL_ISUPPORTS(MIMEStreamHeaderVisitor, nsIHttpHeaderVisitor) + +static bool SerializeLazyInputStream(nsIInputStream* aStream, + IPCStream& aValue) { + MOZ_ASSERT(aStream); + MOZ_ASSERT(XRE_IsParentProcess()); + + // If we're serializing a MIME stream, ensure we preserve header data which + // would not be preserved by a RemoteLazyInputStream wrapper. + if (nsCOMPtr<nsIMIMEInputStream> mimeStream = do_QueryInterface(aStream)) { + MIMEInputStreamParams params; + params.startedReading() = false; + + nsCOMPtr<nsIHttpHeaderVisitor> visitor = + new MIMEStreamHeaderVisitor(params.headers()); + if (NS_WARN_IF(NS_FAILED(mimeStream->VisitHeaders(visitor)))) { + return false; + } + + nsCOMPtr<nsIInputStream> dataStream; + if (NS_FAILED(mimeStream->GetData(getter_AddRefs(dataStream)))) { + return false; + } + if (dataStream) { + IPCStream data; + if (!SerializeLazyInputStream(dataStream, data)) { + return false; + } + params.optionalStream().emplace(std::move(data.stream())); + } + + aValue.stream() = std::move(params); + return true; + } + + RefPtr<RemoteLazyInputStream> lazyStream = + RemoteLazyInputStream::WrapStream(aStream); + if (NS_WARN_IF(!lazyStream)) { + return false; + } + + aValue.stream() = RemoteLazyInputStreamParams(WrapNotNull(lazyStream)); + + return true; +} + +} // anonymous namespace + +bool SerializeIPCStream(already_AddRefed<nsIInputStream> aInputStream, + IPCStream& aValue, bool aAllowLazy) { + nsCOMPtr<nsIInputStream> stream(std::move(aInputStream)); + if (!stream) { + MOZ_ASSERT_UNREACHABLE( + "Use the Maybe<...> overload to serialize optional nsIInputStreams"); + return false; + } + + // When requesting a delayed start stream from the parent process, serialize + // it as a remote lazy stream to avoid bloating payloads. + if (aAllowLazy && XRE_IsParentProcess()) { + return SerializeLazyInputStream(stream, aValue); + } + + if (nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(stream)) { + // If you change this size, please also update the payload size in + // test_reload_large_postdata.html. + const uint64_t kTooLargeStream = 1024 * 1024; + + uint32_t sizeUsed = 0; + serializable->Serialize(aValue.stream(), kTooLargeStream, &sizeUsed); + + MOZ_ASSERT(sizeUsed <= kTooLargeStream); + + if (aValue.stream().type() == InputStreamParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } + return true; + } + + InputStreamHelper::SerializeInputStreamAsPipe(stream, aValue.stream()); + if (aValue.stream().type() == InputStreamParams::T__None) { + MOZ_ASSERT_UNREACHABLE("Serializing as a pipe failed"); + return false; + } + return true; +} + +bool SerializeIPCStream(already_AddRefed<nsIInputStream> aInputStream, + Maybe<IPCStream>& aValue, bool aAllowLazy) { + nsCOMPtr<nsIInputStream> stream(std::move(aInputStream)); + if (!stream) { + aValue.reset(); + return true; + } + + IPCStream value; + if (SerializeIPCStream(stream.forget(), value, aAllowLazy)) { + aValue = Some(value); + return true; + } + return false; +} + +already_AddRefed<nsIInputStream> DeserializeIPCStream(const IPCStream& aValue) { + return InputStreamHelper::DeserializeInputStream(aValue.stream()); +} + +already_AddRefed<nsIInputStream> DeserializeIPCStream( + const Maybe<IPCStream>& aValue) { + if (aValue.isNothing()) { + return nullptr; + } + + return DeserializeIPCStream(aValue.ref()); +} + +} // namespace mozilla::ipc + +void IPC::ParamTraits<nsIInputStream*>::Write(IPC::MessageWriter* aWriter, + nsIInputStream* aParam) { + mozilla::Maybe<mozilla::ipc::IPCStream> stream; + if (!mozilla::ipc::SerializeIPCStream(do_AddRef(aParam), stream, + /* aAllowLazy */ true)) { + MOZ_CRASH("Failed to serialize nsIInputStream"); + } + + WriteParam(aWriter, stream); +} + +bool IPC::ParamTraits<nsIInputStream*>::Read(IPC::MessageReader* aReader, + RefPtr<nsIInputStream>* aResult) { + mozilla::Maybe<mozilla::ipc::IPCStream> ipcStream; + if (!ReadParam(aReader, &ipcStream)) { + return false; + } + + *aResult = mozilla::ipc::DeserializeIPCStream(ipcStream); + return true; +} diff --git a/ipc/glue/IPCStreamUtils.h b/ipc/glue/IPCStreamUtils.h new file mode 100644 index 0000000000..c681f7cf1a --- /dev/null +++ b/ipc/glue/IPCStreamUtils.h @@ -0,0 +1,51 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_IPCStreamUtils_h +#define mozilla_ipc_IPCStreamUtils_h + +#include "mozilla/ipc/IPCStream.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" + +namespace mozilla::ipc { + +// Serialize an IPCStream to be sent over IPC fallibly. +// +// If |aAllowLazy| is true the stream may be serialized as a +// RemoteLazyInputStream when being sent from child to parent. +// +// ParamTraits<nsIInputStream> may be used instead if serialization cannot be +// fallible. +[[nodiscard]] bool SerializeIPCStream( + already_AddRefed<nsIInputStream> aInputStream, IPCStream& aValue, + bool aAllowLazy); + +// If serialization fails, `aValue` will be initialized to `Nothing()`, so this +// return value is safe to ignore. +bool SerializeIPCStream(already_AddRefed<nsIInputStream> aInputStream, + Maybe<IPCStream>& aValue, bool aAllowLazy); + +// Deserialize an IPCStream received from an actor call. These methods +// work in both the child and parent. +already_AddRefed<nsIInputStream> DeserializeIPCStream(const IPCStream& aValue); + +already_AddRefed<nsIInputStream> DeserializeIPCStream( + const Maybe<IPCStream>& aValue); + +} // namespace mozilla::ipc + +namespace IPC { + +template <> +struct ParamTraits<nsIInputStream*> { + static void Write(MessageWriter* aWriter, nsIInputStream* aParam); + static bool Read(MessageReader* aReader, RefPtr<nsIInputStream>* aResult); +}; + +} // namespace IPC + +#endif // mozilla_ipc_IPCStreamUtils_h diff --git a/ipc/glue/IPCTypes.h b/ipc/glue/IPCTypes.h new file mode 100644 index 0000000000..fc6bf765e2 --- /dev/null +++ b/ipc/glue/IPCTypes.h @@ -0,0 +1,20 @@ +/* -*- 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/. */ + +#ifndef IPC_GLUE_IPCTYPES_H_ +#define IPC_GLUE_IPCTYPES_H_ + +#include <cstdint> + +namespace mozilla { + +// This is a cross-platform approximation to HANDLE, which we expect +// to be typedef'd to void* or thereabouts. +typedef uintptr_t WindowsHandle; + +} // namespace mozilla + +#endif // IPC_GLUE_IPCTYPES_H_ diff --git a/ipc/glue/IPDLParamTraits.h b/ipc/glue/IPDLParamTraits.h new file mode 100644 index 0000000000..48649d92de --- /dev/null +++ b/ipc/glue/IPDLParamTraits.h @@ -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/. */ + +#ifndef mozilla_ipc_IPDLParamTraits_h +#define mozilla_ipc_IPDLParamTraits_h + +#include "chrome/common/ipc_message_utils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Variant.h" + +#include "nsTArray.h" + +#include <type_traits> + +namespace mozilla { +namespace ipc { + +class IProtocol; + +// +// IPDLParamTraits was an extended version of ParamTraits. Unlike ParamTraits, +// IPDLParamTraits passes an additional IProtocol* argument to the +// write and read methods. +// +// Previously this was required for serializing and deserializing types which +// require knowledge of which protocol they're being sent over, however the +// actor is now available through `IPC::Message{Writer,Reader}::GetActor()` so +// the extra argument is no longer necessary. Please use `IPC::ParamTraits` in +// the future. +// +// Types which implement IPDLParamTraits are also supported by ParamTraits. +// +template <typename P> +struct IPDLParamTraits {}; + +// +// WriteIPDLParam and ReadIPDLParam are like IPC::WriteParam and IPC::ReadParam, +// however, they also accept a redundant extra actor argument. +// +// NOTE: WriteIPDLParam takes a universal reference, so that it can support +// whatever reference type is supported by the underlying ParamTraits::Write +// implementation. +// +template <typename P> +static MOZ_NEVER_INLINE void WriteIPDLParam(IPC::MessageWriter* aWriter, + IProtocol* aActor, P&& aParam) { + MOZ_ASSERT(aActor == aWriter->GetActor()); + IPC::WriteParam(aWriter, std::forward<P>(aParam)); +} + +template <typename P> +static MOZ_NEVER_INLINE bool ReadIPDLParam(IPC::MessageReader* aReader, + IProtocol* aActor, P* aResult) { + MOZ_ASSERT(aActor == aReader->GetActor()); + return IPC::ReadParam(aReader, aResult); +} + +} // namespace ipc +} // namespace mozilla + +#endif // defined(mozilla_ipc_IPDLParamTraits_h) diff --git a/ipc/glue/IPDLStructMember.h b/ipc/glue/IPDLStructMember.h new file mode 100644 index 0000000000..d2e3636054 --- /dev/null +++ b/ipc/glue/IPDLStructMember.h @@ -0,0 +1,37 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_IPDLStructMember_h +#define mozilla_ipc_IPDLStructMember_h + +#include <type_traits> +#include <utility> +#include "mozilla/Attributes.h" + +namespace mozilla::ipc { + +// For types which are trivially default constructible, like `int`, force the +// value constructor by wrapping the type in this struct. This is an +// implementation detail which will be hidden by the generated IPDL compiler +// code. +template <typename T> +struct IPDLStructMemberWrapper { + template <typename... Args> + MOZ_IMPLICIT IPDLStructMemberWrapper(Args&&... aArgs) + : mVal(std::forward<Args>(aArgs)...) {} + MOZ_IMPLICIT operator T&() { return mVal; } + MOZ_IMPLICIT operator const T&() const { return mVal; } + T mVal{}; +}; + +template <typename T> +using IPDLStructMember = + std::conditional_t<std::is_trivially_default_constructible_v<T>, + IPDLStructMemberWrapper<T>, T>; + +} // namespace mozilla::ipc + +#endif // mozilla_ipc_IPDLStructMember_h diff --git a/ipc/glue/IdleSchedulerChild.cpp b/ipc/glue/IdleSchedulerChild.cpp new file mode 100644 index 0000000000..31cfb5457d --- /dev/null +++ b/ipc/glue/IdleSchedulerChild.cpp @@ -0,0 +1,151 @@ +/* -*- 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 "mozilla/ipc/IdleSchedulerChild.h" +#include "mozilla/ipc/IdleSchedulerParent.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/Atomics.h" +#include "mozilla/IdlePeriodState.h" +#include "mozilla/Telemetry.h" +#include "BackgroundChild.h" + +namespace mozilla::ipc { + +static IdleSchedulerChild* sMainThreadIdleScheduler = nullptr; +static bool sIdleSchedulerDestroyed = false; + +IdleSchedulerChild::~IdleSchedulerChild() { + if (sMainThreadIdleScheduler == this) { + sMainThreadIdleScheduler = nullptr; + sIdleSchedulerDestroyed = true; + } + MOZ_ASSERT(!mIdlePeriodState); +} + +void IdleSchedulerChild::Init(IdlePeriodState* aIdlePeriodState) { + mIdlePeriodState = aIdlePeriodState; + + RefPtr<IdleSchedulerChild> scheduler = this; + auto resolve = + [&](std::tuple<mozilla::Maybe<SharedMemoryHandle>, uint32_t>&& aResult) { + if (std::get<0>(aResult)) { + mActiveCounter.SetHandle(std::move(*std::get<0>(aResult)), false); + mActiveCounter.Map(sizeof(int32_t)); + mChildId = std::get<1>(aResult); + if (mChildId && mIdlePeriodState && mIdlePeriodState->IsActive()) { + SetActive(); + } + } + }; + + auto reject = [&](ResponseRejectReason) {}; + SendInitForIdleUse(std::move(resolve), std::move(reject)); +} + +IPCResult IdleSchedulerChild::RecvIdleTime(uint64_t aId, TimeDuration aBudget) { + if (mIdlePeriodState) { + mIdlePeriodState->SetIdleToken(aId, aBudget); + } + return IPC_OK(); +} + +void IdleSchedulerChild::SetActive() { + if (mChildId && CanSend() && mActiveCounter.memory()) { + ++(static_cast<Atomic<int32_t>*>( + mActiveCounter.memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER]); + ++(static_cast<Atomic<int32_t>*>(mActiveCounter.memory())[mChildId]); + } +} + +bool IdleSchedulerChild::SetPaused() { + if (mChildId && CanSend() && mActiveCounter.memory()) { + --(static_cast<Atomic<int32_t>*>(mActiveCounter.memory())[mChildId]); + // The following expression reduces the global activity count and checks if + // it drops below the cpu counter limit. + return (static_cast<Atomic<int32_t>*>( + mActiveCounter + .memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER])-- == + static_cast<Atomic<int32_t>*>( + mActiveCounter.memory())[NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER]; + } + + return false; +} + +RefPtr<IdleSchedulerChild::MayGCPromise> IdleSchedulerChild::MayGCNow() { + if (mIsRequestingGC || mIsDoingGC) { + return MayGCPromise::CreateAndResolve(false, __func__); + } + + mIsRequestingGC = true; + return SendRequestGC()->Then( + GetMainThreadSerialEventTarget(), __func__, + [self = RefPtr(this)](bool aIgnored) { + // Only one of these may be true at a time. + MOZ_ASSERT(!(self->mIsRequestingGC && self->mIsDoingGC)); + + // The parent process always says yes, sometimes after a delay. + if (self->mIsRequestingGC) { + self->mIsRequestingGC = false; + self->mIsDoingGC = true; + return MayGCPromise::CreateAndResolve(true, __func__); + } + return MayGCPromise::CreateAndResolve(false, __func__); + }, + [self = RefPtr(this)](ResponseRejectReason reason) { + self->mIsRequestingGC = false; + return MayGCPromise::CreateAndReject(reason, __func__); + }); +} + +void IdleSchedulerChild::StartedGC() { + // Only one of these may be true at a time. + MOZ_ASSERT(!(mIsRequestingGC && mIsDoingGC)); + + // If mRequestingGC was true then when the outstanding GC request returns + // it'll see that the GC has already started. + mIsRequestingGC = false; + + if (!mIsDoingGC) { + if (CanSend()) { + SendStartedGC(); + } + mIsDoingGC = true; + } +} + +void IdleSchedulerChild::DoneGC() { + if (mIsDoingGC) { + if (CanSend()) { + SendDoneGC(); + } + mIsDoingGC = false; + } +} + +IdleSchedulerChild* IdleSchedulerChild::GetMainThreadIdleScheduler() { + MOZ_ASSERT(NS_IsMainThread()); + + if (sMainThreadIdleScheduler) { + return sMainThreadIdleScheduler; + } + + if (sIdleSchedulerDestroyed) { + return nullptr; + } + + ipc::PBackgroundChild* background = + ipc::BackgroundChild::GetOrCreateForCurrentThread(); + if (background) { + // this is nulled out on our destruction, so we don't need to worry + sMainThreadIdleScheduler = new ipc::IdleSchedulerChild(); + MOZ_ALWAYS_TRUE( + background->SendPIdleSchedulerConstructor(sMainThreadIdleScheduler)); + } + return sMainThreadIdleScheduler; +} + +} // namespace mozilla::ipc diff --git a/ipc/glue/IdleSchedulerChild.h b/ipc/glue/IdleSchedulerChild.h new file mode 100644 index 0000000000..cf93084bfc --- /dev/null +++ b/ipc/glue/IdleSchedulerChild.h @@ -0,0 +1,75 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_IdleSchedulerChild_h__ +#define mozilla_ipc_IdleSchedulerChild_h__ + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/ipc/PIdleSchedulerChild.h" + +class nsIIdlePeriod; + +namespace mozilla { +class IdlePeriodState; + +namespace ipc { + +class BackgroundChildImpl; + +class IdleSchedulerChild final : public PIdleSchedulerChild { + public: + IdleSchedulerChild() = default; + + NS_INLINE_DECL_REFCOUNTING(IdleSchedulerChild) + + IPCResult RecvIdleTime(uint64_t aId, TimeDuration aBudget); + + void Init(IdlePeriodState* aIdlePeriodState); + + void Disconnect() { mIdlePeriodState = nullptr; } + + // See similar methods on PrioritizedEventQueue. + void SetActive(); + // Returns true if activity state dropped below cpu count. + bool SetPaused(); + + typedef MozPromise<bool, ResponseRejectReason, true> MayGCPromise; + + // Returns null if a GC or GC request is already in progress. + RefPtr<MayGCPromise> MayGCNow(); + + // Regardless of how a GC is started we get informed via these. + void StartedGC(); + void DoneGC(); + + // Returns nullptr if this is the parent process or the IdleSchedulerChild has + // already been destroyed, eg if IPC is shutting down. + static IdleSchedulerChild* GetMainThreadIdleScheduler(); + + private: + ~IdleSchedulerChild(); + + friend class BackgroundChildImpl; + + // See IdleScheduleParent::sActiveChildCounter + base::SharedMemory mActiveCounter; + + IdlePeriodState* mIdlePeriodState = nullptr; + + uint32_t mChildId = 0; + + // These fields replicate those in IdleSchedulerParent. Tracking them here + // ensures we don't send confusing information to the parent, while + // nsJSEnvironment is free to tell us about any GCs. + bool mIsRequestingGC = false; + bool mIsDoingGC = false; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_IdleSchedulerChild_h__ diff --git a/ipc/glue/IdleSchedulerParent.cpp b/ipc/glue/IdleSchedulerParent.cpp new file mode 100644 index 0000000000..7f91e9e754 --- /dev/null +++ b/ipc/glue/IdleSchedulerParent.cpp @@ -0,0 +1,452 @@ +/* -*- 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 "mozilla/StaticPrefs_page_load.h" +#include "mozilla/StaticPrefs_javascript.h" +#include "mozilla/Unused.h" +#include "mozilla/ipc/IdleSchedulerParent.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/Telemetry.h" +#include "nsSystemInfo.h" +#include "nsThreadUtils.h" +#include "nsITimer.h" +#include "nsIThread.h" + +namespace mozilla::ipc { + +base::SharedMemory* IdleSchedulerParent::sActiveChildCounter = nullptr; +std::bitset<NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT> + IdleSchedulerParent::sInUseChildCounters; +LinkedList<IdleSchedulerParent> IdleSchedulerParent::sIdleAndGCRequests; +int32_t IdleSchedulerParent::sMaxConcurrentIdleTasksInChildProcesses = 1; +uint32_t IdleSchedulerParent::sMaxConcurrentGCs = 1; +uint32_t IdleSchedulerParent::sActiveGCs = 0; +uint32_t IdleSchedulerParent::sChildProcessesRunningPrioritizedOperation = 0; +uint32_t IdleSchedulerParent::sChildProcessesAlive = 0; +nsITimer* IdleSchedulerParent::sStarvationPreventer = nullptr; + +uint32_t IdleSchedulerParent::sNumCPUs = 0; +uint32_t IdleSchedulerParent::sPrefConcurrentGCsMax = 0; +uint32_t IdleSchedulerParent::sPrefConcurrentGCsCPUDivisor = 0; + +IdleSchedulerParent::IdleSchedulerParent() { + MOZ_ASSERT(!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads)); + + sChildProcessesAlive++; + + uint32_t max_gcs_pref = + StaticPrefs::javascript_options_concurrent_multiprocess_gcs_max(); + uint32_t cpu_divisor_pref = + StaticPrefs::javascript_options_concurrent_multiprocess_gcs_cpu_divisor(); + if (!max_gcs_pref) { + max_gcs_pref = UINT32_MAX; + } + if (!cpu_divisor_pref) { + cpu_divisor_pref = 4; + } + + if (!sNumCPUs) { + // While waiting for the real logical core count behave as if there was + // just one core. + sNumCPUs = 1; + + // nsISystemInfo can be initialized only on the main thread. + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + nsCOMPtr<nsIRunnable> runnable = + NS_NewRunnableFunction("cpucount getter", [thread]() { + ProcessInfo processInfo = {}; + if (NS_SUCCEEDED(CollectProcessInfo(processInfo))) { + uint32_t num_cpus = processInfo.cpuCount; + // We have a new cpu count, Update the number of idle tasks. + if (MOZ_LIKELY(!AppShutdown::IsInOrBeyond( + ShutdownPhase::XPCOMShutdownThreads))) { + nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction( + "IdleSchedulerParent::CalculateNumIdleTasks", [num_cpus]() { + // We're setting this within this lambda because it's run on + // the correct thread and avoids a race. + sNumCPUs = num_cpus; + + // This reads the sPrefConcurrentGCsMax and + // sPrefConcurrentGCsCPUDivisor values set below, it will + // run after the code that sets those. + CalculateNumIdleTasks(); + }); + + thread->Dispatch(runnable, NS_DISPATCH_NORMAL); + } + } + }); + NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK); + } + + if (sPrefConcurrentGCsMax != max_gcs_pref || + sPrefConcurrentGCsCPUDivisor != cpu_divisor_pref) { + // We execute this if these preferences have changed. We also want to make + // sure it executes for the first IdleSchedulerParent, which it does because + // sPrefConcurrentGCsMax and sPrefConcurrentGCsCPUDivisor are initially + // zero. + sPrefConcurrentGCsMax = max_gcs_pref; + sPrefConcurrentGCsCPUDivisor = cpu_divisor_pref; + + CalculateNumIdleTasks(); + } +} + +void IdleSchedulerParent::CalculateNumIdleTasks() { + MOZ_ASSERT(sNumCPUs); + MOZ_ASSERT(sPrefConcurrentGCsMax); + MOZ_ASSERT(sPrefConcurrentGCsCPUDivisor); + + // On one and two processor (or hardware thread) systems this will + // allow one concurrent idle task. + sMaxConcurrentIdleTasksInChildProcesses = int32_t(std::max(sNumCPUs, 1u)); + sMaxConcurrentGCs = + std::min(std::max(sNumCPUs / sPrefConcurrentGCsCPUDivisor, 1u), + sPrefConcurrentGCsMax); + + if (sActiveChildCounter && sActiveChildCounter->memory()) { + static_cast<Atomic<int32_t>*>( + sActiveChildCounter->memory())[NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER] = + static_cast<int32_t>(sMaxConcurrentIdleTasksInChildProcesses); + } + IdleSchedulerParent::Schedule(nullptr); +} + +IdleSchedulerParent::~IdleSchedulerParent() { + // We can't know if an active process just crashed, so we just always expect + // that is the case. + if (mChildId) { + sInUseChildCounters[mChildId] = false; + if (sActiveChildCounter && sActiveChildCounter->memory() && + static_cast<Atomic<int32_t>*>( + sActiveChildCounter->memory())[mChildId]) { + --static_cast<Atomic<int32_t>*>( + sActiveChildCounter + ->memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER]; + static_cast<Atomic<int32_t>*>(sActiveChildCounter->memory())[mChildId] = + 0; + } + } + + if (mRunningPrioritizedOperation) { + --sChildProcessesRunningPrioritizedOperation; + } + + if (mDoingGC) { + // Give back our GC token. + sActiveGCs--; + } + + if (mRequestingGC) { + mRequestingGC.value()(false); + mRequestingGC = Nothing(); + } + + // Remove from the scheduler's queue. + if (isInList()) { + remove(); + } + + MOZ_ASSERT(sChildProcessesAlive > 0); + sChildProcessesAlive--; + if (sChildProcessesAlive == 0) { + MOZ_ASSERT(sIdleAndGCRequests.isEmpty()); + delete sActiveChildCounter; + sActiveChildCounter = nullptr; + + if (sStarvationPreventer) { + sStarvationPreventer->Cancel(); + NS_RELEASE(sStarvationPreventer); + } + } + + Schedule(nullptr); +} + +IPCResult IdleSchedulerParent::RecvInitForIdleUse( + InitForIdleUseResolver&& aResolve) { + // This must already be non-zero, if it is zero then the cleanup code for the + // shared memory (initialised below) will never run. The invariant is that if + // the shared memory is initialsed, then this is non-zero. + MOZ_ASSERT(sChildProcessesAlive > 0); + + MOZ_ASSERT(IsNotDoingIdleTask()); + + // Create a shared memory object which is shared across all the relevant + // processes. + if (!sActiveChildCounter) { + sActiveChildCounter = new base::SharedMemory(); + size_t shmemSize = NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT * sizeof(int32_t); + if (sActiveChildCounter->Create(shmemSize) && + sActiveChildCounter->Map(shmemSize)) { + memset(sActiveChildCounter->memory(), 0, shmemSize); + sInUseChildCounters[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER] = true; + sInUseChildCounters[NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER] = true; + static_cast<Atomic<int32_t>*>( + sActiveChildCounter + ->memory())[NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER] = + static_cast<int32_t>(sMaxConcurrentIdleTasksInChildProcesses); + } else { + delete sActiveChildCounter; + sActiveChildCounter = nullptr; + } + } + Maybe<SharedMemoryHandle> activeCounter; + if (SharedMemoryHandle handle = + sActiveChildCounter ? sActiveChildCounter->CloneHandle() : nullptr) { + activeCounter.emplace(std::move(handle)); + } + + uint32_t unusedId = 0; + for (uint32_t i = 0; i < NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT; ++i) { + if (!sInUseChildCounters[i]) { + sInUseChildCounters[i] = true; + unusedId = i; + break; + } + } + + // If there wasn't an empty item, we'll fallback to 0. + mChildId = unusedId; + + aResolve(std::tuple<mozilla::Maybe<SharedMemoryHandle>&&, const uint32_t&>( + std::move(activeCounter), mChildId)); + return IPC_OK(); +} + +IPCResult IdleSchedulerParent::RecvRequestIdleTime(uint64_t aId, + TimeDuration aBudget) { + MOZ_ASSERT(aBudget); + MOZ_ASSERT(IsNotDoingIdleTask()); + + mCurrentRequestId = aId; + mRequestedIdleBudget = aBudget; + + if (!isInList()) { + sIdleAndGCRequests.insertBack(this); + } + + Schedule(this); + return IPC_OK(); +} + +IPCResult IdleSchedulerParent::RecvIdleTimeUsed(uint64_t aId) { + // The client can either signal that they've used the idle time or they're + // canceling the request. We cannot use a seperate cancel message because it + // could arrive after the parent has granted the request. + MOZ_ASSERT(IsWaitingForIdle() || IsDoingIdleTask()); + + // The parent process will always know the ID of the current request (since + // the IPC channel is reliable). The IDs are provided so that the client can + // check them (it's possible for the client to race ahead of the server). + MOZ_ASSERT(mCurrentRequestId == aId); + + if (IsWaitingForIdle() && !mRequestingGC) { + remove(); + } + mRequestedIdleBudget = TimeDuration(); + Schedule(nullptr); + return IPC_OK(); +} + +IPCResult IdleSchedulerParent::RecvSchedule() { + Schedule(nullptr); + return IPC_OK(); +} + +IPCResult IdleSchedulerParent::RecvRunningPrioritizedOperation() { + ++mRunningPrioritizedOperation; + if (mRunningPrioritizedOperation == 1) { + ++sChildProcessesRunningPrioritizedOperation; + } + return IPC_OK(); +} + +IPCResult IdleSchedulerParent::RecvPrioritizedOperationDone() { + MOZ_ASSERT(mRunningPrioritizedOperation); + + --mRunningPrioritizedOperation; + if (mRunningPrioritizedOperation == 0) { + --sChildProcessesRunningPrioritizedOperation; + Schedule(nullptr); + } + return IPC_OK(); +} + +IPCResult IdleSchedulerParent::RecvRequestGC(RequestGCResolver&& aResolver) { + MOZ_ASSERT(!mDoingGC); + MOZ_ASSERT(!mRequestingGC); + + mRequestingGC = Some(aResolver); + if (!isInList()) { + sIdleAndGCRequests.insertBack(this); + } + + Schedule(nullptr); + return IPC_OK(); +} + +IPCResult IdleSchedulerParent::RecvStartedGC() { + if (mDoingGC) { + return IPC_OK(); + } + + mDoingGC = true; + sActiveGCs++; + + if (mRequestingGC) { + // We have to respond to the request before dropping it, even though the + // content process is already doing the GC. + mRequestingGC.value()(true); + mRequestingGC = Nothing(); + if (!IsWaitingForIdle()) { + remove(); + } + } + + return IPC_OK(); +} + +IPCResult IdleSchedulerParent::RecvDoneGC() { + MOZ_ASSERT(mDoingGC); + sActiveGCs--; + mDoingGC = false; + Schedule(nullptr); + return IPC_OK(); +} + +int32_t IdleSchedulerParent::ActiveCount() { + if (sActiveChildCounter) { + return (static_cast<Atomic<int32_t>*>( + sActiveChildCounter + ->memory())[NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER]); + } + return 0; +} + +bool IdleSchedulerParent::HasSpareCycles(int32_t aActiveCount) { + // We can run a new task if we have a spare core. If we're running a + // prioritised operation we halve the number of regular spare cores. + // + // sMaxConcurrentIdleTasksInChildProcesses will always be >0 so on 1 and 2 + // core systems this will allow 1 idle tasks (0 if running a prioritized + // operation). + MOZ_ASSERT(sMaxConcurrentIdleTasksInChildProcesses > 0); + return sChildProcessesRunningPrioritizedOperation + ? sMaxConcurrentIdleTasksInChildProcesses / 2 > aActiveCount + : sMaxConcurrentIdleTasksInChildProcesses > aActiveCount; +} + +bool IdleSchedulerParent::HasSpareGCCycles() { + return sMaxConcurrentGCs > sActiveGCs; +} + +void IdleSchedulerParent::SendIdleTime() { + // We would assert that IsWaitingForIdle() except after potentially removing + // the task from it's list this will return false. Instead check + // mRequestedIdleBudget. + MOZ_ASSERT(mRequestedIdleBudget); + Unused << SendIdleTime(mCurrentRequestId, mRequestedIdleBudget); +} + +void IdleSchedulerParent::SendMayGC() { + MOZ_ASSERT(mRequestingGC); + mRequestingGC.value()(true); + mRequestingGC = Nothing(); + mDoingGC = true; + sActiveGCs++; +} + +void IdleSchedulerParent::Schedule(IdleSchedulerParent* aRequester) { + // Tasks won't update the active count until after they receive their message + // and start to run, so make a copy of it here and increment it for every task + // we schedule. It will become an estimate of how many tasks will be active + // shortly. + int32_t activeCount = ActiveCount(); + + if (aRequester && aRequester->mRunningPrioritizedOperation) { + // Prioritised operations are requested only for idle time requests, so this + // must be an idle time request. + MOZ_ASSERT(aRequester->IsWaitingForIdle()); + + // If the requester is prioritized, just let it run itself. + if (aRequester->isInList() && !aRequester->mRequestingGC) { + aRequester->remove(); + } + aRequester->SendIdleTime(); + activeCount++; + } + + RefPtr<IdleSchedulerParent> idleRequester = sIdleAndGCRequests.getFirst(); + + bool has_spare_cycles = HasSpareCycles(activeCount); + bool has_spare_gc_cycles = HasSpareGCCycles(); + + while (idleRequester && (has_spare_cycles || has_spare_gc_cycles)) { + // Get the next element before potentially removing the current one from the + // list. + RefPtr<IdleSchedulerParent> next = idleRequester->getNext(); + + if (has_spare_cycles && idleRequester->IsWaitingForIdle()) { + // We can run an idle task. + activeCount++; + if (!idleRequester->mRequestingGC) { + idleRequester->remove(); + } + idleRequester->SendIdleTime(); + has_spare_cycles = HasSpareCycles(activeCount); + } + + if (has_spare_gc_cycles && idleRequester->mRequestingGC) { + if (!idleRequester->IsWaitingForIdle()) { + idleRequester->remove(); + } + idleRequester->SendMayGC(); + has_spare_gc_cycles = HasSpareGCCycles(); + } + + idleRequester = next; + } + + if (!sIdleAndGCRequests.isEmpty() && HasSpareCycles(activeCount)) { + EnsureStarvationTimer(); + } +} + +void IdleSchedulerParent::EnsureStarvationTimer() { + // Even though idle runnables aren't really guaranteed to get run ever (which + // is why most of them have the timer fallback), try to not let any child + // process' idle handling to starve forever in case other processes are busy + if (!sStarvationPreventer) { + // Reuse StaticPrefs::page_load_deprioritization_period(), since that + // is used on child side when deciding the minimum idle period. + NS_NewTimerWithFuncCallback( + &sStarvationPreventer, StarvationCallback, nullptr, + StaticPrefs::page_load_deprioritization_period(), + nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, "StarvationCallback"); + } +} + +void IdleSchedulerParent::StarvationCallback(nsITimer* aTimer, void* aData) { + RefPtr<IdleSchedulerParent> idleRequester = sIdleAndGCRequests.getFirst(); + while (idleRequester) { + if (idleRequester->IsWaitingForIdle()) { + // Treat the first process waiting for idle time as running prioritized + // operation so that it gets run. + ++idleRequester->mRunningPrioritizedOperation; + ++sChildProcessesRunningPrioritizedOperation; + Schedule(idleRequester); + --idleRequester->mRunningPrioritizedOperation; + --sChildProcessesRunningPrioritizedOperation; + break; + } + + idleRequester = idleRequester->getNext(); + } + NS_RELEASE(sStarvationPreventer); +} + +} // namespace mozilla::ipc diff --git a/ipc/glue/IdleSchedulerParent.h b/ipc/glue/IdleSchedulerParent.h new file mode 100644 index 0000000000..a4ae130e28 --- /dev/null +++ b/ipc/glue/IdleSchedulerParent.h @@ -0,0 +1,131 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_IdleSchedulerParent_h__ +#define mozilla_ipc_IdleSchedulerParent_h__ + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/LinkedList.h" +#include "mozilla/ipc/PIdleSchedulerParent.h" +#include "base/shared_memory.h" +#include <bitset> + +#define NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT 1024 +#define NS_IDLE_SCHEDULER_INDEX_OF_ACTIVITY_COUNTER 0 +#define NS_IDLE_SCHEDULER_INDEX_OF_CPU_COUNTER 1 + +class nsITimer; + +namespace mozilla { + +namespace ipc { + +class BackgroundParentImpl; + +class IdleSchedulerParent final + : public PIdleSchedulerParent, + public LinkedListElement<IdleSchedulerParent> { + public: + NS_INLINE_DECL_REFCOUNTING(IdleSchedulerParent) + + IPCResult RecvInitForIdleUse(InitForIdleUseResolver&& aResolve); + IPCResult RecvRequestIdleTime(uint64_t aId, TimeDuration aBudget); + IPCResult RecvIdleTimeUsed(uint64_t aId); + IPCResult RecvSchedule(); + IPCResult RecvRunningPrioritizedOperation(); + IPCResult RecvPrioritizedOperationDone(); + IPCResult RecvRequestGC(RequestGCResolver&& aResolve); + IPCResult RecvStartedGC(); + IPCResult RecvDoneGC(); + + private: + friend class BackgroundParentImpl; + IdleSchedulerParent(); + ~IdleSchedulerParent(); + + static void CalculateNumIdleTasks(); + + static int32_t ActiveCount(); + static void Schedule(IdleSchedulerParent* aRequester); + static bool HasSpareCycles(int32_t aActiveCount); + static bool HasSpareGCCycles(); + using PIdleSchedulerParent::SendIdleTime; + void SendIdleTime(); + void SendMayGC(); + + static void EnsureStarvationTimer(); + static void StarvationCallback(nsITimer* aTimer, void* aData); + + uint64_t mCurrentRequestId = 0; + // For now we don't really use idle budget for scheduling. Zero if the + // process isn't requestiong or running an idle task. + TimeDuration mRequestedIdleBudget; + + // Counting all the prioritized operations the process is doing. + uint32_t mRunningPrioritizedOperation = 0; + + // Only one of these may be true at a time, giving three states: + // No active GC request, A pending GC request, or a granted GC request. + Maybe<RequestGCResolver> mRequestingGC; + bool mDoingGC = false; + + uint32_t mChildId = 0; + + // Current state, only one of these may be true at a time. + bool IsWaitingForIdle() const { return isInList() && mRequestedIdleBudget; } + bool IsDoingIdleTask() const { return !isInList() && mRequestedIdleBudget; } + bool IsNotDoingIdleTask() const { return !mRequestedIdleBudget; } + + // Shared memory for counting how many child processes are running + // tasks. This memory is shared across all the child processes. + // The [0] is used for counting all the processes and + // [childId] is for counting per process activity. + // This way the global activity can be checked in a fast way by just looking + // at [0] value. + // [1] is used for cpu count for child processes. + static base::SharedMemory* sActiveChildCounter; + // A bit is set if there is a child with child Id as the offset. + // The bit is used to check per child specific activity counters in + // sActiveChildCounter. + static std::bitset<NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT> + sInUseChildCounters; + + // Processes on this list have requested (but the request hasn't yet been + // granted) idle time or to start a GC or both. + // + // Either or both their mRequestedIdleBudget or mRequestingGC fields are + // non-zero. Child processes not on this list have either been granted all + // their requests not made a request ever or since they last finished an idle + // or GC task. + // + // Use the methods above to determine a process' idle time state, or check the + // mRequestingGC and mDoingGC fields for the GC state. + static LinkedList<IdleSchedulerParent> sIdleAndGCRequests; + + static int32_t sMaxConcurrentIdleTasksInChildProcesses; + static uint32_t sMaxConcurrentGCs; + static uint32_t sActiveGCs; + + // Counting all the child processes which have at least one prioritized + // operation. + static uint32_t sChildProcessesRunningPrioritizedOperation; + + // When this hits zero, it's time to free the shared memory and pack up. + static uint32_t sChildProcessesAlive; + + static nsITimer* sStarvationPreventer; + + static uint32_t sNumCPUs; + static uint32_t sPrefConcurrentGCsMax; + static uint32_t sPrefConcurrentGCsCPUDivisor; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_IdleSchedulerParent_h__ diff --git a/ipc/glue/InputStreamParams.ipdlh b/ipc/glue/InputStreamParams.ipdlh new file mode 100644 index 0000000000..2491c477af --- /dev/null +++ b/ipc/glue/InputStreamParams.ipdlh @@ -0,0 +1,101 @@ +/* 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 ProtocolTypes; + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; +[RefCounted] using mozilla::ipc::DataPipeReceiver from "mozilla/ipc/DataPipe.h"; +[RefCounted] using mozilla::RemoteLazyInputStream from "mozilla/RemoteLazyInputStream.h"; + +namespace mozilla { +namespace ipc { + +struct HeaderEntry +{ + nsCString name; + nsCString value; +}; + +struct StringInputStreamParams +{ + nsCString data; +}; + +struct FileInputStreamParams +{ + FileDescriptor fileDescriptor; + int32_t behaviorFlags; + int32_t ioFlags; +}; + +struct MultiplexInputStreamParams +{ + InputStreamParams[] streams; + uint32_t currentStream; + nsresult status; + bool startedReadingCurrent; +}; + +struct SlicedInputStreamParams +{ + InputStreamParams stream; + uint64_t start; + uint64_t length; + uint64_t curPos; + bool closed; +}; + +struct RemoteLazyInputStreamParams +{ + RemoteLazyInputStream stream; +}; + +struct DataPipeReceiverStreamParams +{ + DataPipeReceiver pipe; +}; + +union InputStreamParams +{ + StringInputStreamParams; + FileInputStreamParams; + BufferedInputStreamParams; + MIMEInputStreamParams; + MultiplexInputStreamParams; + SlicedInputStreamParams; + RemoteLazyInputStreamParams; + InputStreamLengthWrapperParams; + EncryptedFileInputStreamParams; + DataPipeReceiverStreamParams; +}; + +struct EncryptedFileInputStreamParams +{ + FileInputStreamParams fileInputStreamParams; + uint8_t[] key; + uint32_t blockSize; +}; + +struct BufferedInputStreamParams +{ + InputStreamParams? optionalStream; + uint32_t bufferSize; +}; + +struct MIMEInputStreamParams +{ + InputStreamParams? optionalStream; + HeaderEntry[] headers; + bool startedReading; +}; + +struct InputStreamLengthWrapperParams +{ + InputStreamParams stream; + int64_t length; + bool consumed; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/InputStreamUtils.cpp b/ipc/glue/InputStreamUtils.cpp new file mode 100644 index 0000000000..039582c94a --- /dev/null +++ b/ipc/glue/InputStreamUtils.cpp @@ -0,0 +1,211 @@ +/* -*- 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 "InputStreamUtils.h" + +#include "nsIIPCSerializableInputStream.h" + +#include "mozilla/Assertions.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/quota/DecryptingInputStream_impl.h" +#include "mozilla/dom/quota/IPCStreamCipherStrategy.h" +#include "mozilla/ipc/DataPipe.h" +#include "mozilla/InputStreamLengthHelper.h" +#include "mozilla/RemoteLazyInputStream.h" +#include "mozilla/RemoteLazyInputStreamChild.h" +#include "mozilla/RemoteLazyInputStreamStorage.h" +#include "mozilla/SlicedInputStream.h" +#include "mozilla/InputStreamLengthWrapper.h" +#include "nsBufferedStreams.h" +#include "nsComponentManagerUtils.h" +#include "nsDebug.h" +#include "nsFileStreams.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsID.h" +#include "nsIMIMEInputStream.h" +#include "nsIMultiplexInputStream.h" +#include "nsIPipe.h" +#include "nsMIMEInputStream.h" +#include "nsMultiplexInputStream.h" +#include "nsNetCID.h" +#include "nsStreamUtils.h" +#include "nsStringStream.h" +#include "nsXULAppAPI.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace mozilla { +namespace ipc { + +void InputStreamHelper::SerializedComplexity(nsIInputStream* aInputStream, + uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + MOZ_ASSERT(aInputStream); + + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(aInputStream); + if (!serializable) { + MOZ_CRASH("Input stream is not serializable!"); + } + + serializable->SerializedComplexity(aMaxSize, aSizeUsed, aPipes, + aTransferables); +} + +void InputStreamHelper::SerializeInputStream(nsIInputStream* aInputStream, + InputStreamParams& aParams, + uint32_t aMaxSize, + uint32_t* aSizeUsed) { + MOZ_ASSERT(aInputStream); + + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(aInputStream); + if (!serializable) { + MOZ_CRASH("Input stream is not serializable!"); + } + + serializable->Serialize(aParams, aMaxSize, aSizeUsed); + + if (aParams.type() == InputStreamParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } +} + +void InputStreamHelper::SerializeInputStreamAsPipe(nsIInputStream* aInputStream, + InputStreamParams& aParams) { + MOZ_ASSERT(aInputStream); + + // Let's try to take the length using InputStreamLengthHelper. If the length + // cannot be taken synchronously, and its length is needed, the stream needs + // to be fully copied in memory on the deserialization side. + int64_t length; + if (!InputStreamLengthHelper::GetSyncLength(aInputStream, &length)) { + length = -1; + } + + RefPtr<DataPipeSender> sender; + RefPtr<DataPipeReceiver> receiver; + nsresult rv = NewDataPipe(kDefaultDataPipeCapacity, getter_AddRefs(sender), + getter_AddRefs(receiver)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + + rv = + NS_AsyncCopy(aInputStream, sender, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS, + kDefaultDataPipeCapacity, nullptr, nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + aParams = DataPipeReceiverStreamParams(WrapNotNull(receiver)); + if (length != -1) { + aParams = InputStreamLengthWrapperParams(aParams, length, false); + } +} + +already_AddRefed<nsIInputStream> InputStreamHelper::DeserializeInputStream( + const InputStreamParams& aParams) { + if (aParams.type() == InputStreamParams::TRemoteLazyInputStreamParams) { + const RemoteLazyInputStreamParams& params = + aParams.get_RemoteLazyInputStreamParams(); + + // If the RemoteLazyInputStream already has an internal stream, unwrap it. + // This is required as some code unfortunately depends on the precise + // topology of received streams, and cannot handle being passed a + // `RemoteLazyInputStream` in the parent process. + nsCOMPtr<nsIInputStream> innerStream; + if (XRE_IsParentProcess() && + NS_SUCCEEDED( + params.stream()->TakeInternalStream(getter_AddRefs(innerStream)))) { + return innerStream.forget(); + } + return do_AddRef(params.stream().get()); + } + + if (aParams.type() == InputStreamParams::TDataPipeReceiverStreamParams) { + const DataPipeReceiverStreamParams& pipeParams = + aParams.get_DataPipeReceiverStreamParams(); + return do_AddRef(pipeParams.pipe().get()); + } + + nsCOMPtr<nsIIPCSerializableInputStream> serializable; + + switch (aParams.type()) { + case InputStreamParams::TStringInputStreamParams: { + nsCOMPtr<nsIInputStream> stream; + NS_NewCStringInputStream(getter_AddRefs(stream), ""_ns); + serializable = do_QueryInterface(stream); + } break; + + case InputStreamParams::TFileInputStreamParams: { + nsCOMPtr<nsIFileInputStream> stream; + nsFileInputStream::Create(NS_GET_IID(nsIFileInputStream), + getter_AddRefs(stream)); + serializable = do_QueryInterface(stream); + } break; + + case InputStreamParams::TBufferedInputStreamParams: { + nsCOMPtr<nsIBufferedInputStream> stream; + nsBufferedInputStream::Create(NS_GET_IID(nsIBufferedInputStream), + getter_AddRefs(stream)); + serializable = do_QueryInterface(stream); + } break; + + case InputStreamParams::TMIMEInputStreamParams: { + nsCOMPtr<nsIMIMEInputStream> stream; + nsMIMEInputStreamConstructor(NS_GET_IID(nsIMIMEInputStream), + getter_AddRefs(stream)); + serializable = do_QueryInterface(stream); + } break; + + case InputStreamParams::TMultiplexInputStreamParams: { + nsCOMPtr<nsIMultiplexInputStream> stream; + nsMultiplexInputStreamConstructor(NS_GET_IID(nsIMultiplexInputStream), + getter_AddRefs(stream)); + serializable = do_QueryInterface(stream); + } break; + + case InputStreamParams::TSlicedInputStreamParams: + serializable = new SlicedInputStream(); + break; + + case InputStreamParams::TInputStreamLengthWrapperParams: + serializable = new InputStreamLengthWrapper(); + break; + + case InputStreamParams::TEncryptedFileInputStreamParams: + serializable = new dom::quota::DecryptingInputStream< + dom::quota::IPCStreamCipherStrategy>(); + break; + + default: + MOZ_ASSERT(false, "Unknown params!"); + return nullptr; + } + + MOZ_ASSERT(serializable); + + if (!serializable->Deserialize(aParams)) { + MOZ_ASSERT(false, "Deserialize failed!"); + return nullptr; + } + + nsCOMPtr<nsIInputStream> stream = do_QueryInterface(serializable); + MOZ_ASSERT(stream); + + return stream.forget(); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/InputStreamUtils.h b/ipc/glue/InputStreamUtils.h new file mode 100644 index 0000000000..27a65b02e2 --- /dev/null +++ b/ipc/glue/InputStreamUtils.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_InputStreamUtils_h +#define mozilla_ipc_InputStreamUtils_h + +#include "mozilla/ipc/InputStreamParams.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsTArray.h" + +namespace mozilla { +namespace ipc { + +class FileDescriptor; + +// If you want to serialize an inputStream, please use SerializeIPCStream or +// nsIInputStream directly. +class InputStreamHelper { + public: + static void SerializedComplexity(nsIInputStream* aInputStream, + uint32_t aMaxSize, uint32_t* aSizeUsed, + uint32_t* aPipes, uint32_t* aTransferables); + + // These 2 methods allow to serialize an inputStream into InputStreamParams. + // The manager is needed in case a stream needs to serialize itself as + // IPCRemoteStream. + // The stream serializes itself fully only if the resulting IPC message will + // be smaller than |aMaxSize|. Otherwise, the stream serializes itself as a + // DataPipe, and, its content will be sent to the other side of the IPC pipe + // in chunks. The IPC message size is returned into |aSizeUsed|. + static void SerializeInputStream(nsIInputStream* aInputStream, + InputStreamParams& aParams, + uint32_t aMaxSize, uint32_t* aSizeUsed); + + // When a stream wants to serialize itself as a DataPipe, it uses this method. + static void SerializeInputStreamAsPipe(nsIInputStream* aInputStream, + InputStreamParams& aParams); + + static already_AddRefed<nsIInputStream> DeserializeInputStream( + const InputStreamParams& aParams); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_InputStreamUtils_h diff --git a/ipc/glue/LaunchError.cpp b/ipc/glue/LaunchError.cpp new file mode 100644 index 0000000000..04ae34cfa6 --- /dev/null +++ b/ipc/glue/LaunchError.cpp @@ -0,0 +1,31 @@ +/* -*- 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 "LaunchError.h" + +#if defined(XP_WIN) +# include "mozilla/WinHeaderOnlyUtils.h" +#endif + +namespace mozilla::ipc { + +LaunchError::LaunchError(const char* aFunction, OsError aError) + : mFunction(aFunction), mError(aError) {} + +#if defined(XP_WIN) +LaunchError::LaunchError(const char* aFunction, PRErrorCode aError) + : mFunction(aFunction), mError((int)aError) {} + +LaunchError::LaunchError(const char* aFunction, DWORD aError) + : mFunction(aFunction), + mError(WindowsError::FromWin32Error(aError).AsHResult()) {} +#endif // defined(XP_WIN) + +const char* LaunchError::FunctionName() const { return mFunction; } + +OsError LaunchError::ErrorCode() const { return mError; } + +} // namespace mozilla::ipc diff --git a/ipc/glue/LaunchError.h b/ipc/glue/LaunchError.h new file mode 100644 index 0000000000..efa8ba4de4 --- /dev/null +++ b/ipc/glue/LaunchError.h @@ -0,0 +1,40 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_LaunchError_h +#define mozilla_ipc_LaunchError_h + +#include "prerror.h" + +#if defined(XP_WIN) +# include <windows.h> +# include <winerror.h> +using OsError = HRESULT; +#else +using OsError = int; +#endif + +namespace mozilla::ipc { + +class LaunchError { + public: + explicit LaunchError(const char* aFunction, OsError aError = 0); +#if defined(XP_WIN) + explicit LaunchError(const char* aFunction, PRErrorCode aError); + explicit LaunchError(const char* aFunction, DWORD aError); +#endif // defined(XP_WIN) + + const char* FunctionName() const; + OsError ErrorCode() const; + + private: + const char* mFunction; + OsError mError; +}; + +} // namespace mozilla::ipc + +#endif // ifndef mozilla_ipc_LaunchError_h diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp new file mode 100644 index 0000000000..a7fc135748 --- /dev/null +++ b/ipc/glue/MessageChannel.cpp @@ -0,0 +1,2491 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + */ +/* 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 "mozilla/ipc/MessageChannel.h" + +#include <math.h> + +#include <utility> + +#include "CrashAnnotations.h" +#include "mozilla/Assertions.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Fuzzing.h" +#include "mozilla/IntentionalCrash.h" +#include "mozilla/Logging.h" +#include "mozilla/Monitor.h" +#include "mozilla/Mutex.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/ipc/NodeController.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "nsAppRunner.h" +#include "nsContentUtils.h" +#include "nsIDirectTaskDispatcher.h" +#include "nsTHashMap.h" +#include "nsDebug.h" +#include "nsExceptionHandler.h" +#include "nsIMemoryReporter.h" +#include "nsISupportsImpl.h" +#include "nsPrintfCString.h" +#include "nsThreadUtils.h" + +#ifdef XP_WIN +# include "mozilla/gfx/Logging.h" +#endif + +#ifdef FUZZING_SNAPSHOT +# include "mozilla/fuzzing/IPCFuzzController.h" +#endif + +// Undo the damage done by mozzconf.h +#undef compress + +static mozilla::LazyLogModule sLogModule("ipc"); +#define IPC_LOG(...) MOZ_LOG(sLogModule, LogLevel::Debug, (__VA_ARGS__)) + +/* + * IPC design: + * + * There are two kinds of messages: async and sync. Sync messages are blocking. + * + * Terminology: To dispatch a message Foo is to run the RecvFoo code for + * it. This is also called "handling" the message. + * + * Sync and async messages can sometimes "nest" inside other sync messages + * (i.e., while waiting for the sync reply, we can dispatch the inner + * message). The three possible nesting levels are NOT_NESTED, + * NESTED_INSIDE_SYNC, and NESTED_INSIDE_CPOW. The intended uses are: + * NOT_NESTED - most messages. + * NESTED_INSIDE_SYNC - CPOW-related messages, which are always sync + * and can go in either direction. + * NESTED_INSIDE_CPOW - messages where we don't want to dispatch + * incoming CPOWs while waiting for the response. + * These nesting levels are ordered: NOT_NESTED, NESTED_INSIDE_SYNC, + * NESTED_INSIDE_CPOW. Async messages cannot be NESTED_INSIDE_SYNC but they can + * be NESTED_INSIDE_CPOW. + * + * To avoid jank, the parent process is not allowed to send NOT_NESTED sync + * messages. When a process is waiting for a response to a sync message M0, it + * will dispatch an incoming message M if: + * 1. M has a higher nesting level than M0, or + * 2. if M has the same nesting level as M0 and we're in the child, or + * 3. if M has the same nesting level as M0 and it was sent by the other side + * while dispatching M0. + * The idea is that messages with higher nesting should take precendence. The + * purpose of rule 2 is to handle a race where both processes send to each other + * simultaneously. In this case, we resolve the race in favor of the parent (so + * the child dispatches first). + * + * Messages satisfy the following properties: + * A. When waiting for a response to a sync message, we won't dispatch any + * messages of a lower nesting level. + * B. Messages of the same nesting level will be dispatched roughly in the + * order they were sent. The exception is when the parent and child send + * sync messages to each other simulataneously. In this case, the parent's + * message is dispatched first. While it is dispatched, the child may send + * further nested messages, and these messages may be dispatched before the + * child's original message. We can consider ordering to be preserved here + * because we pretend that the child's original message wasn't sent until + * after the parent's message is finished being dispatched. + * + * When waiting for a sync message reply, we dispatch an async message only if + * it is NESTED_INSIDE_CPOW. Normally NESTED_INSIDE_CPOW async + * messages are sent only from the child. However, the parent can send + * NESTED_INSIDE_CPOW async messages when it is creating a bridged protocol. + */ + +using namespace mozilla; +using namespace mozilla::ipc; + +using mozilla::MonitorAutoLock; +using mozilla::MonitorAutoUnlock; +using mozilla::dom::AutoNoJSAPI; + +#define IPC_ASSERT(_cond, ...) \ + do { \ + AssertWorkerThread(); \ + mMonitor->AssertCurrentThreadOwns(); \ + if (!(_cond)) DebugAbort(__FILE__, __LINE__, #_cond, ##__VA_ARGS__); \ + } while (0) + +static MessageChannel* gParentProcessBlocker = nullptr; + +namespace mozilla { +namespace ipc { + +static const uint32_t kMinTelemetryMessageSize = 4096; + +// Note: we round the time we spend waiting for a response to the nearest +// millisecond. So a min value of 1 ms actually captures from 500us and above. +// This is used for both the sending and receiving side telemetry for sync IPC, +// (IPC_SYNC_MAIN_LATENCY_MS and IPC_SYNC_RECEIVE_MS). +static const uint32_t kMinTelemetrySyncIPCLatencyMs = 1; + +// static +bool MessageChannel::sIsPumpingMessages = false; + +class AutoEnterTransaction { + public: + explicit AutoEnterTransaction(MessageChannel* aChan, int32_t aMsgSeqno, + int32_t aTransactionID, int aNestedLevel) + MOZ_REQUIRES(*aChan->mMonitor) + : mChan(aChan), + mActive(true), + mOutgoing(true), + mNestedLevel(aNestedLevel), + mSeqno(aMsgSeqno), + mTransaction(aTransactionID), + mNext(mChan->mTransactionStack) { + mChan->mMonitor->AssertCurrentThreadOwns(); + mChan->mTransactionStack = this; + } + + explicit AutoEnterTransaction(MessageChannel* aChan, + const IPC::Message& aMessage) + MOZ_REQUIRES(*aChan->mMonitor) + : mChan(aChan), + mActive(true), + mOutgoing(false), + mNestedLevel(aMessage.nested_level()), + mSeqno(aMessage.seqno()), + mTransaction(aMessage.transaction_id()), + mNext(mChan->mTransactionStack) { + mChan->mMonitor->AssertCurrentThreadOwns(); + + if (!aMessage.is_sync()) { + mActive = false; + return; + } + + mChan->mTransactionStack = this; + } + + ~AutoEnterTransaction() { + mChan->mMonitor->AssertCurrentThreadOwns(); + if (mActive) { + mChan->mTransactionStack = mNext; + } + } + + void Cancel() { + mChan->mMonitor->AssertCurrentThreadOwns(); + AutoEnterTransaction* cur = mChan->mTransactionStack; + MOZ_RELEASE_ASSERT(cur == this); + while (cur && cur->mNestedLevel != IPC::Message::NOT_NESTED) { + // Note that, in the following situation, we will cancel multiple + // transactions: + // 1. Parent sends NESTED_INSIDE_SYNC message P1 to child. + // 2. Child sends NESTED_INSIDE_SYNC message C1 to child. + // 3. Child dispatches P1, parent blocks. + // 4. Child cancels. + // In this case, both P1 and C1 are cancelled. The parent will + // remove C1 from its queue when it gets the cancellation message. + MOZ_RELEASE_ASSERT(cur->mActive); + cur->mActive = false; + cur = cur->mNext; + } + + mChan->mTransactionStack = cur; + + MOZ_RELEASE_ASSERT(IsComplete()); + } + + bool AwaitingSyncReply() const { + MOZ_RELEASE_ASSERT(mActive); + if (mOutgoing) { + return true; + } + return mNext ? mNext->AwaitingSyncReply() : false; + } + + int AwaitingSyncReplyNestedLevel() const { + MOZ_RELEASE_ASSERT(mActive); + if (mOutgoing) { + return mNestedLevel; + } + return mNext ? mNext->AwaitingSyncReplyNestedLevel() : 0; + } + + bool DispatchingSyncMessage() const { + MOZ_RELEASE_ASSERT(mActive); + if (!mOutgoing) { + return true; + } + return mNext ? mNext->DispatchingSyncMessage() : false; + } + + int DispatchingSyncMessageNestedLevel() const { + MOZ_RELEASE_ASSERT(mActive); + if (!mOutgoing) { + return mNestedLevel; + } + return mNext ? mNext->DispatchingSyncMessageNestedLevel() : 0; + } + + int NestedLevel() const { + MOZ_RELEASE_ASSERT(mActive); + return mNestedLevel; + } + + int32_t SequenceNumber() const { + MOZ_RELEASE_ASSERT(mActive); + return mSeqno; + } + + int32_t TransactionID() const { + MOZ_RELEASE_ASSERT(mActive); + return mTransaction; + } + + void ReceivedReply(UniquePtr<IPC::Message> aMessage) { + MOZ_RELEASE_ASSERT(aMessage->seqno() == mSeqno); + MOZ_RELEASE_ASSERT(aMessage->transaction_id() == mTransaction); + MOZ_RELEASE_ASSERT(!mReply); + IPC_LOG("Reply received on worker thread: seqno=%d", mSeqno); + mReply = std::move(aMessage); + MOZ_RELEASE_ASSERT(IsComplete()); + } + + void HandleReply(UniquePtr<IPC::Message> aMessage) { + mChan->mMonitor->AssertCurrentThreadOwns(); + AutoEnterTransaction* cur = mChan->mTransactionStack; + MOZ_RELEASE_ASSERT(cur == this); + while (cur) { + MOZ_RELEASE_ASSERT(cur->mActive); + if (aMessage->seqno() == cur->mSeqno) { + cur->ReceivedReply(std::move(aMessage)); + break; + } + cur = cur->mNext; + MOZ_RELEASE_ASSERT(cur); + } + } + + bool IsComplete() { return !mActive || mReply; } + + bool IsOutgoing() { return mOutgoing; } + + bool IsCanceled() { return !mActive; } + + bool IsBottom() const { return !mNext; } + + bool IsError() { + MOZ_RELEASE_ASSERT(mReply); + return mReply->is_reply_error(); + } + + UniquePtr<IPC::Message> GetReply() { return std::move(mReply); } + + private: + MessageChannel* mChan; + + // Active is true if this transaction is on the mChan->mTransactionStack + // stack. Generally we're not on the stack if the transaction was canceled + // or if it was for a message that doesn't require transactions (an async + // message). + bool mActive; + + // Is this stack frame for an outgoing message? + bool mOutgoing; + + // Properties of the message being sent/received. + int mNestedLevel; + int32_t mSeqno; + int32_t mTransaction; + + // Next item in mChan->mTransactionStack. + AutoEnterTransaction* mNext; + + // Pointer the a reply received for this message, if one was received. + UniquePtr<IPC::Message> mReply; +}; + +class PendingResponseReporter final : public nsIMemoryReporter { + ~PendingResponseReporter() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + MOZ_COLLECT_REPORT( + "unresolved-ipc-responses", KIND_OTHER, UNITS_COUNT, + MessageChannel::gUnresolvedResponses, + "Outstanding IPC async message responses that are still not resolved."); + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(PendingResponseReporter, nsIMemoryReporter) + +class ChannelCountReporter final : public nsIMemoryReporter { + ~ChannelCountReporter() = default; + + struct ChannelCounts { + size_t mNow; + size_t mMax; + + ChannelCounts() : mNow(0), mMax(0) {} + + void Inc() { + ++mNow; + if (mMax < mNow) { + mMax = mNow; + } + } + + void Dec() { + MOZ_ASSERT(mNow > 0); + --mNow; + } + }; + + using CountTable = nsTHashMap<nsDepCharHashKey, ChannelCounts>; + + static StaticMutex sChannelCountMutex; + static CountTable* sChannelCounts MOZ_GUARDED_BY(sChannelCountMutex); + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + AutoTArray<std::pair<const char*, ChannelCounts>, 16> counts; + { + StaticMutexAutoLock countLock(sChannelCountMutex); + if (!sChannelCounts) { + return NS_OK; + } + counts.SetCapacity(sChannelCounts->Count()); + for (const auto& entry : *sChannelCounts) { + counts.AppendElement(std::pair{entry.GetKey(), entry.GetData()}); + } + } + + for (const auto& entry : counts) { + nsPrintfCString pathNow("ipc-channels/%s", entry.first); + nsPrintfCString pathMax("ipc-channels-peak/%s", entry.first); + nsPrintfCString descNow( + "Number of IPC channels for" + " top-level actor type %s", + entry.first); + nsPrintfCString descMax( + "Peak number of IPC channels for" + " top-level actor type %s", + entry.first); + + aHandleReport->Callback(""_ns, pathNow, KIND_OTHER, UNITS_COUNT, + entry.second.mNow, descNow, aData); + aHandleReport->Callback(""_ns, pathMax, KIND_OTHER, UNITS_COUNT, + entry.second.mMax, descMax, aData); + } + return NS_OK; + } + + static void Increment(const char* aName) { + StaticMutexAutoLock countLock(sChannelCountMutex); + if (!sChannelCounts) { + sChannelCounts = new CountTable; + } + sChannelCounts->LookupOrInsert(aName).Inc(); + } + + static void Decrement(const char* aName) { + StaticMutexAutoLock countLock(sChannelCountMutex); + MOZ_ASSERT(sChannelCounts); + sChannelCounts->LookupOrInsert(aName).Dec(); + } +}; + +StaticMutex ChannelCountReporter::sChannelCountMutex; +ChannelCountReporter::CountTable* ChannelCountReporter::sChannelCounts; + +NS_IMPL_ISUPPORTS(ChannelCountReporter, nsIMemoryReporter) + +// In child processes, the first MessageChannel is created before +// XPCOM is initialized enough to construct the memory reporter +// manager. This retries every time a MessageChannel is constructed, +// which is good enough in practice. +template <class Reporter> +static void TryRegisterStrongMemoryReporter() { + static Atomic<bool> registered; + if (registered.compareExchange(false, true)) { + RefPtr<Reporter> reporter = new Reporter(); + if (NS_FAILED(RegisterStrongMemoryReporter(reporter))) { + registered = false; + } + } +} + +Atomic<size_t> MessageChannel::gUnresolvedResponses; + +MessageChannel::MessageChannel(const char* aName, IToplevelProtocol* aListener) + : mName(aName), mListener(aListener), mMonitor(new RefCountedMonitor()) { + MOZ_COUNT_CTOR(ipc::MessageChannel); + +#ifdef XP_WIN + mEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + MOZ_RELEASE_ASSERT(mEvent, "CreateEvent failed! Nothing is going to work!"); +#endif + + TryRegisterStrongMemoryReporter<PendingResponseReporter>(); + TryRegisterStrongMemoryReporter<ChannelCountReporter>(); +} + +MessageChannel::~MessageChannel() { + MOZ_COUNT_DTOR(ipc::MessageChannel); + MonitorAutoLock lock(*mMonitor); + MOZ_RELEASE_ASSERT(!mOnCxxStack, + "MessageChannel destroyed while code on CxxStack"); +#ifdef XP_WIN + if (mEvent) { + BOOL ok = CloseHandle(mEvent); + mEvent = nullptr; + + if (!ok) { + gfxDevCrash(mozilla::gfx::LogReason::MessageChannelCloseFailure) + << "MessageChannel failed to close. GetLastError: " << GetLastError(); + } + MOZ_RELEASE_ASSERT(ok); + } else { + gfxDevCrash(mozilla::gfx::LogReason::MessageChannelCloseFailure) + << "MessageChannel destructor ran without an mEvent Handle"; + } +#endif + + // Make sure that the MessageChannel was closed (and therefore cleared) before + // it was destroyed. We can't properly close the channel at this point, as it + // would be unsafe to invoke our listener's callbacks, and we may be being + // destroyed on a thread other than `mWorkerThread`. + if (!IsClosedLocked()) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCFatalErrorProtocol, + nsDependentCString(mName)); + switch (mChannelState) { + case ChannelConnected: + MOZ_CRASH( + "MessageChannel destroyed without being closed " + "(mChannelState == ChannelConnected)."); + break; + case ChannelClosing: + MOZ_CRASH( + "MessageChannel destroyed without being closed " + "(mChannelState == ChannelClosing)."); + break; + case ChannelError: + MOZ_CRASH( + "MessageChannel destroyed without being closed " + "(mChannelState == ChannelError)."); + break; + default: + MOZ_CRASH("MessageChannel destroyed without being closed."); + } + } + + // Double-check other properties for thoroughness. + MOZ_RELEASE_ASSERT(!mLink); + MOZ_RELEASE_ASSERT(mPendingResponses.empty()); + MOZ_RELEASE_ASSERT(!mChannelErrorTask); + MOZ_RELEASE_ASSERT(mPending.isEmpty()); + MOZ_RELEASE_ASSERT(!mShutdownTask); +} + +#ifdef DEBUG +void MessageChannel::AssertMaybeDeferredCountCorrect() { + mMonitor->AssertCurrentThreadOwns(); + + size_t count = 0; + for (MessageTask* task : mPending) { + task->AssertMonitorHeld(*mMonitor); + if (!IsAlwaysDeferred(*task->Msg())) { + count++; + } + } + + MOZ_ASSERT(count == mMaybeDeferredPendingCount); +} +#endif + +// This function returns the current transaction ID. Since the notion of a +// "current transaction" can be hard to define when messages race with each +// other and one gets canceled and the other doesn't, we require that this +// function is only called when the current transaction is known to be for a +// NESTED_INSIDE_SYNC message. In that case, we know for sure what the caller is +// looking for. +int32_t MessageChannel::CurrentNestedInsideSyncTransaction() const { + mMonitor->AssertCurrentThreadOwns(); + if (!mTransactionStack) { + return 0; + } + MOZ_RELEASE_ASSERT(mTransactionStack->NestedLevel() == + IPC::Message::NESTED_INSIDE_SYNC); + return mTransactionStack->TransactionID(); +} + +bool MessageChannel::AwaitingSyncReply() const { + mMonitor->AssertCurrentThreadOwns(); + return mTransactionStack ? mTransactionStack->AwaitingSyncReply() : false; +} + +int MessageChannel::AwaitingSyncReplyNestedLevel() const { + mMonitor->AssertCurrentThreadOwns(); + return mTransactionStack ? mTransactionStack->AwaitingSyncReplyNestedLevel() + : 0; +} + +bool MessageChannel::DispatchingSyncMessage() const { + mMonitor->AssertCurrentThreadOwns(); + return mTransactionStack ? mTransactionStack->DispatchingSyncMessage() + : false; +} + +int MessageChannel::DispatchingSyncMessageNestedLevel() const { + mMonitor->AssertCurrentThreadOwns(); + return mTransactionStack + ? mTransactionStack->DispatchingSyncMessageNestedLevel() + : 0; +} + +static void PrintErrorMessage(Side side, const char* channelName, + const char* msg) { + printf_stderr("\n###!!! [%s][%s] Error: %s\n\n", StringFromIPCSide(side), + channelName, msg); +} + +bool MessageChannel::Connected() const { + mMonitor->AssertCurrentThreadOwns(); + return ChannelConnected == mChannelState; +} + +bool MessageChannel::ConnectedOrClosing() const { + mMonitor->AssertCurrentThreadOwns(); + return ChannelConnected == mChannelState || ChannelClosing == mChannelState; +} + +bool MessageChannel::CanSend() const { + if (!mMonitor) { + return false; + } + MonitorAutoLock lock(*mMonitor); + return Connected(); +} + +void MessageChannel::Clear() { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + MOZ_DIAGNOSTIC_ASSERT(IsClosedLocked(), "MessageChannel cleared too early?"); + MOZ_ASSERT(ChannelClosed == mChannelState || ChannelError == mChannelState); + + // Don't clear mWorkerThread; we use it in AssertWorkerThread(). + // + // Also don't clear mListener. If we clear it, then sending a message + // through this channel after it's Clear()'ed can cause this process to + // crash. + + if (mShutdownTask) { + mShutdownTask->Clear(); + mWorkerThread->UnregisterShutdownTask(mShutdownTask); + } + mShutdownTask = nullptr; + + if (NS_IsMainThread() && gParentProcessBlocker == this) { + gParentProcessBlocker = nullptr; + } + + gUnresolvedResponses -= mPendingResponses.size(); + { + CallbackMap map = std::move(mPendingResponses); + MonitorAutoUnlock unlock(*mMonitor); + for (auto& pair : map) { + pair.second->Reject(ResponseRejectReason::ChannelClosed); + } + } + mPendingResponses.clear(); + + SetIsCrossProcess(false); + + mLink = nullptr; + + if (mChannelErrorTask) { + mChannelErrorTask->Cancel(); + mChannelErrorTask = nullptr; + } + + if (mFlushLazySendTask) { + mFlushLazySendTask->Cancel(); + mFlushLazySendTask = nullptr; + } + + // Free up any memory used by pending messages. + mPending.clear(); + + mMaybeDeferredPendingCount = 0; +} + +bool MessageChannel::Open(ScopedPort aPort, Side aSide, + const nsID& aMessageChannelId, + nsISerialEventTarget* aEventTarget) { + nsCOMPtr<nsISerialEventTarget> eventTarget = + aEventTarget ? aEventTarget : GetCurrentSerialEventTarget(); + MOZ_RELEASE_ASSERT(eventTarget, + "Must open MessageChannel on a nsISerialEventTarget"); + MOZ_RELEASE_ASSERT(eventTarget->IsOnCurrentThread(), + "Must open MessageChannel from worker thread"); + + auto shutdownTask = MakeRefPtr<WorkerTargetShutdownTask>(eventTarget, this); + nsresult rv = eventTarget->RegisterShutdownTask(shutdownTask); + MOZ_ASSERT(rv != NS_ERROR_NOT_IMPLEMENTED, + "target for MessageChannel must support shutdown tasks"); + if (rv == NS_ERROR_UNEXPECTED) { + // If shutdown tasks have already started running, dispatch our shutdown + // task manually. + NS_WARNING("Opening MessageChannel on EventTarget in shutdown"); + rv = eventTarget->Dispatch(shutdownTask->AsRunnable()); + } + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), + "error registering ShutdownTask for MessageChannel"); + + { + MonitorAutoLock lock(*mMonitor); + MOZ_RELEASE_ASSERT(!mLink, "Open() called > once"); + MOZ_RELEASE_ASSERT(ChannelClosed == mChannelState, "Not currently closed"); + MOZ_ASSERT(mSide == UnknownSide); + + mMessageChannelId = aMessageChannelId; + mWorkerThread = eventTarget; + mShutdownTask = shutdownTask; + mLink = MakeUnique<PortLink>(this, std::move(aPort)); + mChannelState = ChannelConnected; + mSide = aSide; + } + + // Notify our listener that the underlying IPC channel has been established. + // IProtocol will use this callback to create the ActorLifecycleProxy, and + // perform an `AddRef` call to keep the actor alive until the channel is + // disconnected. + // + // We unlock our monitor before calling `OnIPCChannelOpened` to ensure that + // any calls back into `MessageChannel` do not deadlock. At this point, we may + // be receiving messages on the IO thread, however we cannot process them on + // the worker thread or have notified our listener until after this function + // returns. + mListener->OnIPCChannelOpened(); + return true; +} + +static Side GetOppSide(Side aSide) { + switch (aSide) { + case ChildSide: + return ParentSide; + case ParentSide: + return ChildSide; + default: + return UnknownSide; + } +} + +bool MessageChannel::Open(MessageChannel* aTargetChan, + nsISerialEventTarget* aEventTarget, Side aSide) { + // Opens a connection to another thread in the same process. + + MOZ_ASSERT(aTargetChan, "Need a target channel"); + + nsID channelId = nsID::GenerateUUID(); + + std::pair<ScopedPort, ScopedPort> ports = + NodeController::GetSingleton()->CreatePortPair(); + + // NOTE: This dispatch must be sync as it captures locals by non-owning + // reference, however we can't use `NS_DispatchAndSpinEventLoopUntilComplete` + // as that will spin a nested event loop, and doesn't work with certain types + // of calling event targets. + base::WaitableEvent event(/* manual_reset */ true, + /* initially_signaled */ false); + MOZ_ALWAYS_SUCCEEDS(aEventTarget->Dispatch(NS_NewCancelableRunnableFunction( + "ipc::MessageChannel::OpenAsOtherThread", [&]() { + aTargetChan->Open(std::move(ports.second), GetOppSide(aSide), channelId, + aEventTarget); + event.Signal(); + }))); + bool ok = event.Wait(); + MOZ_RELEASE_ASSERT(ok); + + // Now that the other side has connected, open the port on our side. + return Open(std::move(ports.first), aSide, channelId); +} + +bool MessageChannel::OpenOnSameThread(MessageChannel* aTargetChan, + mozilla::ipc::Side aSide) { + auto [porta, portb] = NodeController::GetSingleton()->CreatePortPair(); + + nsID channelId = nsID::GenerateUUID(); + + aTargetChan->mIsSameThreadChannel = true; + mIsSameThreadChannel = true; + + auto* currentThread = GetCurrentSerialEventTarget(); + return aTargetChan->Open(std::move(portb), GetOppSide(aSide), channelId, + currentThread) && + Open(std::move(porta), aSide, channelId, currentThread); +} + +bool MessageChannel::Send(UniquePtr<Message> aMsg) { + if (aMsg->size() >= kMinTelemetryMessageSize) { + Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE2, aMsg->size()); + } + + MOZ_RELEASE_ASSERT(!aMsg->is_sync()); + MOZ_RELEASE_ASSERT(aMsg->nested_level() != IPC::Message::NESTED_INSIDE_SYNC); + + AutoSetValue<bool> setOnCxxStack(mOnCxxStack, true); + + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + if (MSG_ROUTING_NONE == aMsg->routing_id()) { + ReportMessageRouteError("MessageChannel::Send"); + return false; + } + + if (aMsg->seqno() == 0) { + aMsg->set_seqno(NextSeqno()); + } + + MonitorAutoLock lock(*mMonitor); + if (!Connected()) { + ReportConnectionError("Send", aMsg->type()); + return false; + } + + AddProfilerMarker(*aMsg, MessageDirection::eSending); + SendMessageToLink(std::move(aMsg)); + return true; +} + +void MessageChannel::SendMessageToLink(UniquePtr<Message> aMsg) { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + // If the channel is not cross-process, there's no reason to be lazy, so we + // ignore the flag in that case. + if (aMsg->is_lazy_send() && mIsCrossProcess) { + // If this is the first lazy message in the queue and our worker thread + // supports direct task dispatch, dispatch a task to flush messages, + // ensuring we don't leave them pending forever. + if (!mFlushLazySendTask) { + if (nsCOMPtr<nsIDirectTaskDispatcher> dispatcher = + do_QueryInterface(mWorkerThread)) { + mFlushLazySendTask = new FlushLazySendMessagesRunnable(this); + MOZ_ALWAYS_SUCCEEDS( + dispatcher->DispatchDirectTask(do_AddRef(mFlushLazySendTask))); + } + } + if (mFlushLazySendTask) { + mFlushLazySendTask->PushMessage(std::move(aMsg)); + return; + } + } + + if (mFlushLazySendTask) { + FlushLazySendMessages(); + } + mLink->SendMessage(std::move(aMsg)); +} + +void MessageChannel::FlushLazySendMessages() { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + // Clean up any SendLazyTask which might be pending. + auto messages = mFlushLazySendTask->TakeMessages(); + mFlushLazySendTask = nullptr; + + // Send all lazy messages, then clear the queue. + for (auto& msg : messages) { + mLink->SendMessage(std::move(msg)); + } +} + +UniquePtr<MessageChannel::UntypedCallbackHolder> MessageChannel::PopCallback( + const Message& aMsg, int32_t aActorId) { + auto iter = mPendingResponses.find(aMsg.seqno()); + if (iter != mPendingResponses.end() && iter->second->mActorId == aActorId && + iter->second->mReplyMsgId == aMsg.type()) { + UniquePtr<MessageChannel::UntypedCallbackHolder> ret = + std::move(iter->second); + mPendingResponses.erase(iter); + gUnresolvedResponses--; + return ret; + } + return nullptr; +} + +void MessageChannel::RejectPendingResponsesForActor(int32_t aActorId) { + auto itr = mPendingResponses.begin(); + while (itr != mPendingResponses.end()) { + if (itr->second.get()->mActorId != aActorId) { + ++itr; + continue; + } + itr->second.get()->Reject(ResponseRejectReason::ActorDestroyed); + // Take special care of advancing the iterator since we are + // removing it while iterating. + itr = mPendingResponses.erase(itr); + gUnresolvedResponses--; + } +} + +class BuildIDsMatchMessage : public IPC::Message { + public: + BuildIDsMatchMessage() + : IPC::Message(MSG_ROUTING_NONE, BUILD_IDS_MATCH_MESSAGE_TYPE) {} + void Log(const std::string& aPrefix, FILE* aOutf) const { + fputs("(special `Build IDs match' message)", aOutf); + } +}; + +// Send the parent a special async message to confirm when the parent and child +// are of the same buildID. Skips sending the message and returns false if the +// buildIDs don't match. This is a minor variation on +// MessageChannel::Send(Message* aMsg). +bool MessageChannel::SendBuildIDsMatchMessage(const char* aParentBuildID) { + MOZ_ASSERT(!XRE_IsParentProcess()); + + nsCString parentBuildID(aParentBuildID); + nsCString childBuildID(mozilla::PlatformBuildID()); + + if (parentBuildID != childBuildID) { + // The build IDs didn't match, usually because an update occurred in the + // background. + return false; + } + + auto msg = MakeUnique<BuildIDsMatchMessage>(); + + MOZ_RELEASE_ASSERT(!msg->is_sync()); + MOZ_RELEASE_ASSERT(msg->nested_level() != IPC::Message::NESTED_INSIDE_SYNC); + + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + // Don't check for MSG_ROUTING_NONE. + + MonitorAutoLock lock(*mMonitor); + if (!Connected()) { + ReportConnectionError("SendBuildIDsMatchMessage", msg->type()); + return false; + } + +#if defined(MOZ_DEBUG) && defined(ENABLE_TESTS) + // Technically, the behavior is interesting for any kind of process + // but when exercising tests, we want to crash only a content process and + // avoid making noise with other kind of processes crashing + if (const char* dontSend = PR_GetEnv("MOZ_BUILDID_MATCH_DONTSEND")) { + if (dontSend[0] == '1') { + // Bug 1732999: We are going to crash, so we need to advise leak check + // tooling to avoid intermittent missing leakcheck + NoteIntentionalCrash(XRE_GetProcessTypeString()); + if (XRE_IsContentProcess()) { + return false; + } + } + } +#endif + + SendMessageToLink(std::move(msg)); + return true; +} + +class CancelMessage : public IPC::Message { + public: + explicit CancelMessage(int transaction) + : IPC::Message(MSG_ROUTING_NONE, CANCEL_MESSAGE_TYPE) { + set_transaction_id(transaction); + } + static bool Read(const Message* msg) { return true; } + void Log(const std::string& aPrefix, FILE* aOutf) const { + fputs("(special `Cancel' message)", aOutf); + } +}; + +bool MessageChannel::MaybeInterceptSpecialIOMessage(const Message& aMsg) { + mMonitor->AssertCurrentThreadOwns(); + + if (MSG_ROUTING_NONE == aMsg.routing_id()) { + if (GOODBYE_MESSAGE_TYPE == aMsg.type()) { + // We've received a GOODBYE message, close the connection and mark + // ourselves as "Closing". + mLink->Close(); + mChannelState = ChannelClosing; + if (LoggingEnabledFor(mListener->GetProtocolName(), mSide)) { + printf( + "[%s %u] NOTE: %s%s actor received `Goodbye' message. Closing " + "channel.\n", + XRE_GeckoProcessTypeToString(XRE_GetProcessType()), + static_cast<uint32_t>(base::GetCurrentProcId()), + mListener->GetProtocolName(), StringFromIPCSide(mSide)); + } + + // Notify the worker thread that the connection has been closed, as we + // will not receive an `OnChannelErrorFromLink` after calling + // `mLink->Close()`. + if (AwaitingSyncReply()) { + NotifyWorkerThread(); + } + PostErrorNotifyTask(); + return true; + } else if (CANCEL_MESSAGE_TYPE == aMsg.type()) { + IPC_LOG("Cancel from message"); + CancelTransaction(aMsg.transaction_id()); + NotifyWorkerThread(); + return true; + } else if (BUILD_IDS_MATCH_MESSAGE_TYPE == aMsg.type()) { + IPC_LOG("Build IDs match message"); + mBuildIDsConfirmedMatch = true; + return true; + } else if (IMPENDING_SHUTDOWN_MESSAGE_TYPE == aMsg.type()) { + IPC_LOG("Impending Shutdown received"); + ProcessChild::NotifiedImpendingShutdown(); + return true; + } + } + return false; +} + +/* static */ +bool MessageChannel::IsAlwaysDeferred(const Message& aMsg) { + // If a message is not NESTED_INSIDE_CPOW and not sync, then we always defer + // it. + return aMsg.nested_level() != IPC::Message::NESTED_INSIDE_CPOW && + !aMsg.is_sync(); +} + +bool MessageChannel::ShouldDeferMessage(const Message& aMsg) { + // Never defer messages that have the highest nested level, even async + // ones. This is safe because only the child can send these messages, so + // they can never nest. + if (aMsg.nested_level() == IPC::Message::NESTED_INSIDE_CPOW) { + MOZ_ASSERT(!IsAlwaysDeferred(aMsg)); + return false; + } + + // Unless they're NESTED_INSIDE_CPOW, we always defer async messages. + // Note that we never send an async NESTED_INSIDE_SYNC message. + if (!aMsg.is_sync()) { + MOZ_RELEASE_ASSERT(aMsg.nested_level() == IPC::Message::NOT_NESTED); + MOZ_ASSERT(IsAlwaysDeferred(aMsg)); + return true; + } + + MOZ_ASSERT(!IsAlwaysDeferred(aMsg)); + + int msgNestedLevel = aMsg.nested_level(); + int waitingNestedLevel = AwaitingSyncReplyNestedLevel(); + + // Always defer if the nested level of the incoming message is less than the + // nested level of the message we're awaiting. + if (msgNestedLevel < waitingNestedLevel) return true; + + // Never defer if the message has strictly greater nested level. + if (msgNestedLevel > waitingNestedLevel) return false; + + // When both sides send sync messages of the same nested level, we resolve the + // race by dispatching in the child and deferring the incoming message in + // the parent. However, the parent still needs to dispatch nested sync + // messages. + // + // Deferring in the parent only sort of breaks message ordering. When the + // child's message comes in, we can pretend the child hasn't quite + // finished sending it yet. Since the message is sync, we know that the + // child hasn't moved on yet. + return mSide == ParentSide && + aMsg.transaction_id() != CurrentNestedInsideSyncTransaction(); +} + +void MessageChannel::OnMessageReceivedFromLink(UniquePtr<Message> aMsg) { + mMonitor->AssertCurrentThreadOwns(); + MOZ_ASSERT(mChannelState == ChannelConnected); + + if (MaybeInterceptSpecialIOMessage(*aMsg)) { + return; + } + + mListener->OnChannelReceivedMessage(*aMsg); + + // If we're awaiting a sync reply, we know that it needs to be immediately + // handled to unblock us. + if (aMsg->is_sync() && aMsg->is_reply()) { + IPC_LOG("Received reply seqno=%d xid=%d", aMsg->seqno(), + aMsg->transaction_id()); + + if (aMsg->seqno() == mTimedOutMessageSeqno) { + // Drop the message, but allow future sync messages to be sent. + IPC_LOG("Received reply to timedout message; igoring; xid=%d", + mTimedOutMessageSeqno); + EndTimeout(); + return; + } + + MOZ_RELEASE_ASSERT(AwaitingSyncReply()); + MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno); + + mTransactionStack->HandleReply(std::move(aMsg)); + NotifyWorkerThread(); + return; + } + + // Nested messages cannot be compressed. + MOZ_RELEASE_ASSERT(aMsg->compress_type() == IPC::Message::COMPRESSION_NONE || + aMsg->nested_level() == IPC::Message::NOT_NESTED); + + if (aMsg->compress_type() == IPC::Message::COMPRESSION_ENABLED && + !mPending.isEmpty()) { + auto* last = mPending.getLast(); + last->AssertMonitorHeld(*mMonitor); + bool compress = last->Msg()->type() == aMsg->type() && + last->Msg()->routing_id() == aMsg->routing_id(); + if (compress) { + // This message type has compression enabled, and the back of the + // queue was the same message type and routed to the same destination. + // Replace it with the newer message. + MOZ_RELEASE_ASSERT(last->Msg()->compress_type() == + IPC::Message::COMPRESSION_ENABLED); + last->Msg() = std::move(aMsg); + return; + } + } else if (aMsg->compress_type() == IPC::Message::COMPRESSION_ALL && + !mPending.isEmpty()) { + for (MessageTask* p = mPending.getLast(); p; p = p->getPrevious()) { + p->AssertMonitorHeld(*mMonitor); + if (p->Msg()->type() == aMsg->type() && + p->Msg()->routing_id() == aMsg->routing_id()) { + // This message type has compression enabled, and the queue + // holds a message with the same message type and routed to the + // same destination. Erase it. Note that, since we always + // compress these redundancies, There Can Be Only One. + MOZ_RELEASE_ASSERT(p->Msg()->compress_type() == + IPC::Message::COMPRESSION_ALL); + MOZ_RELEASE_ASSERT(IsAlwaysDeferred(*p->Msg())); + p->remove(); + break; + } + } + } + + bool alwaysDeferred = IsAlwaysDeferred(*aMsg); + + bool shouldWakeUp = AwaitingSyncReply() && !ShouldDeferMessage(*aMsg); + + IPC_LOG("Receive from link; seqno=%d, xid=%d, shouldWakeUp=%d", aMsg->seqno(), + aMsg->transaction_id(), shouldWakeUp); + + // There are two cases we're concerned about, relating to the state of the + // worker thread: + // + // (1) We are waiting on a sync reply - worker thread is blocked on the + // IPC monitor. + // - If the message is NESTED_INSIDE_SYNC, we wake up the worker thread to + // deliver the message depending on ShouldDeferMessage. Otherwise, we + // leave it in the mPending queue, posting a task to the worker event + // loop, where it will be processed once the synchronous reply has been + // received. + // + // (2) We are not waiting on a reply. + // - We post a task to the worker event loop. + // + // Note that, we may notify the worker thread even though the monitor is not + // blocked. This is okay, since we always check for pending events before + // blocking again. + + RefPtr<MessageTask> task = new MessageTask(this, std::move(aMsg)); + mPending.insertBack(task); + + if (!alwaysDeferred) { + mMaybeDeferredPendingCount++; + } + + if (shouldWakeUp) { + NotifyWorkerThread(); + } + + // Although we usually don't need to post a message task if + // shouldWakeUp is true, it's easier to post anyway than to have to + // guarantee that every Send call processes everything it's supposed to + // before returning. + task->AssertMonitorHeld(*mMonitor); + task->Post(); +} + +void MessageChannel::PeekMessages( + const std::function<bool(const Message& aMsg)>& aInvoke) { + // FIXME: We shouldn't be holding the lock for aInvoke! + MonitorAutoLock lock(*mMonitor); + + for (MessageTask* it : mPending) { + it->AssertMonitorHeld(*mMonitor); + const Message& msg = *it->Msg(); + if (!aInvoke(msg)) { + break; + } + } +} + +void MessageChannel::ProcessPendingRequests( + ActorLifecycleProxy* aProxy, AutoEnterTransaction& aTransaction) { + mMonitor->AssertCurrentThreadOwns(); + + AssertMaybeDeferredCountCorrect(); + if (mMaybeDeferredPendingCount == 0) { + return; + } + + IPC_LOG("ProcessPendingRequests for seqno=%d, xid=%d", + aTransaction.SequenceNumber(), aTransaction.TransactionID()); + + // Loop until there aren't any more nested messages to process. + for (;;) { + // If we canceled during ProcessPendingRequest, then we need to leave + // immediately because the results of ShouldDeferMessage will be + // operating with weird state (as if no Send is in progress). That could + // cause even NOT_NESTED sync messages to be processed (but not + // NOT_NESTED async messages), which would break message ordering. + if (aTransaction.IsCanceled()) { + return; + } + + Vector<UniquePtr<Message>> toProcess; + + for (MessageTask* p = mPending.getFirst(); p;) { + p->AssertMonitorHeld(*mMonitor); + UniquePtr<Message>& msg = p->Msg(); + + MOZ_RELEASE_ASSERT(!aTransaction.IsCanceled(), + "Calling ShouldDeferMessage when cancelled"); + bool defer = ShouldDeferMessage(*msg); + + // Only log the interesting messages. + if (msg->is_sync() || + msg->nested_level() == IPC::Message::NESTED_INSIDE_CPOW) { + IPC_LOG("ShouldDeferMessage(seqno=%d) = %d", msg->seqno(), defer); + } + + if (!defer) { + MOZ_ASSERT(!IsAlwaysDeferred(*msg)); + + if (!toProcess.append(std::move(msg))) MOZ_CRASH(); + + mMaybeDeferredPendingCount--; + + p = p->removeAndGetNext(); + continue; + } + p = p->getNext(); + } + + if (toProcess.empty()) { + break; + } + + // Processing these messages could result in more messages, so we + // loop around to check for more afterwards. + + for (auto& msg : toProcess) { + ProcessPendingRequest(aProxy, std::move(msg)); + } + } + + AssertMaybeDeferredCountCorrect(); +} + +bool MessageChannel::Send(UniquePtr<Message> aMsg, UniquePtr<Message>* aReply) { + mozilla::TimeStamp start = TimeStamp::Now(); + if (aMsg->size() >= kMinTelemetryMessageSize) { + Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE2, aMsg->size()); + } + + // Sanity checks. + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + MOZ_RELEASE_ASSERT(!mIsSameThreadChannel, + "sync send over same-thread channel will deadlock!"); + + RefPtr<ActorLifecycleProxy> proxy = Listener()->GetLifecycleProxy(); + +#ifdef XP_WIN + SyncStackFrame frame(this); + NeuteredWindowRegion neuteredRgn(mFlags & + REQUIRE_DEFERRED_MESSAGE_PROTECTION); +#endif + + AutoSetValue<bool> setOnCxxStack(mOnCxxStack, true); + + MonitorAutoLock lock(*mMonitor); + + if (mTimedOutMessageSeqno) { + // Don't bother sending another sync message if a previous one timed out + // and we haven't received a reply for it. Once the original timed-out + // message receives a reply, we'll be able to send more sync messages + // again. + IPC_LOG("Send() failed due to previous timeout"); + mLastSendError = SyncSendError::PreviousTimeout; + return false; + } + + if (DispatchingSyncMessageNestedLevel() == IPC::Message::NOT_NESTED && + aMsg->nested_level() > IPC::Message::NOT_NESTED) { + // Don't allow sending CPOWs while we're dispatching a sync message. + IPC_LOG("Nested level forbids send"); + mLastSendError = SyncSendError::SendingCPOWWhileDispatchingSync; + return false; + } + + if (DispatchingSyncMessageNestedLevel() == IPC::Message::NESTED_INSIDE_CPOW || + DispatchingAsyncMessageNestedLevel() == + IPC::Message::NESTED_INSIDE_CPOW) { + // Generally only the parent dispatches urgent messages. And the only + // sync messages it can send are NESTED_INSIDE_SYNC. Mainly we want to + // ensure here that we don't return false for non-CPOW messages. + MOZ_RELEASE_ASSERT(aMsg->nested_level() == + IPC::Message::NESTED_INSIDE_SYNC); + IPC_LOG("Sending while dispatching urgent message"); + mLastSendError = SyncSendError::SendingCPOWWhileDispatchingUrgent; + return false; + } + + if (aMsg->nested_level() < DispatchingSyncMessageNestedLevel() || + aMsg->nested_level() < AwaitingSyncReplyNestedLevel()) { + MOZ_RELEASE_ASSERT(DispatchingSyncMessage() || DispatchingAsyncMessage()); + IPC_LOG("Cancel from Send"); + auto cancel = + MakeUnique<CancelMessage>(CurrentNestedInsideSyncTransaction()); + CancelTransaction(CurrentNestedInsideSyncTransaction()); + SendMessageToLink(std::move(cancel)); + } + + IPC_ASSERT(aMsg->is_sync(), "can only Send() sync messages here"); + + IPC_ASSERT(aMsg->nested_level() >= DispatchingSyncMessageNestedLevel(), + "can't send sync message of a lesser nested level than what's " + "being dispatched"); + IPC_ASSERT(AwaitingSyncReplyNestedLevel() <= aMsg->nested_level(), + "nested sync message sends must be of increasing nested level"); + IPC_ASSERT( + DispatchingSyncMessageNestedLevel() != IPC::Message::NESTED_INSIDE_CPOW, + "not allowed to send messages while dispatching urgent messages"); + + IPC_ASSERT( + DispatchingAsyncMessageNestedLevel() != IPC::Message::NESTED_INSIDE_CPOW, + "not allowed to send messages while dispatching urgent messages"); + + if (!Connected()) { + ReportConnectionError("SendAndWait", aMsg->type()); + mLastSendError = SyncSendError::NotConnectedBeforeSend; + return false; + } + + aMsg->set_seqno(NextSeqno()); + + int32_t seqno = aMsg->seqno(); + int nestedLevel = aMsg->nested_level(); + msgid_t replyType = aMsg->type() + 1; + + AutoEnterTransaction* stackTop = mTransactionStack; + + // If the most recent message on the stack is NESTED_INSIDE_SYNC, then our + // message should nest inside that and we use the same transaction + // ID. Otherwise we need a new transaction ID (so we use the seqno of the + // message we're sending). + bool nest = + stackTop && stackTop->NestedLevel() == IPC::Message::NESTED_INSIDE_SYNC; + int32_t transaction = nest ? stackTop->TransactionID() : seqno; + aMsg->set_transaction_id(transaction); + + AutoEnterTransaction transact(this, seqno, transaction, nestedLevel); + + IPC_LOG("Send seqno=%d, xid=%d", seqno, transaction); + + // aMsg will be destroyed soon, let's keep its type. + const char* msgName = aMsg->name(); + const msgid_t msgType = aMsg->type(); + + AddProfilerMarker(*aMsg, MessageDirection::eSending); + SendMessageToLink(std::move(aMsg)); + + while (true) { + MOZ_RELEASE_ASSERT(!transact.IsCanceled()); + ProcessPendingRequests(proxy, transact); + if (transact.IsComplete()) { + break; + } + if (!Connected()) { + ReportConnectionError("Send", msgType); + mLastSendError = SyncSendError::DisconnectedDuringSend; + return false; + } + + MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno); + MOZ_RELEASE_ASSERT(!transact.IsComplete()); + MOZ_RELEASE_ASSERT(mTransactionStack == &transact); + + bool maybeTimedOut = !WaitForSyncNotify(); + + if (mListener->NeedArtificialSleep()) { + MonitorAutoUnlock unlock(*mMonitor); + mListener->ArtificialSleep(); + } + + if (!Connected()) { + ReportConnectionError("SendAndWait", msgType); + mLastSendError = SyncSendError::DisconnectedDuringSend; + return false; + } + + if (transact.IsCanceled()) { + break; + } + + MOZ_RELEASE_ASSERT(mTransactionStack == &transact); + + // We only time out a message if it initiated a new transaction (i.e., + // if neither side has any other message Sends on the stack). + bool canTimeOut = transact.IsBottom(); + if (maybeTimedOut && canTimeOut && !ShouldContinueFromTimeout()) { + // Since ShouldContinueFromTimeout drops the lock, we need to + // re-check all our conditions here. We shouldn't time out if any of + // these things happen because there won't be a reply to the timed + // out message in these cases. + if (transact.IsComplete()) { + break; + } + + IPC_LOG("Timing out Send: xid=%d", transaction); + + mTimedOutMessageSeqno = seqno; + mTimedOutMessageNestedLevel = nestedLevel; + mLastSendError = SyncSendError::TimedOut; + return false; + } + + if (transact.IsCanceled()) { + break; + } + } + + if (transact.IsCanceled()) { + IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction); + mLastSendError = SyncSendError::CancelledAfterSend; + return false; + } + + if (transact.IsError()) { + IPC_LOG("Error: seqno=%d, xid=%d", seqno, transaction); + mLastSendError = SyncSendError::ReplyError; + return false; + } + + uint32_t latencyMs = round((TimeStamp::Now() - start).ToMilliseconds()); + IPC_LOG("Got reply: seqno=%d, xid=%d, msgName=%s, latency=%ums", seqno, + transaction, msgName, latencyMs); + + UniquePtr<Message> reply = transact.GetReply(); + + MOZ_RELEASE_ASSERT(reply); + MOZ_RELEASE_ASSERT(reply->is_reply(), "expected reply"); + MOZ_RELEASE_ASSERT(!reply->is_reply_error()); + MOZ_RELEASE_ASSERT(reply->seqno() == seqno); + MOZ_RELEASE_ASSERT(reply->type() == replyType, "wrong reply type"); + MOZ_RELEASE_ASSERT(reply->is_sync()); + + AddProfilerMarker(*reply, MessageDirection::eReceiving); + + if (reply->size() >= kMinTelemetryMessageSize) { + Telemetry::Accumulate(Telemetry::IPC_REPLY_SIZE, + nsDependentCString(msgName), reply->size()); + } + + *aReply = std::move(reply); + + // NOTE: Only collect IPC_SYNC_MAIN_LATENCY_MS on the main thread (bug + // 1343729) + if (NS_IsMainThread() && latencyMs >= kMinTelemetrySyncIPCLatencyMs) { + Telemetry::Accumulate(Telemetry::IPC_SYNC_MAIN_LATENCY_MS, + nsDependentCString(msgName), latencyMs); + } + return true; +} + +bool MessageChannel::HasPendingEvents() { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + return ConnectedOrClosing() && !mPending.isEmpty(); +} + +bool MessageChannel::ProcessPendingRequest(ActorLifecycleProxy* aProxy, + UniquePtr<Message> aUrgent) { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + IPC_LOG("Process pending: seqno=%d, xid=%d", aUrgent->seqno(), + aUrgent->transaction_id()); + + // keep the error relevant information + msgid_t msgType = aUrgent->type(); + + DispatchMessage(aProxy, std::move(aUrgent)); + if (!ConnectedOrClosing()) { + ReportConnectionError("ProcessPendingRequest", msgType); + return false; + } + + return true; +} + +bool MessageChannel::ShouldRunMessage(const Message& aMsg) { + if (!mTimedOutMessageSeqno) { + return true; + } + + // If we've timed out a message and we're awaiting the reply to the timed + // out message, we have to be careful what messages we process. Here's what + // can go wrong: + // 1. child sends a NOT_NESTED sync message S + // 2. parent sends a NESTED_INSIDE_SYNC sync message H at the same time + // 3. parent times out H + // 4. child starts processing H and sends a NESTED_INSIDE_SYNC message H' + // nested within the same transaction + // 5. parent dispatches S and sends reply + // 6. child asserts because it instead expected a reply to H'. + // + // To solve this, we refuse to process S in the parent until we get a reply + // to H. More generally, let the timed out message be M. We don't process a + // message unless the child would need the response to that message in order + // to process M. Those messages are the ones that have a higher nested level + // than M or that are part of the same transaction as M. + if (aMsg.nested_level() < mTimedOutMessageNestedLevel || + (aMsg.nested_level() == mTimedOutMessageNestedLevel && + aMsg.transaction_id() != mTimedOutMessageSeqno)) { + return false; + } + + return true; +} + +void MessageChannel::RunMessage(ActorLifecycleProxy* aProxy, + MessageTask& aTask) { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + aTask.AssertMonitorHeld(*mMonitor); + + UniquePtr<Message>& msg = aTask.Msg(); + + if (!ConnectedOrClosing()) { + ReportConnectionError("RunMessage", msg->type()); + return; + } + + // Check that we're going to run the first message that's valid to run. +#if 0 +# ifdef DEBUG + for (MessageTask* task : mPending) { + if (task == &aTask) { + break; + } + + MOZ_ASSERT(!ShouldRunMessage(*task->Msg()) || + aTask.Msg()->priority() != task->Msg()->priority()); + + } +# endif +#endif + + if (!ShouldRunMessage(*msg)) { + return; + } + + MOZ_RELEASE_ASSERT(aTask.isInList()); + aTask.remove(); + + if (!IsAlwaysDeferred(*msg)) { + mMaybeDeferredPendingCount--; + } + + DispatchMessage(aProxy, std::move(msg)); +} + +NS_IMPL_ISUPPORTS_INHERITED(MessageChannel::MessageTask, CancelableRunnable, + nsIRunnablePriority, nsIRunnableIPCMessageType) + +static uint32_t ToRunnablePriority(IPC::Message::PriorityValue aPriority) { + switch (aPriority) { + case IPC::Message::NORMAL_PRIORITY: + return nsIRunnablePriority::PRIORITY_NORMAL; + case IPC::Message::INPUT_PRIORITY: + return nsIRunnablePriority::PRIORITY_INPUT_HIGH; + case IPC::Message::VSYNC_PRIORITY: + return nsIRunnablePriority::PRIORITY_VSYNC; + case IPC::Message::MEDIUMHIGH_PRIORITY: + return nsIRunnablePriority::PRIORITY_MEDIUMHIGH; + case IPC::Message::CONTROL_PRIORITY: + return nsIRunnablePriority::PRIORITY_CONTROL; + default: + MOZ_ASSERT_UNREACHABLE(); + return nsIRunnablePriority::PRIORITY_NORMAL; + } +} + +MessageChannel::MessageTask::MessageTask(MessageChannel* aChannel, + UniquePtr<Message> aMessage) + : CancelableRunnable(aMessage->name()), + mMonitor(aChannel->mMonitor), + mChannel(aChannel), + mMessage(std::move(aMessage)), + mPriority(ToRunnablePriority(mMessage->priority())), + mScheduled(false) +#ifdef FUZZING_SNAPSHOT + , + mIsFuzzMsg(mMessage->IsFuzzMsg()), + mFuzzStopped(false) +#endif +{ + MOZ_DIAGNOSTIC_ASSERT(mMessage, "message may not be null"); +#ifdef FUZZING_SNAPSHOT + if (mIsFuzzMsg) { + MOZ_FUZZING_IPC_MT_CTOR(); + } +#endif +} + +MessageChannel::MessageTask::~MessageTask() { +#ifdef FUZZING_SNAPSHOT + // We track fuzzing messages until their run is complete. To make sure + // that we don't miss messages that are for some reason destroyed without + // being run (e.g. canceled), we catch this condition in the destructor. + if (mIsFuzzMsg && !mFuzzStopped) { + MOZ_FUZZING_IPC_MT_STOP(); + } else if (!mIsFuzzMsg && !fuzzing::Nyx::instance().started()) { + MOZ_FUZZING_IPC_PRE_FUZZ_MT_STOP(); + } +#endif +} + +nsresult MessageChannel::MessageTask::Run() { + mMonitor->AssertNotCurrentThreadOwns(); + + // Drop the toplevel actor's lifecycle proxy outside of our monitor if we take + // it, as destroying our ActorLifecycleProxy reference can acquire the + // monitor. + RefPtr<ActorLifecycleProxy> proxy; + + MonitorAutoLock lock(*mMonitor); + + // In case we choose not to run this message, we may need to be able to Post + // it again. + mScheduled = false; + + if (!isInList()) { + return NS_OK; + } + +#ifdef FUZZING_SNAPSHOT + if (!mIsFuzzMsg) { + if (fuzzing::Nyx::instance().started()) { + // Once we started fuzzing, prevent non-fuzzing tasks from being + // run and potentially blocking worker threads. + // + // TODO: This currently blocks all MessageTasks from running, not + // just those belonging to the target process pair. We currently + // do this for performance reasons, but it should be re-evaluated + // at a later stage when we found a better snapshot point. + return NS_OK; + } + // Record all running tasks prior to fuzzing, so we can wait for + // them to settle before snapshotting. + MOZ_FUZZING_IPC_PRE_FUZZ_MT_RUN(); + } +#endif + + Channel()->AssertWorkerThread(); + mMonitor->AssertSameMonitor(*Channel()->mMonitor); + proxy = Channel()->Listener()->GetLifecycleProxy(); + Channel()->RunMessage(proxy, *this); + +#ifdef FUZZING_SNAPSHOT + if (mIsFuzzMsg && !mFuzzStopped) { + MOZ_FUZZING_IPC_MT_STOP(); + mFuzzStopped = true; + } +#endif + return NS_OK; +} + +// Warning: This method removes the receiver from whatever list it might be in. +nsresult MessageChannel::MessageTask::Cancel() { + mMonitor->AssertNotCurrentThreadOwns(); + + MonitorAutoLock lock(*mMonitor); + + if (!isInList()) { + return NS_OK; + } + + Channel()->AssertWorkerThread(); + mMonitor->AssertSameMonitor(*Channel()->mMonitor); + if (!IsAlwaysDeferred(*Msg())) { + Channel()->mMaybeDeferredPendingCount--; + } + + remove(); + +#ifdef FUZZING_SNAPSHOT + if (mIsFuzzMsg && !mFuzzStopped) { + MOZ_FUZZING_IPC_MT_STOP(); + mFuzzStopped = true; + } +#endif + + return NS_OK; +} + +void MessageChannel::MessageTask::Post() { + mMonitor->AssertCurrentThreadOwns(); + mMonitor->AssertSameMonitor(*Channel()->mMonitor); + MOZ_RELEASE_ASSERT(!mScheduled); + MOZ_RELEASE_ASSERT(isInList()); + + mScheduled = true; + + Channel()->mWorkerThread->Dispatch(do_AddRef(this)); +} + +NS_IMETHODIMP +MessageChannel::MessageTask::GetPriority(uint32_t* aPriority) { + *aPriority = mPriority; + return NS_OK; +} + +NS_IMETHODIMP +MessageChannel::MessageTask::GetType(uint32_t* aType) { + mMonitor->AssertNotCurrentThreadOwns(); + + MonitorAutoLock lock(*mMonitor); + if (!mMessage) { + // If mMessage has been moved already elsewhere, we can't know what the type + // has been. + return NS_ERROR_FAILURE; + } + + *aType = mMessage->type(); + return NS_OK; +} + +void MessageChannel::DispatchMessage(ActorLifecycleProxy* aProxy, + UniquePtr<Message> aMsg) { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + Maybe<AutoNoJSAPI> nojsapi; + if (NS_IsMainThread() && CycleCollectedJSContext::Get()) { + nojsapi.emplace(); + } + + UniquePtr<Message> reply; + +#ifdef FUZZING_SNAPSHOT + if (IsCrossProcess()) { + aMsg = mozilla::fuzzing::IPCFuzzController::instance().replaceIPCMessage( + std::move(aMsg)); + } +#endif + + IPC_LOG("DispatchMessage: seqno=%d, xid=%d", aMsg->seqno(), + aMsg->transaction_id()); + AddProfilerMarker(*aMsg, MessageDirection::eReceiving); + + { + AutoEnterTransaction transaction(this, *aMsg); + + int id = aMsg->transaction_id(); + MOZ_RELEASE_ASSERT(!aMsg->is_sync() || id == transaction.TransactionID()); + + { + MonitorAutoUnlock unlock(*mMonitor); + AutoSetValue<bool> setOnCxxStack(mOnCxxStack, true); + + mListener->ArtificialSleep(); + + if (aMsg->is_sync()) { + DispatchSyncMessage(aProxy, *aMsg, reply); + } else { + DispatchAsyncMessage(aProxy, *aMsg); + } + + mListener->ArtificialSleep(); + } + + if (reply && transaction.IsCanceled()) { + // The transaction has been canceled. Don't send a reply. + IPC_LOG("Nulling out reply due to cancellation, seqno=%d, xid=%d", + aMsg->seqno(), id); + reply = nullptr; + } + } + +#ifdef FUZZING_SNAPSHOT + if (aMsg->IsFuzzMsg()) { + mozilla::fuzzing::IPCFuzzController::instance().syncAfterReplace(); + } +#endif + + if (reply && ChannelConnected == mChannelState) { + IPC_LOG("Sending reply seqno=%d, xid=%d", aMsg->seqno(), + aMsg->transaction_id()); + AddProfilerMarker(*reply, MessageDirection::eSending); + + SendMessageToLink(std::move(reply)); + } +} + +void MessageChannel::DispatchSyncMessage(ActorLifecycleProxy* aProxy, + const Message& aMsg, + UniquePtr<Message>& aReply) { + AssertWorkerThread(); + + mozilla::TimeStamp start = TimeStamp::Now(); + + int nestedLevel = aMsg.nested_level(); + + MOZ_RELEASE_ASSERT(nestedLevel == IPC::Message::NOT_NESTED || + NS_IsMainThread()); + + MessageChannel* dummy; + MessageChannel*& blockingVar = + mSide == ChildSide && NS_IsMainThread() ? gParentProcessBlocker : dummy; + + Result rv; + { + AutoSetValue<MessageChannel*> blocked(blockingVar, this); + rv = aProxy->Get()->OnMessageReceived(aMsg, aReply); + } + + uint32_t latencyMs = round((TimeStamp::Now() - start).ToMilliseconds()); + if (latencyMs >= kMinTelemetrySyncIPCLatencyMs) { + Telemetry::Accumulate(Telemetry::IPC_SYNC_RECEIVE_MS, + nsDependentCString(aMsg.name()), latencyMs); + } + + if (!MaybeHandleError(rv, aMsg, "DispatchSyncMessage")) { + aReply = Message::ForSyncDispatchError(aMsg.nested_level()); + } + aReply->set_seqno(aMsg.seqno()); + aReply->set_transaction_id(aMsg.transaction_id()); +} + +void MessageChannel::DispatchAsyncMessage(ActorLifecycleProxy* aProxy, + const Message& aMsg) { + AssertWorkerThread(); + MOZ_RELEASE_ASSERT(!aMsg.is_sync()); + + if (aMsg.routing_id() == MSG_ROUTING_NONE) { + NS_WARNING("unhandled special message!"); + MaybeHandleError(MsgNotKnown, aMsg, "DispatchAsyncMessage"); + return; + } + + Result rv; + { + int nestedLevel = aMsg.nested_level(); + AutoSetValue<bool> async(mDispatchingAsyncMessage, true); + AutoSetValue<int> nestedLevelSet(mDispatchingAsyncMessageNestedLevel, + nestedLevel); + rv = aProxy->Get()->OnMessageReceived(aMsg); + } + MaybeHandleError(rv, aMsg, "DispatchAsyncMessage"); +} + +void MessageChannel::EnqueuePendingMessages() { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + // XXX performance tuning knob: could process all or k pending + // messages here, rather than enqueuing for later processing + + RepostAllMessages(); +} + +bool MessageChannel::WaitResponse(bool aWaitTimedOut) { + AssertWorkerThread(); + if (aWaitTimedOut) { + if (mInTimeoutSecondHalf) { + // We've really timed out this time. + return false; + } + // Try a second time. + mInTimeoutSecondHalf = true; + } else { + mInTimeoutSecondHalf = false; + } + return true; +} + +#ifndef XP_WIN +bool MessageChannel::WaitForSyncNotify() { + AssertWorkerThread(); +# ifdef DEBUG + // WARNING: We don't release the lock here. We can't because the link + // could signal at this time and we would miss it. Instead we require + // ArtificialTimeout() to be extremely simple. + if (mListener->ArtificialTimeout()) { + return false; + } +# endif + + MOZ_RELEASE_ASSERT(!mIsSameThreadChannel, + "Wait on same-thread channel will deadlock!"); + + TimeDuration timeout = (kNoTimeout == mTimeoutMs) + ? TimeDuration::Forever() + : TimeDuration::FromMilliseconds(mTimeoutMs); + CVStatus status = mMonitor->Wait(timeout); + + // If the timeout didn't expire, we know we received an event. The + // converse is not true. + return WaitResponse(status == CVStatus::Timeout); +} + +void MessageChannel::NotifyWorkerThread() { mMonitor->Notify(); } +#endif + +bool MessageChannel::ShouldContinueFromTimeout() { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + bool cont; + { + MonitorAutoUnlock unlock(*mMonitor); + cont = mListener->ShouldContinueFromReplyTimeout(); + mListener->ArtificialSleep(); + } + + static enum { + UNKNOWN, + NOT_DEBUGGING, + DEBUGGING + } sDebuggingChildren = UNKNOWN; + + if (sDebuggingChildren == UNKNOWN) { + sDebuggingChildren = + getenv("MOZ_DEBUG_CHILD_PROCESS") || getenv("MOZ_DEBUG_CHILD_PAUSE") + ? DEBUGGING + : NOT_DEBUGGING; + } + if (sDebuggingChildren == DEBUGGING) { + return true; + } + + return cont; +} + +void MessageChannel::SetReplyTimeoutMs(int32_t aTimeoutMs) { + // Set channel timeout value. Since this is broken up into + // two period, the minimum timeout value is 2ms. + AssertWorkerThread(); + mTimeoutMs = + (aTimeoutMs <= 0) ? kNoTimeout : (int32_t)ceil((double)aTimeoutMs / 2.0); +} + +void MessageChannel::ReportConnectionError(const char* aFunctionName, + const uint32_t aMsgType) const { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + const char* errorMsg = nullptr; + switch (mChannelState) { + case ChannelClosed: + errorMsg = "Closed channel: cannot send/recv"; + break; + case ChannelClosing: + errorMsg = "Channel closing: too late to send, messages will be lost"; + break; + case ChannelError: + errorMsg = "Channel error: cannot send/recv"; + break; + + default: + MOZ_CRASH("unreached"); + } + + // IPC connection errors are fairly common, especially "Channel closing: too + // late to send/recv, messages will be lost", so shouldn't be being reported + // on release builds, as that's misleading as to their severity. + NS_WARNING(nsPrintfCString("IPC Connection Error: [%s][%s] %s(msgname=%s) %s", + StringFromIPCSide(mSide), mName, aFunctionName, + IPC::StringFromIPCMessageType(aMsgType), errorMsg) + .get()); + + MonitorAutoUnlock unlock(*mMonitor); + mListener->ProcessingError(MsgDropped, errorMsg); +} + +void MessageChannel::ReportMessageRouteError(const char* channelName) const { + PrintErrorMessage(mSide, channelName, "Need a route"); + mListener->ProcessingError(MsgRouteError, "MsgRouteError"); +} + +bool MessageChannel::MaybeHandleError(Result code, const Message& aMsg, + const char* channelName) { + if (MsgProcessed == code) return true; + +#ifdef FUZZING_SNAPSHOT + mozilla::fuzzing::IPCFuzzController::instance().OnMessageError(code, aMsg); +#endif + + const char* errorMsg = nullptr; + switch (code) { + case MsgNotKnown: + errorMsg = "Unknown message: not processed"; + break; + case MsgNotAllowed: + errorMsg = "Message not allowed: cannot be sent/recvd in this state"; + break; + case MsgPayloadError: + errorMsg = "Payload error: message could not be deserialized"; + break; + case MsgProcessingError: + errorMsg = + "Processing error: message was deserialized, but the handler " + "returned false (indicating failure)"; + break; + case MsgRouteError: + errorMsg = "Route error: message sent to unknown actor ID"; + break; + case MsgValueError: + errorMsg = + "Value error: message was deserialized, but contained an illegal " + "value"; + break; + + default: + MOZ_CRASH("unknown Result code"); + return false; + } + + char reason[512]; + const char* msgname = aMsg.name(); + if (msgname[0] == '?') { + SprintfLiteral(reason, "(msgtype=0x%X) %s", aMsg.type(), errorMsg); + } else { + SprintfLiteral(reason, "%s %s", msgname, errorMsg); + } + + PrintErrorMessage(mSide, channelName, reason); + + // Error handled in mozilla::ipc::IPCResult. + if (code == MsgProcessingError) { + return false; + } + + mListener->ProcessingError(code, reason); + + return false; +} + +void MessageChannel::OnChannelErrorFromLink() { + mMonitor->AssertCurrentThreadOwns(); + MOZ_ASSERT(mChannelState == ChannelConnected); + + IPC_LOG("OnChannelErrorFromLink"); + + if (AwaitingSyncReply()) { + NotifyWorkerThread(); + } + + if (mAbortOnError) { + // mAbortOnError is set by main actors (e.g., ContentChild) to ensure + // that the process terminates even if normal shutdown is prevented. + // A MOZ_CRASH() here is not helpful because crash reporting relies + // on the parent process which we know is dead or otherwise unusable. + // + // Additionally, the parent process can (and often is) killed on Android + // when apps are backgrounded. We don't need to report a crash for + // normal behavior in that case. + printf_stderr("Exiting due to channel error.\n"); + ProcessChild::QuickExit(); + } + mChannelState = ChannelError; + mMonitor->Notify(); + + PostErrorNotifyTask(); +} + +void MessageChannel::NotifyMaybeChannelError(ReleasableMonitorAutoLock& aLock) { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + aLock.AssertCurrentThreadOwns(); + MOZ_ASSERT(mChannelState != ChannelConnected); + + if (ChannelClosing == mChannelState || ChannelClosed == mChannelState) { + // the channel closed, but we received a "Goodbye" message warning us + // about it. no worries + mChannelState = ChannelClosed; + NotifyChannelClosed(aLock); + return; + } + + MOZ_ASSERT(ChannelError == mChannelState); + + Clear(); + + // IPDL assumes these notifications do not fire twice, so we do not let + // that happen. + if (mNotifiedChannelDone) { + return; + } + mNotifiedChannelDone = true; + + // Let our listener know that the channel errored. This may cause the + // channel to be deleted. Release our caller's `MonitorAutoLock` before + // invoking the listener, as this may call back into MessageChannel, and/or + // cause the channel to be destroyed. + aLock.Unlock(); + mListener->OnChannelError(); +} + +void MessageChannel::OnNotifyMaybeChannelError() { + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + + // This lock guard may be reset by `NotifyMaybeChannelError` before invoking + // listener callbacks which may destroy this `MessageChannel`. + // + // Acquiring the lock here also allows us to ensure that + // `OnChannelErrorFromLink` has finished running before this task is allowed + // to continue. + ReleasableMonitorAutoLock lock(*mMonitor); + + mChannelErrorTask = nullptr; + + if (IsOnCxxStack()) { + // This used to post a 10ms delayed task; however not all + // nsISerialEventTarget implementations support delayed dispatch. + // The delay being completely arbitrary, we may not as well have any. + PostErrorNotifyTask(); + return; + } + + // This call may destroy `this`. + NotifyMaybeChannelError(lock); +} + +void MessageChannel::PostErrorNotifyTask() { + mMonitor->AssertCurrentThreadOwns(); + + if (mChannelErrorTask) { + return; + } + + // This must be the last code that runs on this thread! + mChannelErrorTask = NewNonOwningCancelableRunnableMethod( + "ipc::MessageChannel::OnNotifyMaybeChannelError", this, + &MessageChannel::OnNotifyMaybeChannelError); + mWorkerThread->Dispatch(do_AddRef(mChannelErrorTask)); +} + +// Special async message. +class GoodbyeMessage : public IPC::Message { + public: + GoodbyeMessage() : IPC::Message(MSG_ROUTING_NONE, GOODBYE_MESSAGE_TYPE) {} + static bool Read(const Message* msg) { return true; } + void Log(const std::string& aPrefix, FILE* aOutf) const { + fputs("(special `Goodbye' message)", aOutf); + } +}; + +void MessageChannel::InduceConnectionError() { + MonitorAutoLock lock(*mMonitor); + + // Either connected or closing, immediately convert to an error and notify. + switch (mChannelState) { + case ChannelConnected: + // The channel is still actively connected. Immediately shut down the + // connection with our peer and simulate it invoking + // OnChannelErrorFromLink on us. + // + // This will update the state to ChannelError, preventing new messages + // from being processed, leading to an error being reported asynchronously + // to our listener. + mLink->Close(); + OnChannelErrorFromLink(); + return; + + case ChannelClosing: + // An notify task has already been posted. Update mChannelState to stop + // processing new messages and treat the notification as an error. + mChannelState = ChannelError; + return; + + default: + // Either already closed or errored. Nothing to do. + MOZ_ASSERT(mChannelState == ChannelClosed || + mChannelState == ChannelError); + return; + } +} + +void MessageChannel::NotifyImpendingShutdown() { + UniquePtr<Message> msg = + MakeUnique<Message>(MSG_ROUTING_NONE, IMPENDING_SHUTDOWN_MESSAGE_TYPE); + MonitorAutoLock lock(*mMonitor); + if (Connected()) { + SendMessageToLink(std::move(msg)); + } +} + +void MessageChannel::Close() { + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + + // This lock guard may be reset by `Notify{ChannelClosed,MaybeChannelError}` + // before invoking listener callbacks which may destroy this `MessageChannel`. + ReleasableMonitorAutoLock lock(*mMonitor); + + switch (mChannelState) { + case ChannelError: + // See bug 538586: if the listener gets deleted while the + // IO thread's NotifyChannelError event is still enqueued + // and subsequently deletes us, then the error event will + // also be deleted and the listener will never be notified + // of the channel error. + NotifyMaybeChannelError(lock); + return; + case ChannelClosed: + // Slightly unexpected but harmless; ignore. See bug 1554244. + return; + + default: + // Notify the other side that we're about to close our socket. If we've + // already received a Goodbye from the other side (and our state is + // ChannelClosing), there's no reason to send one. + if (ChannelConnected == mChannelState) { + SendMessageToLink(MakeUnique<GoodbyeMessage>()); + } + mLink->Close(); + mChannelState = ChannelClosed; + NotifyChannelClosed(lock); + return; + } +} + +void MessageChannel::NotifyChannelClosed(ReleasableMonitorAutoLock& aLock) { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + aLock.AssertCurrentThreadOwns(); + + if (ChannelClosed != mChannelState) { + MOZ_CRASH("channel should have been closed!"); + } + + Clear(); + + // IPDL assumes these notifications do not fire twice, so we do not let + // that happen. + if (mNotifiedChannelDone) { + return; + } + mNotifiedChannelDone = true; + + // Let our listener know that the channel was closed. This may cause the + // channel to be deleted. Release our caller's `MonitorAutoLock` before + // invoking the listener, as this may call back into MessageChannel, and/or + // cause the channel to be destroyed. + aLock.Unlock(); + mListener->OnChannelClose(); +} + +void MessageChannel::DebugAbort(const char* file, int line, const char* cond, + const char* why, bool reply) { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + printf_stderr( + "###!!! [MessageChannel][%s][%s:%d] " + "Assertion (%s) failed. %s %s\n", + StringFromIPCSide(mSide), file, line, cond, why, reply ? "(reply)" : ""); + + MessageQueue pending = std::move(mPending); + while (!pending.isEmpty()) { + pending.getFirst()->AssertMonitorHeld(*mMonitor); + printf_stderr(" [ %s%s ]\n", + pending.getFirst()->Msg()->is_sync() ? "sync" : "async", + pending.getFirst()->Msg()->is_reply() ? "reply" : ""); + pending.popFirst(); + } + + MOZ_CRASH_UNSAFE(why); +} + +void MessageChannel::AddProfilerMarker(const IPC::Message& aMessage, + MessageDirection aDirection) { + mMonitor->AssertCurrentThreadOwns(); + + if (profiler_feature_active(ProfilerFeature::IPCMessages)) { + base::ProcessId pid = mListener->OtherPidMaybeInvalid(); + // Only record markers for IPCs with a valid pid. + // And if one of the profiler mutexes is locked on this thread, don't record + // markers, because we don't want to expose profiler IPCs due to the + // profiler itself, and also to avoid possible re-entrancy issues. + if (pid != base::kInvalidProcessId && + !profiler_is_locked_on_current_thread()) { + // The current timestamp must be given to the `IPCMarker` payload. + [[maybe_unused]] const TimeStamp now = TimeStamp::Now(); + bool isThreadBeingProfiled = + profiler_thread_is_being_profiled_for_markers(); + PROFILER_MARKER( + "IPC", IPC, + mozilla::MarkerOptions( + mozilla::MarkerTiming::InstantAt(now), + // If the thread is being profiled, add the marker to + // the current thread. If the thread is not being + // profiled, add the marker to the main thread. It + // will appear in the main thread's IPC track. Profiler analysis + // UI correlates all the IPC markers from different threads and + // generates processed markers. + isThreadBeingProfiled ? mozilla::MarkerThreadId::CurrentThread() + : mozilla::MarkerThreadId::MainThread()), + IPCMarker, now, now, pid, aMessage.seqno(), aMessage.type(), mSide, + aDirection, MessagePhase::Endpoint, aMessage.is_sync(), + // aOriginThreadId: If the thread is being profiled, do not include a + // thread ID, as it's the same as the markers. Only include this field + // when the marker is being sent from another thread. + isThreadBeingProfiled ? mozilla::MarkerThreadId{} + : mozilla::MarkerThreadId::CurrentThread()); + } + } +} + +void MessageChannel::EndTimeout() { + mMonitor->AssertCurrentThreadOwns(); + + IPC_LOG("Ending timeout of seqno=%d", mTimedOutMessageSeqno); + mTimedOutMessageSeqno = 0; + mTimedOutMessageNestedLevel = 0; + + RepostAllMessages(); +} + +void MessageChannel::RepostAllMessages() { + mMonitor->AssertCurrentThreadOwns(); + + bool needRepost = false; + for (MessageTask* task : mPending) { + task->AssertMonitorHeld(*mMonitor); + if (!task->IsScheduled()) { + needRepost = true; + break; + } + } + if (!needRepost) { + // If everything is already scheduled to run, do nothing. + return; + } + + // In some cases we may have deferred dispatch of some messages in the + // queue. Now we want to run them again. However, we can't just re-post + // those messages since the messages after them in mPending would then be + // before them in the event queue. So instead we cancel everything and + // re-post all messages in the correct order. + MessageQueue queue = std::move(mPending); + while (RefPtr<MessageTask> task = queue.popFirst()) { + task->AssertMonitorHeld(*mMonitor); + RefPtr<MessageTask> newTask = new MessageTask(this, std::move(task->Msg())); + newTask->AssertMonitorHeld(*mMonitor); + mPending.insertBack(newTask); + newTask->Post(); + } + + AssertMaybeDeferredCountCorrect(); +} + +void MessageChannel::CancelTransaction(int transaction) { + mMonitor->AssertCurrentThreadOwns(); + + // When we cancel a transaction, we need to behave as if there's no longer + // any IPC on the stack. Anything we were dispatching or sending will get + // canceled. Consequently, we have to update the state variables below. + // + // We also need to ensure that when any IPC functions on the stack return, + // they don't reset these values using an RAII class like AutoSetValue. To + // avoid that, these RAII classes check if the variable they set has been + // tampered with (by us). If so, they don't reset the variable to the old + // value. + + IPC_LOG("CancelTransaction: xid=%d", transaction); + + // An unusual case: We timed out a transaction which the other side then + // cancelled. In this case we just leave the timedout state and try to + // forget this ever happened. + if (transaction == mTimedOutMessageSeqno) { + IPC_LOG("Cancelled timed out message %d", mTimedOutMessageSeqno); + EndTimeout(); + + // Normally mCurrentTransaction == 0 here. But it can be non-zero if: + // 1. Parent sends NESTED_INSIDE_SYNC message H. + // 2. Parent times out H. + // 3. Child dispatches H and sends nested message H' (same transaction). + // 4. Parent dispatches H' and cancels. + MOZ_RELEASE_ASSERT(!mTransactionStack || + mTransactionStack->TransactionID() == transaction); + if (mTransactionStack) { + mTransactionStack->Cancel(); + } + } else { + MOZ_RELEASE_ASSERT(mTransactionStack->TransactionID() == transaction); + mTransactionStack->Cancel(); + } + + bool foundSync = false; + for (MessageTask* p = mPending.getFirst(); p;) { + p->AssertMonitorHeld(*mMonitor); + UniquePtr<Message>& msg = p->Msg(); + + // If there was a race between the parent and the child, then we may + // have a queued sync message. We want to drop this message from the + // queue since if will get cancelled along with the transaction being + // cancelled. This happens if the message in the queue is + // NESTED_INSIDE_SYNC. + if (msg->is_sync() && msg->nested_level() != IPC::Message::NOT_NESTED) { + MOZ_RELEASE_ASSERT(!foundSync); + MOZ_RELEASE_ASSERT(msg->transaction_id() != transaction); + IPC_LOG("Removing msg from queue seqno=%d xid=%d", msg->seqno(), + msg->transaction_id()); + foundSync = true; + if (!IsAlwaysDeferred(*msg)) { + mMaybeDeferredPendingCount--; + } + p = p->removeAndGetNext(); + continue; + } + + p = p->getNext(); + } + + AssertMaybeDeferredCountCorrect(); +} + +void MessageChannel::CancelCurrentTransaction() { + MonitorAutoLock lock(*mMonitor); + if (DispatchingSyncMessageNestedLevel() >= IPC::Message::NESTED_INSIDE_SYNC) { + if (DispatchingSyncMessageNestedLevel() == + IPC::Message::NESTED_INSIDE_CPOW || + DispatchingAsyncMessageNestedLevel() == + IPC::Message::NESTED_INSIDE_CPOW) { + mListener->IntentionalCrash(); + } + + IPC_LOG("Cancel requested: current xid=%d", + CurrentNestedInsideSyncTransaction()); + MOZ_RELEASE_ASSERT(DispatchingSyncMessage()); + auto cancel = + MakeUnique<CancelMessage>(CurrentNestedInsideSyncTransaction()); + CancelTransaction(CurrentNestedInsideSyncTransaction()); + SendMessageToLink(std::move(cancel)); + } +} + +void CancelCPOWs() { + MOZ_ASSERT(NS_IsMainThread()); + + if (gParentProcessBlocker) { + mozilla::Telemetry::Accumulate(mozilla::Telemetry::IPC_TRANSACTION_CANCEL, + true); + gParentProcessBlocker->CancelCurrentTransaction(); + } +} + +bool MessageChannel::IsCrossProcess() const { + mMonitor->AssertCurrentThreadOwns(); + return mIsCrossProcess; +} + +void MessageChannel::SetIsCrossProcess(bool aIsCrossProcess) { + mMonitor->AssertCurrentThreadOwns(); + if (aIsCrossProcess == mIsCrossProcess) { + return; + } + mIsCrossProcess = aIsCrossProcess; + if (mIsCrossProcess) { + ChannelCountReporter::Increment(mName); + } else { + ChannelCountReporter::Decrement(mName); + } +} + +NS_IMPL_ISUPPORTS(MessageChannel::WorkerTargetShutdownTask, + nsITargetShutdownTask) + +MessageChannel::WorkerTargetShutdownTask::WorkerTargetShutdownTask( + nsISerialEventTarget* aTarget, MessageChannel* aChannel) + : mTarget(aTarget), mChannel(aChannel) {} + +void MessageChannel::WorkerTargetShutdownTask::TargetShutdown() { + MOZ_RELEASE_ASSERT(mTarget->IsOnCurrentThread()); + IPC_LOG("Closing channel due to event target shutdown"); + if (MessageChannel* channel = std::exchange(mChannel, nullptr)) { + channel->Close(); + } +} + +void MessageChannel::WorkerTargetShutdownTask::Clear() { + MOZ_RELEASE_ASSERT(mTarget->IsOnCurrentThread()); + mChannel = nullptr; +} + +NS_IMPL_ISUPPORTS_INHERITED0(MessageChannel::FlushLazySendMessagesRunnable, + CancelableRunnable) + +MessageChannel::FlushLazySendMessagesRunnable::FlushLazySendMessagesRunnable( + MessageChannel* aChannel) + : CancelableRunnable("MessageChannel::FlushLazyMessagesRunnable"), + mChannel(aChannel) {} + +NS_IMETHODIMP MessageChannel::FlushLazySendMessagesRunnable::Run() { + if (mChannel) { + MonitorAutoLock lock(*mChannel->mMonitor); + MOZ_ASSERT(mChannel->mFlushLazySendTask == this); + mChannel->FlushLazySendMessages(); + } + return NS_OK; +} + +nsresult MessageChannel::FlushLazySendMessagesRunnable::Cancel() { + mQueue.Clear(); + mChannel = nullptr; + return NS_OK; +} + +void MessageChannel::FlushLazySendMessagesRunnable::PushMessage( + UniquePtr<Message> aMsg) { + MOZ_ASSERT(mChannel); + mQueue.AppendElement(std::move(aMsg)); +} + +nsTArray<UniquePtr<IPC::Message>> +MessageChannel::FlushLazySendMessagesRunnable::TakeMessages() { + mChannel = nullptr; + return std::move(mQueue); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/MessageChannel.h b/ipc/glue/MessageChannel.h new file mode 100644 index 0000000000..67540b4ac8 --- /dev/null +++ b/ipc/glue/MessageChannel.h @@ -0,0 +1,888 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + */ +/* 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 ipc_glue_MessageChannel_h +#define ipc_glue_MessageChannel_h + +#include "ipc/EnumSerializer.h" +#include "mozilla/Atomics.h" +#include "mozilla/BaseProfilerMarkers.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Monitor.h" +#include "mozilla/Vector.h" +#if defined(XP_WIN) +# include "mozilla/ipc/Neutering.h" +#endif // defined(XP_WIN) + +#include <functional> +#include <map> +#include <stack> +#include <vector> + +#include "MessageLink.h" // for HasResultCodes +#include "mozilla/ipc/ScopedPort.h" +#include "nsITargetShutdownTask.h" + +#ifdef FUZZING_SNAPSHOT +# include "mozilla/fuzzing/IPCFuzzController.h" +#endif + +class MessageLoop; + +namespace IPC { +template <typename T> +struct ParamTraits; +} + +namespace mozilla { +namespace ipc { + +class IToplevelProtocol; +class ActorLifecycleProxy; + +class RefCountedMonitor : public Monitor { + public: + RefCountedMonitor() : Monitor("mozilla.ipc.MessageChannel.mMonitor") {} + + void AssertSameMonitor(const RefCountedMonitor& aOther) const + MOZ_REQUIRES(*this) MOZ_ASSERT_CAPABILITY(aOther) { + MOZ_ASSERT(this == &aOther); + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedMonitor) + + private: + ~RefCountedMonitor() = default; +}; + +enum class MessageDirection { + eSending, + eReceiving, +}; + +enum class MessagePhase { + Endpoint, + TransferStart, + TransferEnd, +}; + +enum class SyncSendError { + SendSuccess, + PreviousTimeout, + SendingCPOWWhileDispatchingSync, + SendingCPOWWhileDispatchingUrgent, + NotConnectedBeforeSend, + DisconnectedDuringSend, + CancelledBeforeSend, + CancelledAfterSend, + TimedOut, + ReplyError, +}; + +enum class ResponseRejectReason { + SendError, + ChannelClosed, + HandlerRejected, + ActorDestroyed, + ResolverDestroyed, + EndGuard_, +}; + +template <typename T> +using ResolveCallback = std::function<void(T&&)>; + +using RejectCallback = std::function<void(ResponseRejectReason)>; + +enum ChannelState { + ChannelClosed, + ChannelConnected, + ChannelClosing, + ChannelError +}; + +class AutoEnterTransaction; + +class MessageChannel : HasResultCodes { + friend class PortLink; + + typedef mozilla::Monitor Monitor; + + public: + using Message = IPC::Message; + + struct UntypedCallbackHolder { + UntypedCallbackHolder(int32_t aActorId, Message::msgid_t aReplyMsgId, + RejectCallback&& aReject) + : mActorId(aActorId), + mReplyMsgId(aReplyMsgId), + mReject(std::move(aReject)) {} + + virtual ~UntypedCallbackHolder() = default; + + void Reject(ResponseRejectReason&& aReason) { mReject(std::move(aReason)); } + + int32_t mActorId; + Message::msgid_t mReplyMsgId; + RejectCallback mReject; + }; + + template <typename Value> + struct CallbackHolder : public UntypedCallbackHolder { + CallbackHolder(int32_t aActorId, Message::msgid_t aReplyMsgId, + ResolveCallback<Value>&& aResolve, RejectCallback&& aReject) + : UntypedCallbackHolder(aActorId, aReplyMsgId, std::move(aReject)), + mResolve(std::move(aResolve)) {} + + void Resolve(Value&& aReason) { mResolve(std::move(aReason)); } + + ResolveCallback<Value> mResolve; + }; + + private: + static Atomic<size_t> gUnresolvedResponses; + friend class PendingResponseReporter; + + public: + static constexpr int32_t kNoTimeout = INT32_MIN; + + using ScopedPort = mozilla::ipc::ScopedPort; + + explicit MessageChannel(const char* aName, IToplevelProtocol* aListener); + ~MessageChannel(); + + IToplevelProtocol* Listener() const { return mListener; } + + // Returns the event target which the worker lives on and must be used for + // operations on the current thread. Only safe to access after the + // MessageChannel has been opened. + nsISerialEventTarget* GetWorkerEventTarget() const { return mWorkerThread; } + + // "Open" a connection using an existing ScopedPort. The ScopedPort must be + // valid and connected to a remote. + // + // The `aEventTarget` parameter must be on the current thread. + bool Open(ScopedPort aPort, Side aSide, const nsID& aMessageChannelId, + nsISerialEventTarget* aEventTarget = nullptr); + + // "Open" a connection to another thread in the same process. + // + // Returns true if the transport layer was successfully connected, + // i.e., mChannelState == ChannelConnected. + // + // For more details on the process of opening a channel between + // threads, see the extended comment on this function + // in MessageChannel.cpp. + bool Open(MessageChannel* aTargetChan, nsISerialEventTarget* aEventTarget, + Side aSide); + + // "Open" a connection to an actor on the current thread. + // + // Returns true if the transport layer was successfully connected, + // i.e., mChannelState == ChannelConnected. + // + // Same-thread channels may not perform synchronous or blocking message + // sends, to avoid deadlocks. + bool OpenOnSameThread(MessageChannel* aTargetChan, Side aSide); + + /** + * This sends a special message that is processed on the IO thread, so that + * other actors can know that the process will soon shutdown. + */ + void NotifyImpendingShutdown() MOZ_EXCLUDES(*mMonitor); + + // Close the underlying transport channel. + void Close() MOZ_EXCLUDES(*mMonitor); + + // Induce an error in this MessageChannel's connection. + // + // After this method is called, no more message notifications will be + // delivered to the listener, and the channel will be unable to send or + // receive future messages, as if the peer dropped the connection + // unexpectedly. + // + // The OnChannelError notification will be delivered either asynchronously or + // during an explicit call to Close(), whichever happens first. + // + // NOTE: If SetAbortOnError(true) has been called on this MessageChannel, + // calling this function will immediately exit the current process. + void InduceConnectionError() MOZ_EXCLUDES(*mMonitor); + + void SetAbortOnError(bool abort) MOZ_EXCLUDES(*mMonitor) { + MonitorAutoLock lock(*mMonitor); + mAbortOnError = abort; + } + + // Call aInvoke for each pending message until it returns false. + // XXX: You must get permission from an IPC peer to use this function + // since it requires custom deserialization and re-orders events. + void PeekMessages(const std::function<bool(const Message& aMsg)>& aInvoke) + MOZ_EXCLUDES(*mMonitor); + + // Misc. behavioral traits consumers can request for this channel + enum ChannelFlags { + REQUIRE_DEFAULT = 0, + // Windows: if this channel operates on the UI thread, indicates + // WindowsMessageLoop code should enable deferred native message + // handling to prevent deadlocks. Should only be used for protocols + // that manage child processes which might create native UI, like + // plugins. + REQUIRE_DEFERRED_MESSAGE_PROTECTION = 1 << 0, + }; + void SetChannelFlags(ChannelFlags aFlags) { mFlags = aFlags; } + ChannelFlags GetChannelFlags() { return mFlags; } + + // Asynchronously send a message to the other side of the channel + bool Send(UniquePtr<Message> aMsg) MOZ_EXCLUDES(*mMonitor); + + // Asynchronously send a message to the other side of the channel + // and wait for asynchronous reply. + template <typename Value> + void Send(UniquePtr<Message> aMsg, int32_t aActorId, + Message::msgid_t aReplyMsgId, ResolveCallback<Value>&& aResolve, + RejectCallback&& aReject) MOZ_EXCLUDES(*mMonitor) { + int32_t seqno = NextSeqno(); + aMsg->set_seqno(seqno); + if (!Send(std::move(aMsg))) { + aReject(ResponseRejectReason::SendError); + return; + } + + UniquePtr<UntypedCallbackHolder> callback = + MakeUnique<CallbackHolder<Value>>( + aActorId, aReplyMsgId, std::move(aResolve), std::move(aReject)); + mPendingResponses.insert(std::make_pair(seqno, std::move(callback))); + gUnresolvedResponses++; + } + + bool SendBuildIDsMatchMessage(const char* aParentBuildID) + MOZ_EXCLUDES(*mMonitor); + bool DoBuildIDsMatch() MOZ_EXCLUDES(*mMonitor) { + MonitorAutoLock lock(*mMonitor); + return mBuildIDsConfirmedMatch; + } + + // Synchronously send |aMsg| (i.e., wait for |aReply|) + bool Send(UniquePtr<Message> aMsg, UniquePtr<Message>* aReply) + MOZ_EXCLUDES(*mMonitor); + + bool CanSend() const MOZ_EXCLUDES(*mMonitor); + + // Remove and return a callback that needs reply + UniquePtr<UntypedCallbackHolder> PopCallback(const Message& aMsg, + int32_t aActorId); + + // Used to reject and remove pending responses owned by the given + // actor when it's about to be destroyed. + void RejectPendingResponsesForActor(int32_t aActorId); + + // If sending a sync message returns an error, this function gives a more + // descriptive error message. + SyncSendError LastSendError() const { + AssertWorkerThread(); + return mLastSendError; + } + + void SetReplyTimeoutMs(int32_t aTimeoutMs); + + bool IsOnCxxStack() const { return mOnCxxStack; } + + void CancelCurrentTransaction() MOZ_EXCLUDES(*mMonitor); + + // IsClosed and NumQueuedMessages are safe to call from any thread, but + // may provide an out-of-date value. + bool IsClosed() MOZ_EXCLUDES(*mMonitor) { + MonitorAutoLock lock(*mMonitor); + return IsClosedLocked(); + } + bool IsClosedLocked() const MOZ_REQUIRES(*mMonitor) { + mMonitor->AssertCurrentThreadOwns(); + return mLink ? mLink->IsClosed() : true; + } + + static bool IsPumpingMessages() { return sIsPumpingMessages; } + static void SetIsPumpingMessages(bool aIsPumping) { + sIsPumpingMessages = aIsPumping; + } + + /** + * Does this MessageChannel currently cross process boundaries? + */ + bool IsCrossProcess() const MOZ_REQUIRES(*mMonitor); + void SetIsCrossProcess(bool aIsCrossProcess) MOZ_REQUIRES(*mMonitor); + + nsID GetMessageChannelId() const { + MonitorAutoLock lock(*mMonitor); + return mMessageChannelId; + } + +#ifdef FUZZING_SNAPSHOT + Maybe<mojo::core::ports::PortName> GetPortName() { + MonitorAutoLock lock(*mMonitor); + return mLink ? mLink->GetPortName() : Nothing(); + } +#endif + +#ifdef XP_WIN + struct MOZ_STACK_CLASS SyncStackFrame { + explicit SyncStackFrame(MessageChannel* channel); + ~SyncStackFrame(); + + bool mSpinNestedEvents; + bool mListenerNotified; + MessageChannel* mChannel; + + // The previous stack frame for this channel. + SyncStackFrame* mPrev; + + // The previous stack frame on any channel. + SyncStackFrame* mStaticPrev; + }; + friend struct MessageChannel::SyncStackFrame; + + static bool IsSpinLoopActive() { + for (SyncStackFrame* frame = sStaticTopFrame; frame; frame = frame->mPrev) { + if (frame->mSpinNestedEvents) return true; + } + return false; + } + + protected: + // The deepest sync stack frame for this channel. + SyncStackFrame* mTopFrame = nullptr; + + bool mIsSyncWaitingOnNonMainThread = false; + + // The deepest sync stack frame on any channel. + static SyncStackFrame* sStaticTopFrame; + + public: + void ProcessNativeEventsInInterruptCall(); + static void NotifyGeckoEventDispatch(); + + private: + void SpinInternalEventLoop(); +#endif // defined(XP_WIN) + + private: + void PostErrorNotifyTask() MOZ_REQUIRES(*mMonitor); + void OnNotifyMaybeChannelError() MOZ_EXCLUDES(*mMonitor); + void ReportConnectionError(const char* aFunctionName, + const uint32_t aMsgTyp) const + MOZ_REQUIRES(*mMonitor); + void ReportMessageRouteError(const char* channelName) const + MOZ_EXCLUDES(*mMonitor); + bool MaybeHandleError(Result code, const Message& aMsg, + const char* channelName) MOZ_EXCLUDES(*mMonitor); + + void Clear() MOZ_REQUIRES(*mMonitor); + + bool HasPendingEvents() MOZ_REQUIRES(*mMonitor); + + void ProcessPendingRequests(ActorLifecycleProxy* aProxy, + AutoEnterTransaction& aTransaction) + MOZ_REQUIRES(*mMonitor); + bool ProcessPendingRequest(ActorLifecycleProxy* aProxy, + UniquePtr<Message> aUrgent) + MOZ_REQUIRES(*mMonitor); + + void EnqueuePendingMessages() MOZ_REQUIRES(*mMonitor); + + // Dispatches an incoming message to its appropriate handler. + void DispatchMessage(ActorLifecycleProxy* aProxy, UniquePtr<Message> aMsg) + MOZ_REQUIRES(*mMonitor); + + // DispatchMessage will route to one of these functions depending on the + // protocol type of the message. + void DispatchSyncMessage(ActorLifecycleProxy* aProxy, const Message& aMsg, + UniquePtr<Message>& aReply) MOZ_EXCLUDES(*mMonitor); + void DispatchAsyncMessage(ActorLifecycleProxy* aProxy, const Message& aMsg) + MOZ_EXCLUDES(*mMonitor); + + // Return true if the wait ended because a notification was received. + // + // Return false if the time elapsed from when we started the process of + // waiting until afterwards exceeded the currently allotted timeout. + // That *DOES NOT* mean false => "no event" (== timeout); there are many + // circumstances that could cause the measured elapsed time to exceed the + // timeout EVEN WHEN we were notified. + // + // So in sum: true is a meaningful return value; false isn't, + // necessarily. + bool WaitForSyncNotify() MOZ_REQUIRES(*mMonitor); + + bool WaitResponse(bool aWaitTimedOut); + + bool ShouldContinueFromTimeout() MOZ_REQUIRES(*mMonitor); + + void EndTimeout() MOZ_REQUIRES(*mMonitor); + void CancelTransaction(int transaction) MOZ_REQUIRES(*mMonitor); + + void RepostAllMessages() MOZ_REQUIRES(*mMonitor); + + int32_t NextSeqno() { + AssertWorkerThread(); + return (mSide == ChildSide) ? --mNextSeqno : ++mNextSeqno; + } + + void DebugAbort(const char* file, int line, const char* cond, const char* why, + bool reply = false) MOZ_REQUIRES(*mMonitor); + + void AddProfilerMarker(const IPC::Message& aMessage, + MessageDirection aDirection) MOZ_REQUIRES(*mMonitor); + + private: + // Returns true if we're dispatching an async message's callback. + bool DispatchingAsyncMessage() const { + AssertWorkerThread(); + return mDispatchingAsyncMessage; + } + + int DispatchingAsyncMessageNestedLevel() const { + AssertWorkerThread(); + return mDispatchingAsyncMessageNestedLevel; + } + + // Check if there is still a live connection to our peer. This may change to + // `false` at any time due to the connection to our peer being closed or + // dropped (e.g. due to a crash). + bool Connected() const MOZ_REQUIRES(*mMonitor); + + // Check if there is either still a live connection to our peer, or we have + // received a `Goodbye` from our peer, and are actively shutting down our + // connection with our peer. + bool ConnectedOrClosing() const MOZ_REQUIRES(*mMonitor); + + private: + // Executed on the IO thread. + void NotifyWorkerThread() MOZ_REQUIRES(*mMonitor); + + // Return true if |aMsg| is a special message targeted at the IO + // thread, in which case it shouldn't be delivered to the worker. + bool MaybeInterceptSpecialIOMessage(const Message& aMsg) + MOZ_REQUIRES(*mMonitor); + + // Returns true if ShouldDeferMessage(aMsg) is guaranteed to return true. + // Otherwise, the result of ShouldDeferMessage(aMsg) may be true or false, + // depending on context. + static bool IsAlwaysDeferred(const Message& aMsg); + + // Helper for sending a message via the link. If the message is [LazySend], it + // will be queued, and if the message is not-[LazySend], it will flush any + // pending [LazySend] messages. + void SendMessageToLink(UniquePtr<Message> aMsg) MOZ_REQUIRES(*mMonitor); + + // Called to flush [LazySend] messages to the link. + void FlushLazySendMessages() MOZ_REQUIRES(*mMonitor); + + bool WasTransactionCanceled(int transaction); + bool ShouldDeferMessage(const Message& aMsg) MOZ_REQUIRES(*mMonitor); + void OnMessageReceivedFromLink(UniquePtr<Message> aMsg) + MOZ_REQUIRES(*mMonitor); + void OnChannelErrorFromLink() MOZ_REQUIRES(*mMonitor); + + private: + // Clear this channel, and notify the listener that the channel has either + // closed or errored. + // + // These methods must be called on the worker thread, passing in a + // `ReleasableMonitorAutoLock`. This lock guard will be reset before the + // listener is called, allowing for the monitor to be unlocked before the + // MessageChannel is potentially destroyed. + void NotifyChannelClosed(ReleasableMonitorAutoLock& aLock) + MOZ_REQUIRES(*mMonitor); + void NotifyMaybeChannelError(ReleasableMonitorAutoLock& aLock) + MOZ_REQUIRES(*mMonitor); + + private: + void AssertWorkerThread() const { + MOZ_ASSERT(mWorkerThread, "Channel hasn't been opened yet"); + MOZ_RELEASE_ASSERT(mWorkerThread && mWorkerThread->IsOnCurrentThread(), + "not on worker thread!"); + } + + private: + class MessageTask : public CancelableRunnable, + public LinkedListElement<RefPtr<MessageTask>>, + public nsIRunnablePriority, + public nsIRunnableIPCMessageType { + public: + explicit MessageTask(MessageChannel* aChannel, UniquePtr<Message> aMessage); + MessageTask() = delete; + MessageTask(const MessageTask&) = delete; + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD Run() override; + nsresult Cancel() override; + NS_IMETHOD GetPriority(uint32_t* aPriority) override; + NS_DECL_NSIRUNNABLEIPCMESSAGETYPE + void Post() MOZ_REQUIRES(*mMonitor); + + bool IsScheduled() const MOZ_REQUIRES(*mMonitor) { + mMonitor->AssertCurrentThreadOwns(); + return mScheduled; + } + + UniquePtr<Message>& Msg() MOZ_REQUIRES(*mMonitor) { + MOZ_DIAGNOSTIC_ASSERT(mMessage, "message was moved"); + return mMessage; + } + const UniquePtr<Message>& Msg() const MOZ_REQUIRES(*mMonitor) { + MOZ_DIAGNOSTIC_ASSERT(mMessage, "message was moved"); + return mMessage; + } + + void AssertMonitorHeld(const RefCountedMonitor& aMonitor) + MOZ_REQUIRES(aMonitor) MOZ_ASSERT_CAPABILITY(*mMonitor) { + aMonitor.AssertSameMonitor(*mMonitor); + } + + private: + ~MessageTask(); + + MessageChannel* Channel() MOZ_REQUIRES(*mMonitor) { + mMonitor->AssertCurrentThreadOwns(); + MOZ_RELEASE_ASSERT(isInList()); + return mChannel; + } + + // The connected MessageChannel's monitor. Guards `mChannel` and + // `mScheduled`. + RefPtr<RefCountedMonitor> const mMonitor; + // The channel which this MessageTask is associated with. Only valid while + // `mMonitor` is held, and this MessageTask `isInList()`. + MessageChannel* const mChannel; + UniquePtr<Message> mMessage MOZ_GUARDED_BY(*mMonitor); + uint32_t const mPriority; + bool mScheduled : 1 MOZ_GUARDED_BY(*mMonitor); +#ifdef FUZZING_SNAPSHOT + const bool mIsFuzzMsg; + bool mFuzzStopped MOZ_GUARDED_BY(*mMonitor); +#endif + }; + + bool ShouldRunMessage(const Message& aMsg) MOZ_REQUIRES(*mMonitor); + void RunMessage(ActorLifecycleProxy* aProxy, MessageTask& aTask) + MOZ_REQUIRES(*mMonitor); + + class WorkerTargetShutdownTask final : public nsITargetShutdownTask { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + WorkerTargetShutdownTask(nsISerialEventTarget* aTarget, + MessageChannel* aChannel); + + void TargetShutdown() override; + void Clear(); + + private: + ~WorkerTargetShutdownTask() = default; + + const nsCOMPtr<nsISerialEventTarget> mTarget; + // Cleared by MessageChannel before it is destroyed. + MessageChannel* MOZ_NON_OWNING_REF mChannel; + }; + + class FlushLazySendMessagesRunnable final : public CancelableRunnable { + public: + explicit FlushLazySendMessagesRunnable(MessageChannel* aChannel); + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD Run() override; + nsresult Cancel() override; + + void PushMessage(UniquePtr<Message> aMsg); + nsTArray<UniquePtr<Message>> TakeMessages(); + + private: + ~FlushLazySendMessagesRunnable() = default; + + // Cleared by MessageChannel before it is destroyed. + MessageChannel* MOZ_NON_OWNING_REF mChannel; + + // LazySend messages which haven't been sent yet, but will be sent as soon + // as a non-LazySend message is sent, or this runnable is executed. + nsTArray<UniquePtr<Message>> mQueue; + }; + + typedef LinkedList<RefPtr<MessageTask>> MessageQueue; + typedef std::map<size_t, UniquePtr<UntypedCallbackHolder>> CallbackMap; + typedef IPC::Message::msgid_t msgid_t; + + private: + // This will be a string literal, so lifetime is not an issue. + const char* const mName; + + // ID for each MessageChannel. Set when it is opened, and never cleared + // afterwards. + // + // This ID is only intended for diagnostics, debugging, and reporting + // purposes, and shouldn't be used for message routing or permissions checks. + nsID mMessageChannelId MOZ_GUARDED_BY(*mMonitor) = {}; + + // Based on presumption the listener owns and overlives the channel, + // this is never nullified. + IToplevelProtocol* const mListener; + + // This monitor guards all state in this MessageChannel, except where + // otherwise noted. It is refcounted so a reference to it can be shared with + // IPC listener objects which need to access weak references to this + // `MessageChannel`. + RefPtr<RefCountedMonitor> const mMonitor; + + ChannelState mChannelState MOZ_GUARDED_BY(*mMonitor) = ChannelClosed; + Side mSide = UnknownSide; + bool mIsCrossProcess MOZ_GUARDED_BY(*mMonitor) = false; + UniquePtr<MessageLink> mLink MOZ_GUARDED_BY(*mMonitor); + + // NotifyMaybeChannelError runnable + RefPtr<CancelableRunnable> mChannelErrorTask MOZ_GUARDED_BY(*mMonitor); + + // Thread we are allowed to send and receive on. Set in Open(); never + // changed, and we can only call Open() once. We shouldn't be accessing + // from multiple threads before Open(). + nsCOMPtr<nsISerialEventTarget> mWorkerThread; + + // Shutdown task to close the channel before mWorkerThread goes away. + RefPtr<WorkerTargetShutdownTask> mShutdownTask MOZ_GUARDED_BY(*mMonitor); + + // Task to force sending lazy messages when mQueuedLazyMessages is non-empty. + RefPtr<FlushLazySendMessagesRunnable> mFlushLazySendTask + MOZ_GUARDED_BY(*mMonitor); + + // Timeout periods are broken up in two to prevent system suspension from + // triggering an abort. This method (called by WaitForEvent with a 'did + // timeout' flag) decides if we should wait again for half of mTimeoutMs + // or give up. + // only accessed on WorkerThread + int32_t mTimeoutMs = kNoTimeout; + bool mInTimeoutSecondHalf = false; + + // Worker-thread only; sequence numbers for messages that require + // replies. + int32_t mNextSeqno = 0; + + static bool sIsPumpingMessages; + + // If ::Send returns false, this gives a more descriptive error. + SyncSendError mLastSendError = SyncSendError::SendSuccess; + + template <class T> + class AutoSetValue { + public: + explicit AutoSetValue(T& var, const T& newValue) + : mVar(var), mPrev(var), mNew(newValue) { + mVar = newValue; + } + ~AutoSetValue() { + // The value may have been zeroed if the transaction was + // canceled. In that case we shouldn't return it to its previous + // value. + if (mVar == mNew) { + mVar = mPrev; + } + } + + private: + T& mVar; + T mPrev; + T mNew; + }; + + bool mDispatchingAsyncMessage = false; + int mDispatchingAsyncMessageNestedLevel = 0; + + // When we send an urgent request from the parent process, we could race + // with an RPC message that was issued by the child beforehand. In this + // case, if the parent were to wake up while waiting for the urgent reply, + // and process the RPC, it could send an additional urgent message. The + // child would wake up to process the urgent message (as it always will), + // then send a reply, which could be received by the parent out-of-order + // with respect to the first urgent reply. + // + // To address this problem, urgent or RPC requests are associated with a + // "transaction". Whenever one side of the channel wishes to start a + // chain of RPC/urgent messages, it allocates a new transaction ID. Any + // messages the parent receives, not apart of this transaction, are + // deferred. When issuing RPC/urgent requests on top of a started + // transaction, the initiating transaction ID is used. + // + // To ensure IDs are unique, we use sequence numbers for transaction IDs, + // which grow in opposite directions from child to parent. + + friend class AutoEnterTransaction; + AutoEnterTransaction* mTransactionStack MOZ_GUARDED_BY(*mMonitor) = nullptr; + + int32_t CurrentNestedInsideSyncTransaction() const MOZ_REQUIRES(*mMonitor); + + bool AwaitingSyncReply() const MOZ_REQUIRES(*mMonitor); + int AwaitingSyncReplyNestedLevel() const MOZ_REQUIRES(*mMonitor); + + bool DispatchingSyncMessage() const MOZ_REQUIRES(*mMonitor); + int DispatchingSyncMessageNestedLevel() const MOZ_REQUIRES(*mMonitor); + +#ifdef DEBUG + void AssertMaybeDeferredCountCorrect() MOZ_REQUIRES(*mMonitor); +#else + void AssertMaybeDeferredCountCorrect() MOZ_REQUIRES(*mMonitor) {} +#endif + + // If a sync message times out, we store its sequence number here. Any + // future sync messages will fail immediately. Once the reply for original + // sync message is received, we allow sync messages again. + // + // When a message times out, nothing is done to inform the other side. The + // other side will eventually dispatch the message and send a reply. Our + // side is responsible for replying to all sync messages sent by the other + // side when it dispatches the timed out message. The response is always an + // error. + // + // A message is only timed out if it initiated a transaction. This avoids + // hitting a lot of corner cases with message nesting that we don't really + // care about. + int32_t mTimedOutMessageSeqno MOZ_GUARDED_BY(*mMonitor) = 0; + int mTimedOutMessageNestedLevel MOZ_GUARDED_BY(*mMonitor) = 0; + + // Queue of all incoming messages. + // + // If both this side and the other side are functioning correctly, the other + // side can send as many async messages as it wants before sending us a + // blocking message. After sending a blocking message, the other side must be + // blocked, and thus can't send us any more messages until we process the sync + // in-msg. + // + MessageQueue mPending MOZ_GUARDED_BY(*mMonitor); + + // The number of messages in mPending for which IsAlwaysDeferred is false + // (i.e., the number of messages that might not be deferred, depending on + // context). + size_t mMaybeDeferredPendingCount MOZ_GUARDED_BY(*mMonitor) = 0; + + // Is there currently MessageChannel logic for this channel on the C++ stack? + // This member is only accessed on the worker thread, and so is not protected + // by mMonitor. + bool mOnCxxStack = false; + + // Map of async Callbacks that are still waiting replies. + CallbackMap mPendingResponses; + +#ifdef XP_WIN + HANDLE mEvent; +#endif + + // Should the channel abort the process from the I/O thread when + // a channel error occurs? + bool mAbortOnError MOZ_GUARDED_BY(*mMonitor) = false; + + // True if the listener has already been notified of a channel close or + // error. + bool mNotifiedChannelDone MOZ_GUARDED_BY(*mMonitor) = false; + + // See SetChannelFlags + ChannelFlags mFlags = REQUIRE_DEFAULT; + + bool mBuildIDsConfirmedMatch MOZ_GUARDED_BY(*mMonitor) = false; + + // If this is true, both ends of this message channel have event targets + // on the same thread. + bool mIsSameThreadChannel = false; +}; + +void CancelCPOWs(); + +} // namespace ipc +} // namespace mozilla + +namespace IPC { +template <> +struct ParamTraits<mozilla::ipc::ResponseRejectReason> + : public ContiguousEnumSerializer< + mozilla::ipc::ResponseRejectReason, + mozilla::ipc::ResponseRejectReason::SendError, + mozilla::ipc::ResponseRejectReason::EndGuard_> {}; +} // namespace IPC + +namespace geckoprofiler::markers { + +struct IPCMarker { + static constexpr mozilla::Span<const char> MarkerTypeName() { + return mozilla::MakeStringSpan("IPC"); + } + static void StreamJSONMarkerData( + mozilla::baseprofiler::SpliceableJSONWriter& aWriter, + mozilla::TimeStamp aStart, mozilla::TimeStamp aEnd, int32_t aOtherPid, + int32_t aMessageSeqno, IPC::Message::msgid_t aMessageType, + mozilla::ipc::Side aSide, mozilla::ipc::MessageDirection aDirection, + mozilla::ipc::MessagePhase aPhase, bool aSync, + mozilla::MarkerThreadId aOriginThreadId) { + using namespace mozilla::ipc; + // This payload still streams a startTime and endTime property because it + // made the migration to MarkerTiming on the front-end easier. + aWriter.TimeProperty("startTime", aStart); + aWriter.TimeProperty("endTime", aEnd); + + aWriter.IntProperty("otherPid", aOtherPid); + aWriter.IntProperty("messageSeqno", aMessageSeqno); + aWriter.StringProperty( + "messageType", + mozilla::MakeStringSpan(IPC::StringFromIPCMessageType(aMessageType))); + aWriter.StringProperty("side", IPCSideToString(aSide)); + aWriter.StringProperty("direction", + aDirection == MessageDirection::eSending + ? mozilla::MakeStringSpan("sending") + : mozilla::MakeStringSpan("receiving")); + aWriter.StringProperty("phase", IPCPhaseToString(aPhase)); + aWriter.BoolProperty("sync", aSync); + if (!aOriginThreadId.IsUnspecified()) { + // Tech note: If `ToNumber()` returns a uint64_t, the conversion to + // int64_t is "implementation-defined" before C++20. This is acceptable + // here, because this is a one-way conversion to a unique identifier + // that's used to visually separate data by thread on the front-end. + aWriter.IntProperty( + "threadId", + static_cast<int64_t>(aOriginThreadId.ThreadId().ToNumber())); + } + } + static mozilla::MarkerSchema MarkerTypeDisplay() { + return mozilla::MarkerSchema::SpecialFrontendLocation{}; + } + + private: + static mozilla::Span<const char> IPCSideToString(mozilla::ipc::Side aSide) { + switch (aSide) { + case mozilla::ipc::ParentSide: + return mozilla::MakeStringSpan("parent"); + case mozilla::ipc::ChildSide: + return mozilla::MakeStringSpan("child"); + case mozilla::ipc::UnknownSide: + return mozilla::MakeStringSpan("unknown"); + default: + MOZ_ASSERT_UNREACHABLE("Invalid IPC side"); + return mozilla::MakeStringSpan("<invalid IPC side>"); + } + } + + static mozilla::Span<const char> IPCPhaseToString( + mozilla::ipc::MessagePhase aPhase) { + switch (aPhase) { + case mozilla::ipc::MessagePhase::Endpoint: + return mozilla::MakeStringSpan("endpoint"); + case mozilla::ipc::MessagePhase::TransferStart: + return mozilla::MakeStringSpan("transferStart"); + case mozilla::ipc::MessagePhase::TransferEnd: + return mozilla::MakeStringSpan("transferEnd"); + default: + MOZ_ASSERT_UNREACHABLE("Invalid IPC phase"); + return mozilla::MakeStringSpan("<invalid IPC phase>"); + } + } +}; + +} // namespace geckoprofiler::markers + +#endif // ifndef ipc_glue_MessageChannel_h diff --git a/ipc/glue/MessageLink.cpp b/ipc/glue/MessageLink.cpp new file mode 100644 index 0000000000..5505322a2f --- /dev/null +++ b/ipc/glue/MessageLink.cpp @@ -0,0 +1,206 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + */ +/* 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 "mozilla/ipc/MessageLink.h" +#include "mojo/core/ports/event.h" +#include "mojo/core/ports/node.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/ipc/NodeController.h" +#include "chrome/common/ipc_channel.h" +#include "base/task.h" + +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "nsDebug.h" +#include "nsExceptionHandler.h" +#include "nsISupportsImpl.h" +#include "nsPrintfCString.h" +#include "nsXULAppAPI.h" + +using namespace mozilla; + +namespace mozilla { +namespace ipc { + +const char* StringFromIPCSide(Side side) { + switch (side) { + case ChildSide: + return "Child"; + case ParentSide: + return "Parent"; + default: + return "Unknown"; + } +} + +MessageLink::MessageLink(MessageChannel* aChan) : mChan(aChan) {} + +MessageLink::~MessageLink() { +#ifdef DEBUG + mChan = nullptr; +#endif +} + +class PortLink::PortObserverThunk : public NodeController::PortObserver { + public: + PortObserverThunk(RefCountedMonitor* aMonitor, PortLink* aLink) + : mMonitor(aMonitor), mLink(aLink) {} + + void OnPortStatusChanged() override { + MonitorAutoLock lock(*mMonitor); + if (mLink) { + mLink->OnPortStatusChanged(); + } + } + + private: + friend class PortLink; + + // The monitor from our PortLink's MessageChannel. Guards access to `mLink`. + RefPtr<RefCountedMonitor> mMonitor; + + // Cleared by `PortLink` in `PortLink::Clear()`. + PortLink* MOZ_NON_OWNING_REF mLink; +}; + +PortLink::PortLink(MessageChannel* aChan, ScopedPort aPort) + : MessageLink(aChan), mNode(aPort.Controller()), mPort(aPort.Release()) { + mChan->mMonitor->AssertCurrentThreadOwns(); + + mObserver = new PortObserverThunk(mChan->mMonitor, this); + mNode->SetPortObserver(mPort, mObserver); + + // Dispatch an event to the IO loop to trigger an initial + // `OnPortStatusChanged` to deliver any pending messages. This needs to be run + // asynchronously from a different thread (or in the case of a same-thread + // channel, from the current thread), for now due to assertions in + // `MessageChannel`. + nsCOMPtr<nsIRunnable> openRunnable = NewRunnableMethod( + "PortLink::Open", mObserver, &PortObserverThunk::OnPortStatusChanged); + if (aChan->mIsSameThreadChannel) { + aChan->mWorkerThread->Dispatch(openRunnable.forget()); + } else { + XRE_GetIOMessageLoop()->PostTask(openRunnable.forget()); + } +} + +PortLink::~PortLink() { + MOZ_RELEASE_ASSERT(!mObserver, "PortLink destroyed without being closed!"); +} + +void PortLink::SendMessage(UniquePtr<Message> aMessage) { + mChan->mMonitor->AssertCurrentThreadOwns(); + + if (aMessage->size() > IPC::Channel::kMaximumMessageSize) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCMessageName, + nsDependentCString(aMessage->name())); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCMessageSize, + static_cast<unsigned int>(aMessage->size())); + MOZ_CRASH("IPC message size is too large"); + } + aMessage->AssertAsLargeAsHeader(); + + RefPtr<PortObserverThunk> observer = mObserver; + if (!observer) { + NS_WARNING("Ignoring message to closed PortLink"); + return; + } + + // Make local copies of relevant member variables, so we can unlock the + // monitor for the rest of this function. This protects us in case `this` is + // deleted during the call (although that shouldn't happen in practice). + // + // We don't want the monitor to be held when calling into ports, as we may be + // re-entrantly called by our `PortObserverThunk` which will attempt to + // acquire the monitor. + RefPtr<RefCountedMonitor> monitor = mChan->mMonitor; + RefPtr<NodeController> node = mNode; + PortRef port = mPort; + + bool ok = false; + monitor->AssertCurrentThreadOwns(); + { + MonitorAutoUnlock guard(*monitor); + ok = node->SendUserMessage(port, std::move(aMessage)); + } + if (!ok) { + // The send failed, but double-check that we weren't closed racily while + // sending, which could lead to an invalid state error. + if (observer->mLink) { + MOZ_CRASH("Invalid argument to SendUserMessage"); + } + NS_WARNING("Message dropped as PortLink was closed"); + } +} + +void PortLink::Close() { + mChan->mMonitor->AssertCurrentThreadOwns(); + + if (!mObserver) { + // We're already being closed. + return; + } + + Clear(); +} + +void PortLink::Clear() { + mChan->mMonitor->AssertCurrentThreadOwns(); + + // NOTE: We're calling into `ports` with our monitor held! Usually, this could + // lead to deadlocks due to the PortObserverThunk acquiring the lock + // re-entrantly, but is OK here as we're immediately clearing the port's + // observer. We shouldn't have issues with any re-entrant calls on this thread + // acquiring this MessageChannel's monitor. + // + // We also clear out the reference in `mObserver` back to this type so that + // notifications from other threads won't try to call us again once we release + // the monitor. + mNode->SetPortObserver(mPort, nullptr); + mObserver->mLink = nullptr; + mObserver = nullptr; + mNode->ClosePort(mPort); +} + +void PortLink::OnPortStatusChanged() { + mChan->mMonitor->AssertCurrentThreadOwns(); + + // Check if the port's remoteness status has updated, and tell our channel if + // it has. + if (Maybe<PortStatus> status = mNode->GetStatus(mPort); + status && status->peer_remote != mChan->IsCrossProcess()) { + mChan->SetIsCrossProcess(status->peer_remote); + } + + while (mObserver) { + UniquePtr<IPC::Message> message; + if (!mNode->GetMessage(mPort, &message)) { + Clear(); + mChan->OnChannelErrorFromLink(); + return; + } + if (!message) { + return; + } + + mChan->OnMessageReceivedFromLink(std::move(message)); + } +} + +bool PortLink::IsClosed() const { + if (Maybe<PortStatus> status = mNode->GetStatus(mPort)) { + return !(status->has_messages || status->receiving_messages); + } + return true; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/MessageLink.h b/ipc/glue/MessageLink.h new file mode 100644 index 0000000000..1ad33030b7 --- /dev/null +++ b/ipc/glue/MessageLink.h @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + */ +/* 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 ipc_glue_MessageLink_h +#define ipc_glue_MessageLink_h + +#include <cstdint> +#include "base/message_loop.h" +#include "mojo/core/ports/node.h" +#include "mojo/core/ports/port_ref.h" +#include "mozilla/Assertions.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/ipc/ScopedPort.h" + +namespace IPC { +class Message; +class MessageReader; +class MessageWriter; +} // namespace IPC + +namespace mozilla { +namespace ipc { + +class MessageChannel; +class NodeController; + +struct HasResultCodes { + enum Result { + MsgProcessed, + MsgDropped, + MsgNotKnown, + MsgNotAllowed, + MsgPayloadError, + MsgProcessingError, + MsgRouteError, + MsgValueError + }; +}; + +enum Side : uint8_t { ParentSide, ChildSide, UnknownSide }; + +const char* StringFromIPCSide(Side side); + +class MessageLink { + public: + typedef IPC::Message Message; + + explicit MessageLink(MessageChannel* aChan); + virtual ~MessageLink(); + + // n.b.: These methods all require that the channel monitor is + // held when they are invoked. + virtual void SendMessage(mozilla::UniquePtr<Message> msg) = 0; + + // Synchronously close the connection, such that no further notifications will + // be delivered to the MessageChannel instance. Must be called with the + // channel monitor held. + virtual void Close() = 0; + + virtual bool IsClosed() const = 0; + +#ifdef FUZZING_SNAPSHOT + virtual Maybe<mojo::core::ports::PortName> GetPortName() { return Nothing(); } +#endif + + protected: + MessageChannel* mChan; +}; + +class PortLink final : public MessageLink { + using PortRef = mojo::core::ports::PortRef; + using PortStatus = mojo::core::ports::PortStatus; + using UserMessage = mojo::core::ports::UserMessage; + using UserMessageEvent = mojo::core::ports::UserMessageEvent; + + public: + PortLink(MessageChannel* aChan, ScopedPort aPort); + virtual ~PortLink(); + + void SendMessage(UniquePtr<Message> aMessage) override; + void Close() override; + + bool IsClosed() const override; + +#ifdef FUZZING_SNAPSHOT + Maybe<mojo::core::ports::PortName> GetPortName() override { + return Some(mPort.name()); + } +#endif + + private: + class PortObserverThunk; + friend class PortObserverThunk; + + void OnPortStatusChanged(); + + // Called either when an error is detected on the port from the port observer, + // or when `SendClose()` is called. + void Clear(); + + const RefPtr<NodeController> mNode; + const PortRef mPort; + + RefPtr<PortObserverThunk> mObserver; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef ipc_glue_MessageLink_h diff --git a/ipc/glue/MessagePump.cpp b/ipc/glue/MessagePump.cpp new file mode 100644 index 0000000000..50c916608c --- /dev/null +++ b/ipc/glue/MessagePump.cpp @@ -0,0 +1,336 @@ +/* -*- 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 "MessagePump.h" + +#include "nsIThread.h" +#include "nsITimer.h" +#include "nsICancelableRunnable.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/scoped_nsautorelease_pool.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "nsComponentManagerUtils.h" +#include "nsDebug.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsTimerImpl.h" +#include "nsXULAppAPI.h" +#include "prthread.h" + +using base::TimeTicks; +using namespace mozilla::ipc; + +#ifdef DEBUG +static MessagePump::Delegate* gFirstDelegate; +#endif + +namespace mozilla { +namespace ipc { + +class DoWorkRunnable final : public CancelableRunnable, + public nsITimerCallback { + public: + explicit DoWorkRunnable(MessagePump* aPump) + : CancelableRunnable("ipc::DoWorkRunnable"), mPump(aPump) { + MOZ_ASSERT(aPump); + } + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSITIMERCALLBACK + nsresult Cancel() override; + + private: + ~DoWorkRunnable() = default; + + MessagePump* mPump; + // DoWorkRunnable is designed as a stateless singleton. Do not add stateful + // members here! +}; + +} /* namespace ipc */ +} /* namespace mozilla */ + +MessagePump::MessagePump(nsISerialEventTarget* aEventTarget) + : mEventTarget(aEventTarget) { + mDoWorkEvent = new DoWorkRunnable(this); +} + +MessagePump::~MessagePump() = default; + +void MessagePump::Run(MessagePump::Delegate* aDelegate) { + MOZ_ASSERT(keep_running_); + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "Use mozilla::ipc::MessagePumpForNonMainThreads instead!"); + MOZ_RELEASE_ASSERT(!mEventTarget); + + nsIThread* thisThread = NS_GetCurrentThread(); + MOZ_ASSERT(thisThread); + + mDelayedWorkTimer = NS_NewTimer(); + MOZ_ASSERT(mDelayedWorkTimer); + + base::ScopedNSAutoreleasePool autoReleasePool; + + for (;;) { + autoReleasePool.Recycle(); + + bool did_work = NS_ProcessNextEvent(thisThread, false) ? true : false; + if (!keep_running_) break; + + // NB: it is crucial *not* to directly call |aDelegate->DoWork()| + // here. To ensure that MessageLoop tasks and XPCOM events have + // equal priority, we sensitively rely on processing exactly one + // Task per DoWorkRunnable XPCOM event. + + did_work |= aDelegate->DoDelayedWork(&delayed_work_time_); + + if (did_work && delayed_work_time_.is_null()) mDelayedWorkTimer->Cancel(); + + if (!keep_running_) break; + + if (did_work) continue; + + did_work = aDelegate->DoIdleWork(); + if (!keep_running_) break; + + if (did_work) continue; + + // This will either sleep or process an event. + NS_ProcessNextEvent(thisThread, true); + } + + mDelayedWorkTimer->Cancel(); + + keep_running_ = true; +} + +void MessagePump::ScheduleWork() { + // Make sure the event loop wakes up. + if (mEventTarget) { + mEventTarget->Dispatch(mDoWorkEvent, NS_DISPATCH_NORMAL); + } else { + // Some things (like xpcshell) don't use the app shell and so Run hasn't + // been called. We still need to wake up the main thread. + NS_DispatchToMainThread(mDoWorkEvent); + } + event_.Signal(); +} + +void MessagePump::ScheduleWorkForNestedLoop() { + // This method is called when our MessageLoop has just allowed + // nested tasks. In our setup, whenever that happens we know that + // DoWork() will be called "soon", so there's no need to pay the + // cost of what will be a no-op nsThread::Dispatch(mDoWorkEvent). +} + +void MessagePump::ScheduleDelayedWork(const base::TimeTicks& aDelayedTime) { + // To avoid racing on mDelayedWorkTimer, we need to be on the same thread as + // ::Run(). + MOZ_RELEASE_ASSERT((!mEventTarget && NS_IsMainThread()) || + mEventTarget->IsOnCurrentThread()); + + if (!mDelayedWorkTimer) { + mDelayedWorkTimer = NS_NewTimer(); + if (!mDelayedWorkTimer) { + // Called before XPCOM has started up? We can't do this correctly. + NS_WARNING("Delayed task might not run!"); + delayed_work_time_ = aDelayedTime; + return; + } + } + + if (!delayed_work_time_.is_null()) { + mDelayedWorkTimer->Cancel(); + } + + delayed_work_time_ = aDelayedTime; + + // TimeDelta's constructor initializes to 0 + base::TimeDelta delay; + if (aDelayedTime > base::TimeTicks::Now()) + delay = aDelayedTime - base::TimeTicks::Now(); + + uint32_t delayMS = uint32_t(delay.InMilliseconds()); + mDelayedWorkTimer->InitWithCallback(mDoWorkEvent, delayMS, + nsITimer::TYPE_ONE_SHOT); +} + +nsISerialEventTarget* MessagePump::GetXPCOMThread() { + if (mEventTarget) { + return mEventTarget; + } + + // Main thread + return GetMainThreadSerialEventTarget(); +} + +void MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate) { + aDelegate->DoDelayedWork(&delayed_work_time_); + if (!delayed_work_time_.is_null()) { + ScheduleDelayedWork(delayed_work_time_); + } +} + +NS_IMPL_ISUPPORTS_INHERITED(DoWorkRunnable, CancelableRunnable, + nsITimerCallback) + +NS_IMETHODIMP +DoWorkRunnable::Run() { + MessageLoop* loop = MessageLoop::current(); + MOZ_ASSERT(loop); + + bool nestableTasksAllowed = loop->NestableTasksAllowed(); + + // MessageLoop::RunTask() disallows nesting, but our Frankenventloop will + // always dispatch DoWork() below from what looks to MessageLoop like a nested + // context. So we unconditionally allow nesting here. + loop->SetNestableTasksAllowed(true); + loop->DoWork(); + loop->SetNestableTasksAllowed(nestableTasksAllowed); + + return NS_OK; +} + +NS_IMETHODIMP +DoWorkRunnable::Notify(nsITimer* aTimer) { + MessageLoop* loop = MessageLoop::current(); + MOZ_ASSERT(loop); + + bool nestableTasksAllowed = loop->NestableTasksAllowed(); + loop->SetNestableTasksAllowed(true); + mPump->DoDelayedWork(loop); + loop->SetNestableTasksAllowed(nestableTasksAllowed); + + return NS_OK; +} + +nsresult DoWorkRunnable::Cancel() { + // Workers require cancelable runnables, but we can't really cancel cleanly + // here. If we don't process this runnable then we will leave something + // unprocessed in the message_loop. Therefore, eagerly complete our work + // instead by immediately calling Run(). Run() should be called separately + // after this. Unfortunately we cannot use flags to verify this because + // DoWorkRunnable is a stateless singleton that can be in the event queue + // multiple times simultaneously. + MOZ_ALWAYS_SUCCEEDS(Run()); + return NS_OK; +} + +void MessagePumpForChildProcess::Run(base::MessagePump::Delegate* aDelegate) { + if (mFirstRun) { + MOZ_ASSERT(aDelegate && !gFirstDelegate); +#ifdef DEBUG + gFirstDelegate = aDelegate; +#endif + + mFirstRun = false; + if (NS_FAILED(XRE_RunAppShell())) { + NS_WARNING("Failed to run app shell?!"); + } + + MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate); +#ifdef DEBUG + gFirstDelegate = nullptr; +#endif + + return; + } + + MOZ_ASSERT(aDelegate && aDelegate == gFirstDelegate); + + // We can get to this point in startup with Tasks in our loop's + // incoming_queue_ or pending_queue_, but without a matching + // DoWorkRunnable(). In MessagePump::Run() above, we sensitively + // depend on *not* directly calling delegate->DoWork(), because that + // prioritizes Tasks above XPCOM events. However, from this point + // forward, any Task posted to our loop is guaranteed to have a + // DoWorkRunnable enqueued for it. + // + // So we just flush the pending work here and move on. + MessageLoop* loop = MessageLoop::current(); + bool nestableTasksAllowed = loop->NestableTasksAllowed(); + loop->SetNestableTasksAllowed(true); + + while (aDelegate->DoWork()) + ; + + loop->SetNestableTasksAllowed(nestableTasksAllowed); + + // Really run. + mozilla::ipc::MessagePump::Run(aDelegate); +} + +void MessagePumpForNonMainThreads::Run(base::MessagePump::Delegate* aDelegate) { + MOZ_ASSERT(keep_running_); + MOZ_RELEASE_ASSERT(!NS_IsMainThread(), + "Use mozilla::ipc::MessagePump instead!"); + + nsIThread* thread = NS_GetCurrentThread(); + MOZ_RELEASE_ASSERT(mEventTarget->IsOnCurrentThread()); + + mDelayedWorkTimer = NS_NewTimer(mEventTarget); + MOZ_ASSERT(mDelayedWorkTimer); + + // Chromium event notifications to be processed will be received by this + // event loop as a DoWorkRunnables via ScheduleWork. Chromium events that + // were received before our thread is valid, however, will not generate + // runnable wrappers. We must process any of these before we enter this + // loop, or we will forever have unprocessed chromium messages in our queue. + // + // Note we would like to request a flush of the chromium event queue + // using a runnable on the xpcom side, but some thread implementations + // (dom workers) get cranky if we call ScheduleWork here (ScheduleWork + // calls dispatch on mEventTarget) before the thread processes an event. As + // such, clear the queue manually. + while (aDelegate->DoWork()) { + } + + base::ScopedNSAutoreleasePool autoReleasePool; + for (;;) { + autoReleasePool.Recycle(); + + bool didWork = NS_ProcessNextEvent(thread, false) ? true : false; + if (!keep_running_) { + break; + } + + didWork |= aDelegate->DoDelayedWork(&delayed_work_time_); + + if (didWork && delayed_work_time_.is_null()) { + mDelayedWorkTimer->Cancel(); + } + + if (!keep_running_) { + break; + } + + if (didWork) { + continue; + } + + DebugOnly<bool> didIdleWork = aDelegate->DoIdleWork(); + MOZ_ASSERT(!didIdleWork); + if (!keep_running_) { + break; + } + + if (didWork) { + continue; + } + + // This will either sleep or process an event. + NS_ProcessNextEvent(thread, true); + } + + mDelayedWorkTimer->Cancel(); + + keep_running_ = true; +} diff --git a/ipc/glue/MessagePump.h b/ipc/glue/MessagePump.h new file mode 100644 index 0000000000..50b97efd79 --- /dev/null +++ b/ipc/glue/MessagePump.h @@ -0,0 +1,203 @@ +/* -*- 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/. */ + +#ifndef __IPC_GLUE_MESSAGEPUMP_H__ +#define __IPC_GLUE_MESSAGEPUMP_H__ + +#include "base/message_pump_default.h" +#if defined(XP_WIN) +# include "base/message_pump_win.h" +#elif defined(XP_DARWIN) +# include "base/message_pump_mac.h" +#endif + +#include "base/time.h" +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsIThreadInternal.h" + +class nsIEventTarget; +class nsITimer; + +namespace mozilla { +namespace ipc { + +class DoWorkRunnable; + +class MessagePump : public base::MessagePumpDefault { + friend class DoWorkRunnable; + + public: + explicit MessagePump(nsISerialEventTarget* aEventTarget); + + // From base::MessagePump. + virtual void Run(base::MessagePump::Delegate* aDelegate) override; + + // From base::MessagePump. + virtual void ScheduleWork() override; + + // From base::MessagePump. + virtual void ScheduleWorkForNestedLoop() override; + + // From base::MessagePump. + virtual void ScheduleDelayedWork( + const base::TimeTicks& aDelayedWorkTime) override; + + virtual nsISerialEventTarget* GetXPCOMThread() override; + + protected: + virtual ~MessagePump(); + + private: + // Only called by DoWorkRunnable. + void DoDelayedWork(base::MessagePump::Delegate* aDelegate); + + protected: + nsISerialEventTarget* mEventTarget; + + // mDelayedWorkTimer and mEventTarget are set in Run() by this class or its + // subclasses. + nsCOMPtr<nsITimer> mDelayedWorkTimer; + + private: + // Only accessed by this class. + RefPtr<DoWorkRunnable> mDoWorkEvent; +}; + +class MessagePumpForChildProcess final : public MessagePump { + public: + MessagePumpForChildProcess() : MessagePump(nullptr), mFirstRun(true) {} + + virtual void Run(base::MessagePump::Delegate* aDelegate) override; + + private: + ~MessagePumpForChildProcess() = default; + + bool mFirstRun; +}; + +class MessagePumpForNonMainThreads final : public MessagePump { + public: + explicit MessagePumpForNonMainThreads(nsISerialEventTarget* aEventTarget) + : MessagePump(aEventTarget) {} + + virtual void Run(base::MessagePump::Delegate* aDelegate) override; + + private: + ~MessagePumpForNonMainThreads() = default; +}; + +#if defined(XP_WIN) +// Extends the TYPE_UI message pump to process xpcom events. +class MessagePumpForNonMainUIThreads final : public base::MessagePumpForUI, + public nsIThreadObserver { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSITHREADOBSERVER + + public: + explicit MessagePumpForNonMainUIThreads(nsISerialEventTarget* aEventTarget) + : mInWait(false), mWaitLock("mInWait") {} + + // The main run loop for this thread. + virtual void DoRunLoop() override; + + virtual nsISerialEventTarget* GetXPCOMThread() override { + return nullptr; // not sure what to do with this one + } + + protected: + void SetInWait() { + MutexAutoLock lock(mWaitLock); + mInWait = true; + } + + void ClearInWait() { + MutexAutoLock lock(mWaitLock); + mInWait = false; + } + + bool GetInWait() { + MutexAutoLock lock(mWaitLock); + return mInWait; + } + + private: + ~MessagePumpForNonMainUIThreads() {} + + bool mInWait MOZ_GUARDED_BY(mWaitLock); + mozilla::Mutex mWaitLock; +}; +#elif defined(XP_DARWIN) +// Extends the CFRunLoopBase message pump to process xpcom events. Based on +// MessagePumpNSRunLoop. +class MessagePumpForNonMainUIThreads final + : public base::MessagePumpCFRunLoopBase, + public nsIThreadObserver { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSITHREADOBSERVER + + public: + explicit MessagePumpForNonMainUIThreads(nsISerialEventTarget* aEventTarget); + + void DoRun(base::MessagePump::Delegate* aDelegate) override; + void Quit() override; + + nsISerialEventTarget* GetXPCOMThread() override { return mEventTarget; } + + private: + ~MessagePumpForNonMainUIThreads(); + + nsISerialEventTarget* mEventTarget; + + // A source that doesn't do anything but provide something signalable + // attached to the run loop. This source will be signalled when Quit + // is called, to cause the loop to wake up so that it can stop. + CFRunLoopSourceRef quit_source_; + + // False after Quit is called. + bool keep_running_; + + DISALLOW_COPY_AND_ASSIGN(MessagePumpForNonMainUIThreads); +}; +#endif // defined(XP_DARWIN) + +#if defined(MOZ_WIDGET_ANDROID) +/*` + * The MessagePumpForAndroidUI exists to enable IPDL in the Android UI thread. + * The Android UI thread event loop is controlled by Android. This prevents + * running an existing MessagePump implementation in the Android UI thread. In + * order to enable IPDL on the Android UI thread it is necessary to have a + * non-looping MessagePump. This class enables forwarding of nsIRunnables from + * MessageLoop::PostTask_Helper to the registered nsIEventTarget with out the + * need to control the event loop. The only member function that should be + * invoked is GetXPCOMThread. All other member functions will invoke MOZ_CRASH + */ +class MessagePumpForAndroidUI : public base::MessagePump { + public: + explicit MessagePumpForAndroidUI(nsISerialEventTarget* aEventTarget) + : mEventTarget(aEventTarget) {} + + virtual void Run(Delegate* delegate); + virtual void Quit(); + virtual void ScheduleWork(); + virtual void ScheduleDelayedWork(const base::TimeTicks& delayed_work_time); + virtual nsISerialEventTarget* GetXPCOMThread() { return mEventTarget; } + + private: + ~MessagePumpForAndroidUI() {} + MessagePumpForAndroidUI() {} + + nsISerialEventTarget* mEventTarget; +}; +#endif // defined(MOZ_WIDGET_ANDROID) + +} /* namespace ipc */ +} /* namespace mozilla */ + +#endif /* __IPC_GLUE_MESSAGEPUMP_H__ */ diff --git a/ipc/glue/MessagePump_android.cpp b/ipc/glue/MessagePump_android.cpp new file mode 100644 index 0000000000..f237d4d7ed --- /dev/null +++ b/ipc/glue/MessagePump_android.cpp @@ -0,0 +1,26 @@ +/* -*- 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 "MessagePump.h" + +namespace mozilla::ipc { +void MessagePumpForAndroidUI::Run(Delegate* delegate) { + MOZ_CRASH("MessagePumpForAndroidUI should never be Run."); +} + +void MessagePumpForAndroidUI::Quit() { + MOZ_CRASH("MessagePumpForAndroidUI should never be Quit."); +} + +void MessagePumpForAndroidUI::ScheduleWork() { + MOZ_CRASH("MessagePumpForAndroidUI should never ScheduleWork"); +} + +void MessagePumpForAndroidUI::ScheduleDelayedWork( + const base::TimeTicks& delayed_work_time) { + MOZ_CRASH("MessagePumpForAndroidUI should never ScheduleDelayedWork"); +} +} // namespace mozilla::ipc diff --git a/ipc/glue/MessagePump_mac.mm b/ipc/glue/MessagePump_mac.mm new file mode 100644 index 0000000000..69b8f1f87e --- /dev/null +++ b/ipc/glue/MessagePump_mac.mm @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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 "MessagePump.h" + +#include <Foundation/Foundation.h> + +#include "base/scoped_nsautorelease_pool.h" +#include "mozilla/ProfilerMarkers.h" +#include "nsISupportsImpl.h" + +using namespace mozilla::ipc; + +static void NoOp(void* info) {} + +NS_IMPL_ADDREF_INHERITED(MessagePumpForNonMainUIThreads, MessagePump) +NS_IMPL_RELEASE_INHERITED(MessagePumpForNonMainUIThreads, MessagePump) +NS_IMPL_QUERY_INTERFACE(MessagePumpForNonMainUIThreads, nsIThreadObserver) + +MessagePumpForNonMainUIThreads::MessagePumpForNonMainUIThreads( + nsISerialEventTarget* aEventTarget) + : mEventTarget(aEventTarget), keep_running_(true) { + MOZ_ASSERT(mEventTarget); + CFRunLoopSourceContext source_context = CFRunLoopSourceContext(); + source_context.perform = NoOp; + quit_source_ = CFRunLoopSourceCreate(nullptr, // allocator + 0, // priority + &source_context); + CFRunLoopAddSource(run_loop(), quit_source_, kCFRunLoopCommonModes); +} + +MessagePumpForNonMainUIThreads::~MessagePumpForNonMainUIThreads() { + CFRunLoopRemoveSource(run_loop(), quit_source_, kCFRunLoopCommonModes); + CFRelease(quit_source_); +} + +void MessagePumpForNonMainUIThreads::DoRun( + base::MessagePump::Delegate* aDelegate) { + // If this is a chromium thread and no nsThread is associated with it, this + // call will create a new nsThread. + nsIThread* thread = NS_GetCurrentThread(); + MOZ_ASSERT(thread); + + // Set the main thread observer so we can wake up when xpcom events need to + // get processed. + nsCOMPtr<nsIThreadInternal> ti(do_QueryInterface(thread)); + MOZ_ASSERT(ti); + ti->SetObserver(this); + + base::ScopedNSAutoreleasePool autoReleasePool; + + while (keep_running_) { + // Drain the xpcom event loop first. + if (NS_ProcessNextEvent(nullptr, false)) { + continue; + } + + autoReleasePool.Recycle(); + + if (!keep_running_) { + break; + } + + // Now process the CFRunLoop. It exits after running once. + // NSRunLoop manages autorelease pools itself. + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode + beforeDate:[NSDate distantFuture]]; + } + + ti->SetObserver(nullptr); + keep_running_ = true; +} + +void MessagePumpForNonMainUIThreads::Quit() { + keep_running_ = false; + CFRunLoopSourceSignal(quit_source_); + CFRunLoopWakeUp(run_loop()); +} + +NS_IMETHODIMP MessagePumpForNonMainUIThreads::OnDispatchedEvent() { + // ScheduleWork will signal an input source to the run loop, making it exit so + // it can process the xpcom event. + ScheduleWork(); + return NS_OK; +} + +NS_IMETHODIMP +MessagePumpForNonMainUIThreads::OnProcessNextEvent(nsIThreadInternal* thread, + bool mayWait) { + return NS_OK; +} + +NS_IMETHODIMP +MessagePumpForNonMainUIThreads::AfterProcessNextEvent(nsIThreadInternal* thread, + bool eventWasProcessed) { + return NS_OK; +} diff --git a/ipc/glue/MessagePump_windows.cpp b/ipc/glue/MessagePump_windows.cpp new file mode 100644 index 0000000000..c0109f3f43 --- /dev/null +++ b/ipc/glue/MessagePump_windows.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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 "MessagePump.h" + +using namespace mozilla::ipc; + +NS_IMPL_ADDREF_INHERITED(MessagePumpForNonMainUIThreads, MessagePump) +NS_IMPL_RELEASE_INHERITED(MessagePumpForNonMainUIThreads, MessagePump) +NS_IMPL_QUERY_INTERFACE(MessagePumpForNonMainUIThreads, nsIThreadObserver) + +#define CHECK_QUIT_STATE \ + { \ + if (state_->should_quit) { \ + break; \ + } \ + } + +void MessagePumpForNonMainUIThreads::DoRunLoop() { + MOZ_RELEASE_ASSERT(!NS_IsMainThread(), + "Use mozilla::ipc::MessagePump instead!"); + + // If this is a chromium thread and no nsThread is associated + // with it, this call will create a new nsThread. + nsIThread* thread = NS_GetCurrentThread(); + MOZ_ASSERT(thread); + + // Set the main thread observer so we can wake up when + // xpcom events need to get processed. + nsCOMPtr<nsIThreadInternal> ti(do_QueryInterface(thread)); + MOZ_ASSERT(ti); + ti->SetObserver(this); + + for (;;) { + bool didWork = NS_ProcessNextEvent(thread, false); + + didWork |= ProcessNextWindowsMessage(); + CHECK_QUIT_STATE + + didWork |= state_->delegate->DoWork(); + CHECK_QUIT_STATE + + didWork |= state_->delegate->DoDelayedWork(&delayed_work_time_); + if (didWork && delayed_work_time_.is_null()) { + KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this)); + } + CHECK_QUIT_STATE + + if (didWork) { + continue; + } + + DebugOnly<bool> didIdleWork = state_->delegate->DoIdleWork(); + MOZ_ASSERT(!didIdleWork); + CHECK_QUIT_STATE + + SetInWait(); + bool hasWork = NS_HasPendingEvents(thread); + if (didWork || hasWork) { + ClearInWait(); + continue; + } + WaitForWork(); // Calls MsgWaitForMultipleObjectsEx(QS_ALLINPUT) + ClearInWait(); + } + + ClearInWait(); + + ti->SetObserver(nullptr); +} + +NS_IMETHODIMP +MessagePumpForNonMainUIThreads::OnDispatchedEvent() { + // If our thread is sleeping in DoRunLoop's call to WaitForWork() and an + // event posts to the nsIThread event queue - break our thread out of + // chromium's WaitForWork. + if (GetInWait()) { + ScheduleWork(); + } + return NS_OK; +} + +NS_IMETHODIMP +MessagePumpForNonMainUIThreads::OnProcessNextEvent(nsIThreadInternal* thread, + bool mayWait) { + return NS_OK; +} + +NS_IMETHODIMP +MessagePumpForNonMainUIThreads::AfterProcessNextEvent(nsIThreadInternal* thread, + bool eventWasProcessed) { + return NS_OK; +} diff --git a/ipc/glue/MiniTransceiver.cpp b/ipc/glue/MiniTransceiver.cpp new file mode 100644 index 0000000000..efb12d2fe2 --- /dev/null +++ b/ipc/glue/MiniTransceiver.cpp @@ -0,0 +1,255 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 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 "mozilla/ipc/MiniTransceiver.h" +#include "chrome/common/ipc_message.h" +#include "chrome/common/ipc_message_utils.h" +#include "base/eintr_wrapper.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Sprintf.h" +#include "mozilla/ScopeExit.h" +#include "nsDebug.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <string.h> +#include <errno.h> + +namespace mozilla::ipc { + +static const size_t kMaxIOVecSize = 64; +static const size_t kMaxDataSize = 8 * 1024; +static const size_t kMaxNumFds = 16; + +MiniTransceiver::MiniTransceiver(int aFd, DataBufferClear aDataBufClear) + : mFd(aFd), +#ifdef DEBUG + mState(STATE_NONE), +#endif + mDataBufClear(aDataBufClear) { +} + +namespace { + +/** + * Initialize the IO vector for sending data and the control buffer for sending + * FDs. + */ +static void InitMsgHdr(msghdr* aHdr, int aIOVSize, size_t aMaxNumFds) { + aHdr->msg_name = nullptr; + aHdr->msg_namelen = 0; + aHdr->msg_flags = 0; + + // Prepare the IO vector to receive the content of message. + auto* iov = new iovec[aIOVSize]; + aHdr->msg_iov = iov; + aHdr->msg_iovlen = aIOVSize; + + // Prepare the control buffer to receive file descriptors. + const size_t cbufSize = CMSG_SPACE(sizeof(int) * aMaxNumFds); + auto* cbuf = new char[cbufSize]; + // Avoid valgrind complaints about uninitialized padding (but also, + // fill with a value that isn't a valid fd, just in case). + memset(cbuf, 255, cbufSize); + aHdr->msg_control = cbuf; + aHdr->msg_controllen = cbufSize; +} + +/** + * Delete resources allocated by InitMsgHdr(). + */ +static void DeinitMsgHdr(msghdr* aHdr) { + delete aHdr->msg_iov; + delete static_cast<char*>(aHdr->msg_control); +} + +} // namespace + +void MiniTransceiver::PrepareFDs(msghdr* aHdr, IPC::Message& aMsg) { + // Set control buffer to send file descriptors of the Message. + size_t num_fds = aMsg.attached_handles_.Length(); + + cmsghdr* cmsg = CMSG_FIRSTHDR(aHdr); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds); + for (size_t i = 0; i < num_fds; ++i) { + reinterpret_cast<int*>(CMSG_DATA(cmsg))[i] = + aMsg.attached_handles_[i].get(); + } + + // This number will be sent in the header of the message. So, we + // can check it at the other side. + aMsg.header()->num_handles = num_fds; +} + +size_t MiniTransceiver::PrepareBuffers(msghdr* aHdr, IPC::Message& aMsg) { + // Set iovec to send for all buffers of the Message. + iovec* iov = aHdr->msg_iov; + size_t iovlen = 0; + size_t bytes_to_send = 0; + for (Pickle::BufferList::IterImpl iter(aMsg.Buffers()); !iter.Done(); + iter.Advance(aMsg.Buffers(), iter.RemainingInSegment())) { + char* data = iter.Data(); + size_t size = iter.RemainingInSegment(); + iov[iovlen].iov_base = data; + iov[iovlen].iov_len = size; + iovlen++; + MOZ_ASSERT(iovlen <= kMaxIOVecSize); + bytes_to_send += size; + } + MOZ_ASSERT(bytes_to_send <= kMaxDataSize); + aHdr->msg_iovlen = iovlen; + + return bytes_to_send; +} + +bool MiniTransceiver::Send(IPC::Message& aMsg) { +#ifdef DEBUG + if (mState == STATE_SENDING) { + MOZ_CRASH( + "STATE_SENDING: It violates of request-response and no concurrent " + "rules"); + } + mState = STATE_SENDING; +#endif + + auto clean_fdset = MakeScopeExit([&] { aMsg.attached_handles_.Clear(); }); + + size_t num_fds = aMsg.attached_handles_.Length(); + msghdr hdr{}; + InitMsgHdr(&hdr, kMaxIOVecSize, num_fds); + + UniquePtr<msghdr, decltype(&DeinitMsgHdr)> uniq(&hdr, &DeinitMsgHdr); + + PrepareFDs(&hdr, aMsg); + DebugOnly<size_t> bytes_to_send = PrepareBuffers(&hdr, aMsg); + + ssize_t bytes_written = HANDLE_EINTR(sendmsg(mFd, &hdr, 0)); + + if (bytes_written < 0) { + char error[128]; + SprintfLiteral(error, "sendmsg: %s", strerror(errno)); + NS_WARNING(error); + return false; + } + MOZ_ASSERT(bytes_written == (ssize_t)bytes_to_send, + "The message is too big?!"); + + return true; +} + +unsigned MiniTransceiver::RecvFDs(msghdr* aHdr, int* aAllFds, + unsigned aMaxFds) { + if (aHdr->msg_controllen == 0) { + return 0; + } + + unsigned num_all_fds = 0; + for (cmsghdr* cmsg = CMSG_FIRSTHDR(aHdr); cmsg; + cmsg = CMSG_NXTHDR(aHdr, cmsg)) { + MOZ_ASSERT(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS, + "Accept only SCM_RIGHTS to receive file descriptors"); + + unsigned payload_sz = cmsg->cmsg_len - CMSG_LEN(0); + MOZ_ASSERT(payload_sz % sizeof(int) == 0); + + // Add fds to |aAllFds| + unsigned num_part_fds = payload_sz / sizeof(int); + int* part_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + MOZ_ASSERT(num_all_fds + num_part_fds <= aMaxFds); + + memcpy(aAllFds + num_all_fds, part_fds, num_part_fds * sizeof(int)); + num_all_fds += num_part_fds; + } + return num_all_fds; +} + +bool MiniTransceiver::RecvData(char* aDataBuf, size_t aBufSize, + uint32_t* aMsgSize, int* aFdsBuf, + unsigned aMaxFds, unsigned* aNumFds) { + msghdr hdr; + InitMsgHdr(&hdr, 1, aMaxFds); + + UniquePtr<msghdr, decltype(&DeinitMsgHdr)> uniq(&hdr, &DeinitMsgHdr); + + // The buffer to collect all fds received from the socket. + int* all_fds = aFdsBuf; + unsigned num_all_fds = 0; + + size_t total_readed = 0; + uint32_t msgsz = 0; + while (msgsz == 0 || total_readed < msgsz) { + // Set IO vector with the begin of the unused buffer. + hdr.msg_iov->iov_base = aDataBuf + total_readed; + hdr.msg_iov->iov_len = (msgsz == 0 ? aBufSize : msgsz) - total_readed; + + // Read the socket + ssize_t bytes_readed = HANDLE_EINTR(recvmsg(mFd, &hdr, 0)); + if (bytes_readed <= 0) { + // Closed or error! + return false; + } + total_readed += bytes_readed; + MOZ_ASSERT(total_readed <= aBufSize); + + if (msgsz == 0) { + // Parse the size of the message. + // Get 0 if data in the buffer is no enough to get message size. + msgsz = IPC::Message::MessageSize(aDataBuf, aDataBuf + total_readed); + } + + num_all_fds += RecvFDs(&hdr, all_fds + num_all_fds, aMaxFds - num_all_fds); + } + + *aMsgSize = msgsz; + *aNumFds = num_all_fds; + return true; +} + +bool MiniTransceiver::Recv(UniquePtr<IPC::Message>& aMsg) { +#ifdef DEBUG + if (mState == STATE_RECEIVING) { + MOZ_CRASH( + "STATE_RECEIVING: It violates of request-response and no concurrent " + "rules"); + } + mState = STATE_RECEIVING; +#endif + + UniquePtr<char[]> databuf = MakeUnique<char[]>(kMaxDataSize); + uint32_t msgsz = 0; + int all_fds[kMaxNumFds]; + unsigned num_all_fds = 0; + + if (!RecvData(databuf.get(), kMaxDataSize, &msgsz, all_fds, kMaxDataSize, + &num_all_fds)) { + return false; + } + + // Create Message from databuf & all_fds. + UniquePtr<IPC::Message> msg = MakeUnique<IPC::Message>(databuf.get(), msgsz); + nsTArray<UniqueFileHandle> handles(num_all_fds); + for (unsigned i = 0; i < num_all_fds; ++i) { + handles.AppendElement(UniqueFileHandle(all_fds[i])); + } + msg->SetAttachedFileHandles(std::move(handles)); + + if (mDataBufClear == DataBufferClear::AfterReceiving) { + // Avoid content processes from reading the content of + // messages. + memset(databuf.get(), 0, msgsz); + } + + MOZ_ASSERT(msg->header()->num_handles == msg->attached_handles_.Length(), + "The number of file descriptors in the header is different from" + " the number actually received"); + + aMsg = std::move(msg); + return true; +} + +} // namespace mozilla::ipc diff --git a/ipc/glue/MiniTransceiver.h b/ipc/glue/MiniTransceiver.h new file mode 100644 index 0000000000..9568dc2685 --- /dev/null +++ b/ipc/glue/MiniTransceiver.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 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/. */ +#ifndef __MINITRANSCEIVER_H_ +#define __MINITRANSCEIVER_H_ + +#include "chrome/common/ipc_message.h" +#include "mozilla/Assertions.h" + +struct msghdr; + +namespace mozilla { +namespace ipc { + +enum class DataBufferClear { None, AfterReceiving }; + +/** + * This simple implementation handles the transmissions of IPC + * messages. + * + * It works according to a strict request-response paradigm, no + * concurrent messaging is allowed. Sending a message from A to B must + * be followed by another one from B to A. Because of this we don't + * need to handle data crossing the boundaries of a + * message. Transmission is done via blocking I/O to avoid the + * complexity of asynchronous I/O. + */ +class MiniTransceiver { + public: + /** + * \param aFd should be a blocking, no O_NONBLOCK, fd. + * \param aClearDataBuf is true to clear data buffers after + * receiving a message. + */ + explicit MiniTransceiver( + int aFd, DataBufferClear aDataBufClear = DataBufferClear::None); + + bool Send(IPC::Message& aMsg); + inline bool SendInfallible(IPC::Message& aMsg, const char* aCrashMessage) { + bool Ok = Send(aMsg); + if (!Ok) { + MOZ_CRASH_UNSAFE(aCrashMessage); + } + return Ok; + } + + /** + * \param aMsg will hold the content of the received message. + * \return false if the fd is closed or with an error. + */ + bool Recv(UniquePtr<IPC::Message>& aMsg); + inline bool RecvInfallible(UniquePtr<IPC::Message>& aMsg, + const char* aCrashMessage) { + bool Ok = Recv(aMsg); + if (!Ok) { + MOZ_CRASH_UNSAFE(aCrashMessage); + } + return Ok; + } + + int GetFD() { return mFd; } + + private: + /** + * Set control buffer to make file descriptors ready to be sent + * through a socket. + */ + void PrepareFDs(msghdr* aHdr, IPC::Message& aMsg); + /** + * Collect buffers of the message and make them ready to be sent. + * + * \param aHdr is the structure going to be passed to sendmsg(). + * \param aMsg is the Message to send. + */ + size_t PrepareBuffers(msghdr* aHdr, IPC::Message& aMsg); + /** + * Collect file descriptors received. + * + * \param aAllFds is where to store file descriptors. + * \param aMaxFds is how many file descriptors can be stored in aAllFds. + * \return the number of received file descriptors. + */ + unsigned RecvFDs(msghdr* aHdr, int* aAllFds, unsigned aMaxFds); + /** + * Received data from the socket. + * + * \param aDataBuf is where to store the data from the socket. + * \param aBufSize is the size of the buffer. + * \param aMsgSize returns how many bytes were readed from the socket. + * \param aFdsBuf is the buffer to return file desriptors received. + * \param aMaxFds is the number of file descriptors that can be held. + * \param aNumFds returns the number of file descriptors received. + * \return true if sucess, or false for error. + */ + bool RecvData(char* aDataBuf, size_t aBufSize, uint32_t* aMsgSize, + int* aFdsBuf, unsigned aMaxFds, unsigned* aNumFds); + + int mFd; // The file descriptor of the socket for IPC. + +#ifdef DEBUG + enum State { + STATE_NONE, + STATE_SENDING, + STATE_RECEIVING, + }; + State mState; +#endif + + // Clear all received data in temp buffers to avoid data leaking. + DataBufferClear mDataBufClear; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // __MINITRANSCEIVER_H_ diff --git a/ipc/glue/Neutering.h b/ipc/glue/Neutering.h new file mode 100644 index 0000000000..44cbe042ae --- /dev/null +++ b/ipc/glue/Neutering.h @@ -0,0 +1,70 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_Neutering_h +#define mozilla_ipc_Neutering_h + +/** + * This header declares RAII wrappers for Window neutering. See + * WindowsMessageLoop.cpp for more details. + */ + +namespace mozilla { +namespace ipc { + +/** + * This class is a RAII wrapper around Window neutering. As long as a + * NeuteredWindowRegion object is instantiated, Win32 windows belonging to the + * current thread will be neutered. It is safe to nest multiple instances of + * this class. + */ +class MOZ_RAII NeuteredWindowRegion { + public: + explicit NeuteredWindowRegion(bool aDoNeuter); + ~NeuteredWindowRegion(); + + /** + * This function clears any backlog of nonqueued messages that are pending for + * the current thread. + */ + void PumpOnce(); + + private: + bool mNeuteredByThis; +}; + +/** + * This class is analagous to MutexAutoUnlock for Mutex; it is an RAII class + * that is to be instantiated within a NeuteredWindowRegion, thus temporarily + * disabling neutering for the remainder of its enclosing block. + * @see NeuteredWindowRegion + */ +class MOZ_RAII DeneuteredWindowRegion { + public: + explicit DeneuteredWindowRegion(); + ~DeneuteredWindowRegion(); + + private: + bool mReneuter; +}; + +class MOZ_RAII SuppressedNeuteringRegion { + public: + explicit SuppressedNeuteringRegion(); + ~SuppressedNeuteringRegion(); + + static inline bool IsNeuteringSuppressed() { return sSuppressNeutering; } + + private: + bool mReenable; + + static bool sSuppressNeutering; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_Neutering_h diff --git a/ipc/glue/NodeChannel.cpp b/ipc/glue/NodeChannel.cpp new file mode 100644 index 0000000000..70e67bc473 --- /dev/null +++ b/ipc/glue/NodeChannel.cpp @@ -0,0 +1,305 @@ +/* -*- 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 "mozilla/ipc/NodeChannel.h" +#include "chrome/common/ipc_message.h" +#include "chrome/common/ipc_message_utils.h" +#include "mojo/core/ports/name.h" +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/ProtocolMessageUtils.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +#ifdef FUZZING_SNAPSHOT +# include "mozilla/fuzzing/IPCFuzzController.h" +#endif + +template <> +struct IPC::ParamTraits<mozilla::ipc::NodeChannel::Introduction> { + using paramType = mozilla::ipc::NodeChannel::Introduction; + static void Write(MessageWriter* aWriter, paramType&& aParam) { + WriteParam(aWriter, aParam.mName); + WriteParam(aWriter, std::move(aParam.mHandle)); + WriteParam(aWriter, aParam.mMode); + WriteParam(aWriter, aParam.mMyPid); + WriteParam(aWriter, aParam.mOtherPid); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->mName) && + ReadParam(aReader, &aResult->mHandle) && + ReadParam(aReader, &aResult->mMode) && + ReadParam(aReader, &aResult->mMyPid) && + ReadParam(aReader, &aResult->mOtherPid); + } +}; + +namespace mozilla::ipc { + +NodeChannel::NodeChannel(const NodeName& aName, + UniquePtr<IPC::Channel> aChannel, Listener* aListener, + base::ProcessId aPid, + GeckoChildProcessHost* aChildProcessHost) + : mListener(aListener), + mName(aName), + mOtherPid(aPid), + mChannel(std::move(aChannel)), + mChildProcessHost(aChildProcessHost) {} + +NodeChannel::~NodeChannel() { Close(); } + +// Called when the NodeChannel's refcount drops to `0`. +void NodeChannel::Destroy() { + // Dispatch the `delete` operation to the IO thread. We need to do this even + // if we're already on the IO thread, as we could be in an `IPC::Channel` + // callback which unfortunately will not hold a strong reference to keep + // `this` alive. + MessageLoop* ioThread = XRE_GetIOMessageLoop(); + if (ioThread->IsAcceptingTasks()) { + ioThread->PostTask(NewNonOwningRunnableMethod("NodeChannel::Destroy", this, + &NodeChannel::FinalDestroy)); + return; + } + + // If the IOThread has already been destroyed, we must be shutting it down and + // need to synchronously invoke `FinalDestroy` to ensure we're cleaned up + // before the thread dies. This is safe as we can't be in a non-owning + // IPC::Channel callback at this point. + if (MessageLoop::current() == ioThread) { + FinalDestroy(); + return; + } + + MOZ_ASSERT_UNREACHABLE("Leaking NodeChannel after IOThread destroyed!"); +} + +void NodeChannel::FinalDestroy() { + AssertIOThread(); + delete this; +} + +void NodeChannel::Start() { + AssertIOThread(); + + if (!mChannel->Connect(this)) { + OnChannelError(); + } +} + +void NodeChannel::Close() { + AssertIOThread(); + + if (mState.exchange(State::Closed) != State::Closed) { + mChannel->Close(); + } +} + +void NodeChannel::SetOtherPid(base::ProcessId aNewPid) { + AssertIOThread(); + MOZ_ASSERT(aNewPid != base::kInvalidProcessId); + + base::ProcessId previousPid = base::kInvalidProcessId; + if (!mOtherPid.compare_exchange_strong(previousPid, aNewPid)) { + // The PID was already set before this call, double-check that it's correct. + MOZ_RELEASE_ASSERT(previousPid == aNewPid, + "Different sources disagree on the correct pid?"); + } + + mChannel->SetOtherPid(aNewPid); +} + +#ifdef XP_MACOSX +void NodeChannel::SetMachTaskPort(task_t aTask) { + AssertIOThread(); + + if (mState != State::Closed) { + mChannel->SetOtherMachTask(aTask); + } +} +#endif + +void NodeChannel::SendEventMessage(UniquePtr<IPC::Message> aMessage) { + // Make sure we're not sending a message with one of our special internal + // types ,as those should only be sent using the corresponding methods on + // NodeChannel. + MOZ_DIAGNOSTIC_ASSERT(aMessage->type() != BROADCAST_MESSAGE_TYPE && + aMessage->type() != INTRODUCE_MESSAGE_TYPE && + aMessage->type() != REQUEST_INTRODUCTION_MESSAGE_TYPE && + aMessage->type() != ACCEPT_INVITE_MESSAGE_TYPE); + SendMessage(std::move(aMessage)); +} + +void NodeChannel::RequestIntroduction(const NodeName& aPeerName) { + MOZ_ASSERT(aPeerName != mojo::core::ports::kInvalidNodeName); + auto message = MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL, + REQUEST_INTRODUCTION_MESSAGE_TYPE); + IPC::MessageWriter writer(*message); + WriteParam(&writer, aPeerName); + SendMessage(std::move(message)); +} + +void NodeChannel::Introduce(Introduction aIntroduction) { + auto message = + MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL, INTRODUCE_MESSAGE_TYPE); + IPC::MessageWriter writer(*message); + WriteParam(&writer, std::move(aIntroduction)); + SendMessage(std::move(message)); +} + +void NodeChannel::Broadcast(UniquePtr<IPC::Message> aMessage) { + MOZ_DIAGNOSTIC_ASSERT(aMessage->type() == BROADCAST_MESSAGE_TYPE, + "Can only broadcast messages with the correct type"); + SendMessage(std::move(aMessage)); +} + +void NodeChannel::AcceptInvite(const NodeName& aRealName, + const PortName& aInitialPort) { + MOZ_ASSERT(aRealName != mojo::core::ports::kInvalidNodeName); + MOZ_ASSERT(aInitialPort != mojo::core::ports::kInvalidPortName); + auto message = + MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL, ACCEPT_INVITE_MESSAGE_TYPE); + IPC::MessageWriter writer(*message); + WriteParam(&writer, aRealName); + WriteParam(&writer, aInitialPort); + SendMessage(std::move(message)); +} + +void NodeChannel::SendMessage(UniquePtr<IPC::Message> aMessage) { + if (aMessage->size() > IPC::Channel::kMaximumMessageSize) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCMessageName, + nsDependentCString(aMessage->name())); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCMessageSize, + static_cast<unsigned int>(aMessage->size())); + MOZ_CRASH("IPC message size is too large"); + } + aMessage->AssertAsLargeAsHeader(); + +#ifdef FUZZING_SNAPSHOT + if (mBlockSendRecv) { + return; + } +#endif + + if (mState != State::Active) { + NS_WARNING("Dropping message as channel has been closed"); + return; + } + + // NOTE: As this is not guaranteed to be running on the I/O thread, the + // channel may have become closed since we checked above. IPC::Channel will + // handle that and return `false` here, so we can re-check `mState`. + if (!mChannel->Send(std::move(aMessage))) { + NS_WARNING("Call to Send() failed"); + + // If we're still active, update `mState` to `State::Closing`, and dispatch + // a runnable to actually close our channel. + State expected = State::Active; + if (mState.compare_exchange_strong(expected, State::Closing)) { + XRE_GetIOMessageLoop()->PostTask( + NewRunnableMethod("NodeChannel::CloseForSendError", this, + &NodeChannel::OnChannelError)); + } + } +} + +void NodeChannel::OnMessageReceived(UniquePtr<IPC::Message> aMessage) { + AssertIOThread(); + +#ifdef FUZZING_SNAPSHOT + if (mBlockSendRecv && !aMessage->IsFuzzMsg()) { + return; + } +#endif + + IPC::MessageReader reader(*aMessage); + switch (aMessage->type()) { + case REQUEST_INTRODUCTION_MESSAGE_TYPE: { + NodeName name; + if (IPC::ReadParam(&reader, &name)) { + mListener->OnRequestIntroduction(mName, name); + return; + } + break; + } + case INTRODUCE_MESSAGE_TYPE: { + Introduction introduction; + if (IPC::ReadParam(&reader, &introduction)) { + mListener->OnIntroduce(mName, std::move(introduction)); + return; + } + break; + } + case BROADCAST_MESSAGE_TYPE: { + mListener->OnBroadcast(mName, std::move(aMessage)); + return; + } + case ACCEPT_INVITE_MESSAGE_TYPE: { + NodeName realName; + PortName initialPort; + if (IPC::ReadParam(&reader, &realName) && + IPC::ReadParam(&reader, &initialPort)) { + mListener->OnAcceptInvite(mName, realName, initialPort); + return; + } + break; + } + // Assume all unrecognized types are intended as user event messages, and + // deliver them to our listener as such. This allows us to use the same type + // field for both internal messages and protocol messages. + // + // FIXME: Consider doing something cleaner in the future? + case EVENT_MESSAGE_TYPE: + default: { +#ifdef FUZZING_SNAPSHOT + if (!fuzzing::IPCFuzzController::instance().ObserveIPCMessage( + this, *aMessage)) { + return; + } +#endif + + mListener->OnEventMessage(mName, std::move(aMessage)); + return; + } + } + + // If we got to this point without early returning the message was malformed + // in some way. Report an error. + + NS_WARNING("NodeChannel received a malformed message"); + OnChannelError(); +} + +void NodeChannel::OnChannelConnected(base::ProcessId aPeerPid) { + AssertIOThread(); + + SetOtherPid(aPeerPid); + + // We may need to tell the GeckoChildProcessHost which we were created by that + // the channel has been connected to unblock completing the process launch. + if (mChildProcessHost) { + mChildProcessHost->OnChannelConnected(aPeerPid); + } +} + +void NodeChannel::OnChannelError() { + AssertIOThread(); + + State prev = mState.exchange(State::Closed); + if (prev == State::Closed) { + return; + } + + // Clean up the channel. + mChannel->Close(); + + // Tell our listener about the error. + mListener->OnChannelError(mName); +} + +} // namespace mozilla::ipc diff --git a/ipc/glue/NodeChannel.h b/ipc/glue/NodeChannel.h new file mode 100644 index 0000000000..fb3d297348 --- /dev/null +++ b/ipc/glue/NodeChannel.h @@ -0,0 +1,178 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_NodeChannel_h +#define mozilla_ipc_NodeChannel_h + +#include "mojo/core/ports/node.h" +#include "mojo/core/ports/node_delegate.h" +#include "base/process.h" +#include "chrome/common/ipc_message.h" +#include "chrome/common/ipc_channel.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "nsISupports.h" +#include "nsTHashMap.h" +#include "mozilla/Queue.h" +#include "mozilla/DataMutex.h" +#include "mozilla/UniquePtr.h" + +#ifdef FUZZING_SNAPSHOT +# include "mozilla/fuzzing/IPCFuzzController.h" +#endif + +namespace mozilla::ipc { + +class GeckoChildProcessHost; +class NodeController; + +// Represents a live connection between our Node and a remote process. This +// object acts as an IPC::Channel listener and performs basic processing on +// messages as they're passed between processes. + +class NodeChannel final : public IPC::Channel::Listener { + using NodeName = mojo::core::ports::NodeName; + using PortName = mojo::core::ports::PortName; + +#ifdef FUZZING_SNAPSHOT + // Required because IPCFuzzController calls OnMessageReceived. + friend class mozilla::fuzzing::IPCFuzzController; +#endif + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(NodeChannel, Destroy()) + + struct Introduction { + NodeName mName; + IPC::Channel::ChannelHandle mHandle; + IPC::Channel::Mode mMode; + base::ProcessId mMyPid = base::kInvalidProcessId; + base::ProcessId mOtherPid = base::kInvalidProcessId; + }; + + class Listener { + public: + virtual ~Listener() = default; + + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + virtual void OnEventMessage(const NodeName& aFromNode, + UniquePtr<IPC::Message> aMessage) = 0; + virtual void OnBroadcast(const NodeName& aFromNode, + UniquePtr<IPC::Message> aMessage) = 0; + virtual void OnIntroduce(const NodeName& aFromNode, + Introduction aIntroduction) = 0; + virtual void OnRequestIntroduction(const NodeName& aFromNode, + const NodeName& aName) = 0; + virtual void OnAcceptInvite(const NodeName& aFromNode, + const NodeName& aRealName, + const PortName& aInitialPort) = 0; + virtual void OnChannelError(const NodeName& aFromNode) = 0; + }; + + NodeChannel(const NodeName& aName, UniquePtr<IPC::Channel> aChannel, + Listener* aListener, + base::ProcessId aPid = base::kInvalidProcessId, + GeckoChildProcessHost* aChildProcessHost = nullptr); + + // Send the given message over this peer channel link. May be called from any + // thread. + void SendEventMessage(UniquePtr<IPC::Message> aMessage); + + // Ask the broker process to broadcast this message to every node. May be + // called from any thread. + void Broadcast(UniquePtr<IPC::Message> aMessage); + + // Ask the broker process to introduce this node to another node with the + // given name. May be called from any thread. + void RequestIntroduction(const NodeName& aPeerName); + + // Send an introduction to the target node. May be called from any thread. + void Introduce(Introduction aIntroduction); + + void AcceptInvite(const NodeName& aRealName, const PortName& aInitialPort); + + // The PID of the remote process, once known. May be called from any thread. + base::ProcessId OtherPid() const { return mOtherPid; } + + // Start communicating with the remote process using this NodeChannel. MUST BE + // CALLED FROM THE IO THREAD. + void Start(); + + // Stop communicating with the remote process using this NodeChannel, MUST BE + // CALLED FROM THE IO THREAD. + void Close(); + + // Only ever called by NodeController to update the name after an invite has + // completed. MUST BE CALLED FROM THE IO THREAD. + void SetName(const NodeName& aNewName) { mName = aNewName; } + +#ifdef FUZZING_SNAPSHOT + // MUST BE CALLED FROM THE IO THREAD. + const NodeName& GetName() { return mName; } +#endif + + // Update the known PID for the remote process. MUST BE CALLED FROM THE IO + // THREAD. + void SetOtherPid(base::ProcessId aNewPid); + +#ifdef XP_MACOSX + // Called by the GeckoChildProcessHost to provide the task_t for the peer + // process. MUST BE CALLED FROM THE IO THREAD. + void SetMachTaskPort(task_t aTask); +#endif + + private: + ~NodeChannel(); + + void Destroy(); + void FinalDestroy(); + + void SendMessage(UniquePtr<IPC::Message> aMessage); + + // IPC::Channel::Listener implementation + void OnMessageReceived(UniquePtr<IPC::Message> aMessage) override; + void OnChannelConnected(base::ProcessId aPeerPid) override; + void OnChannelError() override; + + // NOTE: This strong reference will create a reference cycle between the + // listener and the NodeChannel while it is in use. The Listener must clear + // its reference to the NodeChannel to avoid leaks before shutdown. + const RefPtr<Listener> mListener; + + // The apparent name of this Node. This may change during the invite process + // while waiting for the remote node name to be communicated to us. + + // WARNING: This must only be accessed on the IO thread. + NodeName mName; + + // NOTE: This won't change once the connection has been established, but may + // be `-1` until then. This will only be written to on the IO thread, but may + // be read from other threads. + std::atomic<base::ProcessId> mOtherPid; + + // WARNING: Most methods on the IPC::Channel are only safe to call on the IO + // thread, however it is safe to call `Send()` and `IsClosed()` from other + // threads. See IPC::Channel's documentation for details. + const mozilla::UniquePtr<IPC::Channel> mChannel; + + // The state will start out as `State::Active`, and will only transition to + // `State::Closed` on the IO thread. If a Send fails, the state will + // transition to `State::Closing`, and a runnable will be dispatched to the + // I/O thread to notify callbacks. + enum class State { Active, Closing, Closed }; + std::atomic<State> mState = State::Active; + +#ifdef FUZZING_SNAPSHOT + std::atomic<bool> mBlockSendRecv = false; +#endif + + // WARNING: Must only be accessed on the IO thread. + WeakPtr<mozilla::ipc::GeckoChildProcessHost> mChildProcessHost; +}; + +} // namespace mozilla::ipc + +#endif diff --git a/ipc/glue/NodeController.cpp b/ipc/glue/NodeController.cpp new file mode 100644 index 0000000000..532e4fa509 --- /dev/null +++ b/ipc/glue/NodeController.cpp @@ -0,0 +1,869 @@ +/* -*- 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 "mozilla/ipc/NodeController.h" +#include "MainThreadUtils.h" +#include "base/process_util.h" +#include "chrome/common/ipc_message.h" +#include "mojo/core/ports/name.h" +#include "mojo/core/ports/node.h" +#include "mojo/core/ports/port_locker.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/RandomNum.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Assertions.h" +#include "mozilla/ToString.h" +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/mozalloc.h" +#include "nsISerialEventTarget.h" +#include "nsTArray.h" +#include "nsXULAppAPI.h" +#include "nsPrintfCString.h" + +#define PORTS_ALWAYS_OK(expr) MOZ_ALWAYS_TRUE(mojo::core::ports::OK == (expr)) + +namespace mozilla::ipc { + +static StaticRefPtr<NodeController> gNodeController; + +static LazyLogModule gNodeControllerLog{"NodeController"}; + +// Helper logger macro which includes the name of the `this` NodeController in +// the logged messages. +#define NODECONTROLLER_LOG(level_, fmt_, ...) \ + MOZ_LOG(gNodeControllerLog, level_, \ + ("[%s]: " fmt_, ToString(mName).c_str(), ##__VA_ARGS__)) + +// Helper warning macro which both does logger logging and emits NS_WARNING logs +// under debug mode. +#ifdef DEBUG +# define NODECONTROLLER_WARNING(fmt_, ...) \ + do { \ + nsPrintfCString warning("[%s]: " fmt_, ToString(mName).c_str(), \ + ##__VA_ARGS__); \ + NS_WARNING(warning.get()); \ + MOZ_LOG(gNodeControllerLog, LogLevel::Debug, ("%s", warning.get())); \ + } while (0) +#else +# define NODECONTROLLER_WARNING(fmt_, ...) \ + NODECONTROLLER_LOG(LogLevel::Warning, fmt_, ##__VA_ARGS__) +#endif + +NodeController::NodeController(const NodeName& aName) + : mName(aName), mNode(MakeUnique<Node>(aName, this)) {} + +NodeController::~NodeController() { + auto state = mState.Lock(); + MOZ_RELEASE_ASSERT(state->mPeers.IsEmpty(), + "Destroying NodeController before closing all peers"); + MOZ_RELEASE_ASSERT(state->mInvites.IsEmpty(), + "Destroying NodeController before closing all invites"); +}; + +// FIXME: Actually provide some way to create the thing. +/* static */ NodeController* NodeController::GetSingleton() { + MOZ_ASSERT(gNodeController); + return gNodeController; +} + +std::pair<ScopedPort, ScopedPort> NodeController::CreatePortPair() { + PortRef port0, port1; + PORTS_ALWAYS_OK(mNode->CreatePortPair(&port0, &port1)); + return {ScopedPort{std::move(port0), this}, + ScopedPort{std::move(port1), this}}; +} + +auto NodeController::GetPort(const PortName& aName) -> PortRef { + PortRef port; + int rv = mNode->GetPort(aName, &port); + if (NS_WARN_IF(rv != mojo::core::ports::OK)) { + NODECONTROLLER_WARNING("Call to GetPort(%s) Failed", + ToString(aName).c_str()); + return {}; + } + return port; +} + +void NodeController::SetPortObserver(const PortRef& aPort, + PortObserver* aObserver) { + PORTS_ALWAYS_OK(mNode->SetUserData(aPort, aObserver)); +} + +auto NodeController::GetStatus(const PortRef& aPort) -> Maybe<PortStatus> { + PortStatus status{}; + int rv = mNode->GetStatus(aPort, &status); + if (rv != mojo::core::ports::OK) { + return Nothing(); + } + return Some(status); +} + +void NodeController::ClosePort(const PortRef& aPort) { + PORTS_ALWAYS_OK(mNode->ClosePort(aPort)); +} + +bool NodeController::GetMessage(const PortRef& aPort, + UniquePtr<IPC::Message>* aMessage) { + UniquePtr<UserMessageEvent> messageEvent; + int rv = mNode->GetMessage(aPort, &messageEvent, nullptr); + if (rv != mojo::core::ports::OK) { + if (rv == mojo::core::ports::ERROR_PORT_PEER_CLOSED) { + return false; + } + MOZ_CRASH("GetMessage on port in invalid state"); + } + + if (messageEvent) { + UniquePtr<IPC::Message> message = messageEvent->TakeMessage<IPC::Message>(); + + // If our UserMessageEvent has any ports directly attached to it, fetch them + // from our node and attach them to the IPC::Message we extracted. + // + // It's important to only do this if we have nonempty set of ports on the + // event, as we may have never serialized our IPC::Message's ports onto the + // event if it was routed in-process. + if (messageEvent->num_ports() > 0) { + nsTArray<ScopedPort> attachedPorts(messageEvent->num_ports()); + for (size_t i = 0; i < messageEvent->num_ports(); ++i) { + attachedPorts.AppendElement( + ScopedPort{GetPort(messageEvent->ports()[i]), this}); + } + message->SetAttachedPorts(std::move(attachedPorts)); + } + + *aMessage = std::move(message); + } else { + *aMessage = nullptr; + } + return true; +} + +bool NodeController::SendUserMessage(const PortRef& aPort, + UniquePtr<IPC::Message> aMessage) { + auto messageEvent = MakeUnique<UserMessageEvent>(0); + messageEvent->AttachMessage(std::move(aMessage)); + + int rv = mNode->SendUserMessage(aPort, std::move(messageEvent)); + if (rv == mojo::core::ports::OK) { + return true; + } + if (rv == mojo::core::ports::ERROR_PORT_PEER_CLOSED) { + NODECONTROLLER_LOG(LogLevel::Debug, + "Ignoring message to port %s as peer was closed", + ToString(aPort.name()).c_str()); + return true; + } + NODECONTROLLER_WARNING("Failed to send message to port %s", + ToString(aPort.name()).c_str()); + return false; +} + +auto NodeController::SerializeEventMessage(UniquePtr<Event> aEvent, + const NodeName* aRelayTarget, + uint32_t aType) + -> UniquePtr<IPC::Message> { + UniquePtr<IPC::Message> message; + if (aEvent->type() == Event::kUserMessage) { + MOZ_DIAGNOSTIC_ASSERT( + aType == EVENT_MESSAGE_TYPE, + "Can only send a UserMessage in an EVENT_MESSAGE_TYPE"); + message = static_cast<UserMessageEvent*>(aEvent.get()) + ->TakeMessage<IPC::Message>(); + } else { + message = MakeUnique<IPC::Message>(MSG_ROUTING_CONTROL, aType); + } + + message->set_relay(aRelayTarget != nullptr); + + size_t length = aEvent->GetSerializedSize(); + if (aRelayTarget) { + length += sizeof(NodeName); + } + + // Use an intermediate buffer to serialize to avoid potential issues with the + // segmented `IPC::Message` bufferlist. This should be fairly cheap, as the + // majority of events are fairly small. + Vector<char, 256, InfallibleAllocPolicy> buffer; + (void)buffer.initLengthUninitialized(length); + if (aRelayTarget) { + memcpy(buffer.begin(), aRelayTarget, sizeof(NodeName)); + aEvent->Serialize(buffer.begin() + sizeof(NodeName)); + } else { + aEvent->Serialize(buffer.begin()); + } + + message->WriteFooter(buffer.begin(), buffer.length()); + message->set_event_footer_size(buffer.length()); + +#ifdef DEBUG + // Debug-assert that we can read the same data back out of the buffer. + MOZ_ASSERT(message->event_footer_size() == length); + Vector<char, 256, InfallibleAllocPolicy> buffer2; + (void)buffer2.initLengthUninitialized(message->event_footer_size()); + MOZ_ASSERT(message->ReadFooter(buffer2.begin(), buffer2.length(), + /* truncate */ false)); + MOZ_ASSERT(!memcmp(buffer2.begin(), buffer.begin(), buffer.length())); +#endif + + return message; +} + +auto NodeController::DeserializeEventMessage(UniquePtr<IPC::Message> aMessage, + NodeName* aRelayTarget) + -> UniquePtr<Event> { + if (aMessage->is_relay() && !aRelayTarget) { + NODECONTROLLER_WARNING("Unexpected relay message '%s'", aMessage->name()); + return nullptr; + } + + Vector<char, 256, InfallibleAllocPolicy> buffer; + (void)buffer.initLengthUninitialized(aMessage->event_footer_size()); + // Truncate the message when reading the footer, so that the extra footer data + // is no longer present in the message. This allows future code to eventually + // send the same `IPC::Message` to another process. + if (!aMessage->ReadFooter(buffer.begin(), buffer.length(), + /* truncate */ true)) { + NODECONTROLLER_WARNING("Call to ReadFooter for message '%s' Failed", + aMessage->name()); + return nullptr; + } + aMessage->set_event_footer_size(0); + + UniquePtr<Event> event; + if (aRelayTarget) { + MOZ_ASSERT(aMessage->is_relay()); + if (buffer.length() < sizeof(NodeName)) { + NODECONTROLLER_WARNING( + "Insufficient space in message footer for message '%s'", + aMessage->name()); + return nullptr; + } + memcpy(aRelayTarget, buffer.begin(), sizeof(NodeName)); + event = Event::Deserialize(buffer.begin() + sizeof(NodeName), + buffer.length() - sizeof(NodeName)); + } else { + event = Event::Deserialize(buffer.begin(), buffer.length()); + } + + if (!event) { + NODECONTROLLER_WARNING("Call to Event::Deserialize for message '%s' Failed", + aMessage->name()); + return nullptr; + } + + if (event->type() == Event::kUserMessage) { + static_cast<UserMessageEvent*>(event.get()) + ->AttachMessage(std::move(aMessage)); + } + return event; +} + +already_AddRefed<NodeChannel> NodeController::GetNodeChannel( + const NodeName& aName) { + auto state = mState.Lock(); + return do_AddRef(state->mPeers.Get(aName)); +} + +void NodeController::DropPeer(NodeName aNodeName) { + AssertIOThread(); + +#ifdef FUZZING_SNAPSHOT + MOZ_FUZZING_IPC_DROP_PEER("NodeController::DropPeer"); +#endif + + Invite invite; + RefPtr<NodeChannel> channel; + nsTArray<PortRef> pendingMerges; + { + auto state = mState.Lock(); + state->mPeers.Remove(aNodeName, &channel); + state->mPendingMessages.Remove(aNodeName); + state->mInvites.Remove(aNodeName, &invite); + state->mPendingMerges.Remove(aNodeName, &pendingMerges); + } + + NODECONTROLLER_LOG(LogLevel::Info, "Dropping Peer %s (pid: %" PRIPID ")", + ToString(aNodeName).c_str(), + channel ? channel->OtherPid() : base::kInvalidProcessId); + + if (channel) { + channel->Close(); + } + if (invite.mChannel) { + invite.mChannel->Close(); + } + if (invite.mToMerge.is_valid()) { + // Ignore any possible errors here. + (void)mNode->ClosePort(invite.mToMerge); + } + for (auto& port : pendingMerges) { + // Ignore any possible errors here. + (void)mNode->ClosePort(port); + } + mNode->LostConnectionToNode(aNodeName); +} + +void NodeController::ForwardEvent(const NodeName& aNode, + UniquePtr<Event> aEvent) { + if (aNode == mName) { + (void)mNode->AcceptEvent(mName, std::move(aEvent)); + } else { + // On Windows and macOS, messages holding HANDLEs or mach ports must be + // relayed via the broker process so it can transfer ownership. + bool needsRelay = false; +#if defined(XP_WIN) || defined(XP_MACOSX) + if (!IsBroker() && aNode != kBrokerNodeName && + aEvent->type() == Event::kUserMessage) { + auto* userEvent = static_cast<UserMessageEvent*>(aEvent.get()); + needsRelay = + userEvent->HasMessage() && + userEvent->GetMessage<IPC::Message>()->num_relayed_attachments() > 0; + } +#endif + + UniquePtr<IPC::Message> message = + SerializeEventMessage(std::move(aEvent), needsRelay ? &aNode : nullptr); + MOZ_ASSERT(message->is_relay() == needsRelay, + "Message relay status set incorrectly"); + + RefPtr<NodeChannel> peer; + RefPtr<NodeChannel> broker; + bool needsIntroduction = false; + { + auto state = mState.Lock(); + + // Check if we know this peer. If we don't, we'll need to request an + // introduction. + peer = state->mPeers.Get(aNode); + if (!peer || needsRelay) { + if (IsBroker()) { + NODECONTROLLER_WARNING("Ignoring message '%s' to unknown peer %s", + message->name(), ToString(aNode).c_str()); + return; + } + + broker = state->mPeers.Get(kBrokerNodeName); + if (!broker) { + NODECONTROLLER_WARNING( + "Ignoring message '%s' to peer %s due to a missing broker", + message->name(), ToString(aNode).c_str()); + return; + } + + if (!needsRelay) { + auto& queue = + state->mPendingMessages.LookupOrInsertWith(aNode, [&]() { + needsIntroduction = true; + return Queue<UniquePtr<IPC::Message>, 64>{}; + }); + queue.Push(std::move(message)); + } + } + } + + MOZ_ASSERT(!needsIntroduction || !needsRelay, + "Only one of the two should ever be set"); + + if (needsRelay) { + NODECONTROLLER_LOG(LogLevel::Info, + "Relaying message '%s' for peer %s due to %" PRIu32 + " attachments", + message->name(), ToString(aNode).c_str(), + message->num_relayed_attachments()); + MOZ_ASSERT(message->num_relayed_attachments() > 0 && broker); + broker->SendEventMessage(std::move(message)); + } else if (needsIntroduction) { + MOZ_ASSERT(broker); + broker->RequestIntroduction(aNode); + } else if (peer) { + peer->SendEventMessage(std::move(message)); + } + } +} + +void NodeController::BroadcastEvent(UniquePtr<Event> aEvent) { + UniquePtr<IPC::Message> message = + SerializeEventMessage(std::move(aEvent), nullptr, BROADCAST_MESSAGE_TYPE); + + if (IsBroker()) { + OnBroadcast(mName, std::move(message)); + } else if (RefPtr<NodeChannel> broker = GetNodeChannel(kBrokerNodeName)) { + broker->Broadcast(std::move(message)); + } else { + NODECONTROLLER_WARNING( + "Trying to broadcast event, but no connection to broker"); + } +} + +void NodeController::PortStatusChanged(const PortRef& aPortRef) { + RefPtr<UserData> userData; + int rv = mNode->GetUserData(aPortRef, &userData); + if (rv != mojo::core::ports::OK) { + NODECONTROLLER_WARNING("GetUserData call for port '%s' failed", + ToString(aPortRef.name()).c_str()); + return; + } + if (userData) { + // All instances of `UserData` attached to ports in this node must be of + // type `PortObserver`, so we can call `OnPortStatusChanged` directly on + // them. + static_cast<PortObserver*>(userData.get())->OnPortStatusChanged(); + } +} + +void NodeController::OnEventMessage(const NodeName& aFromNode, + UniquePtr<IPC::Message> aMessage) { + AssertIOThread(); + + bool isRelay = aMessage->is_relay(); + if (isRelay && aMessage->num_relayed_attachments() == 0) { + NODECONTROLLER_WARNING( + "Invalid relay message without relayed attachments from peer %s!", + ToString(aFromNode).c_str()); + DropPeer(aFromNode); + return; + } + + NodeName relayTarget; + UniquePtr<Event> event = DeserializeEventMessage( + std::move(aMessage), isRelay ? &relayTarget : nullptr); + if (!event) { + NODECONTROLLER_WARNING("Invalid EventMessage from peer %s!", + ToString(aFromNode).c_str()); + DropPeer(aFromNode); + return; + } + + NodeName fromNode = aFromNode; +#if defined(XP_WIN) || defined(XP_MACOSX) + if (isRelay) { + if (event->type() != Event::kUserMessage) { + NODECONTROLLER_WARNING( + "Unexpected relay of non-UserMessage event from peer %s!", + ToString(aFromNode).c_str()); + DropPeer(aFromNode); + return; + } + + // If we're the broker, then we'll need to forward this message on to the + // true recipient. To do this, we re-serialize the message, passing along + // the original source node, and send it to the final node. + if (IsBroker()) { + UniquePtr<IPC::Message> message = + SerializeEventMessage(std::move(event), &aFromNode); + if (!message) { + NODECONTROLLER_WARNING( + "Relaying EventMessage from peer %s failed to re-serialize!", + ToString(aFromNode).c_str()); + DropPeer(aFromNode); + return; + } + MOZ_ASSERT(message->is_relay(), "Message stopped being a relay message?"); + MOZ_ASSERT(message->num_relayed_attachments() > 0, + "Message doesn't have relayed attachments?"); + + NODECONTROLLER_LOG( + LogLevel::Info, + "Relaying message '%s' from peer %s to peer %s (%" PRIu32 + " attachments)", + message->name(), ToString(aFromNode).c_str(), + ToString(relayTarget).c_str(), message->num_relayed_attachments()); + + RefPtr<NodeChannel> peer; + { + auto state = mState.Lock(); + peer = state->mPeers.Get(relayTarget); + } + if (!peer) { + NODECONTROLLER_WARNING( + "Dropping relayed message from %s to unknown peer %s", + ToString(aFromNode).c_str(), ToString(relayTarget).c_str()); + return; + } + + peer->SendEventMessage(std::move(message)); + return; + } + + // Otherwise, we're the final recipient, so we can continue & process the + // message as usual. + if (aFromNode != kBrokerNodeName) { + NODECONTROLLER_WARNING( + "Unexpected relayed EventMessage from non-broker peer %s!", + ToString(aFromNode).c_str()); + DropPeer(aFromNode); + return; + } + fromNode = relayTarget; + + NODECONTROLLER_LOG(LogLevel::Info, "Got relayed message from peer %s", + ToString(fromNode).c_str()); + } +#endif + + // If we're getting a requested port merge from another process, check to make + // sure that we're expecting the request, and record that the merge has + // arrived so we don't try to close the port on error. + if (event->type() == Event::kMergePort) { + // Check that the target port for the merge actually exists. + auto targetPort = GetPort(event->port_name()); + if (!targetPort.is_valid()) { + NODECONTROLLER_WARNING( + "Unexpected MergePortEvent from peer %s for unknown port %s", + ToString(fromNode).c_str(), ToString(event->port_name()).c_str()); + DropPeer(fromNode); + return; + } + + // Check if `targetPort` is in our pending merges entry for the given source + // node. If this makes the `mPendingMerges` entry empty, remove it. + bool expectingMerge = [&] { + auto state = mState.Lock(); + auto pendingMerges = state->mPendingMerges.Lookup(aFromNode); + if (!pendingMerges) { + return false; + } + size_t removed = pendingMerges->RemoveElementsBy( + [&](auto& port) { return port.name() == targetPort.name(); }); + if (removed != 0 && pendingMerges->IsEmpty()) { + pendingMerges.Remove(); + } + return removed != 0; + }(); + + if (!expectingMerge) { + NODECONTROLLER_WARNING( + "Unexpected MergePortEvent from peer %s for port %s", + ToString(fromNode).c_str(), ToString(event->port_name()).c_str()); + DropPeer(fromNode); + return; + } + } + + (void)mNode->AcceptEvent(fromNode, std::move(event)); +} + +void NodeController::OnBroadcast(const NodeName& aFromNode, + UniquePtr<IPC::Message> aMessage) { + MOZ_DIAGNOSTIC_ASSERT(aMessage->type() == BROADCAST_MESSAGE_TYPE); + + // NOTE: This method may be called off of the IO thread by the + // `BroadcastEvent` node callback. + if (!IsBroker()) { + NODECONTROLLER_WARNING("Broadcast request received by non-broker node"); + return; + } + + UniquePtr<Event> event = DeserializeEventMessage(std::move(aMessage)); + if (!event) { + NODECONTROLLER_WARNING("Invalid broadcast message from peer"); + return; + } + + nsTArray<RefPtr<NodeChannel>> peers; + { + auto state = mState.Lock(); + peers.SetCapacity(state->mPeers.Count()); + for (const auto& peer : state->mPeers.Values()) { + peers.AppendElement(peer); + } + } + for (const auto& peer : peers) { + // NOTE: This `clone` operation is only supported for a limited number of + // message types by the ports API, which provides some extra security by + // only allowing those specific types of messages to be broadcasted. + // Messages which don't support `CloneForBroadcast` cannot be broadcast, and + // the ports library will not attempt to broadcast them. + auto clone = event->CloneForBroadcast(); + if (!clone) { + NODECONTROLLER_WARNING("Attempt to broadcast unsupported message"); + break; + } + + peer->SendEventMessage(SerializeEventMessage(std::move(clone))); + } +} + +void NodeController::OnIntroduce(const NodeName& aFromNode, + NodeChannel::Introduction aIntroduction) { + AssertIOThread(); + + if (aFromNode != kBrokerNodeName) { + NODECONTROLLER_WARNING("Introduction received from non-broker node"); + DropPeer(aFromNode); + return; + } + + MOZ_ASSERT(aIntroduction.mMyPid == base::GetCurrentProcId(), + "We're the wrong process to receive this?"); + + if (!aIntroduction.mHandle) { + NODECONTROLLER_WARNING("Could not be introduced to peer %s", + ToString(aIntroduction.mName).c_str()); + mNode->LostConnectionToNode(aIntroduction.mName); + + auto state = mState.Lock(); + state->mPendingMessages.Remove(aIntroduction.mName); + return; + } + + auto channel = + MakeUnique<IPC::Channel>(std::move(aIntroduction.mHandle), + aIntroduction.mMode, aIntroduction.mOtherPid); + auto nodeChannel = MakeRefPtr<NodeChannel>( + aIntroduction.mName, std::move(channel), this, aIntroduction.mOtherPid); + + { + auto state = mState.Lock(); + bool isNew = false; + state->mPeers.LookupOrInsertWith(aIntroduction.mName, [&]() { + isNew = true; + return nodeChannel; + }); + if (!isNew) { + // We got a duplicate introduction. This can happen during normal + // execution if both sides request an introduction at the same time. We + // can just ignore the second one, as they'll arrive in the same order in + // both processes. + nodeChannel->Close(); + return; + } + + // Deliver any pending messages, then remove the entry from our table. We do + // this while `mState` is still held to ensure that these messages are + // all sent before another thread can observe the newly created channel. + // As the channel hasn't been `Connect()`-ed yet, this will only queue the + // messages up to be sent, so is OK to do with the mutex held. These + // messages will be processed to be sent during `Start()` below, which is + // performed outside of the lock. + if (auto pending = state->mPendingMessages.Lookup(aIntroduction.mName)) { + while (!pending->IsEmpty()) { + nodeChannel->SendEventMessage(pending->Pop()); + } + pending.Remove(); + } + } + + // NodeChannel::Start must be called with the lock not held, as it may lead to + // callbacks being made into `OnChannelError` or `OnMessageReceived`, which + // will attempt to re-acquire our lock. + nodeChannel->Start(); +} + +void NodeController::OnRequestIntroduction(const NodeName& aFromNode, + const NodeName& aName) { + AssertIOThread(); + if (NS_WARN_IF(!IsBroker())) { + return; + } + + RefPtr<NodeChannel> peerA = GetNodeChannel(aFromNode); + if (!peerA || aName == mojo::core::ports::kInvalidNodeName) { + NODECONTROLLER_WARNING("Invalid OnRequestIntroduction message from node %s", + ToString(aFromNode).c_str()); + DropPeer(aFromNode); + return; + } + + RefPtr<NodeChannel> peerB = GetNodeChannel(aName); + IPC::Channel::ChannelHandle handleA, handleB; + if (!peerB || !IPC::Channel::CreateRawPipe(&handleA, &handleB)) { + NODECONTROLLER_WARNING( + "Rejecting introduction request from '%s' for unknown peer '%s'", + ToString(aFromNode).c_str(), ToString(aName).c_str()); + + // We don't know this peer, or ran into issues creating the descriptor! Send + // an invalid introduction to content to clean up any pending outbound + // messages. + NodeChannel::Introduction intro{aName, nullptr, IPC::Channel::MODE_SERVER, + peerA->OtherPid(), base::kInvalidProcessId}; + peerA->Introduce(std::move(intro)); + return; + } + + NodeChannel::Introduction introA{aName, std::move(handleA), + IPC::Channel::MODE_SERVER, peerA->OtherPid(), + peerB->OtherPid()}; + NodeChannel::Introduction introB{aFromNode, std::move(handleB), + IPC::Channel::MODE_CLIENT, peerB->OtherPid(), + peerA->OtherPid()}; + peerA->Introduce(std::move(introA)); + peerB->Introduce(std::move(introB)); +} + +void NodeController::OnAcceptInvite(const NodeName& aFromNode, + const NodeName& aRealName, + const PortName& aInitialPort) { + AssertIOThread(); + if (!IsBroker()) { + NODECONTROLLER_WARNING("Ignoring AcceptInvite message as non-broker"); + return; + } + + if (aRealName == mojo::core::ports::kInvalidNodeName || + aInitialPort == mojo::core::ports::kInvalidPortName) { + NODECONTROLLER_WARNING("Invalid name in AcceptInvite message"); + DropPeer(aFromNode); + return; + } + + bool inserted = false; + Invite invite; + { + auto state = mState.Lock(); + MOZ_ASSERT(state->mPendingMessages.IsEmpty(), + "Shouldn't have pending messages in broker"); + + // Try to remove the source node from our invites list and insert it into + // our peers map under the new name. + if (state->mInvites.Remove(aFromNode, &invite)) { + MOZ_ASSERT(invite.mChannel && invite.mToMerge.is_valid()); + state->mPeers.LookupOrInsertWith(aRealName, [&]() { + inserted = true; + return invite.mChannel; + }); + } + } + if (!inserted) { + NODECONTROLLER_WARNING("Invalid AcceptInvite message from node %s", + ToString(aFromNode).c_str()); + DropPeer(aFromNode); + return; + } + + // Update the name of the node. This field is only accessed from the IO + // thread, so it's safe to update it without a lock held. + invite.mChannel->SetName(aRealName); + + // Start the port merge to allow our existing initial port to begin + // communicating with the remote port. + PORTS_ALWAYS_OK(mNode->MergePorts(invite.mToMerge, aRealName, aInitialPort)); +} + +void NodeController::OnChannelError(const NodeName& aFromNode) { + AssertIOThread(); + DropPeer(aFromNode); +} + +static mojo::core::ports::NodeName RandomNodeName() { + return {RandomUint64OrDie(), RandomUint64OrDie()}; +} + +std::tuple<ScopedPort, RefPtr<NodeChannel>> NodeController::InviteChildProcess( + UniquePtr<IPC::Channel> aChannel, + GeckoChildProcessHost* aChildProcessHost) { + MOZ_ASSERT(IsBroker()); + AssertIOThread(); + + // Create the peer with a randomly generated name, and store it in `mInvites`. + // This channel and name will be used for communication with the node until it + // sends us its' real name in an `AcceptInvite` message. + auto ports = CreatePortPair(); + auto inviteName = RandomNodeName(); + auto nodeChannel = + MakeRefPtr<NodeChannel>(inviteName, std::move(aChannel), this, + base::kInvalidProcessId, aChildProcessHost); + { + auto state = mState.Lock(); + MOZ_DIAGNOSTIC_ASSERT(!state->mPeers.Contains(inviteName), + "UUID conflict?"); + MOZ_DIAGNOSTIC_ASSERT(!state->mInvites.Contains(inviteName), + "UUID conflict?"); + state->mInvites.InsertOrUpdate(inviteName, + Invite{nodeChannel, ports.second.Release()}); + } + + nodeChannel->Start(); + return std::tuple{std::move(ports.first), std::move(nodeChannel)}; +} + +void NodeController::InitBrokerProcess() { + AssertIOThread(); + MOZ_ASSERT(!gNodeController); + gNodeController = new NodeController(kBrokerNodeName); +} + +ScopedPort NodeController::InitChildProcess(UniquePtr<IPC::Channel> aChannel, + base::ProcessId aParentPid) { + AssertIOThread(); + MOZ_ASSERT(!gNodeController); + + auto nodeName = RandomNodeName(); + gNodeController = new NodeController(nodeName); + + auto ports = gNodeController->CreatePortPair(); + PortRef toMerge = ports.second.Release(); + + // Mark the port as expecting a pending merge. This is a duplicate of the + // information tracked by `mPendingMerges`, and was added by upstream + // chromium. + // See https://chromium-review.googlesource.com/c/chromium/src/+/3289065 + { + mojo::core::ports::SinglePortLocker locker(&toMerge); + locker.port()->pending_merge_peer = true; + } + + auto nodeChannel = MakeRefPtr<NodeChannel>( + kBrokerNodeName, std::move(aChannel), gNodeController, aParentPid); + { + auto state = gNodeController->mState.Lock(); + MOZ_DIAGNOSTIC_ASSERT(!state->mPeers.Contains(kBrokerNodeName)); + state->mPeers.InsertOrUpdate(kBrokerNodeName, nodeChannel); + MOZ_DIAGNOSTIC_ASSERT(!state->mPendingMerges.Contains(kBrokerNodeName)); + state->mPendingMerges.LookupOrInsert(kBrokerNodeName) + .AppendElement(toMerge); + } + + nodeChannel->Start(); + nodeChannel->AcceptInvite(nodeName, toMerge.name()); + return std::move(ports.first); +} + +void NodeController::CleanUp() { + AssertIOThread(); + MOZ_ASSERT(gNodeController); + + RefPtr<NodeController> nodeController = gNodeController; + gNodeController = nullptr; + + // Collect all objects from our state which need to be cleaned up. + nsTArray<NodeName> lostConnections; + nsTArray<RefPtr<NodeChannel>> channelsToClose; + nsTArray<PortRef> portsToClose; + { + auto state = nodeController->mState.Lock(); + for (const auto& chan : state->mPeers) { + lostConnections.AppendElement(chan.GetKey()); + channelsToClose.AppendElement(chan.GetData()); + } + for (const auto& invite : state->mInvites.Values()) { + channelsToClose.AppendElement(invite.mChannel); + portsToClose.AppendElement(invite.mToMerge); + } + for (const auto& pendingPorts : state->mPendingMerges.Values()) { + portsToClose.AppendElements(pendingPorts); + } + state->mPeers.Clear(); + state->mPendingMessages.Clear(); + state->mInvites.Clear(); + state->mPendingMerges.Clear(); + } + for (auto& nodeChannel : channelsToClose) { + nodeChannel->Close(); + } + for (auto& port : portsToClose) { + nodeController->mNode->ClosePort(port); + } + for (auto& name : lostConnections) { + nodeController->mNode->LostConnectionToNode(name); + } +} + +#undef NODECONTROLLER_LOG +#undef NODECONTROLLER_WARNING + +} // namespace mozilla::ipc diff --git a/ipc/glue/NodeController.h b/ipc/glue/NodeController.h new file mode 100644 index 0000000000..5356b85084 --- /dev/null +++ b/ipc/glue/NodeController.h @@ -0,0 +1,176 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_NodeController_h +#define mozilla_ipc_NodeController_h + +#include "mojo/core/ports/event.h" +#include "mojo/core/ports/name.h" +#include "mojo/core/ports/node.h" +#include "mojo/core/ports/node_delegate.h" +#include "chrome/common/ipc_message.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "nsTHashMap.h" +#include "mozilla/Queue.h" +#include "mozilla/DataMutex.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/ipc/NodeChannel.h" + +namespace mozilla::ipc { + +class GeckoChildProcessHost; + +class NodeController final : public mojo::core::ports::NodeDelegate, + public NodeChannel::Listener { + using NodeName = mojo::core::ports::NodeName; + using PortName = mojo::core::ports::PortName; + using PortRef = mojo::core::ports::PortRef; + using Event = mojo::core::ports::Event; + using Node = mojo::core::ports::Node; + using UserData = mojo::core::ports::UserData; + using PortStatus = mojo::core::ports::PortStatus; + using UserMessageEvent = mojo::core::ports::UserMessageEvent; + using UserMessage = mojo::core::ports::UserMessage; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NodeController, override) + + // Return the global singleton instance. The returned value is only valid + // while the IO thread is alive. + static NodeController* GetSingleton(); + + class PortObserver : public UserData { + public: + virtual void OnPortStatusChanged() = 0; + + protected: + ~PortObserver() override = default; + }; + + // NOTE: For now there will always be a single broker process, and all + // processes in the graph need to be able to talk to it (the parent process). + // Give it a fixed node name for now to simplify things. + // + // If we ever decide to have multiple node networks intercommunicating (e.g. + // multiple instances or background services), we may need to change this. + static constexpr NodeName kBrokerNodeName{0x1, 0x1}; + + bool IsBroker() const { return mName == kBrokerNodeName; } + + // Mint a new connected pair of ports within the current process. + std::pair<ScopedPort, ScopedPort> CreatePortPair(); + + // Get a reference to the port with the given name. Returns an invalid + // `PortRef` if the name wasn't found. + PortRef GetPort(const PortName& aName); + + // Set the observer for the given port. This observer will be notified when + // the status of the port changes. + void SetPortObserver(const PortRef& aPort, PortObserver* aObserver); + + // See `mojo::core::ports::Node::GetStatus` + Maybe<PortStatus> GetStatus(const PortRef& aPort); + + // See `mojo::core::ports::Node::ClosePort` + void ClosePort(const PortRef& aPort); + + // Send a message to the the port's connected peer. + bool SendUserMessage(const PortRef& aPort, UniquePtr<IPC::Message> aMessage); + + // Get the next message from the port's message queue. + // Will set `*aMessage` to the found message, or `nullptr`. + // Returns `false` and sets `*aMessage` to `nullptr` if no further messages + // will be delivered to this port as its peer has been closed. + bool GetMessage(const PortRef& aPort, UniquePtr<IPC::Message>* aMessage); + + // Called in the broker process from GeckoChildProcessHost to introduce a new + // child process into the network. Returns a `PortRef` which can be used to + // communicate with the `PortRef` returned from `InitChildProcess`, and a + // reference to the `NodeChannel` created for the new process. The port can + // immediately have messages sent to it. + std::tuple<ScopedPort, RefPtr<NodeChannel>> InviteChildProcess( + UniquePtr<IPC::Channel> aChannel, + GeckoChildProcessHost* aChildProcessHost); + + // Called as the IO thread is started in the parent process. + static void InitBrokerProcess(); + + // Called as the IO thread is started in a child process. + static ScopedPort InitChildProcess(UniquePtr<IPC::Channel> aChannel, + base::ProcessId aParentPid); + + // Called when the IO thread is torn down. + static void CleanUp(); + + private: + explicit NodeController(const NodeName& aName); + ~NodeController(); + + UniquePtr<IPC::Message> SerializeEventMessage( + UniquePtr<Event> aEvent, const NodeName* aRelayTarget = nullptr, + uint32_t aType = EVENT_MESSAGE_TYPE); + UniquePtr<Event> DeserializeEventMessage(UniquePtr<IPC::Message> aMessage, + NodeName* aRelayTarget = nullptr); + + // Get the `NodeChannel` for the named node. + already_AddRefed<NodeChannel> GetNodeChannel(const NodeName& aName); + + // Stop communicating with this peer. Must be called on the IO thread. + void DropPeer(NodeName aNodeName); + + // Message Handlers + void OnEventMessage(const NodeName& aFromNode, + UniquePtr<IPC::Message> aMessage) override; + void OnBroadcast(const NodeName& aFromNode, + UniquePtr<IPC::Message> aMessage) override; + void OnIntroduce(const NodeName& aFromNode, + NodeChannel::Introduction aIntroduction) override; + void OnRequestIntroduction(const NodeName& aFromNode, + const NodeName& aName) override; + void OnAcceptInvite(const NodeName& aFromNode, const NodeName& aRealName, + const PortName& aInitialPort) override; + void OnChannelError(const NodeName& aFromNode) override; + + // NodeDelegate Implementation + void ForwardEvent(const NodeName& aNode, UniquePtr<Event> aEvent) override; + void BroadcastEvent(UniquePtr<Event> aEvent) override; + void PortStatusChanged(const PortRef& aPortRef) override; + + const NodeName mName; + const UniquePtr<Node> mNode; + + template <class T> + using NodeMap = nsTHashMap<NodeNameHashKey, T>; + + struct Invite { + // The channel which is being invited. This will have a temporary name until + // the invite is completed. + RefPtr<NodeChannel> mChannel; + // The port which will be merged with the port information from the new + // child process when recieved. + PortRef mToMerge; + }; + + struct State { + // Channels for connecting to all known peers. + NodeMap<RefPtr<NodeChannel>> mPeers; + + // Messages which are queued for peers which we been introduced to yet. + NodeMap<Queue<UniquePtr<IPC::Message>, 64>> mPendingMessages; + + // Connections for peers being invited to the network. + NodeMap<Invite> mInvites; + + // Ports which are waiting to be merged by a particular peer node. + NodeMap<nsTArray<PortRef>> mPendingMerges; + }; + + DataMutex<State> mState{"NodeController::mState"}; +}; + +} // namespace mozilla::ipc + +#endif diff --git a/ipc/glue/PBackground.ipdl b/ipc/glue/PBackground.ipdl new file mode 100644 index 0000000000..94b0079ff7 --- /dev/null +++ b/ipc/glue/PBackground.ipdl @@ -0,0 +1,296 @@ +/* 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 protocol PBackgroundDataBridge; +include protocol PBackgroundIDBFactory; +include protocol PBackgroundIndexedDBUtils; +include protocol PBackgroundSDBConnection; +include protocol PBackgroundLSDatabase; +include protocol PBackgroundLSObserver; +include protocol PBackgroundLSRequest; +include protocol PBackgroundLSSimpleRequest; +include protocol PBackgroundLocalStorageCache; +include protocol PBackgroundSessionStorageManager; +include protocol PBackgroundSessionStorageService; +include protocol PBackgroundStorage; +include protocol PBackgroundTest; +include protocol PBroadcastChannel; +include protocol PCache; +include protocol PCacheStorage; +include protocol PCacheStreamControl; +include protocol PClientManager; +include protocol PEndpointForReport; +include protocol PFileSystemManager; +include protocol PFileSystemRequest; +include protocol PGamepadEventChannel; +include protocol PGamepadTestChannel; +include protocol PHttpBackgroundChannel; +include protocol PIdleScheduler; +include protocol PRemoteWorker; +include protocol PRemoteWorkerController; +include protocol PRemoteWorkerService; +include protocol PSharedWorker; +include protocol PTemporaryIPCBlob; +include protocol PFileCreator; +include protocol PMessagePort; +include protocol PCameras; +include protocol PLockManager; +include protocol PMIDIManager; +include protocol PMIDIPort; +include protocol PQuota; +include protocol PServiceWorker; +include protocol PServiceWorkerContainer; +include protocol PServiceWorkerManager; +include protocol PServiceWorkerRegistration; +include protocol PWebAuthnTransaction; +include protocol PUDPSocket; +include protocol PVsync; +include protocol PRemoteDecoderManager; +include protocol PWebTransport; +include protocol PFetch; + +include ClientIPCTypes; +include DOMTypes; +include IPCBlob; +include IPCServiceWorkerDescriptor; +include IPCServiceWorkerRegistrationDescriptor; +include PBackgroundLSSharedTypes; +include PBackgroundSharedTypes; +include PBackgroundIDBSharedTypes; +include PFileSystemParams; +include ProtocolTypes; +include RemoteWorkerTypes; +include MIDITypes; + +include "mozilla/dom/cache/IPCUtils.h"; +include "mozilla/dom/quota/SerializationHelpers.h"; +include "mozilla/dom/PermissionMessageUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; + +using mozilla::dom::cache::Namespace + from "mozilla/dom/cache/Types.h"; + +using class mozilla::dom::SSCacheCopy from "mozilla/dom/PBackgroundSessionStorageCache.h"; + +using mozilla::RemoteDecodeIn from "mozilla/RemoteDecoderManagerChild.h"; + +using mozilla::camera::CamerasAccessStatus from "mozilla/media/CamerasTypes.h"; + +namespace mozilla { +namespace ipc { + +[NeedsOtherPid, ChildImpl=virtual, ParentImpl=virtual, ChildProc=anydom] +sync protocol PBackground +{ + manages PBackgroundIDBFactory; + manages PBackgroundIndexedDBUtils; + manages PBackgroundSDBConnection; + manages PBackgroundLSDatabase; + manages PBackgroundLSObserver; + manages PBackgroundLSRequest; + manages PBackgroundLSSimpleRequest; + manages PBackgroundLocalStorageCache; + manages PBackgroundSessionStorageManager; + manages PBackgroundSessionStorageService; + manages PBackgroundStorage; + manages PBackgroundTest; + manages PBroadcastChannel; + manages PCache; + manages PCacheStorage; + manages PCacheStreamControl; + manages PClientManager; + manages PEndpointForReport; + manages PFileSystemRequest; + manages PGamepadEventChannel; + manages PGamepadTestChannel; + manages PHttpBackgroundChannel; + manages PIdleScheduler; + manages PLockManager; + manages PRemoteWorker; + manages PRemoteWorkerController; + manages PRemoteWorkerService; + manages PSharedWorker; + manages PTemporaryIPCBlob; + manages PFileCreator; + manages PMessagePort; + manages PCameras; + manages PQuota; + manages PServiceWorker; + manages PServiceWorkerContainer; + manages PServiceWorkerManager; + manages PServiceWorkerRegistration; + manages PWebAuthnTransaction; + manages PUDPSocket; + manages PVsync; + manages PFetch; + +parent: + // Only called at startup during mochitests to check the basic infrastructure. + async PBackgroundTest(nsCString testArg); + + async PBackgroundIDBFactory(LoggingInfo loggingInfo); + + async PBackgroundIndexedDBUtils(); + + // Use only for testing! + async FlushPendingFileDeletions(); + + async PBackgroundSDBConnection(PersistenceType persistenceType, + PrincipalInfo principalInfo); + + async PBackgroundLSDatabase(PrincipalInfo principalInfo, + uint32_t privateBrowsingId, + uint64_t datastoreId); + + async PBackgroundLSObserver(uint64_t observerId); + + /** + * Issue an asynchronous request that will be used in a synchronous fashion + * through complex machinations described in `PBackgroundLSRequest.ipdl` and + * `LSObject.h`. + */ + async PBackgroundLSRequest(LSRequestParams params); + + /** + * Issues a simple, non-cancelable asynchronous request that's used in an + * asynchronous fashion by callers. (LSRequest is not simple because it used + * in a synchronous fashion which leads to complexities regarding cancelation, + * see `PBackgroundLSRequest.ipdl` for details.) + */ + async PBackgroundLSSimpleRequest(LSSimpleRequestParams params); + + async PBackgroundLocalStorageCache(PrincipalInfo principalInfo, + nsCString originKey, + uint32_t privateBrowsingId); + + async PBackgroundSessionStorageManager(uint64_t aTopContextId); + + async PBackgroundSessionStorageService(); + + async PBackgroundStorage(nsString profilePath, uint32_t privateBrowsingId); + + /** + * Finish the setup of a new PFileSystemManager top level protocol. + */ + async CreateFileSystemManagerParent( + PrincipalInfo principalInfo, + Endpoint<PFileSystemManagerParent> aParentEndpoint) + returns(nsresult rv); + + /** + * Finish the setup of a new PWebTransport top level protocol. + */ + async CreateWebTransportParent( + nsString aURL, + nullable nsIPrincipal aPrincipal, + IPCClientInfo? aClientInfo, + bool aDedicated, + bool aRequireUnreliable, + uint32_t aCongestionControl, + WebTransportHash[] aServerCertHashes, + Endpoint<PWebTransportParent> aParentEndpoint) + returns(nsresult rv, uint8_t aReliability); // Actually WebTransportReliabityMode enum + + async PVsync(); + + async PCameras(); + + async PUDPSocket(PrincipalInfo? pInfo, nsCString filter); + async PBroadcastChannel(PrincipalInfo pInfo, nsCString origin, nsString channel); + + async PServiceWorkerManager(); + + async ShutdownServiceWorkerRegistrar(); + + async PCacheStorage(Namespace aNamespace, PrincipalInfo aPrincipalInfo); + + async PMessagePort(nsID uuid, nsID destinationUuid, uint32_t sequenceId); + + async MessagePortForceClose(nsID uuid, nsID destinationUuid, uint32_t sequenceId); + + async PQuota(); + + async ShutdownQuotaManager(); + + async ShutdownBackgroundSessionStorageManagers(); + + async PropagateBackgroundSessionStorageManager(uint64_t currentTopContextId, uint64_t targetTopContextId); + + async RemoveBackgroundSessionStorageManager(uint64_t topContextId); + + async GetSessionStorageManagerData( + uint64_t aTopContextId, uint32_t aSizeLimit, bool aCancelSessionStoreTimer) + returns(SSCacheCopy[] aCacheCopy); + + async LoadSessionStorageManagerData(uint64_t aTopContextId, SSCacheCopy[] aOriginCacheCopy); + + async PFileSystemRequest(FileSystemParams params); + + async PGamepadEventChannel(); + + async PGamepadTestChannel(); + + async PHttpBackgroundChannel(uint64_t channelId); + + async PWebAuthnTransaction(); + + async PSharedWorker(RemoteWorkerData data, + uint64_t windowID, + MessagePortIdentifier portIdentifier); + + async PTemporaryIPCBlob(); + + async PFileCreator(nsString aFullPath, nsString aType, nsString aName, + int64_t? lastModified, bool aExistenceCheck, + bool aIsFromNsIFile); + + async PClientManager(); + + async CreateMIDIManager(Endpoint<PMIDIManagerParent> aEndpoint); + async CreateMIDIPort(Endpoint<PMIDIPortParent> aEndpoint, + MIDIPortInfo portInfo, bool sysexEnabled); + async HasMIDIDevice() returns (bool hasDevice); + + // This method is used to propagate storage activities from the child actor + // to the parent actor. See StorageActivityService. + async StorageActivity(PrincipalInfo principalInfo); + + async PServiceWorker(IPCServiceWorkerDescriptor aDescriptor); + + async PRemoteWorkerController(RemoteWorkerData aData); + + async PRemoteWorkerService(); + + async PServiceWorkerContainer(); + + async PServiceWorkerRegistration(IPCServiceWorkerRegistrationDescriptor aDescriptor); + + async PEndpointForReport(nsString aGroupName, PrincipalInfo aPrincipalInfo); + + async RemoveEndpoint(nsString aGroupName, nsCString aEndpointURL, + PrincipalInfo aPrincipalInfo); + + async PIdleScheduler(); + + async EnsureRDDProcessAndCreateBridge() + returns (nsresult rv, Endpoint<PRemoteDecoderManagerChild> aEndpoint); + + async EnsureUtilityProcessAndCreateBridge(RemoteDecodeIn aLocation) + returns (nsresult rv, Endpoint<PRemoteDecoderManagerChild> aEndpoint); + + async PLockManager(nsIPrincipal aPrincipalInfo, nsID aClientId); + + async PFetch(); + + async RequestCameraAccess(bool aAllowPermissionRequest) returns (CamerasAccessStatus rv); + +child: + async PCache(); + async PCacheStreamControl(); + + async PRemoteWorker(RemoteWorkerData data); +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/PBackgroundSharedTypes.ipdlh b/ipc/glue/PBackgroundSharedTypes.ipdlh new file mode 100644 index 0000000000..92175b782d --- /dev/null +++ b/ipc/glue/PBackgroundSharedTypes.ipdlh @@ -0,0 +1,78 @@ +/* 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/. */ + +using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; + +namespace mozilla { +namespace ipc { + +[Comparable] struct ContentSecurityPolicy +{ + nsString policy; + bool reportOnlyFlag; + bool deliveredViaMetaTagFlag; +}; + +[Comparable] struct ContentPrincipalInfo +{ + OriginAttributes attrs; + + // Origin is not simply a part of the spec. Based on the scheme of the URI + // spec, we generate different kind of origins: for instance any file: URL + // shares the same origin, about: URLs have the full spec as origin and so + // on. + // Another important reason why we have this attribute is that + // ContentPrincipalInfo is used out of the main-thread. Having this value + // here allows us to retrive the origin without creating a full nsIPrincipal. + nsCString originNoSuffix; + + nsCString spec; + + nsCString? domain; + + // Like originNoSuffix, baseDomain is used out of the main-thread. + nsCString baseDomain; +}; + +[Comparable] struct SystemPrincipalInfo +{ }; + +[Comparable] struct NullPrincipalInfo +{ + OriginAttributes attrs; + nsCString spec; +}; + +[Comparable] struct ExpandedPrincipalInfo +{ + OriginAttributes attrs; + PrincipalInfo[] allowlist; +}; + +[Comparable] union PrincipalInfo +{ + ContentPrincipalInfo; + SystemPrincipalInfo; + NullPrincipalInfo; + ExpandedPrincipalInfo; +}; + +[Comparable] struct CSPInfo +{ + ContentSecurityPolicy[] policyInfos; + PrincipalInfo requestPrincipalInfo; + nsCString selfURISpec; + nsString referrer; + uint64_t innerWindowID; + bool skipAllowInlineStyleCheck; +}; + +[Comparable] struct WebTransportHash { + nsCString algorithm; + uint8_t[] value; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/PBackgroundStarter.ipdl b/ipc/glue/PBackgroundStarter.ipdl new file mode 100644 index 0000000000..60fd6a3423 --- /dev/null +++ b/ipc/glue/PBackgroundStarter.ipdl @@ -0,0 +1,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 protocol PBackground; + +namespace mozilla { +namespace ipc { + +[NeedsOtherPid, ChildProc=anydom] +async protocol PBackgroundStarter +{ +parent: + async InitBackground(Endpoint<PBackgroundParent> aEndpoint); +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/PBackgroundTest.ipdl b/ipc/glue/PBackgroundTest.ipdl new file mode 100644 index 0000000000..5027010994 --- /dev/null +++ b/ipc/glue/PBackgroundTest.ipdl @@ -0,0 +1,21 @@ +/* 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 protocol PBackground; + +namespace mozilla { +namespace ipc { + +// This is a very simple testing protocol that is only used during mochitests. +[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual] +protocol PBackgroundTest +{ + manager PBackground; + +child: + async __delete__(nsCString testArg); +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/PIdleScheduler.ipdl b/ipc/glue/PIdleScheduler.ipdl new file mode 100644 index 0000000000..1857c12eaf --- /dev/null +++ b/ipc/glue/PIdleScheduler.ipdl @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 protocol PBackground; +using mozilla::TimeDuration from "mozilla/TimeStamp.h"; +[MoveOnly] using base::SharedMemoryHandle from "base/shared_memory.h"; +namespace mozilla { +namespace ipc { + +/** + * PIdleScheduler is the protocol for cross-process idle scheduling. + * Only child processes participate in the scheduling and parent process + * can run its idle tasks whenever it needs to. + * + * The scheduler keeps track of the following things. + * - Activity of the main thread of each child process. A process is active + * when it is running tasks. Because of performance cross-process + * counters in shared memory are used for the activity tracking. There is + * one counter counting the activity state of all the processes and one + * counter for each process. This way if a child process crashes, the global + * counter can be updated by decrementing the per process counter from it. + * - Child processes running prioritized operation. Top level page loads is an + * example of a prioritized operation. When such is ongoing, idle tasks are + * less likely to run. + * - Idle requests. When a child process locally has idle tasks to run, it + * requests idle time from the scheduler. Initially requests go to a wait list + * and the scheduler runs and if there are free logical cores for the child + * processes, idle time is given to the child process, and the process goes to + * the idle list. Once idle time has been consumed or there are no tasks to + * process, child process informs the scheduler and the process is moved back + * to the default queue. + */ +async protocol PIdleScheduler +{ + manager PBackground; + +child: + async IdleTime(uint64_t id, TimeDuration budget); + +parent: + async InitForIdleUse() returns (SharedMemoryHandle? state, uint32_t childId); + async RequestIdleTime(uint64_t id, TimeDuration budget); + async IdleTimeUsed(uint64_t id); + + // Child can send explicit Schedule message to parent if it thinks parent process + // might be able to let some other process to use idle time. + async Schedule(); + + // Note, these two messages can be sent even before InitForIdleUse. + async RunningPrioritizedOperation(); + async PrioritizedOperationDone(); + + // Ask if now would be a good time to GC + async RequestGC() returns (bool may_gc); + + // Let the parent know when we start a GC without asking first. + async StartedGC(); + + // Called for ending any kind of GC. + async DoneGC(); + + // This message is never sent. Each PIdleScheduler actor will stay alive as long as + // its PBackground manager. + async __delete__(); +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/PUtilityAudioDecoder.ipdl b/ipc/glue/PUtilityAudioDecoder.ipdl new file mode 100644 index 0000000000..74dbbd2c54 --- /dev/null +++ b/ipc/glue/PUtilityAudioDecoder.ipdl @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 GraphicsMessages; +include protocol PRemoteDecoderManager; +include protocol PVideoBridge; + +using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h"; +using mozilla::media::MediaCodecsSupported from "MediaCodecsSupport.h"; +using mozilla::RemoteDecodeIn from "mozilla/RemoteDecoderManagerChild.h"; + +#ifdef MOZ_WMF_CDM +using mozilla::MFCDMCapabilitiesIPDL from "mozilla/PMFCDM.h"; +#endif + +namespace mozilla { + +namespace ipc { + +// This protocol allows to run media audio decoding infrastructure on top +// of the Utility process +[ParentProc=Utility, ChildProc=Parent] +protocol PUtilityAudioDecoder +{ +parent: + async NewContentRemoteDecoderManager( + Endpoint<PRemoteDecoderManagerParent> endpoint, ContentParentId parentId); + +#ifdef MOZ_WMF_MEDIA_ENGINE + async InitVideoBridge(Endpoint<PVideoBridgeChild> endpoint, + GfxVarUpdate[] updates, + ContentDeviceData contentDeviceData); + + async UpdateVar(GfxVarUpdate var); +#endif + +#ifdef MOZ_WMF_CDM + async GetKeySystemCapabilities() returns (MFCDMCapabilitiesIPDL[] result); + + async UpdateWidevineL1Path(nsString path); +#endif + +child: + async UpdateMediaCodecsSupported(RemoteDecodeIn aLocation, + MediaCodecsSupported aSupported); + +#ifdef MOZ_WMF_MEDIA_ENGINE + async CompleteCreatedVideoBridge(); +#endif + +}; + +} // namespace ipc + +} // namespace mozilla diff --git a/ipc/glue/PUtilityProcess.ipdl b/ipc/glue/PUtilityProcess.ipdl new file mode 100644 index 0000000000..585590799f --- /dev/null +++ b/ipc/glue/PUtilityProcess.ipdl @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 MemoryReportTypes; +include PrefsTypes; + +include protocol PProfiler; +include protocol PUtilityAudioDecoder; +include protocol PJSOracle; + +#if defined(XP_WIN) +include protocol PWindowsUtils; +include protocol PWinFileDialog; +#endif + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +include protocol PSandboxTesting; +#endif + +include "mozilla/ipc/ByteBufUtils.h"; + +using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h"; + +// Telemetry +using mozilla::Telemetry::HistogramAccumulation from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::KeyedHistogramAccumulation from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h"; + +#if defined(XP_WIN) +[MoveOnly] using mozilla::UntrustedModulesData from "mozilla/UntrustedModulesData.h"; +[MoveOnly] using mozilla::ModulePaths from "mozilla/UntrustedModulesData.h"; +[MoveOnly] using mozilla::ModulesMapResult from "mozilla/UntrustedModulesData.h"; +#endif // defined(XP_WIN) + +namespace mozilla { + +namespace ipc { + +// This protocol allows the UI process to talk to the Utility process. There is +// one instance of this protocol, with the UtilityProcessParent living on the main thread +// of the main process and the UtilityProcessChild living on the main thread of the Utility +// process. +[NeedsOtherPid, ChildProc=Utility] +protocol PUtilityProcess +{ +parent: + async InitCrashReporter(NativeThreadId threadId); + + async AddMemoryReport(MemoryReport aReport); + + // Sent from time-to-time to limit the amount of telemetry vulnerable to loss + // Buffer contains bincoded Rust structs. + // https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/ipc.html + async FOGData(ByteBuf buf); + +#if defined(XP_WIN) + async GetModulesTrust(ModulePaths aModPaths, bool aRunAtNormalPriority) + returns (ModulesMapResult? modMapResult); +#endif // defined(XP_WIN) + + // Messages for sending telemetry to parent process. + async AccumulateChildHistograms(HistogramAccumulation[] accumulations); + async AccumulateChildKeyedHistograms(KeyedHistogramAccumulation[] accumulations); + async UpdateChildScalars(ScalarAction[] actions); + async UpdateChildKeyedScalars(KeyedScalarAction[] actions); + async RecordChildEvents(ChildEventData[] events); + async RecordDiscardedData(DiscardedData data); + + async InitCompleted(); + +child: + async Init(FileDescriptor? sandboxBroker, bool canRecordReleaseTelemetry, bool aIsReadyForBackgroundProcessing); + + async InitProfiler(Endpoint<PProfilerChild> endpoint); + + async RequestMemoryReport(uint32_t generation, + bool anonymize, + bool minimizeMemoryUsage, + FileDescriptor? DMDFile) + returns (uint32_t aGeneration); + + async PreferenceUpdate(Pref pref); + + // Tells the Utility process to flush any pending telemetry. + // Used in tests and ping assembly. Buffer contains bincoded Rust structs. + // https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/ipc.html + async FlushFOGData() returns (ByteBuf buf); + + // Test-only method. + // Asks the Utility process to trigger test-only instrumentation. + // The unused returned value is to have a promise we can await. + async TestTriggerMetrics() returns (bool unused); + + async TestTelemetryProbes(); + + async StartUtilityAudioDecoderService(Endpoint<PUtilityAudioDecoderParent> aEndpoint); + + async StartJSOracleService(Endpoint<PJSOracleChild> aEndpoint); + +#if defined(XP_WIN) + async StartWindowsUtilsService(Endpoint<PWindowsUtilsChild> aEndpoint); + async StartWinFileDialogService(Endpoint<PWinFileDialogChild> aEndpoint); + + async GetUntrustedModulesData() returns (UntrustedModulesData? data); + + /** + * This method is used to notify a child process to start + * processing module loading events in UntrustedModulesProcessor. + * This should be called when the parent process has gone idle. + */ + async UnblockUntrustedModulesThread(); +#endif // defined(XP_WIN) + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) + async InitSandboxTesting(Endpoint<PSandboxTestingChild> aEndpoint); +#endif +}; + +} // namespace ipc + +} // namespace mozilla diff --git a/ipc/glue/ProcessChild.cpp b/ipc/glue/ProcessChild.cpp new file mode 100644 index 0000000000..724d2b09bf --- /dev/null +++ b/ipc/glue/ProcessChild.cpp @@ -0,0 +1,137 @@ +/* -*- 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 "mozilla/ipc/ProcessChild.h" + +#include "Endpoint.h" +#include "nsDebug.h" + +#ifdef XP_WIN +# include <stdlib.h> // for _exit() +# include <synchapi.h> +#else +# include <unistd.h> // for _exit() +# include <time.h> +# include "base/eintr_wrapper.h" +# include "prenv.h" +#endif + +#include "nsAppRunner.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/ipc/IOThreadChild.h" +#include "mozilla/GeckoArgs.h" + +namespace mozilla { +namespace ipc { + +ProcessChild* ProcessChild::gProcessChild; + +static Atomic<bool> sExpectingShutdown(false); + +ProcessChild::ProcessChild(ProcessId aParentPid, const nsID& aMessageChannelId) + : ChildProcess(new IOThreadChild(aParentPid)), + mUILoop(MessageLoop::current()), + mParentPid(aParentPid), + mMessageChannelId(aMessageChannelId) { + MOZ_ASSERT(mUILoop, "UILoop should be created by now"); + MOZ_ASSERT(!gProcessChild, "should only be one ProcessChild"); + gProcessChild = this; +} + +/* static */ +void ProcessChild::AddPlatformBuildID(std::vector<std::string>& aExtraArgs) { + nsCString parentBuildID(mozilla::PlatformBuildID()); + geckoargs::sParentBuildID.Put(parentBuildID.get(), aExtraArgs); +} + +/* static */ +bool ProcessChild::InitPrefs(int aArgc, char* aArgv[]) { + Maybe<uint64_t> prefsHandle = Some(0); + Maybe<uint64_t> prefMapHandle = Some(0); + Maybe<uint64_t> prefsLen = geckoargs::sPrefsLen.Get(aArgc, aArgv); + Maybe<uint64_t> prefMapSize = geckoargs::sPrefMapSize.Get(aArgc, aArgv); + + if (prefsLen.isNothing() || prefMapSize.isNothing()) { + return false; + } + +#ifdef XP_WIN + prefsHandle = geckoargs::sPrefsHandle.Get(aArgc, aArgv); + prefMapHandle = geckoargs::sPrefMapHandle.Get(aArgc, aArgv); + + if (prefsHandle.isNothing() || prefMapHandle.isNothing()) { + return false; + } +#endif + + SharedPreferenceDeserializer deserializer; + return deserializer.DeserializeFromSharedMemory(*prefsHandle, *prefMapHandle, + *prefsLen, *prefMapSize); +} + +#ifdef ENABLE_TESTS +// Allow tests to cause a synthetic delay/"hang" during child process +// shutdown by setting environment variables. +# ifdef XP_UNIX +static void ReallySleep(int aSeconds) { + struct ::timespec snooze = {aSeconds, 0}; + HANDLE_EINTR(nanosleep(&snooze, &snooze)); +} +# else +static void ReallySleep(int aSeconds) { ::Sleep(aSeconds * 1000); } +# endif // Unix/Win +static void SleepIfEnv(const char* aName) { + if (auto* value = PR_GetEnv(aName)) { + ReallySleep(atoi(value)); + } +} +#else // not tests +static void SleepIfEnv(const char* aName) {} +#endif + +ProcessChild::~ProcessChild() { +#ifdef NS_FREE_PERMANENT_DATA + // In this case, we won't early-exit and we'll wait indefinitely for + // child processes to terminate. This sleep is late enough that, in + // content processes, it won't block parent process shutdown, so + // we'll get into late IPC shutdown with processes still running. + SleepIfEnv("MOZ_TEST_CHILD_EXIT_HANG"); +#endif + gProcessChild = nullptr; +} + +/* static */ +void ProcessChild::NotifiedImpendingShutdown() { + sExpectingShutdown = true; + CrashReporter::AppendToCrashReportAnnotation( + CrashReporter::Annotation::IPCShutdownState, + "NotifiedImpendingShutdown"_ns); +} + +/* static */ +bool ProcessChild::ExpectingShutdown() { return sExpectingShutdown; } + +/* static */ +void ProcessChild::QuickExit() { +#ifndef NS_FREE_PERMANENT_DATA + // In this case, we're going to terminate the child process before + // we get to ~ProcessChild above (and terminate the parent process + // before the shutdown hook in ProcessWatcher). Instead, blocking + // earlier will let us exercise ProcessWatcher's kill timer. + SleepIfEnv("MOZ_TEST_CHILD_EXIT_HANG"); +#endif + AppShutdown::DoImmediateExit(); +} + +UntypedEndpoint ProcessChild::TakeInitialEndpoint() { + return UntypedEndpoint{PrivateIPDLInterface{}, + child_thread()->TakeInitialPort(), mMessageChannelId, + base::GetCurrentProcId(), mParentPid}; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProcessChild.h b/ipc/glue/ProcessChild.h new file mode 100644 index 0000000000..29948a9127 --- /dev/null +++ b/ipc/glue/ProcessChild.h @@ -0,0 +1,75 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_ProcessChild_h +#define mozilla_ipc_ProcessChild_h + +#include "Endpoint.h" +#include "base/message_loop.h" +#include "base/process.h" + +#include "chrome/common/child_process.h" + +#include "mozilla/ipc/ProcessUtils.h" + +// ProcessChild is the base class for all subprocesses of the main +// browser process. Its code runs on the thread that started in +// main(). + +namespace mozilla { +namespace ipc { + +class ProcessChild : public ChildProcess { + protected: + typedef base::ProcessId ProcessId; + + public: + explicit ProcessChild(ProcessId aParentPid, const nsID& aMessageChannelId); + + ProcessChild(const ProcessChild&) = delete; + ProcessChild& operator=(const ProcessChild&) = delete; + + virtual ~ProcessChild(); + + virtual bool Init(int aArgc, char* aArgv[]) = 0; + + static void AddPlatformBuildID(std::vector<std::string>& aExtraArgs); + + static bool InitPrefs(int aArgc, char* aArgv[]); + + virtual void CleanUp() {} + + static MessageLoop* message_loop() { return gProcessChild->mUILoop; } + + static void NotifiedImpendingShutdown(); + + static bool ExpectingShutdown(); + + /** + * Exit *now*. Do not shut down XPCOM, do not pass Go, do not run + * static destructors, do not collect $200. + */ + static void QuickExit(); + + protected: + static ProcessChild* current() { return gProcessChild; } + + ProcessId ParentPid() { return mParentPid; } + + UntypedEndpoint TakeInitialEndpoint(); + + private: + static ProcessChild* gProcessChild; + + MessageLoop* mUILoop; + ProcessId mParentPid; + nsID mMessageChannelId; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef mozilla_ipc_ProcessChild_h diff --git a/ipc/glue/ProcessUtils.h b/ipc/glue/ProcessUtils.h new file mode 100644 index 0000000000..b0f146ef6d --- /dev/null +++ b/ipc/glue/ProcessUtils.h @@ -0,0 +1,93 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_ProcessUtils_h +#define mozilla_ipc_ProcessUtils_h + +#include <functional> +#include <vector> + +#include "mozilla/ipc/FileDescriptor.h" +#include "base/shared_memory.h" +#include "mozilla/Maybe.h" +#include "mozilla/Preferences.h" +#include "nsXULAppAPI.h" + +namespace mozilla { +namespace ipc { + +class GeckoChildProcessHost; + +// You probably should call ContentChild::SetProcessName instead of calling +// this directly. +void SetThisProcessName(const char* aName); + +class SharedPreferenceSerializer final { + public: + explicit SharedPreferenceSerializer(); + SharedPreferenceSerializer(SharedPreferenceSerializer&& aOther); + ~SharedPreferenceSerializer(); + + bool SerializeToSharedMemory(const GeckoProcessType aDestinationProcessType, + const nsACString& aDestinationRemoteType); + + size_t GetPrefMapSize() const { return mPrefMapSize; } + size_t GetPrefsLength() const { return mPrefsLength; } + + const UniqueFileHandle& GetPrefsHandle() const { return mPrefsHandle; } + + const UniqueFileHandle& GetPrefMapHandle() const { return mPrefMapHandle; } + + void AddSharedPrefCmdLineArgs(GeckoChildProcessHost& procHost, + std::vector<std::string>& aExtraOpts) const; + + private: + DISALLOW_COPY_AND_ASSIGN(SharedPreferenceSerializer); + size_t mPrefMapSize; + size_t mPrefsLength; + UniqueFileHandle mPrefMapHandle; + UniqueFileHandle mPrefsHandle; +}; + +class SharedPreferenceDeserializer final { + public: + SharedPreferenceDeserializer(); + ~SharedPreferenceDeserializer(); + + bool DeserializeFromSharedMemory(uint64_t aPrefsHandle, + uint64_t aPrefMapHandle, uint64_t aPrefsLen, + uint64_t aPrefMapSize); + + const FileDescriptor& GetPrefMapHandle() const; + + private: + DISALLOW_COPY_AND_ASSIGN(SharedPreferenceDeserializer); + Maybe<FileDescriptor> mPrefMapHandle; + Maybe<size_t> mPrefsLen; + Maybe<size_t> mPrefMapSize; + base::SharedMemory mShmem; +}; + +#ifdef ANDROID +// Android doesn't use -prefsHandle or -prefMapHandle. It gets those FDs +// another way. +void SetPrefsFd(int aFd); +void SetPrefMapFd(int aFd); +#endif + +// Generate command line argument to spawn a child process. If the shared memory +// is not properly initialized, this would be a no-op. +void ExportSharedJSInit(GeckoChildProcessHost& procHost, + std::vector<std::string>& aExtraOpts); + +// Initialize the content used by the JS engine during the initialization of a +// JS::Runtime. +bool ImportSharedJSInit(uint64_t aJsInitHandle, uint64_t aJsInitLen); + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef mozilla_ipc_ProcessUtils_h diff --git a/ipc/glue/ProcessUtils_bsd.cpp b/ipc/glue/ProcessUtils_bsd.cpp new file mode 100644 index 0000000000..12dfa15c6f --- /dev/null +++ b/ipc/glue/ProcessUtils_bsd.cpp @@ -0,0 +1,27 @@ +/* -*- 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 "ProcessUtils.h" + +#include <pthread.h> + +#if !defined(XP_NETBSD) +# include <pthread_np.h> +#endif + +namespace mozilla { +namespace ipc { + +void SetThisProcessName(const char* aName) { +#if defined(XP_NETBSD) + pthread_setname_np(pthread_self(), "%s", (void*)aName); +#else + pthread_set_name_np(pthread_self(), aName); +#endif +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProcessUtils_common.cpp b/ipc/glue/ProcessUtils_common.cpp new file mode 100644 index 0000000000..69eb4bdf28 --- /dev/null +++ b/ipc/glue/ProcessUtils_common.cpp @@ -0,0 +1,275 @@ +/* -*- 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 "ProcessUtils.h" + +#include "mozilla/Preferences.h" +#include "mozilla/GeckoArgs.h" +#include "mozilla/dom/RemoteType.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsPrintfCString.h" + +#include "XPCSelfHostedShmem.h" + +namespace mozilla { +namespace ipc { + +SharedPreferenceSerializer::SharedPreferenceSerializer() + : mPrefMapSize(0), mPrefsLength(0) { + MOZ_COUNT_CTOR(SharedPreferenceSerializer); +} + +SharedPreferenceSerializer::~SharedPreferenceSerializer() { + MOZ_COUNT_DTOR(SharedPreferenceSerializer); +} + +SharedPreferenceSerializer::SharedPreferenceSerializer( + SharedPreferenceSerializer&& aOther) + : mPrefMapSize(aOther.mPrefMapSize), + mPrefsLength(aOther.mPrefsLength), + mPrefMapHandle(std::move(aOther.mPrefMapHandle)), + mPrefsHandle(std::move(aOther.mPrefsHandle)) { + MOZ_COUNT_CTOR(SharedPreferenceSerializer); +} + +bool SharedPreferenceSerializer::SerializeToSharedMemory( + const GeckoProcessType aDestinationProcessType, + const nsACString& aDestinationRemoteType) { + mPrefMapHandle = + Preferences::EnsureSnapshot(&mPrefMapSize).TakePlatformHandle(); + + bool destIsWebContent = + aDestinationProcessType == GeckoProcessType_Content && + (StringBeginsWith(aDestinationRemoteType, WEB_REMOTE_TYPE) || + StringBeginsWith(aDestinationRemoteType, PREALLOC_REMOTE_TYPE)); + + // Serialize the early prefs. + nsAutoCStringN<1024> prefs; + Preferences::SerializePreferences(prefs, destIsWebContent); + mPrefsLength = prefs.Length(); + + base::SharedMemory shm; + // Set up the shared memory. + if (!shm.Create(prefs.Length())) { + NS_ERROR("failed to create shared memory in the parent"); + return false; + } + if (!shm.Map(prefs.Length())) { + NS_ERROR("failed to map shared memory in the parent"); + return false; + } + + // Copy the serialized prefs into the shared memory. + memcpy(static_cast<char*>(shm.memory()), prefs.get(), mPrefsLength); + + mPrefsHandle = shm.TakeHandle(); + return true; +} + +void SharedPreferenceSerializer::AddSharedPrefCmdLineArgs( + mozilla::ipc::GeckoChildProcessHost& procHost, + std::vector<std::string>& aExtraOpts) const { +#if defined(XP_WIN) + // Record the handle as to-be-shared, and pass it via a command flag. This + // works because Windows handles are system-wide. + procHost.AddHandleToShare(GetPrefsHandle().get()); + procHost.AddHandleToShare(GetPrefMapHandle().get()); + geckoargs::sPrefsHandle.Put((uintptr_t)(GetPrefsHandle().get()), aExtraOpts); + geckoargs::sPrefMapHandle.Put((uintptr_t)(GetPrefMapHandle().get()), + aExtraOpts); +#else + // In contrast, Unix fds are per-process. So remap the fd to a fixed one that + // will be used in the child. + // XXX: bug 1440207 is about improving how fixed fds are used. + // + // Note: on Android, AddFdToRemap() sets up the fd to be passed via a Parcel, + // and the fixed fd isn't used. However, we still need to mark it for + // remapping so it doesn't get closed in the child. + procHost.AddFdToRemap(GetPrefsHandle().get(), kPrefsFileDescriptor); + procHost.AddFdToRemap(GetPrefMapHandle().get(), kPrefMapFileDescriptor); +#endif + + // Pass the lengths via command line flags. + geckoargs::sPrefsLen.Put((uintptr_t)(GetPrefsLength()), aExtraOpts); + geckoargs::sPrefMapSize.Put((uintptr_t)(GetPrefMapSize()), aExtraOpts); +} + +#ifdef ANDROID +static int gPrefsFd = -1; +static int gPrefMapFd = -1; + +void SetPrefsFd(int aFd) { gPrefsFd = aFd; } + +void SetPrefMapFd(int aFd) { gPrefMapFd = aFd; } +#endif + +SharedPreferenceDeserializer::SharedPreferenceDeserializer() { + MOZ_COUNT_CTOR(SharedPreferenceDeserializer); +} + +SharedPreferenceDeserializer::~SharedPreferenceDeserializer() { + MOZ_COUNT_DTOR(SharedPreferenceDeserializer); +} + +bool SharedPreferenceDeserializer::DeserializeFromSharedMemory( + uint64_t aPrefsHandle, uint64_t aPrefMapHandle, uint64_t aPrefsLen, + uint64_t aPrefMapSize) { + Maybe<base::SharedMemoryHandle> prefsHandle; + +#ifdef XP_WIN + prefsHandle = Some(UniqueFileHandle(HANDLE((uintptr_t)(aPrefsHandle)))); + if (!aPrefsHandle) { + return false; + } + + FileDescriptor::UniquePlatformHandle handle( + HANDLE((uintptr_t)(aPrefMapHandle))); + if (!aPrefMapHandle) { + return false; + } + + mPrefMapHandle.emplace(std::move(handle)); +#endif + + mPrefsLen = Some((uintptr_t)(aPrefsLen)); + if (!aPrefsLen) { + return false; + } + + mPrefMapSize = Some((uintptr_t)(aPrefMapSize)); + if (!aPrefMapSize) { + return false; + } + +#ifdef ANDROID + // Android is different; get the FD via gPrefsFd instead of a fixed fd. + MOZ_RELEASE_ASSERT(gPrefsFd != -1); + prefsHandle = Some(UniqueFileHandle(gPrefsFd)); + + mPrefMapHandle.emplace(UniqueFileHandle(gPrefMapFd)); +#elif XP_UNIX + prefsHandle = Some(UniqueFileHandle(kPrefsFileDescriptor)); + + mPrefMapHandle.emplace(UniqueFileHandle(kPrefMapFileDescriptor)); +#endif + + if (prefsHandle.isNothing() || mPrefsLen.isNothing() || + mPrefMapHandle.isNothing() || mPrefMapSize.isNothing()) { + return false; + } + + // Init the shared-memory base preference mapping first, so that only changed + // preferences wind up in heap memory. + Preferences::InitSnapshot(mPrefMapHandle.ref(), *mPrefMapSize); + + // Set up early prefs from the shared memory. + if (!mShmem.SetHandle(std::move(*prefsHandle), /* read_only */ true)) { + NS_ERROR("failed to open shared memory in the child"); + return false; + } + if (!mShmem.Map(*mPrefsLen)) { + NS_ERROR("failed to map shared memory in the child"); + return false; + } + Preferences::DeserializePreferences(static_cast<char*>(mShmem.memory()), + *mPrefsLen); + + return true; +} + +const FileDescriptor& SharedPreferenceDeserializer::GetPrefMapHandle() const { + MOZ_ASSERT(mPrefMapHandle.isSome()); + + return mPrefMapHandle.ref(); +} + +#ifdef XP_UNIX +// On Unix, file descriptors are per-process. This value is used when mapping +// a parent process handle to a content process handle. +static const int kJSInitFileDescriptor = 11; +#endif + +void ExportSharedJSInit(mozilla::ipc::GeckoChildProcessHost& procHost, + std::vector<std::string>& aExtraOpts) { +#ifdef ANDROID + // The code to support Android is added in a follow-up patch. + return; +#else + auto& shmem = xpc::SelfHostedShmem::GetSingleton(); + const mozilla::UniqueFileHandle& uniqHandle = shmem.Handle(); + size_t len = shmem.Content().Length(); + + // If the file is not found or the content is empty, then we would start the + // content process without this optimization. + if (!uniqHandle || !len) { + return; + } + + mozilla::detail::FileHandleType handle = uniqHandle.get(); + // command line: [-jsInitHandle handle] -jsInitLen length +# if defined(XP_WIN) + // Record the handle as to-be-shared, and pass it via a command flag. + procHost.AddHandleToShare(HANDLE(handle)); + geckoargs::sJsInitHandle.Put((uintptr_t)(HANDLE(handle)), aExtraOpts); +# else + // In contrast, Unix fds are per-process. So remap the fd to a fixed one that + // will be used in the child. + // XXX: bug 1440207 is about improving how fixed fds are used. + // + // Note: on Android, AddFdToRemap() sets up the fd to be passed via a Parcel, + // and the fixed fd isn't used. However, we still need to mark it for + // remapping so it doesn't get closed in the child. + procHost.AddFdToRemap(handle, kJSInitFileDescriptor); +# endif + + // Pass the lengths via command line flags. + geckoargs::sJsInitLen.Put((uintptr_t)(len), aExtraOpts); +#endif +} + +bool ImportSharedJSInit(uint64_t aJsInitHandle, uint64_t aJsInitLen) { + // This is an optimization, and as such we can safely recover if the command + // line argument are not provided. + if (!aJsInitLen) { + return true; + } + +#ifdef XP_WIN + if (!aJsInitHandle) { + return true; + } +#endif + +#ifdef XP_WIN + base::SharedMemoryHandle handle(HANDLE((uintptr_t)(aJsInitHandle))); + if (!aJsInitHandle) { + return false; + } +#endif + + size_t len = (uintptr_t)(aJsInitLen); + if (!aJsInitLen) { + return false; + } + +#ifdef XP_UNIX + auto handle = UniqueFileHandle(kJSInitFileDescriptor); +#endif + + // Initialize the shared memory with the file handle and size of the content + // of the self-hosted Xdr. + auto& shmem = xpc::SelfHostedShmem::GetSingleton(); + if (!shmem.InitFromChild(std::move(handle), len)) { + NS_ERROR("failed to open shared memory in the child"); + return false; + } + + return true; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProcessUtils_linux.cpp b/ipc/glue/ProcessUtils_linux.cpp new file mode 100644 index 0000000000..da15745753 --- /dev/null +++ b/ipc/glue/ProcessUtils_linux.cpp @@ -0,0 +1,22 @@ +/* -*- 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 "ProcessUtils.h" + +#include "nsString.h" + +#include <sys/prctl.h> + +namespace mozilla { + +namespace ipc { + +void SetThisProcessName(const char* aName) { + prctl(PR_SET_NAME, (unsigned long)aName, 0uL, 0uL, 0uL); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProcessUtils_mac.mm b/ipc/glue/ProcessUtils_mac.mm new file mode 100644 index 0000000000..e850c486aa --- /dev/null +++ b/ipc/glue/ProcessUtils_mac.mm @@ -0,0 +1,116 @@ +/* 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 "ProcessUtils.h" + +#include "nsObjCExceptions.h" +#include "nsCocoaUtils.h" +#include "nsString.h" +#include "mozilla/Sprintf.h" + +#define UNDOCUMENTED_SESSION_CONSTANT ((int)-2) + +namespace mozilla { +namespace ipc { + +static void* sApplicationASN = NULL; +static void* sApplicationInfoItem = NULL; + +void SetThisProcessName(const char* aProcessName) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + nsAutoreleasePool localPool; + + if (!aProcessName || strcmp(aProcessName, "") == 0) { + return; + } + + NSString* currentName = [[[NSBundle mainBundle] localizedInfoDictionary] + objectForKey:(NSString*)kCFBundleNameKey]; + + char formattedName[1024]; + SprintfLiteral(formattedName, "%s %s", [currentName UTF8String], + aProcessName); + + aProcessName = formattedName; + + // This function is based on Chrome/Webkit's and relies on potentially + // dangerous SPI. + typedef CFTypeRef (*LSGetASNType)(); + typedef OSStatus (*LSSetInformationItemType)(int, CFTypeRef, CFStringRef, + CFStringRef, CFDictionaryRef*); + + CFBundleRef launchServices = + ::CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices")); + if (!launchServices) { + NS_WARNING( + "Failed to set process name: Could not open LaunchServices bundle"); + return; + } + + if (!sApplicationASN) { + sApplicationASN = ::CFBundleGetFunctionPointerForName( + launchServices, CFSTR("_LSGetCurrentApplicationASN")); + if (!sApplicationASN) { + NS_WARNING("Failed to set process name: Could not get function pointer " + "for LaunchServices"); + return; + } + } + + LSGetASNType getASNFunc = reinterpret_cast<LSGetASNType>(sApplicationASN); + + if (!sApplicationInfoItem) { + sApplicationInfoItem = ::CFBundleGetFunctionPointerForName( + launchServices, CFSTR("_LSSetApplicationInformationItem")); + } + + LSSetInformationItemType setInformationItemFunc = + reinterpret_cast<LSSetInformationItemType>(sApplicationInfoItem); + + void* displayNameKeyAddr = ::CFBundleGetDataPointerForName( + launchServices, CFSTR("_kLSDisplayNameKey")); + + CFStringRef displayNameKey = nil; + if (displayNameKeyAddr) { + displayNameKey = + reinterpret_cast<CFStringRef>(*(CFStringRef*)displayNameKeyAddr); + } + + // We need this to ensure we have a connection to the Process Manager, not + // doing so will silently fail and process name wont be updated. + ProcessSerialNumber psn; + if (::GetCurrentProcess(&psn) != noErr) { + return; + } + + CFTypeRef currentAsn = getASNFunc ? getASNFunc() : nullptr; + + if (!getASNFunc || !setInformationItemFunc || !displayNameKey || + !currentAsn) { + NS_WARNING("Failed to set process name: Accessing launchServices failed"); + return; + } + + CFStringRef processName = + ::CFStringCreateWithCString(nil, aProcessName, kCFStringEncodingASCII); + if (!processName) { + NS_WARNING("Failed to set process name: Could not create CFStringRef"); + return; + } + + OSErr err = setInformationItemFunc(UNDOCUMENTED_SESSION_CONSTANT, currentAsn, + displayNameKey, processName, + nil); // Optional out param + ::CFRelease(processName); + if (err != noErr) { + NS_WARNING("Failed to set process name: LSSetInformationItemType err"); + return; + } + + return; + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProcessUtils_none.cpp b/ipc/glue/ProcessUtils_none.cpp new file mode 100644 index 0000000000..49566a0720 --- /dev/null +++ b/ipc/glue/ProcessUtils_none.cpp @@ -0,0 +1,15 @@ +/* -*- 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 "ProcessUtils.h" + +namespace mozilla { +namespace ipc { + +void SetThisProcessName(const char* aString) { (void)aString; } + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProtocolMessageUtils.h b/ipc/glue/ProtocolMessageUtils.h new file mode 100644 index 0000000000..48485596ea --- /dev/null +++ b/ipc/glue/ProtocolMessageUtils.h @@ -0,0 +1,121 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef IPC_GLUE_PROTOCOLMESSAGEUTILS_H +#define IPC_GLUE_PROTOCOLMESSAGEUTILS_H + +#include <stdint.h> +#include <string> +#include "base/string_util.h" +#include "chrome/common/ipc_channel.h" +#include "chrome/common/ipc_message_utils.h" +#include "ipc/EnumSerializer.h" +#include "mozilla/Assertions.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProtocolUtils.h" + +class PickleIterator; + +namespace mozilla::ipc { +class FileDescriptor; +template <class PFooSide> +class Endpoint; +template <class PFooSide> +class ManagedEndpoint; +template <typename P> +struct IPDLParamTraits; +} // namespace mozilla::ipc + +namespace IPC { + +class Message; +class MessageReader; +class MessageWriter; + +template <> +struct ParamTraits<Channel::Mode> + : ContiguousEnumSerializerInclusive<Channel::Mode, Channel::MODE_SERVER, + Channel::MODE_CLIENT> {}; + +template <> +struct ParamTraits<IPCMessageStart> + : ContiguousEnumSerializer<IPCMessageStart, IPCMessageStart(0), + LastMsgIndex> {}; + +template <> +struct ParamTraits<mozilla::ipc::ActorHandle> { + typedef mozilla::ipc::ActorHandle paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + IPC::WriteParam(aWriter, aParam.mId); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + int id; + if (IPC::ReadParam(aReader, &id)) { + aResult->mId = id; + return true; + } + return false; + } +}; + +template <> +struct ParamTraits<mozilla::ipc::UntypedEndpoint> { + using paramType = mozilla::ipc::UntypedEndpoint; + + static void Write(MessageWriter* aWriter, paramType&& aParam); + + static bool Read(MessageReader* aReader, paramType* aResult); +}; + +template <class PFooSide> +struct ParamTraits<mozilla::ipc::Endpoint<PFooSide>> + : ParamTraits<mozilla::ipc::UntypedEndpoint> {}; + +} // namespace IPC + +namespace mozilla::ipc { + +template <> +struct IPDLParamTraits<UntypedManagedEndpoint> { + using paramType = UntypedManagedEndpoint; + + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + paramType&& aParam); + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + paramType* aResult); +}; + +template <class PFooSide> +struct IPDLParamTraits<ManagedEndpoint<PFooSide>> { + using paramType = ManagedEndpoint<PFooSide>; + + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + paramType&& aParam) { + IPDLParamTraits<UntypedManagedEndpoint>::Write(aWriter, aActor, + std::move(aParam)); + } + + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + paramType* aResult) { + return IPDLParamTraits<UntypedManagedEndpoint>::Read(aReader, aActor, + aResult); + } +}; + +template <> +struct IPDLParamTraits<FileDescriptor> { + typedef FileDescriptor paramType; + + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + const paramType& aParam); + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + paramType* aResult); +}; +} // namespace mozilla::ipc + +#endif // IPC_GLUE_PROTOCOLMESSAGEUTILS_H diff --git a/ipc/glue/ProtocolTypes.ipdlh b/ipc/glue/ProtocolTypes.ipdlh new file mode 100644 index 0000000000..b1d5316731 --- /dev/null +++ b/ipc/glue/ProtocolTypes.ipdlh @@ -0,0 +1,21 @@ +/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */ +/* 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/. */ + +using struct nsID + from "nsID.h"; + +namespace mozilla { +namespace ipc { + +struct ProtocolFdMapping +{ + uint32_t protocolId; + FileDescriptor fd; +}; + +} +} + diff --git a/ipc/glue/ProtocolUtils.cpp b/ipc/glue/ProtocolUtils.cpp new file mode 100644 index 0000000000..bd78dce607 --- /dev/null +++ b/ipc/glue/ProtocolUtils.cpp @@ -0,0 +1,849 @@ +/* -*- 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 "base/process_util.h" +#include "base/task.h" + +#ifdef XP_UNIX +# include <errno.h> +#endif +#include <type_traits> + +#include "mozilla/IntegerPrintfMacros.h" + +#include "mozilla/ipc/ProtocolMessageUtils.h" +#include "mozilla/ipc/ProtocolUtils.h" + +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/IPDLParamTraits.h" +#include "mozilla/StaticMutex.h" +#if defined(DEBUG) || defined(FUZZING) +# include "mozilla/Tokenizer.h" +#endif +#include "mozilla/Unused.h" +#include "nsPrintfCString.h" +#include "nsReadableUtils.h" + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) +# include "mozilla/sandboxTarget.h" +#endif + +#if defined(XP_WIN) +# include "aclapi.h" +# include "sddl.h" +#endif + +#ifdef FUZZING_SNAPSHOT +# include "mozilla/fuzzing/IPCFuzzController.h" +#endif + +using namespace IPC; + +using base::GetCurrentProcId; +using base::ProcessHandle; +using base::ProcessId; + +namespace mozilla { +namespace ipc { + +/* static */ +IPCResult IPCResult::FailImpl(NotNull<IProtocol*> actor, const char* where, + const char* why) { + // Calls top-level protocol to handle the error. + nsPrintfCString errorMsg("%s %s\n", where, why); + actor->GetIPCChannel()->Listener()->ProcessingError( + HasResultCodes::MsgProcessingError, errorMsg.get()); + +#if defined(DEBUG) && !defined(FUZZING) + // We do not expect IPC_FAIL to ever happen in normal operations. If this + // happens in DEBUG, we most likely see some behavior during a test we should + // really investigate. + nsPrintfCString crashMsg( + "Use IPC_FAIL only in an " + "unrecoverable, unexpected state: %s", + errorMsg.get()); + // We already leak the same information potentially on child process failures + // even in release, and here we are only in DEBUG. + MOZ_CRASH_UNSAFE(crashMsg.get()); +#else + return IPCResult(false); +#endif +} + +void AnnotateSystemError() { + int64_t error = 0; +#if defined(XP_WIN) + error = ::GetLastError(); +#else + error = errno; +#endif + if (error) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCSystemError, + nsPrintfCString("%" PRId64, error)); + } +} + +#if defined(XP_MACOSX) +void AnnotateCrashReportWithErrno(CrashReporter::Annotation tag, int error) { + CrashReporter::AnnotateCrashReport(tag, error); +} +#endif // defined(XP_MACOSX) + +#if defined(DEBUG) || defined(FUZZING) +// If aTopLevelProtocol matches any token in aFilter, return true. +// +// aTopLevelProtocol is a protocol name, without the "Parent" / "Child" suffix. +// aSide indicates whether we're logging parent-side or child-side activity. +// +// aFilter is a list of protocol names separated by commas and/or +// spaces. These may include the "Child" / "Parent" suffix, or omit +// the suffix to log activity on both sides. +// +// This overload is for testability; application code should use the single- +// argument version (defined in the ProtocolUtils.h) which takes the filter from +// the environment. +bool LoggingEnabledFor(const char* aTopLevelProtocol, Side aSide, + const char* aFilter) { + if (!aFilter) { + return false; + } + if (strcmp(aFilter, "1") == 0) { + return true; + } + + const char kDelimiters[] = ", "; + Tokenizer tokens(aFilter, kDelimiters); + Tokenizer::Token t; + while (tokens.Next(t)) { + if (t.Type() == Tokenizer::TOKEN_WORD) { + auto filter = t.AsString(); + + // Since aTopLevelProtocol never includes the "Parent" / "Child" suffix, + // this will only occur when filter doesn't include it either, meaning + // that we should log activity on both sides. + if (filter == aTopLevelProtocol) { + return true; + } + + if (aSide == ParentSide && + StringEndsWith(filter, nsDependentCString("Parent")) && + Substring(filter, 0, filter.Length() - 6) == aTopLevelProtocol) { + return true; + } + + if (aSide == ChildSide && + StringEndsWith(filter, nsDependentCString("Child")) && + Substring(filter, 0, filter.Length() - 5) == aTopLevelProtocol) { + return true; + } + } + } + + return false; +} +#endif // defined(DEBUG) || defined(FUZZING) + +void LogMessageForProtocol(const char* aTopLevelProtocol, + base::ProcessId aOtherPid, + const char* aContextDescription, uint32_t aMessageId, + MessageDirection aDirection) { + nsPrintfCString logMessage( + "[time: %" PRId64 "][%" PRIPID "%s%" PRIPID "] [%s] %s %s\n", PR_Now(), + base::GetCurrentProcId(), + aDirection == MessageDirection::eReceiving ? "<-" : "->", aOtherPid, + aTopLevelProtocol, aContextDescription, + StringFromIPCMessageType(aMessageId)); +#ifdef ANDROID + __android_log_write(ANDROID_LOG_INFO, "GeckoIPC", logMessage.get()); +#endif + fputs(logMessage.get(), stderr); +} + +void ProtocolErrorBreakpoint(const char* aMsg) { + // Bugs that generate these error messages can be tough to + // reproduce. Log always in the hope that someone finds the error + // message. + printf_stderr("IPDL protocol error: %s\n", aMsg); +} + +void PickleFatalError(const char* aMsg, IProtocol* aActor) { + if (aActor) { + aActor->FatalError(aMsg); + } else { + FatalError(aMsg, false); + } +} + +void FatalError(const char* aMsg, bool aIsParent) { +#ifndef FUZZING + ProtocolErrorBreakpoint(aMsg); +#endif + + nsAutoCString formattedMessage("IPDL error: \""); + formattedMessage.AppendASCII(aMsg); + if (aIsParent) { + // We're going to crash the parent process because at this time + // there's no other really nice way of getting a minidump out of + // this process if we're off the main thread. + formattedMessage.AppendLiteral("\". Intentionally crashing."); + NS_ERROR(formattedMessage.get()); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCFatalErrorMsg, nsDependentCString(aMsg)); + AnnotateSystemError(); +#ifndef FUZZING + MOZ_CRASH("IPC FatalError in the parent process!"); +#endif + } else { + formattedMessage.AppendLiteral("\". abort()ing as a result."); +#ifndef FUZZING + MOZ_CRASH_UNSAFE(formattedMessage.get()); +#endif + } +} + +void LogicError(const char* aMsg) { MOZ_CRASH_UNSAFE(aMsg); } + +void ActorIdReadError(const char* aActorDescription) { +#ifndef FUZZING + MOZ_CRASH_UNSAFE_PRINTF("Error deserializing id for %s", aActorDescription); +#endif +} + +void BadActorIdError(const char* aActorDescription) { + nsPrintfCString message("bad id for %s", aActorDescription); + ProtocolErrorBreakpoint(message.get()); +} + +void ActorLookupError(const char* aActorDescription) { + nsPrintfCString message("could not lookup id for %s", aActorDescription); + ProtocolErrorBreakpoint(message.get()); +} + +void MismatchedActorTypeError(const char* aActorDescription) { + nsPrintfCString message("actor that should be of type %s has different type", + aActorDescription); + ProtocolErrorBreakpoint(message.get()); +} + +void UnionTypeReadError(const char* aUnionName) { + MOZ_CRASH_UNSAFE_PRINTF("error deserializing type of union %s", aUnionName); +} + +void ArrayLengthReadError(const char* aElementName) { + MOZ_CRASH_UNSAFE_PRINTF("error deserializing length of %s[]", aElementName); +} + +void SentinelReadError(const char* aClassName) { + MOZ_CRASH_UNSAFE_PRINTF("incorrect sentinel when reading %s", aClassName); +} + +ActorLifecycleProxy::ActorLifecycleProxy(IProtocol* aActor) : mActor(aActor) { + MOZ_ASSERT(mActor); + MOZ_ASSERT(mActor->CanSend(), + "Cannot create LifecycleProxy for non-connected actor!"); + + // Take a reference to our manager's lifecycle proxy to try to hold it & + // ensure it doesn't die before us. + if (mActor->mManager) { + mManager = mActor->mManager->mLifecycleProxy; + } + + // Record that we've taken our first reference to our actor. + mActor->ActorAlloc(); +} + +WeakActorLifecycleProxy* ActorLifecycleProxy::GetWeakProxy() { + if (!mWeakProxy) { + mWeakProxy = new WeakActorLifecycleProxy(this); + } + return mWeakProxy; +} + +ActorLifecycleProxy::~ActorLifecycleProxy() { + if (mWeakProxy) { + mWeakProxy->mProxy = nullptr; + mWeakProxy = nullptr; + } + + // When the LifecycleProxy's lifetime has come to an end, it means that the + // actor should have its `Dealloc` method called on it. In a well-behaved + // actor, this will release the IPC-held reference to the actor. + // + // If the actor has already died before the `LifecycleProxy`, the `IProtocol` + // destructor below will clear our reference to it, preventing us from + // performing a use-after-free here. + if (!mActor) { + return; + } + + // Clear our actor's state back to inactive, and then invoke ActorDealloc. + MOZ_ASSERT(mActor->mLinkStatus == LinkStatus::Destroyed, + "Deallocating non-destroyed actor!"); + mActor->mLifecycleProxy = nullptr; + mActor->mLinkStatus = LinkStatus::Inactive; + mActor->ActorDealloc(); + mActor = nullptr; +} + +WeakActorLifecycleProxy::WeakActorLifecycleProxy(ActorLifecycleProxy* aProxy) + : mProxy(aProxy), mActorEventTarget(GetCurrentSerialEventTarget()) {} + +WeakActorLifecycleProxy::~WeakActorLifecycleProxy() { + MOZ_DIAGNOSTIC_ASSERT(!mProxy, "Destroyed before mProxy was cleared?"); +} + +IProtocol* WeakActorLifecycleProxy::Get() const { + MOZ_DIAGNOSTIC_ASSERT(mActorEventTarget->IsOnCurrentThread()); + return mProxy ? mProxy->Get() : nullptr; +} + +WeakActorLifecycleProxy* IProtocol::GetWeakLifecycleProxy() { + return mLifecycleProxy ? mLifecycleProxy->GetWeakProxy() : nullptr; +} + +IProtocol::~IProtocol() { + // If the actor still has a lifecycle proxy when it is being torn down, it + // means that IPC was not given control over the lifecycle of the actor + // correctly. Usually this means that the actor was destroyed while IPC is + // calling a message handler for it, and the actor incorrectly frees itself + // during that operation. + // + // As this happens unfortunately frequently, due to many odd protocols in + // Gecko, simply emit a warning and clear the weak backreference from our + // LifecycleProxy back to us. + if (mLifecycleProxy) { + NS_WARNING( + nsPrintfCString("Actor destructor for '%s%s' called before IPC " + "lifecycle complete!\n" + "References to this actor may unexpectedly dangle!", + GetProtocolName(), StringFromIPCSide(GetSide())) + .get()); + + mLifecycleProxy->mActor = nullptr; + + // If we are somehow being destroyed while active, make sure that the + // existing IPC reference has been freed. If the status of the actor is + // `Destroyed`, the reference has already been freed, and we shouldn't free + // it a second time. + MOZ_ASSERT(mLinkStatus != LinkStatus::Inactive); + if (mLinkStatus != LinkStatus::Destroyed) { + NS_IF_RELEASE(mLifecycleProxy); + } + mLifecycleProxy = nullptr; + } +} + +// The following methods either directly forward to the toplevel protocol, or +// almost directly do. +int32_t IProtocol::Register(IProtocol* aRouted) { + return mToplevel->Register(aRouted); +} +int32_t IProtocol::RegisterID(IProtocol* aRouted, int32_t aId) { + return mToplevel->RegisterID(aRouted, aId); +} +IProtocol* IProtocol::Lookup(int32_t aId) { return mToplevel->Lookup(aId); } +void IProtocol::Unregister(int32_t aId) { + if (aId == mId) { + mId = kFreedActorId; + } + return mToplevel->Unregister(aId); +} + +Shmem::SharedMemory* IProtocol::CreateSharedMemory(size_t aSize, bool aUnsafe, + int32_t* aId) { + return mToplevel->CreateSharedMemory(aSize, aUnsafe, aId); +} +Shmem::SharedMemory* IProtocol::LookupSharedMemory(int32_t aId) { + return mToplevel->LookupSharedMemory(aId); +} +bool IProtocol::IsTrackingSharedMemory(Shmem::SharedMemory* aSegment) { + return mToplevel->IsTrackingSharedMemory(aSegment); +} +bool IProtocol::DestroySharedMemory(Shmem& aShmem) { + return mToplevel->DestroySharedMemory(aShmem); +} + +MessageChannel* IProtocol::GetIPCChannel() { + return mToplevel->GetIPCChannel(); +} +const MessageChannel* IProtocol::GetIPCChannel() const { + return mToplevel->GetIPCChannel(); +} + +nsISerialEventTarget* IProtocol::GetActorEventTarget() { + return GetIPCChannel()->GetWorkerEventTarget(); +} + +void IProtocol::SetId(int32_t aId) { + MOZ_ASSERT(mId == aId || mLinkStatus == LinkStatus::Inactive); + mId = aId; +} + +Maybe<IProtocol*> IProtocol::ReadActor(IPC::MessageReader* aReader, + bool aNullable, + const char* aActorDescription, + int32_t aProtocolTypeId) { + int32_t id; + if (!IPC::ReadParam(aReader, &id)) { + ActorIdReadError(aActorDescription); + return Nothing(); + } + + if (id == 1 || (id == 0 && !aNullable)) { + BadActorIdError(aActorDescription); + return Nothing(); + } + + if (id == 0) { + return Some(static_cast<IProtocol*>(nullptr)); + } + + IProtocol* listener = this->Lookup(id); + if (!listener) { + ActorLookupError(aActorDescription); + return Nothing(); + } + + if (listener->GetProtocolId() != aProtocolTypeId) { + MismatchedActorTypeError(aActorDescription); + return Nothing(); + } + + return Some(listener); +} + +void IProtocol::FatalError(const char* const aErrorMsg) { + HandleFatalError(aErrorMsg); +} + +void IProtocol::HandleFatalError(const char* aErrorMsg) { + if (IProtocol* manager = Manager()) { + manager->HandleFatalError(aErrorMsg); + return; + } + + mozilla::ipc::FatalError(aErrorMsg, mSide == ParentSide); + if (CanSend()) { + GetIPCChannel()->InduceConnectionError(); + } +} + +bool IProtocol::AllocShmem(size_t aSize, Shmem* aOutMem) { + if (!CanSend()) { + NS_WARNING( + "Shmem not allocated. Cannot communicate with the other actor."); + return false; + } + + Shmem::id_t id; + Shmem::SharedMemory* rawmem(CreateSharedMemory(aSize, false, &id)); + if (!rawmem) { + return false; + } + + *aOutMem = Shmem(rawmem, id, aSize, false); + return true; +} + +bool IProtocol::AllocUnsafeShmem(size_t aSize, Shmem* aOutMem) { + if (!CanSend()) { + NS_WARNING( + "Shmem not allocated. Cannot communicate with the other actor."); + return false; + } + + Shmem::id_t id; + Shmem::SharedMemory* rawmem(CreateSharedMemory(aSize, true, &id)); + if (!rawmem) { + return false; + } + + *aOutMem = Shmem(rawmem, id, aSize, true); + return true; +} + +bool IProtocol::DeallocShmem(Shmem& aMem) { + bool ok = DestroySharedMemory(aMem); +#ifdef DEBUG + if (!ok) { + if (mSide == ChildSide) { + FatalError("bad Shmem"); + } else { + NS_WARNING("bad Shmem"); + } + return false; + } +#endif // DEBUG + aMem.forget(); + return ok; +} + +void IProtocol::SetManager(IProtocol* aManager) { + MOZ_RELEASE_ASSERT(!mManager || mManager == aManager); + mManager = aManager; + mToplevel = aManager->mToplevel; +} + +void IProtocol::SetManagerAndRegister(IProtocol* aManager) { + // Set the manager prior to registering so registering properly inherits + // the manager's event target. + SetManager(aManager); + + aManager->Register(this); +} + +void IProtocol::SetManagerAndRegister(IProtocol* aManager, int32_t aId) { + // Set the manager prior to registering so registering properly inherits + // the manager's event target. + SetManager(aManager); + + aManager->RegisterID(this, aId); +} + +bool IProtocol::ChannelSend(UniquePtr<IPC::Message> aMsg) { + if (CanSend()) { + // NOTE: This send call failing can only occur during toplevel channel + // teardown. As this is an async call, this isn't reasonable to predict or + // respond to, so just drop the message on the floor silently. + GetIPCChannel()->Send(std::move(aMsg)); + return true; + } + + WarnMessageDiscarded(aMsg.get()); + return false; +} + +bool IProtocol::ChannelSend(UniquePtr<IPC::Message> aMsg, + UniquePtr<IPC::Message>* aReply) { + if (CanSend()) { + return GetIPCChannel()->Send(std::move(aMsg), aReply); + } + + WarnMessageDiscarded(aMsg.get()); + return false; +} + +#ifdef DEBUG +void IProtocol::WarnMessageDiscarded(IPC::Message* aMsg) { + NS_WARNING(nsPrintfCString("IPC message '%s' discarded: actor cannot send", + aMsg->name()) + .get()); +} +#endif + +void IProtocol::ActorConnected() { + if (mLinkStatus != LinkStatus::Inactive) { + return; + } + +#ifdef FUZZING_SNAPSHOT + fuzzing::IPCFuzzController::instance().OnActorConnected(this); +#endif + + mLinkStatus = LinkStatus::Connected; + + MOZ_ASSERT(!mLifecycleProxy, "double-connecting live actor"); + mLifecycleProxy = new ActorLifecycleProxy(this); + NS_ADDREF(mLifecycleProxy); // Reference freed in DestroySubtree(); +} + +void IProtocol::DoomSubtree() { + MOZ_ASSERT(CanSend(), "dooming non-connected actor"); + MOZ_ASSERT(mLifecycleProxy, "dooming zombie actor"); + + nsTArray<RefPtr<ActorLifecycleProxy>> managed; + AllManagedActors(managed); + for (ActorLifecycleProxy* proxy : managed) { + // Guard against actor being disconnected or destroyed during previous Doom + IProtocol* actor = proxy->Get(); + if (actor && actor->CanSend()) { + actor->DoomSubtree(); + } + } + + // ActorDoom is called immediately before changing state, this allows messages + // to be sent during ActorDoom immediately before the channel is closed and + // sending messages is disabled. + ActorDoom(); + mLinkStatus = LinkStatus::Doomed; +} + +void IProtocol::DestroySubtree(ActorDestroyReason aWhy) { + MOZ_ASSERT(CanRecv(), "destroying non-connected actor"); + MOZ_ASSERT(mLifecycleProxy, "destroying zombie actor"); + +#ifdef FUZZING_SNAPSHOT + fuzzing::IPCFuzzController::instance().OnActorDestroyed(this); +#endif + + int32_t id = Id(); + + // If we're a managed actor, unregister from our manager + if (Manager()) { + Unregister(id); + } + + // Destroy subtree + ActorDestroyReason subtreeWhy = aWhy; + if (aWhy == Deletion || aWhy == FailedConstructor) { + subtreeWhy = AncestorDeletion; + } + + nsTArray<RefPtr<ActorLifecycleProxy>> managed; + AllManagedActors(managed); + for (ActorLifecycleProxy* proxy : managed) { + // Guard against actor being disconnected or destroyed during previous + // Destroy + IProtocol* actor = proxy->Get(); + if (actor && actor->CanRecv()) { + actor->DestroySubtree(subtreeWhy); + } + } + + // Ensure that we don't send any messages while we're calling `ActorDestroy` + // by setting our state to `Doomed`. + mLinkStatus = LinkStatus::Doomed; + + // The actor is being destroyed, reject any pending responses, invoke + // `ActorDestroy` to destroy it, and then clear our status to + // `LinkStatus::Destroyed`. + GetIPCChannel()->RejectPendingResponsesForActor(id); + ActorDestroy(aWhy); + mLinkStatus = LinkStatus::Destroyed; +} + +IToplevelProtocol::IToplevelProtocol(const char* aName, ProtocolId aProtoId, + Side aSide) + : IRefCountedProtocol(aProtoId, aSide), + mOtherPid(base::kInvalidProcessId), + mLastLocalId(0), + mChannel(aName, this) { + mToplevel = this; +} + +void IToplevelProtocol::SetOtherProcessId(base::ProcessId aOtherPid) { + mOtherPid = aOtherPid; +} + +bool IToplevelProtocol::Open(ScopedPort aPort, const nsID& aMessageChannelId, + base::ProcessId aOtherPid, + nsISerialEventTarget* aEventTarget) { + SetOtherProcessId(aOtherPid); + return GetIPCChannel()->Open(std::move(aPort), mSide, aMessageChannelId, + aEventTarget); +} + +bool IToplevelProtocol::Open(IToplevelProtocol* aTarget, + nsISerialEventTarget* aEventTarget, + mozilla::ipc::Side aSide) { + SetOtherProcessId(base::GetCurrentProcId()); + aTarget->SetOtherProcessId(base::GetCurrentProcId()); + return GetIPCChannel()->Open(aTarget->GetIPCChannel(), aEventTarget, aSide); +} + +bool IToplevelProtocol::OpenOnSameThread(IToplevelProtocol* aTarget, + Side aSide) { + SetOtherProcessId(base::GetCurrentProcId()); + aTarget->SetOtherProcessId(base::GetCurrentProcId()); + return GetIPCChannel()->OpenOnSameThread(aTarget->GetIPCChannel(), aSide); +} + +void IToplevelProtocol::NotifyImpendingShutdown() { + if (CanRecv()) { + GetIPCChannel()->NotifyImpendingShutdown(); + } +} + +void IToplevelProtocol::Close() { GetIPCChannel()->Close(); } + +void IToplevelProtocol::SetReplyTimeoutMs(int32_t aTimeoutMs) { + GetIPCChannel()->SetReplyTimeoutMs(aTimeoutMs); +} + +bool IToplevelProtocol::IsOnCxxStack() const { + return GetIPCChannel()->IsOnCxxStack(); +} + +int32_t IToplevelProtocol::NextId() { + // Generate the next ID to use for a shared memory or protocol. Parent and + // Child sides of the protocol use different pools. + int32_t tag = 0; + if (GetSide() == ParentSide) { + tag |= 1 << 1; + } + + // Check any overflow + MOZ_RELEASE_ASSERT(mLastLocalId < (1 << 29)); + + // Compute the ID to use with the low two bits as our tag, and the remaining + // bits as a monotonic. + return (++mLastLocalId << 2) | tag; +} + +int32_t IToplevelProtocol::Register(IProtocol* aRouted) { + if (aRouted->Id() != kNullActorId && aRouted->Id() != kFreedActorId) { + // If there's already an ID, just return that. + return aRouted->Id(); + } + return RegisterID(aRouted, NextId()); +} + +int32_t IToplevelProtocol::RegisterID(IProtocol* aRouted, int32_t aId) { + aRouted->SetId(aId); + aRouted->ActorConnected(); + MOZ_ASSERT(!mActorMap.Contains(aId), "Don't insert with an existing ID"); + mActorMap.InsertOrUpdate(aId, aRouted); + return aId; +} + +IProtocol* IToplevelProtocol::Lookup(int32_t aId) { return mActorMap.Get(aId); } + +void IToplevelProtocol::Unregister(int32_t aId) { + MOZ_ASSERT(mActorMap.Contains(aId), + "Attempting to remove an ID not in the actor map"); + mActorMap.Remove(aId); +} + +Shmem::SharedMemory* IToplevelProtocol::CreateSharedMemory(size_t aSize, + bool aUnsafe, + Shmem::id_t* aId) { + RefPtr<Shmem::SharedMemory> segment(Shmem::Alloc(aSize)); + if (!segment) { + return nullptr; + } + int32_t id = NextId(); + Shmem shmem(segment.get(), id, aSize, aUnsafe); + + UniquePtr<Message> descriptor = shmem.MkCreatedMessage(MSG_ROUTING_CONTROL); + if (!descriptor) { + return nullptr; + } + Unused << GetIPCChannel()->Send(std::move(descriptor)); + + *aId = shmem.Id(); + Shmem::SharedMemory* rawSegment = segment.get(); + MOZ_ASSERT(!mShmemMap.Contains(*aId), "Don't insert with an existing ID"); + mShmemMap.InsertOrUpdate(*aId, std::move(segment)); + return rawSegment; +} + +Shmem::SharedMemory* IToplevelProtocol::LookupSharedMemory(Shmem::id_t aId) { + auto entry = mShmemMap.Lookup(aId); + return entry ? entry.Data().get() : nullptr; +} + +bool IToplevelProtocol::IsTrackingSharedMemory(Shmem::SharedMemory* segment) { + for (const auto& shmem : mShmemMap.Values()) { + if (segment == shmem) { + return true; + } + } + return false; +} + +bool IToplevelProtocol::DestroySharedMemory(Shmem& shmem) { + Shmem::id_t aId = shmem.Id(); + Shmem::SharedMemory* segment = LookupSharedMemory(aId); + if (!segment) { + return false; + } + + UniquePtr<Message> descriptor = shmem.MkDestroyedMessage(MSG_ROUTING_CONTROL); + + MOZ_ASSERT(mShmemMap.Contains(aId), + "Attempting to remove an ID not in the shmem map"); + mShmemMap.Remove(aId); + + MessageChannel* channel = GetIPCChannel(); + if (!channel->CanSend()) { + return true; + } + + return descriptor && channel->Send(std::move(descriptor)); +} + +void IToplevelProtocol::DeallocShmems() { mShmemMap.Clear(); } + +bool IToplevelProtocol::ShmemCreated(const Message& aMsg) { + Shmem::id_t id; + RefPtr<Shmem::SharedMemory> rawmem(Shmem::OpenExisting(aMsg, &id, true)); + if (!rawmem) { + return false; + } + MOZ_ASSERT(!mShmemMap.Contains(id), "Don't insert with an existing ID"); + mShmemMap.InsertOrUpdate(id, std::move(rawmem)); + return true; +} + +bool IToplevelProtocol::ShmemDestroyed(const Message& aMsg) { + Shmem::id_t id; + MessageReader reader(aMsg); + if (!IPC::ReadParam(&reader, &id)) { + return false; + } + reader.EndRead(); + + mShmemMap.Remove(id); + return true; +} + +IPDLResolverInner::IPDLResolverInner(UniquePtr<IPC::Message> aReply, + IProtocol* aActor) + : mReply(std::move(aReply)), + mWeakProxy(aActor->GetLifecycleProxy()->GetWeakProxy()) {} + +void IPDLResolverInner::ResolveOrReject( + bool aResolve, FunctionRef<void(IPC::Message*, IProtocol*)> aWrite) { + MOZ_ASSERT(mWeakProxy); + MOZ_ASSERT(mWeakProxy->ActorEventTarget()->IsOnCurrentThread()); + MOZ_ASSERT(mReply); + + UniquePtr<IPC::Message> reply = std::move(mReply); + + IProtocol* actor = mWeakProxy->Get(); + if (!actor) { + NS_WARNING(nsPrintfCString("Not resolving response '%s': actor is dead", + reply->name()) + .get()); + return; + } + + IPC::MessageWriter writer(*reply, actor); + WriteIPDLParam(&writer, actor, aResolve); + aWrite(reply.get(), actor); + + actor->ChannelSend(std::move(reply)); +} + +void IPDLResolverInner::Destroy() { + if (mReply) { + NS_PROXY_DELETE_TO_EVENT_TARGET(IPDLResolverInner, + mWeakProxy->ActorEventTarget()); + } else { + // If we've already been consumed, just delete without proxying. This avoids + // leaking the resolver if the actor's thread is already dead. + delete this; + } +} + +IPDLResolverInner::~IPDLResolverInner() { + if (mReply) { + NS_WARNING( + nsPrintfCString( + "Rejecting reply '%s': resolver dropped without being called", + mReply->name()) + .get()); + ResolveOrReject(false, [](IPC::Message* aMessage, IProtocol* aActor) { + IPC::MessageWriter writer(*aMessage, aActor); + ResponseRejectReason reason = ResponseRejectReason::ResolverDestroyed; + WriteIPDLParam(&writer, aActor, reason); + }); + } +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProtocolUtils.h b/ipc/glue/ProtocolUtils.h new file mode 100644 index 0000000000..2a0c64de20 --- /dev/null +++ b/ipc/glue/ProtocolUtils.h @@ -0,0 +1,781 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_ProtocolUtils_h +#define mozilla_ipc_ProtocolUtils_h + +#include <cstddef> +#include <cstdint> +#include <utility> +#include "IPCMessageStart.h" +#include "base/basictypes.h" +#include "base/process.h" +#include "chrome/common/ipc_message.h" +#include "mojo/core/ports/port_ref.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/FunctionRef.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/MessageLink.h" +#include "mozilla/ipc/SharedMemory.h" +#include "mozilla/ipc/Shmem.h" +#include "nsPrintfCString.h" +#include "nsTHashMap.h" +#include "nsDebug.h" +#include "nsISupports.h" +#include "nsTArrayForwardDeclare.h" +#include "nsTHashSet.h" + +// XXX Things that could be moved to ProtocolUtils.cpp +#include "base/process_util.h" // for CloseProcessHandle +#include "prenv.h" // for PR_GetEnv + +#if defined(ANDROID) && defined(DEBUG) +# include <android/log.h> +#endif + +template <typename T> +class nsPtrHashKey; + +// WARNING: this takes into account the private, special-message-type +// enum in ipc_channel.h. They need to be kept in sync. +namespace { +// XXX the max message ID is actually kuint32max now ... when this +// changed, the assumptions of the special message IDs changed in that +// they're not carving out messages from likely-unallocated space, but +// rather carving out messages from the end of space allocated to +// protocol 0. Oops! We can get away with this until protocol 0 +// starts approaching its 65,536th message. +enum { + // Message types used by DataPipe + DATA_PIPE_CLOSED_MESSAGE_TYPE = kuint16max - 18, + DATA_PIPE_BYTES_CONSUMED_MESSAGE_TYPE = kuint16max - 17, + + // Message types used by NodeChannel + ACCEPT_INVITE_MESSAGE_TYPE = kuint16max - 16, + REQUEST_INTRODUCTION_MESSAGE_TYPE = kuint16max - 15, + INTRODUCE_MESSAGE_TYPE = kuint16max - 14, + BROADCAST_MESSAGE_TYPE = kuint16max - 13, + EVENT_MESSAGE_TYPE = kuint16max - 12, + + // Message types used by MessageChannel + MANAGED_ENDPOINT_DROPPED_MESSAGE_TYPE = kuint16max - 11, + MANAGED_ENDPOINT_BOUND_MESSAGE_TYPE = kuint16max - 10, + IMPENDING_SHUTDOWN_MESSAGE_TYPE = kuint16max - 9, + BUILD_IDS_MATCH_MESSAGE_TYPE = kuint16max - 8, + BUILD_ID_MESSAGE_TYPE = kuint16max - 7, // unused + CHANNEL_OPENED_MESSAGE_TYPE = kuint16max - 6, + SHMEM_DESTROYED_MESSAGE_TYPE = kuint16max - 5, + SHMEM_CREATED_MESSAGE_TYPE = kuint16max - 4, + GOODBYE_MESSAGE_TYPE = kuint16max - 3, + CANCEL_MESSAGE_TYPE = kuint16max - 2, + + // kuint16max - 1 is used by ipc_channel.h. +}; + +} // namespace + +class MessageLoop; +class PickleIterator; +class nsISerialEventTarget; + +namespace mozilla { +class SchedulerGroup; + +namespace dom { +class ContentParent; +} // namespace dom + +namespace net { +class NeckoParent; +} // namespace net + +namespace ipc { + +class ProtocolFdMapping; +class ProtocolCloneContext; + +// Used to pass references to protocol actors across the wire. +// Actors created on the parent-side have a positive ID, and actors +// allocated on the child side have a negative ID. +struct ActorHandle { + int mId; +}; + +// What happens if Interrupt calls race? +enum RacyInterruptPolicy { RIPError, RIPChildWins, RIPParentWins }; + +enum class LinkStatus : uint8_t { + // The actor has not established a link yet, or the actor is no longer in use + // by IPC, and its 'Dealloc' method has been called or is being called. + // + // NOTE: This state is used instead of an explicit `Freed` state when IPC no + // longer holds references to the current actor as we currently re-open + // existing actors. Once we fix these poorly behaved actors, this loopback + // state can be split to have the final state not be the same as the initial + // state. + Inactive, + + // A live link is connected to the other side of this actor. + Connected, + + // The link has begun being destroyed. Messages may still be received, but + // cannot be sent. (exception: sync/intr replies may be sent while Doomed). + Doomed, + + // The link has been destroyed, and messages will no longer be sent or + // received. + Destroyed, +}; + +typedef IPCMessageStart ProtocolId; + +// Generated by IPDL compiler +const char* ProtocolIdToName(IPCMessageStart aId); + +class IRefCountedProtocol; +class IToplevelProtocol; +class ActorLifecycleProxy; +class WeakActorLifecycleProxy; +class IPDLResolverInner; +class UntypedManagedEndpoint; + +class IProtocol : public HasResultCodes { + public: + enum ActorDestroyReason { + FailedConstructor, + Deletion, + AncestorDeletion, + NormalShutdown, + AbnormalShutdown, + ManagedEndpointDropped + }; + + typedef base::ProcessId ProcessId; + typedef IPC::Message Message; + + IProtocol(ProtocolId aProtoId, Side aSide) + : mId(0), + mProtocolId(aProtoId), + mSide(aSide), + mLinkStatus(LinkStatus::Inactive), + mLifecycleProxy(nullptr), + mManager(nullptr), + mToplevel(nullptr) {} + + IToplevelProtocol* ToplevelProtocol() { return mToplevel; } + const IToplevelProtocol* ToplevelProtocol() const { return mToplevel; } + + // The following methods either directly forward to the toplevel protocol, or + // almost directly do. + int32_t Register(IProtocol* aRouted); + int32_t RegisterID(IProtocol* aRouted, int32_t aId); + IProtocol* Lookup(int32_t aId); + void Unregister(int32_t aId); + + Shmem::SharedMemory* CreateSharedMemory(size_t aSize, bool aUnsafe, + int32_t* aId); + Shmem::SharedMemory* LookupSharedMemory(int32_t aId); + bool IsTrackingSharedMemory(Shmem::SharedMemory* aSegment); + bool DestroySharedMemory(Shmem& aShmem); + + MessageChannel* GetIPCChannel(); + const MessageChannel* GetIPCChannel() const; + + // Get the nsISerialEventTarget which all messages sent to this actor will be + // processed on. Unless stated otherwise, all operations on IProtocol which + // don't occur on this `nsISerialEventTarget` are unsafe. + nsISerialEventTarget* GetActorEventTarget(); + + // Actor lifecycle and other properties. + ProtocolId GetProtocolId() const { return mProtocolId; } + const char* GetProtocolName() const { return ProtocolIdToName(mProtocolId); } + + int32_t Id() const { return mId; } + IProtocol* Manager() const { return mManager; } + + ActorLifecycleProxy* GetLifecycleProxy() { return mLifecycleProxy; } + WeakActorLifecycleProxy* GetWeakLifecycleProxy(); + + Side GetSide() const { return mSide; } + bool CanSend() const { return mLinkStatus == LinkStatus::Connected; } + bool CanRecv() const { + return mLinkStatus == LinkStatus::Connected || + mLinkStatus == LinkStatus::Doomed; + } + + // Remove or deallocate a managee given its type. + virtual void RemoveManagee(int32_t, IProtocol*) = 0; + virtual void DeallocManagee(int32_t, IProtocol*) = 0; + + Maybe<IProtocol*> ReadActor(IPC::MessageReader* aReader, bool aNullable, + const char* aActorDescription, + int32_t aProtocolTypeId); + + virtual Result OnMessageReceived(const Message& aMessage) = 0; + virtual Result OnMessageReceived(const Message& aMessage, + UniquePtr<Message>& aReply) = 0; + virtual Result OnCallReceived(const Message& aMessage, + UniquePtr<Message>& aReply) = 0; + bool AllocShmem(size_t aSize, Shmem* aOutMem); + bool AllocUnsafeShmem(size_t aSize, Shmem* aOutMem); + bool DeallocShmem(Shmem& aMem); + + void FatalError(const char* const aErrorMsg); + virtual void HandleFatalError(const char* aErrorMsg); + + protected: + virtual ~IProtocol(); + + friend class IToplevelProtocol; + friend class ActorLifecycleProxy; + friend class IPDLResolverInner; + friend class UntypedManagedEndpoint; + + void SetId(int32_t aId); + + // We have separate functions because the accessibility code manually + // calls SetManager. + void SetManager(IProtocol* aManager); + + // Sets the manager for the protocol and registers the protocol with + // its manager, setting up channels for the protocol as well. Not + // for use outside of IPDL. + void SetManagerAndRegister(IProtocol* aManager); + void SetManagerAndRegister(IProtocol* aManager, int32_t aId); + + // Helpers for calling `Send` on our underlying IPC channel. + bool ChannelSend(UniquePtr<IPC::Message> aMsg); + bool ChannelSend(UniquePtr<IPC::Message> aMsg, + UniquePtr<IPC::Message>* aReply); + template <typename Value> + void ChannelSend(UniquePtr<IPC::Message> aMsg, + IPC::Message::msgid_t aReplyMsgId, + ResolveCallback<Value>&& aResolve, + RejectCallback&& aReject) { + if (CanSend()) { + GetIPCChannel()->Send(std::move(aMsg), Id(), aReplyMsgId, + std::move(aResolve), std::move(aReject)); + } else { + WarnMessageDiscarded(aMsg.get()); + aReject(ResponseRejectReason::SendError); + } + } + + // Collect all actors managed by this object in an array. To make this safer + // to iterate, `ActorLifecycleProxy` references are returned rather than raw + // actor pointers. + virtual void AllManagedActors( + nsTArray<RefPtr<ActorLifecycleProxy>>& aActors) const = 0; + + virtual uint32_t AllManagedActorsCount() const = 0; + + // Internal method called when the actor becomes connected. + void ActorConnected(); + + // Called immediately before setting the actor state to doomed, and triggering + // async actor destruction. Messages may be sent from this callback, but no + // later. + // FIXME(nika): This is currently unused! + virtual void ActorDoom() {} + void DoomSubtree(); + + // Called when the actor has been destroyed due to an error, a __delete__ + // message, or a __doom__ reply. + virtual void ActorDestroy(ActorDestroyReason aWhy) {} + void DestroySubtree(ActorDestroyReason aWhy); + + // Called when IPC has acquired its first reference to the actor. This method + // may take references which will later be freed by `ActorDealloc`. + virtual void ActorAlloc() = 0; + + // Called when IPC has released its final reference to the actor. It will call + // the dealloc method, causing the actor to be actually freed. + // + // The actor has been freed after this method returns. + virtual void ActorDealloc() = 0; + + static const int32_t kNullActorId = 0; + static const int32_t kFreedActorId = 1; + + private: +#ifdef DEBUG + void WarnMessageDiscarded(IPC::Message* aMsg); +#else + void WarnMessageDiscarded(IPC::Message*) {} +#endif + + int32_t mId; + ProtocolId mProtocolId; + Side mSide; + LinkStatus mLinkStatus; + ActorLifecycleProxy* mLifecycleProxy; + IProtocol* mManager; + IToplevelProtocol* mToplevel; +}; + +#define IPC_OK() mozilla::ipc::IPCResult::Ok() +#define IPC_FAIL(actor, why) \ + mozilla::ipc::IPCResult::Fail(WrapNotNull(actor), __func__, (why)) +#define IPC_FAIL_NO_REASON(actor) \ + mozilla::ipc::IPCResult::Fail(WrapNotNull(actor), __func__) + +/* + * IPC_FAIL_UNSAFE_PRINTF(actor, format, ...) + * + * Create a failure IPCResult with a dynamic reason-string. + * + * @note This macro causes data collection because IPC failure reasons may be + * sent to crash-stats, where they are publicly visible. Firefox data stewards + * must do data review on usages of this macro. + */ +#define IPC_FAIL_UNSAFE_PRINTF(actor, format, ...) \ + mozilla::ipc::IPCResult::FailUnsafePrintfImpl( \ + WrapNotNull(actor), __func__, nsPrintfCString(format, ##__VA_ARGS__)) + +/** + * All message deserializers and message handlers should return this type via + * the above macros. We use a less generic name here to avoid conflict with + * `mozilla::Result` because we have quite a few `using namespace mozilla::ipc;` + * in the code base. + * + * Note that merely constructing a failure-result, whether directly or via the + * IPC_FAIL macros, causes the associated error message to be processed + * immediately. + */ +class IPCResult { + public: + static IPCResult Ok() { return IPCResult(true); } + + // IPC failure messages can sometimes end up in telemetry. As such, to avoid + // accidentally exfiltrating sensitive information without a data review, we + // require that they be constant strings. + template <size_t N, size_t M> + static IPCResult Fail(NotNull<IProtocol*> aActor, const char (&aWhere)[N], + const char (&aWhy)[M]) { + return FailImpl(aActor, aWhere, aWhy); + } + template <size_t N> + static IPCResult Fail(NotNull<IProtocol*> aActor, const char (&aWhere)[N]) { + return FailImpl(aActor, aWhere, ""); + } + + MOZ_IMPLICIT operator bool() const { return mSuccess; } + + // Only used by IPC_FAIL_UNSAFE_PRINTF (q.v.). Do not call this directly. (Or + // at least get data-review's approval if you do.) + template <size_t N> + static IPCResult FailUnsafePrintfImpl(NotNull<IProtocol*> aActor, + const char (&aWhere)[N], + nsPrintfCString const& aWhy) { + return FailImpl(aActor, aWhere, aWhy.get()); + } + + private: + static IPCResult FailImpl(NotNull<IProtocol*> aActor, const char* aWhere, + const char* aWhy); + + explicit IPCResult(bool aResult) : mSuccess(aResult) {} + bool mSuccess; +}; + +class UntypedEndpoint; + +template <class PFooSide> +class Endpoint; + +template <class PFooSide> +class ManagedEndpoint; + +/** + * All refcounted protocols should inherit this class. + */ +class IRefCountedProtocol : public IProtocol { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + using IProtocol::IProtocol; +}; + +/** + * All top-level protocols should inherit this class. + * + * IToplevelProtocol tracks all top-level protocol actors created from + * this protocol actor. + */ +class IToplevelProtocol : public IRefCountedProtocol { + template <class PFooSide> + friend class Endpoint; + + protected: + explicit IToplevelProtocol(const char* aName, ProtocolId aProtoId, + Side aSide); + ~IToplevelProtocol() = default; + + public: + // Shadow methods on IProtocol which are implemented directly on toplevel + // actors. + int32_t Register(IProtocol* aRouted); + int32_t RegisterID(IProtocol* aRouted, int32_t aId); + IProtocol* Lookup(int32_t aId); + void Unregister(int32_t aId); + + Shmem::SharedMemory* CreateSharedMemory(size_t aSize, bool aUnsafe, + int32_t* aId); + Shmem::SharedMemory* LookupSharedMemory(int32_t aId); + bool IsTrackingSharedMemory(Shmem::SharedMemory* aSegment); + bool DestroySharedMemory(Shmem& aShmem); + + MessageChannel* GetIPCChannel() { return &mChannel; } + const MessageChannel* GetIPCChannel() const { return &mChannel; } + + void SetOtherProcessId(base::ProcessId aOtherPid); + + virtual void OnChannelClose() = 0; + virtual void OnChannelError() = 0; + virtual void ProcessingError(Result aError, const char* aMsgName) {} + + bool Open(ScopedPort aPort, const nsID& aMessageChannelId, + base::ProcessId aOtherPid, + nsISerialEventTarget* aEventTarget = nullptr); + + bool Open(IToplevelProtocol* aTarget, nsISerialEventTarget* aEventTarget, + mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide); + + // Open a toplevel actor such that both ends of the actor's channel are on + // the same thread. This method should be called on the thread to perform + // the link. + // + // WARNING: Attempting to send a sync or intr message on the same thread + // will crash. + bool OpenOnSameThread(IToplevelProtocol* aTarget, + mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide); + + /** + * This sends a special message that is processed on the IO thread, so that + * other actors can know that the process will soon shutdown. + */ + void NotifyImpendingShutdown(); + + void Close(); + + void SetReplyTimeoutMs(int32_t aTimeoutMs); + + void DeallocShmems(); + bool ShmemCreated(const Message& aMsg); + bool ShmemDestroyed(const Message& aMsg); + + virtual bool ShouldContinueFromReplyTimeout() { return false; } + + // WARNING: This function is called with the MessageChannel monitor held. + virtual void IntentionalCrash() { MOZ_CRASH("Intentional IPDL crash"); } + + // The code here is only useful for fuzzing. It should not be used for any + // other purpose. +#ifdef DEBUG + // Returns true if we should simulate a timeout. + // WARNING: This is a testing-only function that is called with the + // MessageChannel monitor held. Don't do anything fancy here or we could + // deadlock. + virtual bool ArtificialTimeout() { return false; } + + // Returns true if we want to cause the worker thread to sleep with the + // monitor unlocked. + virtual bool NeedArtificialSleep() { return false; } + + // This function should be implemented to sleep for some amount of time on + // the worker thread. Will only be called if NeedArtificialSleep() returns + // true. + virtual void ArtificialSleep() {} +#else + bool ArtificialTimeout() { return false; } + bool NeedArtificialSleep() { return false; } + void ArtificialSleep() {} +#endif + + bool IsOnCxxStack() const; + + virtual void ProcessRemoteNativeEventsInInterruptCall() {} + + virtual void OnChannelReceivedMessage(const Message& aMsg) {} + + void OnIPCChannelOpened() { ActorConnected(); } + + base::ProcessId OtherPidMaybeInvalid() const { return mOtherPid; } + + private: + int32_t NextId(); + + template <class T> + using IDMap = nsTHashMap<nsUint32HashKey, T>; + + base::ProcessId mOtherPid; + + // NOTE NOTE NOTE + // Used to be on mState + int32_t mLastLocalId; + IDMap<IProtocol*> mActorMap; + IDMap<RefPtr<Shmem::SharedMemory>> mShmemMap; + + MessageChannel mChannel; +}; + +class IShmemAllocator { + public: + virtual bool AllocShmem(size_t aSize, mozilla::ipc::Shmem* aShmem) = 0; + virtual bool AllocUnsafeShmem(size_t aSize, mozilla::ipc::Shmem* aShmem) = 0; + virtual bool DeallocShmem(mozilla::ipc::Shmem& aShmem) = 0; +}; + +#define FORWARD_SHMEM_ALLOCATOR_TO(aImplClass) \ + virtual bool AllocShmem(size_t aSize, mozilla::ipc::Shmem* aShmem) \ + override { \ + return aImplClass::AllocShmem(aSize, aShmem); \ + } \ + virtual bool AllocUnsafeShmem(size_t aSize, mozilla::ipc::Shmem* aShmem) \ + override { \ + return aImplClass::AllocUnsafeShmem(aSize, aShmem); \ + } \ + virtual bool DeallocShmem(mozilla::ipc::Shmem& aShmem) override { \ + return aImplClass::DeallocShmem(aShmem); \ + } + +inline bool LoggingEnabled() { +#if defined(DEBUG) || defined(FUZZING) + return !!PR_GetEnv("MOZ_IPC_MESSAGE_LOG"); +#else + return false; +#endif +} + +#if defined(DEBUG) || defined(FUZZING) +bool LoggingEnabledFor(const char* aTopLevelProtocol, mozilla::ipc::Side aSide, + const char* aFilter); +#endif + +inline bool LoggingEnabledFor(const char* aTopLevelProtocol, + mozilla::ipc::Side aSide) { +#if defined(DEBUG) || defined(FUZZING) + return LoggingEnabledFor(aTopLevelProtocol, aSide, + PR_GetEnv("MOZ_IPC_MESSAGE_LOG")); +#else + return false; +#endif +} + +MOZ_NEVER_INLINE void LogMessageForProtocol(const char* aTopLevelProtocol, + base::ProcessId aOtherPid, + const char* aContextDescription, + uint32_t aMessageId, + MessageDirection aDirection); + +MOZ_NEVER_INLINE void ProtocolErrorBreakpoint(const char* aMsg); + +// IPC::MessageReader and IPC::MessageWriter call this function for FatalError +// calls which come from serialization/deserialization. +MOZ_NEVER_INLINE void PickleFatalError(const char* aMsg, IProtocol* aActor); + +// The code generator calls this function for errors which come from the +// methods of protocols. Doing this saves codesize by making the error +// cases significantly smaller. +MOZ_NEVER_INLINE void FatalError(const char* aMsg, bool aIsParent); + +// The code generator calls this function for errors which are not +// protocol-specific: errors in generated struct methods or errors in +// transition functions, for instance. Doing this saves codesize by +// by making the error cases significantly smaller. +MOZ_NEVER_INLINE void LogicError(const char* aMsg); + +MOZ_NEVER_INLINE void ActorIdReadError(const char* aActorDescription); + +MOZ_NEVER_INLINE void BadActorIdError(const char* aActorDescription); + +MOZ_NEVER_INLINE void ActorLookupError(const char* aActorDescription); + +MOZ_NEVER_INLINE void MismatchedActorTypeError(const char* aActorDescription); + +MOZ_NEVER_INLINE void UnionTypeReadError(const char* aUnionName); + +MOZ_NEVER_INLINE void ArrayLengthReadError(const char* aElementName); + +MOZ_NEVER_INLINE void SentinelReadError(const char* aElementName); + +/** + * Annotate the crash reporter with the error code from the most recent system + * call. Returns the system error. + */ +void AnnotateSystemError(); + +// The ActorLifecycleProxy is a helper type used internally by IPC to maintain a +// maybe-owning reference to an IProtocol object. For well-behaved actors +// which are not freed until after their `Dealloc` method is called, a +// reference to an actor's `ActorLifecycleProxy` object is an owning one, as the +// `Dealloc` method will only be called when all references to the +// `ActorLifecycleProxy` are released. +// +// Unfortunately, some actors may be destroyed before their `Dealloc` method +// is called. For these actors, `ActorLifecycleProxy` acts as a weak pointer, +// and will begin to return `nullptr` from its `Get()` method once the +// corresponding actor object has been destroyed. +// +// When calling a `Recv` method, IPC will hold a `ActorLifecycleProxy` reference +// to the target actor, meaning that well-behaved actors can behave as though a +// strong reference is being held. +// +// Generic IPC code MUST treat ActorLifecycleProxy references as weak +// references! +class ActorLifecycleProxy { + public: + NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET(ActorLifecycleProxy) + + IProtocol* Get() { return mActor; } + + WeakActorLifecycleProxy* GetWeakProxy(); + + private: + friend class IProtocol; + + explicit ActorLifecycleProxy(IProtocol* aActor); + ~ActorLifecycleProxy(); + + ActorLifecycleProxy(const ActorLifecycleProxy&) = delete; + ActorLifecycleProxy& operator=(const ActorLifecycleProxy&) = delete; + + IProtocol* MOZ_NON_OWNING_REF mActor; + + // Hold a reference to the actor's manager's ActorLifecycleProxy to help + // prevent it from dying while we're still alive! + RefPtr<ActorLifecycleProxy> mManager; + + // When requested, the current self-referencing weak reference for this + // ActorLifecycleProxy. + RefPtr<WeakActorLifecycleProxy> mWeakProxy; +}; + +// Unlike ActorLifecycleProxy, WeakActorLifecycleProxy only holds a weak +// reference to both the proxy and the actual actor, meaning that holding this +// type will not attempt to keep the actor object alive. +// +// This type is safe to hold on threads other than the actor's thread, but is +// _NOT_ safe to access on other threads, as actors and ActorLifecycleProxy +// objects are not threadsafe. +class WeakActorLifecycleProxy final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WeakActorLifecycleProxy) + + // May only be called on the actor's event target. + // Will return `nullptr` if the actor has already been destroyed from IPC's + // point of view. + IProtocol* Get() const; + + // Safe to call on any thread. + nsISerialEventTarget* ActorEventTarget() const { return mActorEventTarget; } + + private: + friend class ActorLifecycleProxy; + + explicit WeakActorLifecycleProxy(ActorLifecycleProxy* aProxy); + ~WeakActorLifecycleProxy(); + + WeakActorLifecycleProxy(const WeakActorLifecycleProxy&) = delete; + WeakActorLifecycleProxy& operator=(const WeakActorLifecycleProxy&) = delete; + + // This field may only be accessed on the actor's thread, and will be + // automatically cleared when the ActorLifecycleProxy is destroyed. + ActorLifecycleProxy* MOZ_NON_OWNING_REF mProxy; + + // The serial event target which owns the actor, and is the only thread where + // it is OK to access the ActorLifecycleProxy. + const nsCOMPtr<nsISerialEventTarget> mActorEventTarget; +}; + +class IPDLResolverInner final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(IPDLResolverInner, + Destroy()) + + explicit IPDLResolverInner(UniquePtr<IPC::Message> aReply, IProtocol* aActor); + + template <typename F> + void Resolve(F&& aWrite) { + ResolveOrReject(true, std::forward<F>(aWrite)); + } + + private: + void ResolveOrReject(bool aResolve, + FunctionRef<void(IPC::Message*, IProtocol*)> aWrite); + + void Destroy(); + ~IPDLResolverInner(); + + UniquePtr<IPC::Message> mReply; + RefPtr<WeakActorLifecycleProxy> mWeakProxy; +}; + +} // namespace ipc + +template <typename Protocol> +class ManagedContainer { + public: + using iterator = typename nsTArray<Protocol*>::const_iterator; + + iterator begin() const { return mArray.begin(); } + iterator end() const { return mArray.end(); } + iterator cbegin() const { return begin(); } + iterator cend() const { return end(); } + + bool IsEmpty() const { return mArray.IsEmpty(); } + uint32_t Count() const { return mArray.Length(); } + + void ToArray(nsTArray<Protocol*>& aArray) const { + aArray.AppendElements(mArray); + } + + bool EnsureRemoved(Protocol* aElement) { + return mArray.RemoveElementSorted(aElement); + } + + void Insert(Protocol* aElement) { + // Equivalent to `InsertElementSorted`, avoiding inserting a duplicate + // element. + size_t index = mArray.IndexOfFirstElementGt(aElement); + if (index == 0 || mArray[index - 1] != aElement) { + mArray.InsertElementAt(index, aElement); + } + } + + void Clear() { mArray.Clear(); } + + private: + nsTArray<Protocol*> mArray; +}; + +template <typename Protocol> +Protocol* LoneManagedOrNullAsserts( + const ManagedContainer<Protocol>& aManagees) { + if (aManagees.IsEmpty()) { + return nullptr; + } + MOZ_ASSERT(aManagees.Count() == 1); + return *aManagees.cbegin(); +} + +template <typename Protocol> +Protocol* SingleManagedOrNull(const ManagedContainer<Protocol>& aManagees) { + if (aManagees.Count() != 1) { + return nullptr; + } + return *aManagees.cbegin(); +} + +} // namespace mozilla + +#endif // mozilla_ipc_ProtocolUtils_h diff --git a/ipc/glue/RandomAccessStreamParams.ipdlh b/ipc/glue/RandomAccessStreamParams.ipdlh new file mode 100644 index 0000000000..8bcf528af2 --- /dev/null +++ b/ipc/glue/RandomAccessStreamParams.ipdlh @@ -0,0 +1,32 @@ +/* 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 IPCQuotaObject; + +namespace mozilla { +namespace ipc { + +// Use RandomAccessStreamParams in your ipdl to represent serialized +// nsIRandomAccessStreams. Then use SerializeRandomAccessStream from +// RandomAccessStreamUtils.h to perform the serialization. +struct FileRandomAccessStreamParams +{ + FileDescriptor fileDescriptor; + int32_t behaviorFlags; +}; + +struct LimitingFileRandomAccessStreamParams +{ + FileRandomAccessStreamParams fileRandomAccessStreamParams; + IPCQuotaObject quotaObject; +}; + +union RandomAccessStreamParams +{ + FileRandomAccessStreamParams; + LimitingFileRandomAccessStreamParams; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/RandomAccessStreamUtils.cpp b/ipc/glue/RandomAccessStreamUtils.cpp new file mode 100644 index 0000000000..d5e66daae6 --- /dev/null +++ b/ipc/glue/RandomAccessStreamUtils.cpp @@ -0,0 +1,88 @@ +/* -*- 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 "RandomAccessStreamUtils.h" + +#include "mozilla/NotNull.h" +#include "mozilla/Result.h" +#include "mozilla/ResultVariant.h" +#include "mozilla/dom/quota/FileStreams.h" +#include "mozilla/ipc/RandomAccessStreamParams.h" +#include "nsFileStreams.h" +#include "nsIInterfaceRequestor.h" +#include "nsIRandomAccessStream.h" + +namespace mozilla::ipc { + +RandomAccessStreamParams SerializeRandomAccessStream( + MovingNotNull<nsCOMPtr<nsIRandomAccessStream>> aStream, + nsIInterfaceRequestor* aCallbacks) { + NotNull<nsCOMPtr<nsIRandomAccessStream>> stream = std::move(aStream); + + RandomAccessStreamParams streamParams = stream->Serialize(aCallbacks); + + MOZ_ASSERT(streamParams.type() != RandomAccessStreamParams::T__None); + + return streamParams; +} + +Maybe<RandomAccessStreamParams> SerializeRandomAccessStream( + nsCOMPtr<nsIRandomAccessStream> aStream, + nsIInterfaceRequestor* aCallbacks) { + if (!aStream) { + return Nothing(); + } + + return Some(SerializeRandomAccessStream( + WrapMovingNotNullUnchecked(std::move(aStream)), aCallbacks)); +} + +Result<MovingNotNull<nsCOMPtr<nsIRandomAccessStream>>, bool> +DeserializeRandomAccessStream(RandomAccessStreamParams& aStreamParams) { + nsCOMPtr<nsIRandomAccessStream> stream; + + switch (aStreamParams.type()) { + case RandomAccessStreamParams::TFileRandomAccessStreamParams: + nsFileRandomAccessStream::Create(NS_GET_IID(nsIFileRandomAccessStream), + getter_AddRefs(stream)); + break; + + case RandomAccessStreamParams::TLimitingFileRandomAccessStreamParams: + stream = new dom::quota::FileRandomAccessStream(); + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unknown params!"); + return Err(false); + } + + MOZ_ASSERT(stream); + + if (!stream->Deserialize(aStreamParams)) { + MOZ_ASSERT_UNREACHABLE("Deserialize failed!"); + return Err(false); + } + + return WrapMovingNotNullUnchecked(std::move(stream)); +} + +Result<nsCOMPtr<nsIRandomAccessStream>, bool> DeserializeRandomAccessStream( + Maybe<RandomAccessStreamParams>& aStreamParams) { + if (aStreamParams.isNothing()) { + return nsCOMPtr<nsIRandomAccessStream>(); + } + + auto res = DeserializeRandomAccessStream(aStreamParams.ref()); + if (res.isErr()) { + return res.propagateErr(); + } + + MovingNotNull<nsCOMPtr<nsIRandomAccessStream>> stream = res.unwrap(); + + return std::move(stream).unwrapBasePtr(); +} + +} // namespace mozilla::ipc diff --git a/ipc/glue/RandomAccessStreamUtils.h b/ipc/glue/RandomAccessStreamUtils.h new file mode 100644 index 0000000000..3ca79537eb --- /dev/null +++ b/ipc/glue/RandomAccessStreamUtils.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_RandomAccessStreamUtils_h +#define mozilla_ipc_RandomAccessStreamUtils_h + +template <class T> +class nsCOMPtr; + +class nsIInterfaceRequestor; +class nsIRandomAccessStream; + +namespace mozilla { + +template <class T> +class Maybe; + +template <typename T> +class MovingNotNull; + +template <typename V, typename E> +class Result; + +namespace ipc { + +class RandomAccessStreamParams; + +// Serialize an nsIRandomAccessStream to be sent over IPC infallibly. +RandomAccessStreamParams SerializeRandomAccessStream( + MovingNotNull<nsCOMPtr<nsIRandomAccessStream>> aStream, + nsIInterfaceRequestor* aCallbacks); + +Maybe<RandomAccessStreamParams> SerializeRandomAccessStream( + nsCOMPtr<nsIRandomAccessStream> aStream, nsIInterfaceRequestor* aCallbacks); + +// Deserialize an nsIRandomAccessStream received from an actor call. These +// methods work in both the child and parent. +Result<MovingNotNull<nsCOMPtr<nsIRandomAccessStream>>, bool> +DeserializeRandomAccessStream(RandomAccessStreamParams& aStreamParams); + +Result<nsCOMPtr<nsIRandomAccessStream>, bool> DeserializeRandomAccessStream( + Maybe<RandomAccessStreamParams>& aStreamParams); + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_RandomAccessStreamUtils_h diff --git a/ipc/glue/RawShmem.cpp b/ipc/glue/RawShmem.cpp new file mode 100644 index 0000000000..f700804301 --- /dev/null +++ b/ipc/glue/RawShmem.cpp @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "RawShmem.h" +#include "mozilla/ipc/ProtocolUtils.h" + +namespace mozilla::ipc { + +UnsafeSharedMemoryHandle::UnsafeSharedMemoryHandle() + : mHandle(ipc::SharedMemoryBasic::NULLHandle()), mSize(0) {} + +UnsafeSharedMemoryHandle::UnsafeSharedMemoryHandle( + UnsafeSharedMemoryHandle&& aOther) noexcept + : mHandle(std::move(aOther.mHandle)), mSize(aOther.mSize) { + aOther.mHandle = ipc::SharedMemoryBasic::NULLHandle(); + aOther.mSize = 0; +} + +UnsafeSharedMemoryHandle& UnsafeSharedMemoryHandle::operator=( + UnsafeSharedMemoryHandle&& aOther) noexcept { + if (this == &aOther) { + return *this; + } + + mHandle = std::move(aOther.mHandle); + mSize = aOther.mSize; + aOther.mHandle = ipc::SharedMemoryBasic::NULLHandle(); + aOther.mSize = 0; + return *this; +} + +Maybe<std::pair<UnsafeSharedMemoryHandle, WritableSharedMemoryMapping>> +UnsafeSharedMemoryHandle::CreateAndMap(size_t aSize) { + if (aSize == 0) { + return Some(std::make_pair(UnsafeSharedMemoryHandle(), + WritableSharedMemoryMapping())); + } + + RefPtr<ipc::SharedMemoryBasic> shm = MakeAndAddRef<ipc::SharedMemoryBasic>(); + if (NS_WARN_IF(!shm->Create(aSize)) || NS_WARN_IF(!shm->Map(aSize))) { + return Nothing(); + } + + auto handle = shm->TakeHandle(); + + auto size = shm->Size(); + + return Some(std::make_pair(UnsafeSharedMemoryHandle(std::move(handle), size), + WritableSharedMemoryMapping(std::move(shm)))); +} + +WritableSharedMemoryMapping::WritableSharedMemoryMapping( + RefPtr<ipc::SharedMemoryBasic>&& aRef) + : mRef(aRef) {} + +Maybe<WritableSharedMemoryMapping> WritableSharedMemoryMapping::Open( + UnsafeSharedMemoryHandle aHandle) { + if (aHandle.mSize == 0) { + return Some(WritableSharedMemoryMapping(nullptr)); + } + + RefPtr<ipc::SharedMemoryBasic> shm = MakeAndAddRef<ipc::SharedMemoryBasic>(); + if (NS_WARN_IF(!shm->SetHandle(std::move(aHandle.mHandle), + ipc::SharedMemory::RightsReadWrite)) || + NS_WARN_IF(!shm->Map(aHandle.mSize))) { + return Nothing(); + } + + shm->CloseHandle(); + + return Some(WritableSharedMemoryMapping(std::move(shm))); +} + +size_t WritableSharedMemoryMapping::Size() const { + if (!mRef) { + return 0; + } + + return mRef->Size(); +} + +Span<uint8_t> WritableSharedMemoryMapping::Bytes() { + if (!mRef) { + return Span<uint8_t>(); + } + + uint8_t* mem = static_cast<uint8_t*>(mRef->memory()); + return Span(mem, mRef->Size()); +} + +} // namespace mozilla::ipc + +namespace IPC { +auto ParamTraits<mozilla::ipc::UnsafeSharedMemoryHandle>::Write( + IPC::MessageWriter* aWriter, paramType&& aVar) -> void { + IPC::WriteParam(aWriter, std::move(aVar.mHandle)); + IPC::WriteParam(aWriter, aVar.mSize); +} + +auto ParamTraits<mozilla::ipc::UnsafeSharedMemoryHandle>::Read( + IPC::MessageReader* aReader, paramType* aVar) -> bool { + return IPC::ReadParam(aReader, &aVar->mHandle) && + IPC::ReadParam(aReader, &aVar->mSize); +} + +} // namespace IPC diff --git a/ipc/glue/RawShmem.h b/ipc/glue/RawShmem.h new file mode 100644 index 0000000000..cebc59e924 --- /dev/null +++ b/ipc/glue/RawShmem.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_IPC_RAWSHMEM_H_ +#define MOZILLA_IPC_RAWSHMEM_H_ + +#include "mozilla/ipc/SharedMemoryBasic.h" +#include "mozilla/Span.h" +#include <utility> + +namespace mozilla::ipc { + +class WritableSharedMemoryMapping; + +/// A handle to shared memory. +/// +/// See the doc comment for `WritableSharedMemoryMapping` below. +class UnsafeSharedMemoryHandle { + friend class WritableSharedMemoryMapping; + friend struct IPC::ParamTraits<UnsafeSharedMemoryHandle>; + + public: + UnsafeSharedMemoryHandle(); + UnsafeSharedMemoryHandle(UnsafeSharedMemoryHandle&& aOther) noexcept; + UnsafeSharedMemoryHandle& operator=( + UnsafeSharedMemoryHandle&& aOther) noexcept; + + /// Attempts to allocate a shmem. + /// + /// Returns `Nothing()` if allocation fails. + /// If `aSize` is zero, a valid empty WritableSharedMemoryMapping is returned. + static Maybe<std::pair<UnsafeSharedMemoryHandle, WritableSharedMemoryMapping>> + CreateAndMap(size_t aSize); + + private: + UnsafeSharedMemoryHandle(SharedMemoryBasic::Handle&& aHandle, uint64_t aSize) + : mHandle(std::move(aHandle)), mSize(aSize) {} + + SharedMemoryBasic::Handle mHandle; + uint64_t mSize; +}; + +/// A Shared memory buffer mapping. +/// +/// Unlike `ipc::Shmem`, the underlying shared memory buffer on each side of +/// the process boundary is only deallocated with there respective +/// `WritableSharedMemoryMapping`. +/// +/// ## Usage +/// +/// Typical usage goes as follows: +/// - Allocate the memory using `UnsafeSharedMemoryHandle::Create`, returning a +/// handle and a mapping. +/// - Send the handle to the other process using an IPDL message. +/// - On the other process, map the shared memory by creating +/// WritableSharedMemoryMapping via `WritableSharedMemoryMapping::Open` and +/// the received handle. +/// +/// Do not send the shared memory handle again, it is only intended to establish +/// the mapping on each side during initialization. The user of this class is +/// responsible for managing the lifetime of the buffers on each side, as well +/// as their identity, by for example storing them in hash map and referring to +/// them via IDs in IPDL message if need be. +/// +/// ## Empty shmems +/// +/// An empty WritableSharedMemoryMapping is one that was created with size zero. +/// It is analogous to a null RefPtr. It can be used like a non-empty shmem, +/// including sending the handle and openning it on another process (resulting +/// in an empty mapping on the other side). +class WritableSharedMemoryMapping { + friend class UnsafeSharedMemoryHandle; + + public: + WritableSharedMemoryMapping() = default; + + WritableSharedMemoryMapping(WritableSharedMemoryMapping&& aMoved) = default; + + WritableSharedMemoryMapping& operator=(WritableSharedMemoryMapping&& aMoved) = + default; + + /// Open the shmem and immediately close the handle. + static Maybe<WritableSharedMemoryMapping> Open( + UnsafeSharedMemoryHandle aHandle); + + // Returns the size in bytes. + size_t Size() const; + + // Returns the shared memory as byte range. + Span<uint8_t> Bytes(); + + private: + explicit WritableSharedMemoryMapping( + RefPtr<mozilla::ipc::SharedMemoryBasic>&& aRef); + + RefPtr<mozilla::ipc::SharedMemoryBasic> mRef; +}; + +} // namespace mozilla::ipc + +namespace IPC { +template <> +struct ParamTraits<mozilla::ipc::UnsafeSharedMemoryHandle> { + typedef mozilla::ipc::UnsafeSharedMemoryHandle paramType; + static void Write(IPC::MessageWriter* aWriter, paramType&& aVar); + static bool Read(IPC::MessageReader* aReader, paramType* aVar); +}; + +} // namespace IPC + +#endif diff --git a/ipc/glue/ScopedPort.cpp b/ipc/glue/ScopedPort.cpp new file mode 100644 index 0000000000..e874796fb2 --- /dev/null +++ b/ipc/glue/ScopedPort.cpp @@ -0,0 +1,77 @@ +/* -*- 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 "mozilla/ipc/ScopedPort.h" +#include "mozilla/ipc/NodeController.h" +#include "chrome/common/ipc_message_utils.h" + +namespace mozilla::ipc { + +void ScopedPort::Reset() { + if (mValid) { + mController->ClosePort(mPort); + } + mValid = false; + mPort = {}; + mController = nullptr; +} + +auto ScopedPort::Release() -> PortRef { + if (!mValid) { + return {}; + } + mValid = false; + mController = nullptr; + return std::exchange(mPort, PortRef{}); +} + +ScopedPort::ScopedPort() = default; + +ScopedPort::~ScopedPort() { Reset(); } + +ScopedPort::ScopedPort(PortRef aPort, NodeController* aController) + : mValid(true), mPort(std::move(aPort)), mController(aController) { + MOZ_ASSERT(mPort.is_valid() && mController); +} + +ScopedPort::ScopedPort(ScopedPort&& aOther) + : mValid(std::exchange(aOther.mValid, false)), + mPort(std::move(aOther.mPort)), + mController(std::move(aOther.mController)) {} + +ScopedPort& ScopedPort::operator=(ScopedPort&& aOther) { + if (this != &aOther) { + Reset(); + mValid = std::exchange(aOther.mValid, false); + mPort = std::move(aOther.mPort); + mController = std::move(aOther.mController); + } + return *this; +} + +} // namespace mozilla::ipc + +void IPC::ParamTraits<mozilla::ipc::ScopedPort>::Write(MessageWriter* aWriter, + paramType&& aParam) { + aWriter->WriteBool(aParam.IsValid()); + if (!aParam.IsValid()) { + return; + } + aWriter->WritePort(std::move(aParam)); +} + +bool IPC::ParamTraits<mozilla::ipc::ScopedPort>::Read(MessageReader* aReader, + paramType* aResult) { + bool isValid = false; + if (!aReader->ReadBool(&isValid)) { + return false; + } + if (!isValid) { + *aResult = {}; + return true; + } + return aReader->ConsumePort(aResult); +} diff --git a/ipc/glue/ScopedPort.h b/ipc/glue/ScopedPort.h new file mode 100644 index 0000000000..e683ba894e --- /dev/null +++ b/ipc/glue/ScopedPort.h @@ -0,0 +1,79 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_RawEndpoint_h +#define mozilla_ipc_RawEndpoint_h + +#include "mojo/core/ports/port_ref.h" + +namespace mozilla::ipc { + +class NodeController; + +/// A uniquely owned raw IPC endpoint, connected to our peers over the node +/// controller. This type can be sent over IPC channels to establish +/// connections. +/// +/// In general, prefer using Endpoint over ScopedPort for type safety reasons. +class ScopedPort { + using PortName = mojo::core::ports::PortName; + using PortRef = mojo::core::ports::PortRef; + + public: + ScopedPort(); + ~ScopedPort(); + + ScopedPort(PortRef aPort, NodeController* aController); + + ScopedPort(ScopedPort&& aOther); + ScopedPort(const ScopedPort&) = delete; + + ScopedPort& operator=(ScopedPort&& aOther); + ScopedPort& operator=(const ScopedPort&) = delete; + + // Allow checking if this `ScopedPort` is valid or not. + bool IsValid() const { return mValid; } + explicit operator bool() const { return IsValid(); } + + // Underlying port and controller which are used by this ScopedPort. + const PortName& Name() const { return mPort.name(); } + const PortRef& Port() const { return mPort; } + NodeController* Controller() const { return mController; } + + // Release ownership over the contained `ScopedPort`, meaning that it will + // not be closed when this ScopedPort is destroyed. This will make the + // endpoint invalid. + PortRef Release(); + + private: + void Reset(); + + bool mValid = false; + PortRef mPort; + RefPtr<NodeController> mController; + + // NOTE: This type does not contain PID information about the other process, + // which will need to be sent separately if necessary. +}; + +} // namespace mozilla::ipc + +namespace IPC { + +template <typename T> +struct ParamTraits; + +template <> +struct ParamTraits<mozilla::ipc::ScopedPort> { + using paramType = mozilla::ipc::ScopedPort; + + static void Write(MessageWriter* aWriter, paramType&& aParam); + static bool Read(MessageReader* aReader, paramType* aResult); +}; + +} // namespace IPC + +#endif diff --git a/ipc/glue/SerializedStructuredCloneBuffer.cpp b/ipc/glue/SerializedStructuredCloneBuffer.cpp new file mode 100644 index 0000000000..9cad1ed826 --- /dev/null +++ b/ipc/glue/SerializedStructuredCloneBuffer.cpp @@ -0,0 +1,79 @@ +/* -*- 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 "mozilla/ipc/SerializedStructuredCloneBuffer.h" +#include "js/StructuredClone.h" + +namespace IPC { + +void ParamTraits<JSStructuredCloneData>::Write(MessageWriter* aWriter, + const paramType& aParam) { + MOZ_ASSERT(!(aParam.Size() % sizeof(uint64_t))); + + // We can only construct shared memory regions up to 4Gb in size, making that + // the maximum possible JSStructuredCloneData size. + mozilla::CheckedUint32 size = aParam.Size(); + if (!size.isValid()) { + aWriter->FatalError("JSStructuredCloneData over 4Gb in size"); + return; + } + WriteParam(aWriter, size.value()); + + MessageBufferWriter bufWriter(aWriter, size.value()); + aParam.ForEachDataChunk([&](const char* aData, size_t aSize) { + return bufWriter.WriteBytes(aData, aSize); + }); +} + +bool ParamTraits<JSStructuredCloneData>::Read(MessageReader* aReader, + paramType* aResult) { + uint32_t length = 0; + if (!ReadParam(aReader, &length)) { + aReader->FatalError("JSStructuredCloneData length read failed"); + return false; + } + MOZ_ASSERT(!(length % sizeof(uint64_t))); + + // Borrowing is not suitable to use for IPC to hand out data because we often + // want to store the data somewhere for processing after IPC has released the + // underlying buffers. + // + // This deserializer previously used a mechanism to transfer ownership over + // the underlying buffers from IPC into the JSStructuredCloneData. This was + // removed when support for serializing over shared memory was added, as the + // benefit for avoiding copies was limited due to it only functioning for + // buffers under 64k in size (as larger buffers would be serialized using + // shared memory), and it added substantial complexity to the BufferList type + // and the IPC serialization layer due to things like buffer alignment. This + // can be revisited in the future if it turns out to be a noticable + // performance regression. (bug 1783242) + + mozilla::BufferList<js::SystemAllocPolicy> buffers(0, 0, 4096); + MessageBufferReader bufReader(aReader, length); + uint32_t read = 0; + while (read < length) { + size_t bufLen; + char* buf = buffers.AllocateBytes(length - read, &bufLen); + if (!buf) { + // Would be nice to allow actor to control behaviour here (bug 1784307) + NS_ABORT_OOM(length - read); + return false; + } + if (!bufReader.ReadBytesInto(buf, bufLen)) { + aReader->FatalError("JSStructuredCloneData ReadBytesInto failed"); + return false; + } + read += bufLen; + } + + MOZ_ASSERT(read == length); + *aResult = JSStructuredCloneData( + std::move(buffers), JS::StructuredCloneScope::DifferentProcess, + OwnTransferablePolicy::IgnoreTransferablesIfAny); + return true; +} + +} // namespace IPC diff --git a/ipc/glue/SerializedStructuredCloneBuffer.h b/ipc/glue/SerializedStructuredCloneBuffer.h new file mode 100644 index 0000000000..ed4e404dbc --- /dev/null +++ b/ipc/glue/SerializedStructuredCloneBuffer.h @@ -0,0 +1,87 @@ +/* -*- 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/. */ + +#ifndef __IPC_GLUE_SERIALIZEDSTRUCTUREDCLONEBUFFER_H__ +#define __IPC_GLUE_SERIALIZEDSTRUCTUREDCLONEBUFFER_H__ + +#include <algorithm> +#include <cstdint> +#include <cstdlib> +#include <string> +#include <utility> +#include "chrome/common/ipc_message.h" +#include "chrome/common/ipc_message_utils.h" +#include "js/AllocPolicy.h" +#include "js/StructuredClone.h" +#include "mozilla/Assertions.h" +#include "mozilla/BufferList.h" +#include "mozilla/Vector.h" +#include "mozilla/mozalloc.h" +class PickleIterator; + +namespace mozilla { +template <typename...> +class Variant; + +namespace detail { +template <typename...> +struct VariantTag; +} +} // namespace mozilla + +namespace mozilla { + +struct SerializedStructuredCloneBuffer final { + SerializedStructuredCloneBuffer() = default; + + SerializedStructuredCloneBuffer(SerializedStructuredCloneBuffer&&) = default; + SerializedStructuredCloneBuffer& operator=( + SerializedStructuredCloneBuffer&&) = default; + + SerializedStructuredCloneBuffer(const SerializedStructuredCloneBuffer&) = + delete; + SerializedStructuredCloneBuffer& operator=( + const SerializedStructuredCloneBuffer& aOther) = delete; + + bool operator==(const SerializedStructuredCloneBuffer& aOther) const { + // The copy assignment operator and the equality operator are + // needed by the IPDL generated code. We relied on the copy + // assignment operator at some places but we never use the + // equality operator. + return false; + } + + JSStructuredCloneData data{JS::StructuredCloneScope::Unassigned}; +}; + +} // namespace mozilla + +namespace IPC { +template <> +struct ParamTraits<JSStructuredCloneData> { + typedef JSStructuredCloneData paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam); + + static bool Read(MessageReader* aReader, paramType* aResult); +}; + +template <> +struct ParamTraits<mozilla::SerializedStructuredCloneBuffer> { + typedef mozilla::SerializedStructuredCloneBuffer paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.data); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->data); + } +}; + +} // namespace IPC + +#endif /* __IPC_GLUE_SERIALIZEDSTRUCTUREDCLONEBUFFER_H__ */ diff --git a/ipc/glue/SetProcessTitle.cpp b/ipc/glue/SetProcessTitle.cpp new file mode 100644 index 0000000000..4d929fb53c --- /dev/null +++ b/ipc/glue/SetProcessTitle.cpp @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/ipc/SetProcessTitle.h" + +#include "nsString.h" + +#ifdef XP_LINUX + +# include "base/set_process_title_linux.h" +# define HAVE_SETPROCTITLE +# define HAVE_SETPROCTITLE_INIT + +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ + defined(__DragonFly__) + +# include <sys/types.h> +# include <unistd.h> +# define HAVE_SETPROCTITLE + +#endif + +namespace mozilla { + +void SetProcessTitle(const std::vector<std::string>& aNewArgv) { +#ifdef HAVE_SETPROCTITLE + nsAutoCStringN<1024> buf; + + bool firstArg = true; + for (const std::string& arg : aNewArgv) { + if (firstArg) { + firstArg = false; + } else { + buf.Append(' '); + } + buf.Append(arg.c_str()); + } + + setproctitle("-%s", buf.get()); +#endif +} + +void SetProcessTitleInit(char** aOrigArgv) { +#ifdef HAVE_SETPROCTITLE_INIT + setproctitle_init(aOrigArgv); +#endif +} + +} // namespace mozilla diff --git a/ipc/glue/SetProcessTitle.h b/ipc/glue/SetProcessTitle.h new file mode 100644 index 0000000000..01b8caa3ce --- /dev/null +++ b/ipc/glue/SetProcessTitle.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_ipc_SetProcessTitle_h +#define mozilla_ipc_SetProcessTitle_h + +#include <string> +#include <vector> + +namespace mozilla { + +void SetProcessTitle(const std::vector<std::string>& aNewArgv); +void SetProcessTitleInit(char** aOrigArgv); + +} // namespace mozilla + +#endif // mozilla_ipc_SetProcessTitle_h diff --git a/ipc/glue/SharedMemory.cpp b/ipc/glue/SharedMemory.cpp new file mode 100644 index 0000000000..4761746658 --- /dev/null +++ b/ipc/glue/SharedMemory.cpp @@ -0,0 +1,84 @@ +/* -*- 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 <math.h> + +#include "nsString.h" +#include "nsIMemoryReporter.h" +#include "mozilla/ipc/SharedMemory.h" +#include "mozilla/Atomics.h" + +namespace mozilla { +namespace ipc { + +static Atomic<size_t> gShmemAllocated; +static Atomic<size_t> gShmemMapped; + +class ShmemReporter final : public nsIMemoryReporter { + ~ShmemReporter() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + MOZ_COLLECT_REPORT( + "shmem-allocated", KIND_OTHER, UNITS_BYTES, gShmemAllocated, + "Memory shared with other processes that is accessible (but not " + "necessarily mapped)."); + + MOZ_COLLECT_REPORT( + "shmem-mapped", KIND_OTHER, UNITS_BYTES, gShmemMapped, + "Memory shared with other processes that is mapped into the address " + "space."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(ShmemReporter, nsIMemoryReporter) + +SharedMemory::SharedMemory() : mAllocSize(0), mMappedSize(0) { + static Atomic<bool> registered; + if (registered.compareExchange(false, true)) { + RegisterStrongMemoryReporter(new ShmemReporter()); + } +} + +/*static*/ +size_t SharedMemory::PageAlignedSize(size_t aSize) { + size_t pageSize = SystemPageSize(); + size_t nPagesNeeded = size_t(ceil(double(aSize) / double(pageSize))); + return pageSize * nPagesNeeded; +} + +void SharedMemory::Created(size_t aNBytes) { + mAllocSize = aNBytes; + gShmemAllocated += mAllocSize; +} + +void SharedMemory::Mapped(size_t aNBytes) { + mMappedSize = aNBytes; + gShmemMapped += mMappedSize; +} + +void SharedMemory::Unmapped() { + MOZ_ASSERT(gShmemMapped >= mMappedSize, "Can't unmap more than mapped"); + gShmemMapped -= mMappedSize; + mMappedSize = 0; +} + +/*static*/ +void SharedMemory::Destroyed() { + MOZ_ASSERT(gShmemAllocated >= mAllocSize, + "Can't destroy more than allocated"); + gShmemAllocated -= mAllocSize; + mAllocSize = 0; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/SharedMemory.h b/ipc/glue/SharedMemory.h new file mode 100644 index 0000000000..818fad5e69 --- /dev/null +++ b/ipc/glue/SharedMemory.h @@ -0,0 +1,142 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_SharedMemory_h +#define mozilla_ipc_SharedMemory_h + +#include "nsDebug.h" +#include "nsISupportsImpl.h" // NS_INLINE_DECL_REFCOUNTING +#include "mozilla/Attributes.h" + +#include "base/process.h" +#include "chrome/common/ipc_message_utils.h" + +// +// This is a low-level wrapper around platform shared memory. Don't +// use it directly; use Shmem allocated through IPDL interfaces. +// +namespace { +enum Rights { RightsNone = 0, RightsRead = 1 << 0, RightsWrite = 1 << 1 }; +} // namespace + +namespace mozilla { + +namespace ipc { +class SharedMemory; +} // namespace ipc + +namespace ipc { + +class SharedMemory { + protected: + virtual ~SharedMemory() { + Unmapped(); + Destroyed(); + } + + public: + enum OpenRights { + RightsReadOnly = RightsRead, + RightsReadWrite = RightsRead | RightsWrite, + }; + + size_t Size() const { return mMappedSize; } + + virtual void* memory() const = 0; + + virtual bool Create(size_t size) = 0; + virtual bool Map(size_t nBytes, void* fixed_address = nullptr) = 0; + virtual void Unmap() = 0; + + virtual void CloseHandle() = 0; + + virtual bool WriteHandle(IPC::MessageWriter* aWriter) = 0; + virtual bool ReadHandle(IPC::MessageReader* aReader) = 0; + + void Protect(char* aAddr, size_t aSize, int aRights) { + char* memStart = reinterpret_cast<char*>(memory()); + if (!memStart) MOZ_CRASH("SharedMemory region points at NULL!"); + char* memEnd = memStart + Size(); + + char* protStart = aAddr; + if (!protStart) MOZ_CRASH("trying to Protect() a NULL region!"); + char* protEnd = protStart + aSize; + + if (!(memStart <= protStart && protEnd <= memEnd)) + MOZ_CRASH("attempt to Protect() a region outside this SharedMemory"); + + // checks alignment etc. + SystemProtect(aAddr, aSize, aRights); + } + + // bug 1168843, compositor thread may create shared memory instances that are + // destroyed by main thread on shutdown, so this must use thread-safe RC to + // avoid hitting assertion + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedMemory) + + static void SystemProtect(char* aAddr, size_t aSize, int aRights); + [[nodiscard]] static bool SystemProtectFallible(char* aAddr, size_t aSize, + int aRights); + static size_t SystemPageSize(); + static size_t PageAlignedSize(size_t aSize); + + protected: + SharedMemory(); + + // Implementations should call these methods on shmem usage changes, + // but *only if* the OS-specific calls are known to have succeeded. + // The methods are expected to be called in the pattern + // + // Created (Mapped Unmapped)* Destroy + // + // but this isn't checked. + void Created(size_t aNBytes); + void Mapped(size_t aNBytes); + void Unmapped(); + void Destroyed(); + + // The size of the shmem region requested in Create(), if + // successful. SharedMemory instances that are opened from a + // foreign handle have an alloc size of 0, even though they have + // access to the alloc-size information. + size_t mAllocSize; + // The size of the region mapped in Map(), if successful. All + // SharedMemorys that are mapped have a non-zero mapped size. + size_t mMappedSize; +}; + +template <typename HandleImpl> +class SharedMemoryCommon : public SharedMemory { + public: + typedef HandleImpl Handle; + + virtual Handle CloneHandle() = 0; + virtual Handle TakeHandle() = 0; + virtual bool IsHandleValid(const Handle& aHandle) const = 0; + virtual bool SetHandle(Handle aHandle, OpenRights aRights) = 0; + + virtual void CloseHandle() override { TakeHandle(); } + + virtual bool WriteHandle(IPC::MessageWriter* aWriter) override { + Handle handle = CloneHandle(); + if (!handle) { + return false; + } + IPC::WriteParam(aWriter, std::move(handle)); + return true; + } + + virtual bool ReadHandle(IPC::MessageReader* aReader) override { + Handle handle; + return IPC::ReadParam(aReader, &handle) && IsHandleValid(handle) && + SetHandle(std::move(handle), RightsReadWrite); + } +}; + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef mozilla_ipc_SharedMemory_h diff --git a/ipc/glue/SharedMemoryBasic.h b/ipc/glue/SharedMemoryBasic.h new file mode 100644 index 0000000000..2f07ae4402 --- /dev/null +++ b/ipc/glue/SharedMemoryBasic.h @@ -0,0 +1,16 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_SharedMemoryBasic_h +#define mozilla_ipc_SharedMemoryBasic_h + +#ifdef XP_DARWIN +# include "mozilla/ipc/SharedMemoryBasic_mach.h" +#else +# include "mozilla/ipc/SharedMemoryBasic_chromium.h" +#endif + +#endif // ifndef mozilla_ipc_SharedMemoryBasic_h diff --git a/ipc/glue/SharedMemoryBasic_chromium.h b/ipc/glue/SharedMemoryBasic_chromium.h new file mode 100644 index 0000000000..8d65e7f189 --- /dev/null +++ b/ipc/glue/SharedMemoryBasic_chromium.h @@ -0,0 +1,89 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_SharedMemoryBasic_chromium_h +#define mozilla_ipc_SharedMemoryBasic_chromium_h + +#include "base/shared_memory.h" +#include "mozilla/ipc/SharedMemory.h" + +#ifdef FUZZING +# include "mozilla/ipc/SharedMemoryFuzzer.h" +#endif + +#include "nsDebug.h" + +// +// This is a low-level wrapper around platform shared memory. Don't +// use it directly; use Shmem allocated through IPDL interfaces. +// + +namespace mozilla { +namespace ipc { + +class SharedMemoryBasic final + : public SharedMemoryCommon<base::SharedMemoryHandle> { + public: + SharedMemoryBasic() = default; + + virtual bool SetHandle(Handle aHandle, OpenRights aRights) override { + return mSharedMemory.SetHandle(std::move(aHandle), + aRights == RightsReadOnly); + } + + virtual bool Create(size_t aNbytes) override { + bool ok = mSharedMemory.Create(aNbytes); + if (ok) { + Created(aNbytes); + } + return ok; + } + + virtual bool Map(size_t nBytes, void* fixed_address = nullptr) override { + bool ok = mSharedMemory.Map(nBytes, fixed_address); + if (ok) { + Mapped(nBytes); + } + return ok; + } + + virtual void Unmap() override { mSharedMemory.Unmap(); } + + virtual void* memory() const override { +#ifdef FUZZING + return SharedMemoryFuzzer::MutateSharedMemory(mSharedMemory.memory(), + mAllocSize); +#else + return mSharedMemory.memory(); +#endif + } + + static Handle NULLHandle() { return base::SharedMemory::NULLHandle(); } + + virtual bool IsHandleValid(const Handle& aHandle) const override { + return base::SharedMemory::IsHandleValid(aHandle); + } + + virtual Handle CloneHandle() override { return mSharedMemory.CloneHandle(); } + + virtual Handle TakeHandle() override { + return mSharedMemory.TakeHandle(false); + } + + static void* FindFreeAddressSpace(size_t size) { + return base::SharedMemory::FindFreeAddressSpace(size); + } + + private: + ~SharedMemoryBasic() = default; + + base::SharedMemory mSharedMemory; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef mozilla_ipc_SharedMemoryBasic_chromium_h diff --git a/ipc/glue/SharedMemoryBasic_mach.h b/ipc/glue/SharedMemoryBasic_mach.h new file mode 100644 index 0000000000..d7ea9b2c39 --- /dev/null +++ b/ipc/glue/SharedMemoryBasic_mach.h @@ -0,0 +1,75 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_SharedMemoryBasic_mach_h +#define mozilla_ipc_SharedMemoryBasic_mach_h + +#include "base/process.h" + +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/ipc/SharedMemory.h" +#include <mach/port.h> + +#ifdef FUZZING +# include "mozilla/ipc/SharedMemoryFuzzer.h" +#endif + +// +// This is a low-level wrapper around platform shared memory. Don't +// use it directly; use Shmem allocated through IPDL interfaces. +// + +class MachPortSender; +class ReceivePort; + +namespace mozilla { +namespace ipc { + +class SharedMemoryBasic final + : public SharedMemoryCommon<mozilla::UniqueMachSendRight> { + public: + SharedMemoryBasic(); + + virtual bool SetHandle(Handle aHandle, OpenRights aRights) override; + + virtual bool Create(size_t aNbytes) override; + + virtual bool Map(size_t nBytes, void* fixed_address = nullptr) override; + + virtual void Unmap() override; + + virtual void* memory() const override { +#ifdef FUZZING + return SharedMemoryFuzzer::MutateSharedMemory(mMemory, mAllocSize); +#else + return mMemory; +#endif + } + + static Handle NULLHandle() { return Handle(); } + + static void* FindFreeAddressSpace(size_t aSize); + + virtual bool IsHandleValid(const Handle& aHandle) const override; + + virtual Handle CloneHandle() override; + + virtual Handle TakeHandle() override; + + private: + ~SharedMemoryBasic(); + + mozilla::UniqueMachSendRight mPort; + // Pointer to mapped region, null if unmapped. + void* mMemory; + // Access rights to map an existing region with. + OpenRights mOpenRights; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef mozilla_ipc_SharedMemoryBasic_mach_h diff --git a/ipc/glue/SharedMemoryBasic_mach.mm b/ipc/glue/SharedMemoryBasic_mach.mm new file mode 100644 index 0000000000..d9992792e9 --- /dev/null +++ b/ipc/glue/SharedMemoryBasic_mach.mm @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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 <map> + +#include <mach/vm_map.h> +#include <mach/mach_port.h> +#if defined(XP_IOS) +# include <mach/vm_map.h> +# define mach_vm_address_t vm_address_t +# define mach_vm_map vm_map +# define mach_vm_read vm_read +# define mach_vm_region_recurse vm_region_recurse_64 +# define mach_vm_size_t vm_size_t +#else +# include <mach/mach_vm.h> +#endif +#include <pthread.h> +#include <unistd.h> +#include "SharedMemoryBasic.h" + +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Printf.h" +#include "mozilla/StaticMutex.h" + +#ifdef DEBUG +# define LOG_ERROR(str, args...) \ + PR_BEGIN_MACRO \ + mozilla::SmprintfPointer msg = mozilla::Smprintf(str, ##args); \ + NS_WARNING(msg.get()); \ + PR_END_MACRO +#else +# define LOG_ERROR(str, args...) \ + do { /* nothing */ \ + } while (0) +#endif + +namespace mozilla { +namespace ipc { + +SharedMemoryBasic::SharedMemoryBasic() + : mPort(MACH_PORT_NULL), mMemory(nullptr), mOpenRights(RightsReadWrite) {} + +SharedMemoryBasic::~SharedMemoryBasic() { + Unmap(); + CloseHandle(); +} + +bool SharedMemoryBasic::SetHandle(Handle aHandle, OpenRights aRights) { + MOZ_ASSERT(mPort == MACH_PORT_NULL, "already initialized"); + + mPort = std::move(aHandle); + mOpenRights = aRights; + return true; +} + +static inline void* toPointer(mach_vm_address_t address) { + return reinterpret_cast<void*>(static_cast<uintptr_t>(address)); +} + +static inline mach_vm_address_t toVMAddress(void* pointer) { + return static_cast<mach_vm_address_t>(reinterpret_cast<uintptr_t>(pointer)); +} + +bool SharedMemoryBasic::Create(size_t size) { + MOZ_ASSERT(mPort == MACH_PORT_NULL, "already initialized"); + + memory_object_size_t memoryObjectSize = round_page(size); + + kern_return_t kr = + mach_make_memory_entry_64(mach_task_self(), &memoryObjectSize, 0, + MAP_MEM_NAMED_CREATE | VM_PROT_DEFAULT, + getter_Transfers(mPort), MACH_PORT_NULL); + if (kr != KERN_SUCCESS || memoryObjectSize < round_page(size)) { + LOG_ERROR("Failed to make memory entry (%zu bytes). %s (%x)\n", size, + mach_error_string(kr), kr); + CloseHandle(); + return false; + } + + Created(size); + return true; +} + +bool SharedMemoryBasic::Map(size_t size, void* fixed_address) { + MOZ_ASSERT(mMemory == nullptr); + + if (MACH_PORT_NULL == mPort) { + return false; + } + + kern_return_t kr; + mach_vm_address_t address = toVMAddress(fixed_address); + + vm_prot_t vmProtection = VM_PROT_READ; + if (mOpenRights == RightsReadWrite) { + vmProtection |= VM_PROT_WRITE; + } + + kr = mach_vm_map(mach_task_self(), &address, round_page(size), 0, + fixed_address ? VM_FLAGS_FIXED : VM_FLAGS_ANYWHERE, + mPort.get(), 0, false, vmProtection, vmProtection, + VM_INHERIT_NONE); + if (kr != KERN_SUCCESS) { + if (!fixed_address) { + LOG_ERROR( + "Failed to map shared memory (%zu bytes) into %x, port %x. %s (%x)\n", + size, mach_task_self(), mach_port_t(mPort.get()), + mach_error_string(kr), kr); + } + return false; + } + + if (fixed_address && fixed_address != toPointer(address)) { + kr = vm_deallocate(mach_task_self(), address, size); + if (kr != KERN_SUCCESS) { + LOG_ERROR("Failed to unmap shared memory at unsuitable address " + "(%zu bytes) from %x, port %x. %s (%x)\n", + size, mach_task_self(), mach_port_t(mPort.get()), + mach_error_string(kr), kr); + } + return false; + } + + mMemory = toPointer(address); + Mapped(size); + return true; +} + +void* SharedMemoryBasic::FindFreeAddressSpace(size_t size) { + mach_vm_address_t address = 0; + size = round_page(size); + if (mach_vm_map(mach_task_self(), &address, size, 0, VM_FLAGS_ANYWHERE, + MEMORY_OBJECT_NULL, 0, false, VM_PROT_NONE, VM_PROT_NONE, + VM_INHERIT_NONE) != KERN_SUCCESS || + vm_deallocate(mach_task_self(), address, size) != KERN_SUCCESS) { + return nullptr; + } + return toPointer(address); +} + +auto SharedMemoryBasic::CloneHandle() -> Handle { + return mozilla::RetainMachSendRight(mPort.get()); +} + +auto SharedMemoryBasic::TakeHandle() -> Handle { + mOpenRights = RightsReadWrite; + return std::move(mPort); +} + +void SharedMemoryBasic::Unmap() { + if (!mMemory) { + return; + } + vm_address_t address = toVMAddress(mMemory); + kern_return_t kr = + vm_deallocate(mach_task_self(), address, round_page(mMappedSize)); + if (kr != KERN_SUCCESS) { + LOG_ERROR("Failed to deallocate shared memory. %s (%x)\n", + mach_error_string(kr), kr); + return; + } + mMemory = nullptr; +} + +bool SharedMemoryBasic::IsHandleValid(const Handle& aHandle) const { + return aHandle != nullptr; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/SharedMemory_posix.cpp b/ipc/glue/SharedMemory_posix.cpp new file mode 100644 index 0000000000..b634058945 --- /dev/null +++ b/ipc/glue/SharedMemory_posix.cpp @@ -0,0 +1,55 @@ +/* -*- 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 <sys/mman.h> // mprotect +#include <unistd.h> // sysconf + +#include "mozilla/ipc/SharedMemory.h" + +#if defined(XP_MACOSX) && defined(__x86_64__) +# include "prenv.h" +#endif + +namespace mozilla { +namespace ipc { + +#if defined(XP_MACOSX) && defined(__x86_64__) +std::atomic<size_t> sPageSizeOverride = 0; +#endif + +void SharedMemory::SystemProtect(char* aAddr, size_t aSize, int aRights) { + if (!SystemProtectFallible(aAddr, aSize, aRights)) { + MOZ_CRASH("can't mprotect()"); + } +} + +bool SharedMemory::SystemProtectFallible(char* aAddr, size_t aSize, + int aRights) { + int flags = 0; + if (aRights & RightsRead) flags |= PROT_READ; + if (aRights & RightsWrite) flags |= PROT_WRITE; + if (RightsNone == aRights) flags = PROT_NONE; + + return 0 == mprotect(aAddr, aSize, flags); +} + +size_t SharedMemory::SystemPageSize() { +#if defined(XP_MACOSX) && defined(__x86_64__) + if (sPageSizeOverride == 0) { + if (PR_GetEnv("MOZ_SHMEM_PAGESIZE_16K")) { + sPageSizeOverride = 16 * 1024; + } else { + sPageSizeOverride = sysconf(_SC_PAGESIZE); + } + } + return sPageSizeOverride; +#else + return sysconf(_SC_PAGESIZE); +#endif +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/SharedMemory_windows.cpp b/ipc/glue/SharedMemory_windows.cpp new file mode 100644 index 0000000000..1cc93687af --- /dev/null +++ b/ipc/glue/SharedMemory_windows.cpp @@ -0,0 +1,41 @@ +/* -*- 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 <windows.h> + +#include "mozilla/ipc/SharedMemory.h" + +namespace mozilla { +namespace ipc { + +void SharedMemory::SystemProtect(char* aAddr, size_t aSize, int aRights) { + if (!SystemProtectFallible(aAddr, aSize, aRights)) { + MOZ_CRASH("can't VirtualProtect()"); + } +} + +bool SharedMemory::SystemProtectFallible(char* aAddr, size_t aSize, + int aRights) { + DWORD flags; + if ((aRights & RightsRead) && (aRights & RightsWrite)) + flags = PAGE_READWRITE; + else if (aRights & RightsRead) + flags = PAGE_READONLY; + else + flags = PAGE_NOACCESS; + + DWORD oldflags; + return VirtualProtect(aAddr, aSize, flags, &oldflags); +} + +size_t SharedMemory::SystemPageSize() { + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/Shmem.cpp b/ipc/glue/Shmem.cpp new file mode 100644 index 0000000000..442c6fb73c --- /dev/null +++ b/ipc/glue/Shmem.cpp @@ -0,0 +1,248 @@ +/* -*- 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 "Shmem.h" + +#include "ProtocolUtils.h" +#include "SharedMemoryBasic.h" +#include "ShmemMessageUtils.h" +#include "chrome/common/ipc_message_utils.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace ipc { + +class ShmemCreated : public IPC::Message { + private: + typedef Shmem::id_t id_t; + + public: + ShmemCreated(int32_t routingId, id_t aIPDLId, size_t aSize) + : IPC::Message( + routingId, SHMEM_CREATED_MESSAGE_TYPE, 0, + HeaderFlags(NESTED_INSIDE_CPOW, CONTROL_PRIORITY, COMPRESSION_NONE, + LAZY_SEND, NOT_CONSTRUCTOR, ASYNC, NOT_REPLY)) { + MOZ_RELEASE_ASSERT(aSize < std::numeric_limits<uint32_t>::max(), + "Tried to create Shmem with size larger than 4GB"); + IPC::MessageWriter writer(*this); + IPC::WriteParam(&writer, aIPDLId); + IPC::WriteParam(&writer, uint32_t(aSize)); + } + + static bool ReadInfo(IPC::MessageReader* aReader, id_t* aIPDLId, + size_t* aSize) { + uint32_t size = 0; + if (!IPC::ReadParam(aReader, aIPDLId) || !IPC::ReadParam(aReader, &size)) { + return false; + } + *aSize = size; + return true; + } + + void Log(const std::string& aPrefix, FILE* aOutf) const { + fputs("(special ShmemCreated msg)", aOutf); + } +}; + +class ShmemDestroyed : public IPC::Message { + private: + typedef Shmem::id_t id_t; + + public: + ShmemDestroyed(int32_t routingId, id_t aIPDLId) + : IPC::Message( + routingId, SHMEM_DESTROYED_MESSAGE_TYPE, 0, + HeaderFlags(NOT_NESTED, NORMAL_PRIORITY, COMPRESSION_NONE, + LAZY_SEND, NOT_CONSTRUCTOR, ASYNC, NOT_REPLY)) { + IPC::MessageWriter writer(*this); + IPC::WriteParam(&writer, aIPDLId); + } +}; + +static already_AddRefed<SharedMemory> NewSegment() { + return MakeAndAddRef<SharedMemoryBasic>(); +} + +static already_AddRefed<SharedMemory> CreateSegment(size_t aNBytes) { + if (!aNBytes) { + return nullptr; + } + RefPtr<SharedMemory> segment = NewSegment(); + if (!segment) { + return nullptr; + } + size_t size = SharedMemory::PageAlignedSize(aNBytes); + if (!segment->Create(size) || !segment->Map(size)) { + return nullptr; + } + return segment.forget(); +} + +static already_AddRefed<SharedMemory> ReadSegment( + const IPC::Message& aDescriptor, Shmem::id_t* aId, size_t* aNBytes) { + if (SHMEM_CREATED_MESSAGE_TYPE != aDescriptor.type()) { + NS_ERROR("expected 'shmem created' message"); + return nullptr; + } + IPC::MessageReader reader(aDescriptor); + if (!ShmemCreated::ReadInfo(&reader, aId, aNBytes)) { + return nullptr; + } + RefPtr<SharedMemory> segment = NewSegment(); + if (!segment) { + return nullptr; + } + if (!segment->ReadHandle(&reader)) { + NS_ERROR("trying to open invalid handle"); + return nullptr; + } + reader.EndRead(); + if (!*aNBytes) { + return nullptr; + } + size_t size = SharedMemory::PageAlignedSize(*aNBytes); + if (!segment->Map(size)) { + return nullptr; + } + // close the handle to the segment after it is mapped + segment->CloseHandle(); + return segment.forget(); +} + +#if defined(DEBUG) + +static void Protect(SharedMemory* aSegment) { + MOZ_ASSERT(aSegment, "null segment"); + aSegment->Protect(reinterpret_cast<char*>(aSegment->memory()), + aSegment->Size(), RightsNone); +} + +static void Unprotect(SharedMemory* aSegment) { + MOZ_ASSERT(aSegment, "null segment"); + aSegment->Protect(reinterpret_cast<char*>(aSegment->memory()), + aSegment->Size(), RightsRead | RightsWrite); +} + +void Shmem::AssertInvariants() const { + MOZ_ASSERT(mSegment, "null segment"); + MOZ_ASSERT(mData, "null data pointer"); + MOZ_ASSERT(mSize > 0, "invalid size"); + // if the segment isn't owned by the current process, these will + // trigger SIGSEGV + char checkMappingFront = *reinterpret_cast<char*>(mData); + char checkMappingBack = *(reinterpret_cast<char*>(mData) + mSize - 1); + + // avoid "unused" warnings for these variables: + Unused << checkMappingFront; + Unused << checkMappingBack; +} + +void Shmem::RevokeRights() { + AssertInvariants(); + + // When sending a non-unsafe shmem, remove read/write rights from the local + // mapping of the segment. + if (!mUnsafe) { + Protect(mSegment); + } +} + +#endif // if defined(DEBUG) + +Shmem::Shmem(SharedMemory* aSegment, id_t aId, size_t aSize, bool aUnsafe) + : mSegment(aSegment), mData(aSegment->memory()), mSize(aSize), mId(aId) { +#ifdef DEBUG + mUnsafe = aUnsafe; + Unprotect(mSegment); +#endif + + MOZ_RELEASE_ASSERT(mSegment->Size() >= mSize, + "illegal size in shared memory segment"); +} + +// static +already_AddRefed<Shmem::SharedMemory> Shmem::Alloc(size_t aNBytes) { + RefPtr<SharedMemory> segment = CreateSegment(aNBytes); + if (!segment) { + return nullptr; + } + + return segment.forget(); +} + +// static +already_AddRefed<Shmem::SharedMemory> Shmem::OpenExisting( + const IPC::Message& aDescriptor, id_t* aId, bool /*unused*/) { + size_t size; + RefPtr<SharedMemory> segment = ReadSegment(aDescriptor, aId, &size); + if (!segment) { + return nullptr; + } + + return segment.forget(); +} + +UniquePtr<IPC::Message> Shmem::MkCreatedMessage(int32_t routingId) { + AssertInvariants(); + + auto msg = MakeUnique<ShmemCreated>(routingId, mId, mSize); + IPC::MessageWriter writer(*msg); + if (!mSegment->WriteHandle(&writer)) { + return nullptr; + } + // close the handle to the segment after it is shared + mSegment->CloseHandle(); + return msg; +} + +UniquePtr<IPC::Message> Shmem::MkDestroyedMessage(int32_t routingId) { + AssertInvariants(); + return MakeUnique<ShmemDestroyed>(routingId, mId); +} + +void IPDLParamTraits<Shmem>::Write(IPC::MessageWriter* aWriter, + IProtocol* aActor, Shmem&& aParam) { + WriteIPDLParam(aWriter, aActor, aParam.mId); + WriteIPDLParam(aWriter, aActor, uint32_t(aParam.mSize)); +#ifdef DEBUG + WriteIPDLParam(aWriter, aActor, aParam.mUnsafe); +#endif + + aParam.RevokeRights(); + aParam.forget(); +} + +bool IPDLParamTraits<Shmem>::Read(IPC::MessageReader* aReader, + IProtocol* aActor, paramType* aResult) { + paramType::id_t id; + uint32_t size; + if (!ReadIPDLParam(aReader, aActor, &id) || + !ReadIPDLParam(aReader, aActor, &size)) { + return false; + } + + bool unsafe = false; +#ifdef DEBUG + if (!ReadIPDLParam(aReader, aActor, &unsafe)) { + return false; + } +#endif + + Shmem::SharedMemory* rawmem = aActor->LookupSharedMemory(id); + if (rawmem) { + if (size > rawmem->Size()) { + return false; + } + + *aResult = Shmem(rawmem, id, size, unsafe); + return true; + } + *aResult = Shmem(); + return true; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/Shmem.h b/ipc/glue/Shmem.h new file mode 100644 index 0000000000..657d5efbdd --- /dev/null +++ b/ipc/glue/Shmem.h @@ -0,0 +1,190 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_Shmem_h +#define mozilla_ipc_Shmem_h + +#include "mozilla/Attributes.h" + +#include "base/basictypes.h" +#include "base/process.h" + +#include "nscore.h" +#include "nsDebug.h" + +#include "mozilla/ipc/SharedMemory.h" +#include "mozilla/Range.h" +#include "mozilla/UniquePtr.h" + +/** + * |Shmem| is one agent in the IPDL shared memory scheme. The way it + works is essentially + * + * (1) C++ code calls, say, |parentActor->AllocShmem(size)| + + * (2) IPDL-generated code creates a |mozilla::ipc::SharedMemory| + * wrapping the bare OS shmem primitives. The code then adds the new + * SharedMemory to the set of shmem segments being managed by IPDL. + * + * (3) IPDL-generated code "shares" the new SharedMemory to the child + * process, and then sends a special asynchronous IPC message to the + * child notifying it of the creation of the segment. (What this + * means is OS specific.) + * + * (4a) The child receives the special IPC message, and using the + * |SharedMemory{Basic}::Handle| it was passed, creates a + * |mozilla::ipc::SharedMemory| in the child + * process. + * + * (4b) After sending the "shmem-created" IPC message, IPDL-generated + * code in the parent returns a |mozilla::ipc::Shmem| back to the C++ + * caller of |parentActor->AllocShmem()|. The |Shmem| is a "weak + * reference" to the underlying |SharedMemory|, which is managed by + * IPDL-generated code. C++ consumers of |Shmem| can't get at the + * underlying |SharedMemory|. + * + * If parent code wants to give access rights to the Shmem to the + * child, it does so by sending its |Shmem| to the child, in an IPDL + * message. The parent's |Shmem| then "dies", i.e. becomes + * inaccessible. This process could be compared to passing a + * "shmem-access baton" between parent and child. + */ + +namespace mozilla::ipc { + +class IProtocol; +class IToplevelProtocol; + +template <typename P> +struct IPDLParamTraits; + +class Shmem final { + friend struct IPDLParamTraits<mozilla::ipc::Shmem>; + friend class mozilla::ipc::IProtocol; + friend class mozilla::ipc::IToplevelProtocol; + + public: + typedef int32_t id_t; + // Low-level wrapper around platform shmem primitives. + typedef mozilla::ipc::SharedMemory SharedMemory; + + Shmem() : mSegment(nullptr), mData(nullptr), mSize(0), mId(0) {} + + Shmem(const Shmem& aOther) = default; + + ~Shmem() { + // Shmem only holds a "weak ref" to the actual segment, which is + // owned by IPDL. So there's nothing interesting to be done here + forget(); + } + + Shmem& operator=(const Shmem& aRhs) = default; + + bool operator==(const Shmem& aRhs) const { return mSegment == aRhs.mSegment; } + + // Returns whether this Shmem is writable by you, and thus whether you can + // transfer writability to another actor. + bool IsWritable() const { return mSegment != nullptr; } + + // Returns whether this Shmem is readable by you, and thus whether you can + // transfer readability to another actor. + bool IsReadable() const { return mSegment != nullptr; } + + // Return a pointer to the user-visible data segment. + template <typename T> + T* get() const { + AssertInvariants(); + AssertAligned<T>(); + + return reinterpret_cast<T*>(mData); + } + + // Return the size of the segment as requested when this shmem + // segment was allocated, in units of T. The underlying mapping may + // actually be larger because of page alignment and private data, + // but this isn't exposed to clients. + template <typename T> + size_t Size() const { + AssertInvariants(); + AssertAligned<T>(); + + return mSize / sizeof(T); + } + + template <typename T> + Range<T> Range() const { + return {get<T>(), Size<T>()}; + } + + private: + // These shouldn't be used directly, use the IPDL interface instead. + + Shmem(SharedMemory* aSegment, id_t aId, size_t aSize, bool aUnsafe); + + id_t Id() const { return mId; } + + SharedMemory* Segment() const { return mSegment; } + +#ifndef DEBUG + void RevokeRights() {} +#else + void RevokeRights(); +#endif + + void forget() { + mSegment = nullptr; + mData = nullptr; + mSize = 0; + mId = 0; +#ifdef DEBUG + mUnsafe = false; +#endif + } + + static already_AddRefed<Shmem::SharedMemory> Alloc(size_t aNBytes); + + // Prepare this to be shared with another process. Return an IPC message that + // contains enough information for the other process to map this segment in + // OpenExisting() below. Return a new message if successful (owned by the + // caller), nullptr if not. + UniquePtr<IPC::Message> MkCreatedMessage(int32_t routingId); + + // Stop sharing this with another process. Return an IPC message that + // contains enough information for the other process to unmap this + // segment. Return a new message if successful (owned by the + // caller), nullptr if not. + UniquePtr<IPC::Message> MkDestroyedMessage(int32_t routingId); + + // Return a SharedMemory instance in this process using the descriptor shared + // to us by the process that created the underlying OS shmem resource. The + // contents of the descriptor depend on the type of SharedMemory that was + // passed to us. + static already_AddRefed<SharedMemory> OpenExisting( + const IPC::Message& aDescriptor, id_t* aId, bool aProtect = false); + + template <typename T> + void AssertAligned() const { + if (0 != (mSize % sizeof(T))) MOZ_CRASH("shmem is not T-aligned"); + } + +#if !defined(DEBUG) + void AssertInvariants() const {} +#else + void AssertInvariants() const; +#endif + + RefPtr<SharedMemory> mSegment; + void* mData; + size_t mSize; + id_t mId; +#ifdef DEBUG + bool mUnsafe = false; +#endif +}; + +} // namespace mozilla::ipc + +#endif // ifndef mozilla_ipc_Shmem_h diff --git a/ipc/glue/ShmemMessageUtils.h b/ipc/glue/ShmemMessageUtils.h new file mode 100644 index 0000000000..c0f8c40215 --- /dev/null +++ b/ipc/glue/ShmemMessageUtils.h @@ -0,0 +1,30 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_ShmemMessageUtils_h +#define mozilla_ipc_ShmemMessageUtils_h + +#include "ipc/IPCMessageUtils.h" +#include "mozilla/ipc/IPDLParamTraits.h" +#include "mozilla/ipc/Shmem.h" + +namespace mozilla { +namespace ipc { + +template <> +struct IPDLParamTraits<Shmem> { + typedef Shmem paramType; + + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + paramType&& aParam); + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + paramType* aResult); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef mozilla_ipc_ShmemMessageUtils_h diff --git a/ipc/glue/SideVariant.h b/ipc/glue/SideVariant.h new file mode 100644 index 0000000000..3082feebde --- /dev/null +++ b/ipc/glue/SideVariant.h @@ -0,0 +1,187 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_SidedVariant_h +#define mozilla_ipc_SidedVariant_h + +#include <variant> +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "ipc/IPCMessageUtils.h" + +namespace mozilla { +namespace ipc { + +/** + * Helper type used by IPDL structs and unions to hold actor pointers with a + * dynamic side. + * + * When sent over IPC, ParentSide will be used for send/recv on parent actors, + * and ChildSide will be used for send/recv on child actors. + */ +template <typename ParentSide, typename ChildSide> +struct SideVariant { + public: + SideVariant() = default; + template <typename U, + std::enable_if_t<std::is_convertible_v<U&&, ParentSide>, int> = 0> + MOZ_IMPLICIT SideVariant(U&& aParent) : mParent(std::forward<U>(aParent)) {} + template <typename U, + std::enable_if_t<std::is_convertible_v<U&&, ChildSide>, int> = 0> + MOZ_IMPLICIT SideVariant(U&& aChild) : mChild(std::forward<U>(aChild)) {} + MOZ_IMPLICIT SideVariant(std::nullptr_t) {} + + MOZ_IMPLICIT SideVariant& operator=(ParentSide aParent) { + mParent = aParent; + mChild = nullptr; + return *this; + } + MOZ_IMPLICIT SideVariant& operator=(ChildSide aChild) { + mChild = aChild; + mParent = nullptr; + return *this; + } + MOZ_IMPLICIT SideVariant& operator=(std::nullptr_t) { + mChild = nullptr; + mParent = nullptr; + return *this; + } + + MOZ_IMPLICIT operator bool() const { return mParent || mChild; } + + bool IsNull() const { return !operator bool(); } + bool IsParent() const { return mParent; } + bool IsChild() const { return mChild; } + + ParentSide AsParent() const { + MOZ_ASSERT(IsNull() || IsParent()); + return mParent; + } + ChildSide AsChild() const { + MOZ_ASSERT(IsNull() || IsChild()); + return mChild; + } + + private: + // As the values are both pointers, this is the same size as a variant would + // be, but has less risk of type confusion, and supports an overall `nullptr` + // value which is neither parent nor child. + ParentSide mParent = nullptr; + ChildSide mChild = nullptr; +}; + +} // namespace ipc + +// NotNull specialization to expose AsChild and AsParent on the NotNull itself +// avoiding unnecessary unwrapping. +template <typename ParentSide, typename ChildSide> +class NotNull<mozilla::ipc::SideVariant<ParentSide, ChildSide>> { + template <typename U> + friend constexpr NotNull<U> WrapNotNull(U aBasePtr); + template <typename U> + friend constexpr NotNull<U> WrapNotNullUnchecked(U aBasePtr); + template <typename U> + friend class NotNull; + + using BasePtr = mozilla::ipc::SideVariant<ParentSide, ChildSide>; + + BasePtr mBasePtr; + + // This constructor is only used by WrapNotNull() and MakeNotNull<U>(). + template <typename U> + constexpr explicit NotNull(U aBasePtr) : mBasePtr(aBasePtr) {} + + public: + // Disallow default construction. + NotNull() = delete; + + // Construct/assign from another NotNull with a compatible base pointer type. + template <typename U, typename = std::enable_if_t< + std::is_convertible_v<const U&, BasePtr>>> + constexpr MOZ_IMPLICIT NotNull(const NotNull<U>& aOther) + : mBasePtr(aOther.get()) { + static_assert(sizeof(BasePtr) == sizeof(NotNull<BasePtr>), + "NotNull must have zero space overhead."); + static_assert(offsetof(NotNull<BasePtr>, mBasePtr) == 0, + "mBasePtr must have zero offset."); + } + + template <typename U, + typename = std::enable_if_t<std::is_convertible_v<U&&, BasePtr>>> + constexpr MOZ_IMPLICIT NotNull(MovingNotNull<U>&& aOther) + : mBasePtr(NotNull{std::move(aOther)}) {} + + // Disallow null checks, which are unnecessary for this type. + explicit operator bool() const = delete; + + // Explicit conversion to a base pointer. Use only to resolve ambiguity or to + // get a castable pointer. + constexpr const BasePtr& get() const { return mBasePtr; } + + // Implicit conversion to a base pointer. Preferable to get(). + constexpr operator const BasePtr&() const { return get(); } + + bool IsParent() const { return get().IsParent(); } + bool IsChild() const { return get().IsChild(); } + + NotNull<ParentSide> AsParent() const { return WrapNotNull(get().AsParent()); } + NotNull<ChildSide> AsChild() const { return WrapNotNull(get().AsChild()); } +}; + +} // namespace mozilla + +namespace IPC { + +template <typename ParentSide, typename ChildSide> +struct ParamTraits<mozilla::ipc::SideVariant<ParentSide, ChildSide>> { + typedef mozilla::ipc::SideVariant<ParentSide, ChildSide> paramType; + + static void Write(IPC::MessageWriter* aWriter, const paramType& aParam) { + if (!aWriter->GetActor()) { + aWriter->FatalError("actor required to serialize this type"); + return; + } + + if (aWriter->GetActor()->GetSide() == mozilla::ipc::ParentSide) { + if (aParam && !aParam.IsParent()) { + aWriter->FatalError("invalid side"); + return; + } + WriteParam(aWriter, aParam.AsParent()); + } else { + if (aParam && !aParam.IsChild()) { + aWriter->FatalError("invalid side"); + return; + } + WriteParam(aWriter, aParam.AsChild()); + } + } + + static ReadResult<paramType> Read(IPC::MessageReader* aReader) { + if (!aReader->GetActor()) { + aReader->FatalError("actor required to deserialize this type"); + return {}; + } + + if (aReader->GetActor()->GetSide() == mozilla::ipc::ParentSide) { + auto parentSide = ReadParam<ParentSide>(aReader); + if (!parentSide) { + return {}; + } + return std::move(*parentSide); + } + auto childSide = ReadParam<ChildSide>(aReader); + if (!childSide) { + return {}; + } + return std::move(*childSide); + } +}; + +} // namespace IPC + +#endif // mozilla_ipc_SidedVariant_h diff --git a/ipc/glue/StringUtil.cpp b/ipc/glue/StringUtil.cpp new file mode 100644 index 0000000000..af589bb720 --- /dev/null +++ b/ipc/glue/StringUtil.cpp @@ -0,0 +1,86 @@ +/* -*- 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 "base/string_util.h" + +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/sys_string_conversions.h" + +#include "base/string_piece.h" +#include "base/string_util.h" + +// FIXME/cjones: these really only pertain to the linux sys string +// converters. +#ifdef XP_WIN +# define ICONV_WCHAR_T_ENCODING "UTF-16" +#else +# define ICONV_WCHAR_T_ENCODING "WCHAR_T" +#endif + +// FIXME/cjones: BIG assumption here that std::string is a good +// container of UTF8-encoded strings. this is probably wrong, as its +// API doesn't really make sense for UTF8. + +namespace base { + +// FIXME/cjones: as its name implies, this function is a hack. +template <typename FromType, typename ToType> +ToType HackyStringConvert(const FromType& in) { + // FIXME/cjones: assumes no non-ASCII characters in |in| + ToType out; + out.resize(in.length()); + for (int i = 0; i < static_cast<int>(in.length()); ++i) + out[i] = static_cast<typename ToType::value_type>(in[i]); + return out; +} + +} // namespace base + +// Implement functions that were in the chromium ICU library, which +// we're not taking. + +std::string WideToUTF8(const std::wstring& wide) { + return base::SysWideToUTF8(wide); +} + +std::wstring UTF8ToWide(const StringPiece& utf8) { + return base::SysUTF8ToWide(utf8); +} + +namespace base { + +// FIXME/cjones: here we're entirely replacing the linux string +// converters, and implementing the one that doesn't exist for OS X +// and Windows. + +#if !defined(XP_DARWIN) && !defined(XP_WIN) +std::string SysWideToUTF8(const std::wstring& wide) { + // FIXME/cjones: do this with iconv + return HackyStringConvert<std::wstring, std::string>(wide); +} +#endif + +#if !defined(XP_DARWIN) && !defined(XP_WIN) +std::wstring SysUTF8ToWide(const StringPiece& utf8) { + // FIXME/cjones: do this with iconv + return HackyStringConvert<StringPiece, std::wstring>(utf8); +} + +std::string SysWideToNativeMB(const std::wstring& wide) { + // TODO(evanm): we can't assume Linux is UTF-8. + return SysWideToUTF8(wide); +} + +std::wstring SysNativeMBToWide(const StringPiece& native_mb) { + // TODO(evanm): we can't assume Linux is UTF-8. + return SysUTF8ToWide(native_mb); +} +#endif + +} // namespace base diff --git a/ipc/glue/TaintingIPCUtils.h b/ipc/glue/TaintingIPCUtils.h new file mode 100644 index 0000000000..d4f61d95b5 --- /dev/null +++ b/ipc/glue/TaintingIPCUtils.h @@ -0,0 +1,41 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_Tainting_h +#define mozilla_ipc_Tainting_h + +#include "mozilla/Tainting.h" + +#include "base/basictypes.h" +#include "base/process.h" + +#include "mozilla/ipc/IPDLParamTraits.h" + +namespace mozilla { +namespace ipc { + +template <typename T> +struct IPDLParamTraits<mozilla::Tainted<T>> { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + const mozilla::Tainted<T>& aParam) { + WriteIPDLParam(aWriter, aActor, aParam.mValue); + } + + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + mozilla::Tainted<T>&& aParam) { + WriteIPDLParam(aWriter, aActor, std::move(aParam.mValue)); + } + + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + mozilla::Tainted<T>* aResult) { + return ReadIPDLParam(aReader, aActor, &(aResult->mValue)); + } +}; + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef mozilla_ipc_Tainting_h diff --git a/ipc/glue/TaskFactory.h b/ipc/glue/TaskFactory.h new file mode 100644 index 0000000000..af63244cf0 --- /dev/null +++ b/ipc/glue/TaskFactory.h @@ -0,0 +1,97 @@ +/* -*- 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/. */ + +#ifndef mozilla_plugins_TaskFactory_h +#define mozilla_plugins_TaskFactory_h + +#include <base/task.h> + +#include <utility> + +/* + * This is based on the ScopedRunnableMethodFactory from + * ipc/chromium/src/base/task.h Chromium's factories assert if tasks are created + * and run on different threads, which is something we need to do in + * PluginModuleParent (hang UI vs. main thread). TaskFactory just provides + * cancellable tasks that don't assert this. This version also allows both + * ScopedMethod and regular Tasks to be generated by the same Factory object. + */ + +namespace mozilla { +namespace ipc { + +template <class T> +class TaskFactory : public RevocableStore { + private: + template <class TaskType> + class TaskWrapper : public TaskType { + public: + template <typename... Args> + explicit TaskWrapper(RevocableStore* store, Args&&... args) + : TaskType(std::forward<Args>(args)...), revocable_(store) {} + + NS_IMETHOD Run() override { + if (!revocable_.revoked()) TaskType::Run(); + return NS_OK; + } + + private: + Revocable revocable_; + }; + + public: + explicit TaskFactory(T* object) : object_(object) {} + + template <typename TaskParamType, typename... Args> + inline already_AddRefed<TaskParamType> NewTask(Args&&... args) { + typedef TaskWrapper<TaskParamType> TaskWrapper; + RefPtr<TaskWrapper> task = + new TaskWrapper(this, std::forward<Args>(args)...); + return task.forget(); + } + + template <class Method, typename... Args> + inline already_AddRefed<Runnable> NewRunnableMethod(Method method, + Args&&... args) { + typedef decltype(base::MakeTuple(std::forward<Args>(args)...)) ArgTuple; + typedef RunnableMethod<Method, ArgTuple> RunnableMethod; + typedef TaskWrapper<RunnableMethod> TaskWrapper; + + RefPtr<TaskWrapper> task = new TaskWrapper( + this, object_, method, base::MakeTuple(std::forward<Args>(args)...)); + + return task.forget(); + } + + protected: + template <class Method, class Params> + class RunnableMethod : public Runnable { + public: + RunnableMethod(T* obj, Method meth, const Params& params) + : Runnable("ipc::TaskFactory::RunnableMethod"), + obj_(obj), + meth_(meth), + params_(params) {} + + NS_IMETHOD Run() override { + DispatchToMethod(obj_, meth_, params_); + return NS_OK; + } + + private: + T* obj_; + Method meth_; + Params params_; + }; + + private: + T* object_; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_plugins_TaskFactory_h diff --git a/ipc/glue/ToplevelActorHolder.h b/ipc/glue/ToplevelActorHolder.h new file mode 100644 index 0000000000..6e95ef8565 --- /dev/null +++ b/ipc/glue/ToplevelActorHolder.h @@ -0,0 +1,45 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_IPC_TOPLEVELACTORHOLDER_H +#define MOZILLA_IPC_TOPLEVELACTORHOLDER_H + +#include "nsISupports.h" + +namespace mozilla::ipc { + +// Class to let us close the actor when we're not using it anymore. You +// should create a single instance of this, and when you have no more +// references it will be destroyed and will Close() the underlying +// top-level channel. +// When you want to send something, you use something like +// aActor->Actor()->SendFoo() + +// You can avoid calling Close() on an un-connected Actor (for example if +// Bind() fails) by calling RemoveActor(); +template <typename T> +class ToplevelActorHolder final { + public: + NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET(ToplevelActorHolder) + + explicit ToplevelActorHolder(T* aActor) : mActor(aActor) {} + + constexpr T* Actor() const { return mActor; } + inline void RemoveActor() { mActor = nullptr; } + + private: + inline ~ToplevelActorHolder() { + if (mActor) { + mActor->Close(); + } + } + + RefPtr<T> mActor; +}; + +} // namespace mozilla::ipc + +#endif // MOZILLA_IPC_TOPLEVELACTORHOLDER_H diff --git a/ipc/glue/TransportSecurityInfoUtils.cpp b/ipc/glue/TransportSecurityInfoUtils.cpp new file mode 100644 index 0000000000..9898e2b579 --- /dev/null +++ b/ipc/glue/TransportSecurityInfoUtils.cpp @@ -0,0 +1,78 @@ +/* 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 "TransportSecurityInfoUtils.h" + +#include "ipc/IPCMessageUtils.h" +#include "mozilla/psm/TransportSecurityInfo.h" +#include "nsNSSCertificate.h" + +namespace IPC { + +void ParamTraits<nsITransportSecurityInfo*>::Write( + MessageWriter* aWriter, nsITransportSecurityInfo* aParam) { + bool nonNull = !!aParam; + WriteParam(aWriter, nonNull); + if (!nonNull) { + return; + } + + aParam->SerializeToIPC(aWriter); +} + +bool ParamTraits<nsITransportSecurityInfo*>::Read( + MessageReader* aReader, RefPtr<nsITransportSecurityInfo>* aResult) { + *aResult = nullptr; + + bool nonNull = false; + if (!ReadParam(aReader, &nonNull)) { + return false; + } + + if (!nonNull) { + return true; + } + + if (!mozilla::psm::TransportSecurityInfo::DeserializeFromIPC(aReader, + aResult)) { + return false; + } + + return true; +} + +void ParamTraits<nsIX509Cert*>::Write(MessageWriter* aWriter, + nsIX509Cert* aParam) { + bool nonNull = !!aParam; + WriteParam(aWriter, nonNull); + if (!nonNull) { + return; + } + + aParam->SerializeToIPC(aWriter); +} + +bool ParamTraits<nsIX509Cert*>::Read(MessageReader* aReader, + RefPtr<nsIX509Cert>* aResult) { + *aResult = nullptr; + + bool nonNull = false; + if (!ReadParam(aReader, &nonNull)) { + return false; + } + + if (!nonNull) { + return true; + } + + RefPtr<nsIX509Cert> cert = new nsNSSCertificate(); + if (!cert->DeserializeFromIPC(aReader)) { + return false; + } + + *aResult = std::move(cert); + return true; +} + +} // namespace IPC diff --git a/ipc/glue/TransportSecurityInfoUtils.h b/ipc/glue/TransportSecurityInfoUtils.h new file mode 100644 index 0000000000..009db8e162 --- /dev/null +++ b/ipc/glue/TransportSecurityInfoUtils.h @@ -0,0 +1,43 @@ +/* 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_ipc_TransportSecurityInfoUtils_h +#define mozilla_ipc_TransportSecurityInfoUtils_h + +#include "ipc/EnumSerializer.h" +#include "mozilla/RefPtr.h" +#include "nsITransportSecurityInfo.h" +#include "nsIX509Cert.h" + +class MessageReader; +class MessageWriter; + +namespace IPC { + +template <typename> +struct ParamTraits; + +template <> +struct ParamTraits<nsITransportSecurityInfo*> { + static void Write(MessageWriter* aWriter, nsITransportSecurityInfo* aParam); + static bool Read(MessageReader* aReader, + RefPtr<nsITransportSecurityInfo>* aResult); +}; + +template <> +struct ParamTraits<nsIX509Cert*> { + static void Write(MessageWriter* aWriter, nsIX509Cert* aCert); + static bool Read(MessageReader* aReader, RefPtr<nsIX509Cert>* aResult); +}; + +template <> +struct ParamTraits<nsITransportSecurityInfo::OverridableErrorCategory> + : public ContiguousEnumSerializerInclusive< + nsITransportSecurityInfo::OverridableErrorCategory, + nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET, + nsITransportSecurityInfo::OverridableErrorCategory::ERROR_TIME> {}; + +} // namespace IPC + +#endif // mozilla_ipc_TransportSecurityInfoUtils_h diff --git a/ipc/glue/URIParams.ipdlh b/ipc/glue/URIParams.ipdlh new file mode 100644 index 0000000000..99b18afd0f --- /dev/null +++ b/ipc/glue/URIParams.ipdlh @@ -0,0 +1,117 @@ +/* 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/. */ + + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; + +include PBackgroundSharedTypes; + +namespace mozilla { +namespace ipc { + +struct SimpleURIParams +{ + nsCString scheme; + nsCString path; + nsCString ref; + nsCString query; +}; + +struct DefaultURIParams +{ + nsCString spec; +}; + +struct StandardURLSegment +{ + uint32_t position; + int32_t length; +}; + +struct StandardURLParams +{ + uint32_t urlType; + int32_t port; + int32_t defaultPort; + nsCString spec; + StandardURLSegment scheme; + StandardURLSegment authority; + StandardURLSegment username; + StandardURLSegment password; + StandardURLSegment host; + StandardURLSegment path; + StandardURLSegment filePath; + StandardURLSegment directory; + StandardURLSegment baseName; + StandardURLSegment extension; + StandardURLSegment query; + StandardURLSegment ref; + bool supportsFileURL; + bool isSubstituting; +}; + +struct JARURIParams +{ + URIParams jarFile; + URIParams jarEntry; + nsCString charset; +}; + +struct IconURIParams +{ + URIParams? uri; + uint32_t size; + nsCString contentType; + nsCString fileName; + nsCString stockIcon; + int32_t iconSize; + int32_t iconState; +}; + +struct HostObjectURIParams +{ + SimpleURIParams simpleParams; + bool revoked; +}; + +union URIParams +{ + SimpleURIParams; + StandardURLParams; + JARURIParams; + IconURIParams; + JSURIParams; + SimpleNestedURIParams; + HostObjectURIParams; + DefaultURIParams; + NestedAboutURIParams; + SubstitutingJARURIParams; +}; + +struct JSURIParams +{ + SimpleURIParams simpleParams; + URIParams? baseURI; +}; + +struct SimpleNestedURIParams +{ + SimpleURIParams simpleParams; + URIParams innerURI; +}; + +struct NestedAboutURIParams +{ + SimpleNestedURIParams nestedParams; + URIParams? baseURI; +}; + +struct SubstitutingJARURIParams +{ + URIParams source; + JARURIParams resolved; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/URIUtils.cpp b/ipc/glue/URIUtils.cpp new file mode 100644 index 0000000000..690509cecc --- /dev/null +++ b/ipc/glue/URIUtils.cpp @@ -0,0 +1,140 @@ +/* -*- 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 "URIUtils.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/BlobURL.h" +#include "mozilla/net/DefaultURI.h" +#include "mozilla/net/SubstitutingJARURI.h" +#include "mozilla/net/SubstitutingURL.h" +#include "nsAboutProtocolHandler.h" +#include "nsComponentManagerUtils.h" +#include "nsDebug.h" +#include "nsID.h" +#include "nsJARURI.h" +#include "nsIIconURI.h" +#include "nsJSProtocolHandler.h" +#include "nsNetCID.h" +#include "nsSimpleNestedURI.h" +#include "nsThreadUtils.h" +#include "nsIURIMutator.h" + +using namespace mozilla::ipc; +using mozilla::ArrayLength; + +namespace { + +NS_DEFINE_CID(kSimpleURIMutatorCID, NS_SIMPLEURIMUTATOR_CID); +NS_DEFINE_CID(kStandardURLMutatorCID, NS_STANDARDURLMUTATOR_CID); +NS_DEFINE_CID(kJARURIMutatorCID, NS_JARURIMUTATOR_CID); +NS_DEFINE_CID(kIconURIMutatorCID, NS_MOZICONURIMUTATOR_CID); + +} // namespace + +namespace mozilla { +namespace ipc { + +void SerializeURI(nsIURI* aURI, URIParams& aParams) { + MOZ_ASSERT(aURI); + + aURI->Serialize(aParams); + if (aParams.type() == URIParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } +} + +void SerializeURI(nsIURI* aURI, Maybe<URIParams>& aParams) { + if (aURI) { + URIParams params; + SerializeURI(aURI, params); + aParams = Some(std::move(params)); + } else { + aParams = Nothing(); + } +} + +already_AddRefed<nsIURI> DeserializeURI(const URIParams& aParams) { + nsCOMPtr<nsIURIMutator> mutator; + + switch (aParams.type()) { + case URIParams::TSimpleURIParams: + mutator = do_CreateInstance(kSimpleURIMutatorCID); + break; + + case URIParams::TStandardURLParams: + if (aParams.get_StandardURLParams().isSubstituting()) { + mutator = new net::SubstitutingURL::Mutator(); + } else { + mutator = do_CreateInstance(kStandardURLMutatorCID); + } + break; + + case URIParams::TJARURIParams: + mutator = do_CreateInstance(kJARURIMutatorCID); + break; + + case URIParams::TJSURIParams: + mutator = new nsJSURI::Mutator(); + break; + + case URIParams::TIconURIParams: + mutator = do_CreateInstance(kIconURIMutatorCID); + break; + + case URIParams::TSimpleNestedURIParams: + mutator = new net::nsSimpleNestedURI::Mutator(); + break; + + case URIParams::THostObjectURIParams: + mutator = new mozilla::dom::BlobURL::Mutator(); + break; + + case URIParams::TDefaultURIParams: + mutator = new mozilla::net::DefaultURI::Mutator(); + break; + + case URIParams::TNestedAboutURIParams: + mutator = new net::nsNestedAboutURI::Mutator(); + break; + + case URIParams::TSubstitutingJARURIParams: + mutator = new net::SubstitutingJARURI::Mutator(); + break; + + default: + MOZ_CRASH("Unknown params!"); + } + + MOZ_ASSERT(mutator); + + nsresult rv = mutator->Deserialize(aParams); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Deserialize failed!"); + return nullptr; + } + + nsCOMPtr<nsIURI> uri; + DebugOnly<nsresult> rv2 = mutator->Finalize(getter_AddRefs(uri)); + MOZ_ASSERT(uri); + MOZ_ASSERT(NS_SUCCEEDED(rv2)); + + return uri.forget(); +} + +already_AddRefed<nsIURI> DeserializeURI(const Maybe<URIParams>& aParams) { + nsCOMPtr<nsIURI> uri; + + if (aParams.isSome()) { + uri = DeserializeURI(aParams.ref()); + } + + return uri.forget(); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/URIUtils.h b/ipc/glue/URIUtils.h new file mode 100644 index 0000000000..7cb8687bbe --- /dev/null +++ b/ipc/glue/URIUtils.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_URIUtils_h +#define mozilla_ipc_URIUtils_h + +#include "mozilla/ipc/URIParams.h" +#include "mozilla/ipc/IPDLParamTraits.h" +#include "nsCOMPtr.h" +#include "nsIURI.h" + +namespace mozilla { +namespace ipc { + +void SerializeURI(nsIURI* aURI, URIParams& aParams); + +void SerializeURI(nsIURI* aURI, Maybe<URIParams>& aParams); + +already_AddRefed<nsIURI> DeserializeURI(const URIParams& aParams); + +already_AddRefed<nsIURI> DeserializeURI(const Maybe<URIParams>& aParams); + +template <> +struct IPDLParamTraits<nsIURI*> { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + nsIURI* aParam) { + Maybe<URIParams> params; + SerializeURI(aParam, params); + WriteIPDLParam(aWriter, aActor, params); + } + + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + RefPtr<nsIURI>* aResult) { + Maybe<URIParams> params; + if (!ReadIPDLParam(aReader, aActor, ¶ms)) { + return false; + } + *aResult = DeserializeURI(params); + return true; + } +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_URIUtils_h diff --git a/ipc/glue/UtilityAudioDecoder.cpp b/ipc/glue/UtilityAudioDecoder.cpp new file mode 100644 index 0000000000..0b28fd601e --- /dev/null +++ b/ipc/glue/UtilityAudioDecoder.cpp @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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 "mozilla/ProcInfo.h" +#include "mozilla/ipc/UtilityAudioDecoder.h" +#include "mozilla/ipc/UtilityProcessChild.h" + +namespace mozilla::ipc { + +UtilityActorName GetAudioActorName(const SandboxingKind aSandbox) { + switch (aSandbox) { + case GENERIC_UTILITY: + return UtilityActorName::AudioDecoder_Generic; +#ifdef MOZ_APPLEMEDIA + case UTILITY_AUDIO_DECODING_APPLE_MEDIA: + return UtilityActorName::AudioDecoder_AppleMedia; +#endif +#ifdef XP_WIN + case UTILITY_AUDIO_DECODING_WMF: + return UtilityActorName::AudioDecoder_WMF; +#endif +#ifdef MOZ_WMF_MEDIA_ENGINE + case MF_MEDIA_ENGINE_CDM: + return UtilityActorName::MfMediaEngineCDM; +#endif + default: + MOZ_CRASH("Unexpected mSandbox for GetActorName()"); + } +} + +nsCString GetChildAudioActorName() { + RefPtr<ipc::UtilityProcessChild> s = ipc::UtilityProcessChild::Get(); + MOZ_ASSERT(s, "Has UtilityProcessChild"); + return nsCString(dom::WebIDLUtilityActorNameValues::GetString( + GetAudioActorName(s->mSandbox))); +} + +} // namespace mozilla::ipc diff --git a/ipc/glue/UtilityAudioDecoder.h b/ipc/glue/UtilityAudioDecoder.h new file mode 100644 index 0000000000..4fed9aab64 --- /dev/null +++ b/ipc/glue/UtilityAudioDecoder.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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/. */ +#ifndef _include_ipc_glue_UtilityAudioDecoder_h__ +#define _include_ipc_glue_UtilityAudioDecoder_h__ + +#include "mozilla/dom/ChromeUtilsBinding.h" +#include "mozilla/ipc/UtilityProcessSandboxing.h" + +namespace mozilla::ipc { + +using UtilityActorName = mozilla::dom::WebIDLUtilityActorName; + +UtilityActorName GetAudioActorName(const SandboxingKind aSandbox); +nsCString GetChildAudioActorName(); + +} // namespace mozilla::ipc + +#endif // _include_ipc_glue_UtilityAudioDecoder_h__ diff --git a/ipc/glue/UtilityAudioDecoderChild.cpp b/ipc/glue/UtilityAudioDecoderChild.cpp new file mode 100644 index 0000000000..88124a1f8b --- /dev/null +++ b/ipc/glue/UtilityAudioDecoderChild.cpp @@ -0,0 +1,232 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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 "UtilityAudioDecoderChild.h" + +#include "base/basictypes.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/dom/ContentParent.h" + +#ifdef MOZ_WMF_MEDIA_ENGINE +# include "mozilla/StaticPrefs_media.h" +# include "mozilla/gfx/GPUProcessManager.h" +# include "mozilla/gfx/gfxVars.h" +# include "mozilla/ipc/UtilityProcessManager.h" +# include "mozilla/layers/PVideoBridge.h" +# include "mozilla/layers/VideoBridgeUtils.h" +#endif + +#ifdef MOZ_WMF_CDM +# include "mozilla/dom/Promise.h" +# include "mozilla/EMEUtils.h" +# include "mozilla/PMFCDM.h" +#endif + +namespace mozilla::ipc { + +NS_IMETHODIMP UtilityAudioDecoderChildShutdownObserver::Observe( + nsISupports* aSubject, const char* aTopic, const char16_t* aData) { + MOZ_ASSERT(strcmp(aTopic, "ipc:utility-shutdown") == 0); + + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, "ipc:utility-shutdown"); + } + + UtilityAudioDecoderChild::Shutdown(mSandbox); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(UtilityAudioDecoderChildShutdownObserver, nsIObserver); + +static EnumeratedArray<SandboxingKind, SandboxingKind::COUNT, + StaticRefPtr<UtilityAudioDecoderChild>> + sAudioDecoderChilds; + +UtilityAudioDecoderChild::UtilityAudioDecoderChild(SandboxingKind aKind) + : mSandbox(aKind), mAudioDecoderChildStart(TimeStamp::Now()) { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + if (observerService) { + auto* obs = new UtilityAudioDecoderChildShutdownObserver(aKind); + observerService->AddObserver(obs, "ipc:utility-shutdown", false); + } +} + +void UtilityAudioDecoderChild::ActorDestroy(ActorDestroyReason aReason) { + MOZ_ASSERT(NS_IsMainThread()); +#ifdef MOZ_WMF_MEDIA_ENGINE + if (mSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM) { + gfx::gfxVars::RemoveReceiver(this); + } +#endif + Shutdown(mSandbox); +} + +void UtilityAudioDecoderChild::Bind( + Endpoint<PUtilityAudioDecoderChild>&& aEndpoint) { + MOZ_ASSERT(NS_IsMainThread()); + if (NS_WARN_IF(!aEndpoint.Bind(this))) { + MOZ_ASSERT_UNREACHABLE("Failed to bind UtilityAudioDecoderChild!"); + return; + } +#ifdef MOZ_WMF_MEDIA_ENGINE + if (mSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM) { + gfx::gfxVars::AddReceiver(this); + } +#endif +} + +/* static */ +void UtilityAudioDecoderChild::Shutdown(SandboxingKind aKind) { + sAudioDecoderChilds[aKind] = nullptr; +} + +/* static */ +RefPtr<UtilityAudioDecoderChild> UtilityAudioDecoderChild::GetSingleton( + SandboxingKind aKind) { + MOZ_ASSERT(NS_IsMainThread()); + bool shutdown = AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown); + if (!sAudioDecoderChilds[aKind] && !shutdown) { + sAudioDecoderChilds[aKind] = new UtilityAudioDecoderChild(aKind); + } + return sAudioDecoderChilds[aKind]; +} + +mozilla::ipc::IPCResult +UtilityAudioDecoderChild::RecvUpdateMediaCodecsSupported( + const RemoteDecodeIn& aLocation, + const media::MediaCodecsSupported& aSupported) { + dom::ContentParent::BroadcastMediaCodecsSupportedUpdate(aLocation, + aSupported); + return IPC_OK(); +} + +#ifdef MOZ_WMF_MEDIA_ENGINE +mozilla::ipc::IPCResult +UtilityAudioDecoderChild::RecvCompleteCreatedVideoBridge() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM); + mHasCreatedVideoBridge = true; + return IPC_OK(); +} + +bool UtilityAudioDecoderChild::HasCreatedVideoBridge() const { + MOZ_ASSERT(NS_IsMainThread()); + return mHasCreatedVideoBridge; +} + +void UtilityAudioDecoderChild::OnVarChanged(const gfx::GfxVarUpdate& aVar) { + MOZ_ASSERT(mSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM); + SendUpdateVar(aVar); +} + +void UtilityAudioDecoderChild::OnCompositorUnexpectedShutdown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM); + mHasCreatedVideoBridge = false; + CreateVideoBridge(); +} + +bool UtilityAudioDecoderChild::CreateVideoBridge() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM); + + if (HasCreatedVideoBridge()) { + return true; + } + + // Build content device data first; this ensure that the GPU process is fully + // ready. + gfx::ContentDeviceData contentDeviceData; + gfxPlatform::GetPlatform()->BuildContentDeviceData(&contentDeviceData); + + gfx::GPUProcessManager* gpuManager = gfx::GPUProcessManager::Get(); + if (!gpuManager) { + NS_WARNING("Failed to get a gpu mananger!"); + return false; + } + + // The child end is the producer of video frames; the parent end is the + // consumer. + base::ProcessId childPid = UtilityProcessManager::GetSingleton() + ->GetProcessParent(mSandbox) + ->OtherPid(); + base::ProcessId parentPid = gpuManager->GPUProcessPid(); + if (parentPid == base::kInvalidProcessId) { + NS_WARNING("GPU process Id is invald!"); + return false; + } + + ipc::Endpoint<layers::PVideoBridgeParent> parentPipe; + ipc::Endpoint<layers::PVideoBridgeChild> childPipe; + nsresult rv = layers::PVideoBridge::CreateEndpoints(parentPid, childPid, + &parentPipe, &childPipe); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create endpoints for video bridge!"); + return false; + } + + nsTArray<gfx::GfxVarUpdate> updates = gfx::gfxVars::FetchNonDefaultVars(); + gpuManager->InitVideoBridge( + std::move(parentPipe), + layers::VideoBridgeSource::MFMediaEngineCDMProcess); + SendInitVideoBridge(std::move(childPipe), updates, contentDeviceData); + return true; +} +#endif + +#ifdef MOZ_WMF_CDM +void UtilityAudioDecoderChild::GetKeySystemCapabilities( + dom::Promise* aPromise) { + EME_LOG("Ask capabilities for all supported CDMs"); + SendGetKeySystemCapabilities()->Then( + NS_GetCurrentThread(), __func__, + [promise = RefPtr<dom::Promise>(aPromise)]( + CopyableTArray<MFCDMCapabilitiesIPDL>&& result) { + FallibleTArray<dom::CDMInformation> cdmInfo; + for (const auto& capabilities : result) { + EME_LOG("Received capabilities for %s", + NS_ConvertUTF16toUTF8(capabilities.keySystem()).get()); + for (const auto& v : capabilities.videoCapabilities()) { + EME_LOG(" capabilities: video=%s", + NS_ConvertUTF16toUTF8(v.contentType()).get()); + } + for (const auto& a : capabilities.audioCapabilities()) { + EME_LOG(" capabilities: audio=%s", + NS_ConvertUTF16toUTF8(a.contentType()).get()); + } + for (const auto& e : capabilities.encryptionSchemes()) { + EME_LOG(" capabilities: encryptionScheme=%s", + EncryptionSchemeStr(e)); + } + auto* info = cdmInfo.AppendElement(fallible); + if (!info) { + promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY); + return; + } + info->mKeySystemName = capabilities.keySystem(); + + KeySystemConfig config; + MFCDMCapabilitiesIPDLToKeySystemConfig(capabilities, config); + info->mCapabilities = config.GetDebugInfo(); + info->mClearlead = + DoesKeySystemSupportClearLead(info->mKeySystemName); + if (capabilities.isHDCP22Compatible()) { + info->mIsHDCP22Compatible = true; + } + } + promise->MaybeResolve(cdmInfo); + }, + [promise = RefPtr<dom::Promise>(aPromise)]( + const mozilla::ipc::ResponseRejectReason& aReason) { + EME_LOG("IPC failure for GetKeySystemCapabilities!"); + promise->MaybeReject(NS_ERROR_DOM_MEDIA_CDM_ERR); + }); +} +#endif + +} // namespace mozilla::ipc diff --git a/ipc/glue/UtilityAudioDecoderChild.h b/ipc/glue/UtilityAudioDecoderChild.h new file mode 100644 index 0000000000..4e6a7792b0 --- /dev/null +++ b/ipc/glue/UtilityAudioDecoderChild.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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/. */ +#ifndef _include_ipc_glue_UtilityAudioDecoderChild_h__ +#define _include_ipc_glue_UtilityAudioDecoderChild_h__ + +#include "mozilla/ProcInfo.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/RefPtr.h" + +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/UtilityProcessParent.h" +#include "mozilla/ipc/UtilityProcessSandboxing.h" +#include "mozilla/ipc/UtilityAudioDecoder.h" +#include "mozilla/ipc/PUtilityAudioDecoderChild.h" + +#ifdef MOZ_WMF_MEDIA_ENGINE +# include "mozilla/gfx/GPUProcessListener.h" +# include "mozilla/gfx/gfxVarReceiver.h" +#endif + +#include "PDMFactory.h" + +namespace mozilla::ipc { + +class UtilityAudioDecoderChildShutdownObserver : public nsIObserver { + public: + explicit UtilityAudioDecoderChildShutdownObserver(SandboxingKind aKind) + : mSandbox(aKind){}; + + NS_DECL_ISUPPORTS + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override; + + private: + virtual ~UtilityAudioDecoderChildShutdownObserver() = default; + + const SandboxingKind mSandbox; +}; + +// This controls performing audio decoding on the utility process and it is +// intended to live on the main process side +class UtilityAudioDecoderChild final : public PUtilityAudioDecoderChild +#ifdef MOZ_WMF_MEDIA_ENGINE + , + public gfx::gfxVarReceiver, + public gfx::GPUProcessListener +#endif +{ + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityAudioDecoderChild, override); + mozilla::ipc::IPCResult RecvUpdateMediaCodecsSupported( + const RemoteDecodeIn& aLocation, + const media::MediaCodecsSupported& aSupported); + + UtilityActorName GetActorName() { return GetAudioActorName(mSandbox); } + + nsresult BindToUtilityProcess(RefPtr<UtilityProcessParent> aUtilityParent) { + Endpoint<PUtilityAudioDecoderChild> utilityAudioDecoderChildEnd; + Endpoint<PUtilityAudioDecoderParent> utilityAudioDecoderParentEnd; + nsresult rv = PUtilityAudioDecoder::CreateEndpoints( + aUtilityParent->OtherPid(), base::GetCurrentProcId(), + &utilityAudioDecoderParentEnd, &utilityAudioDecoderChildEnd); + + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Protocol endpoints failure"); + return NS_ERROR_FAILURE; + } + + if (!aUtilityParent->SendStartUtilityAudioDecoderService( + std::move(utilityAudioDecoderParentEnd))) { + MOZ_ASSERT(false, "StartUtilityAudioDecoder service failure"); + return NS_ERROR_FAILURE; + } + + Bind(std::move(utilityAudioDecoderChildEnd)); + + PROFILER_MARKER_UNTYPED( + "UtilityAudioDecoderChild::BindToUtilityProcess", IPC, + MarkerOptions( + MarkerTiming::IntervalUntilNowFrom(mAudioDecoderChildStart))); + return NS_OK; + } + + void ActorDestroy(ActorDestroyReason aReason) override; + + void Bind(Endpoint<PUtilityAudioDecoderChild>&& aEndpoint); + + static void Shutdown(SandboxingKind aKind); + + static RefPtr<UtilityAudioDecoderChild> GetSingleton(SandboxingKind aKind); + +#ifdef MOZ_WMF_MEDIA_ENGINE + mozilla::ipc::IPCResult RecvCompleteCreatedVideoBridge(); + + bool HasCreatedVideoBridge() const; + + void OnVarChanged(const gfx::GfxVarUpdate& aVar) override; + + void OnCompositorUnexpectedShutdown() override; + + // True if creating a video bridge sucessfully. Currently only used for media + // engine cdm. + bool CreateVideoBridge(); +#endif + +#ifdef MOZ_WMF_CDM + void GetKeySystemCapabilities(dom::Promise* aPromise); +#endif + + private: + explicit UtilityAudioDecoderChild(SandboxingKind aKind); + ~UtilityAudioDecoderChild() = default; + + const SandboxingKind mSandbox; + +#ifdef MOZ_WMF_MEDIA_ENGINE + // True if the utility process has created a video bridge with the GPU prcess. + // Currently only used for media egine cdm. Main thread only. + bool mHasCreatedVideoBridge = false; +#endif + + TimeStamp mAudioDecoderChildStart; +}; + +} // namespace mozilla::ipc + +#endif // _include_ipc_glue_UtilityAudioDecoderChild_h__ diff --git a/ipc/glue/UtilityAudioDecoderParent.cpp b/ipc/glue/UtilityAudioDecoderParent.cpp new file mode 100644 index 0000000000..2eb0936a38 --- /dev/null +++ b/ipc/glue/UtilityAudioDecoderParent.cpp @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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 "UtilityAudioDecoderParent.h" + +#include "GeckoProfiler.h" +#include "nsDebugImpl.h" + +#include "mozilla/RemoteDecoderManagerParent.h" +#include "PDMFactory.h" + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +# include "WMF.h" +# include "WMFDecoderModule.h" +# include "WMFUtils.h" + +# include "mozilla/sandboxTarget.h" +# include "mozilla/ipc/UtilityProcessImpl.h" +#endif // defined(XP_WIN) && defined(MOZ_SANDBOX) + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/StaticPrefs_media.h" +# include "AndroidDecoderModule.h" +#endif + +#include "mozilla/ipc/UtilityProcessChild.h" +#include "mozilla/RemoteDecodeUtils.h" + +#ifdef MOZ_WMF_MEDIA_ENGINE +# include "mozilla/gfx/DeviceManagerDx.h" +# include "mozilla/gfx/gfxVars.h" +# include "gfxConfig.h" +#endif + +#ifdef MOZ_WMF_CDM +# include "mozilla/MFCDMParent.h" +# include "mozilla/PMFCDM.h" +#endif + +namespace mozilla::ipc { + +UtilityAudioDecoderParent::UtilityAudioDecoderParent() + : mKind(GetCurrentSandboxingKind()), + mAudioDecoderParentStart(TimeStamp::Now()) { +#ifdef MOZ_WMF_MEDIA_ENGINE + if (mKind == SandboxingKind::MF_MEDIA_ENGINE_CDM) { + nsDebugImpl::SetMultiprocessMode("MF Media Engine CDM"); + profiler_set_process_name(nsCString("MF Media Engine CDM")); + gfx::gfxConfig::Init(); + gfx::gfxVars::Initialize(); + gfx::DeviceManagerDx::Init(); + return; + } +#endif + if (GetCurrentSandboxingKind() != SandboxingKind::GENERIC_UTILITY) { + nsDebugImpl::SetMultiprocessMode("Utility AudioDecoder"); + profiler_set_process_name(nsCString("Utility AudioDecoder")); + } +} + +UtilityAudioDecoderParent::~UtilityAudioDecoderParent() { +#ifdef MOZ_WMF_MEDIA_ENGINE + if (mKind == SandboxingKind::MF_MEDIA_ENGINE_CDM) { + gfx::gfxConfig::Shutdown(); + gfx::gfxVars::Shutdown(); + gfx::DeviceManagerDx::Shutdown(); + } +#endif +#ifdef MOZ_WMF_CDM + if (mKind == SandboxingKind::MF_MEDIA_ENGINE_CDM) { + MFCDMParent::Shutdown(); + } +#endif +} + +/* static */ +void UtilityAudioDecoderParent::GenericPreloadForSandbox() { +#if defined(MOZ_SANDBOX) && defined(XP_WIN) && defined(MOZ_FFVPX) + // Preload AV dlls so we can enable Binary Signature Policy + // to restrict further dll loads. + UtilityProcessImpl::LoadLibraryOrCrash(L"mozavcodec.dll"); + UtilityProcessImpl::LoadLibraryOrCrash(L"mozavutil.dll"); +#endif // defined(MOZ_SANDBOX) && defined(XP_WIN) && defined(MOZ_FFVPX) +} + +/* static */ +void UtilityAudioDecoderParent::WMFPreloadForSandbox() { +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + // mfplat.dll and mf.dll will be preloaded by + // wmf::MediaFoundationInitializer::HasInitialized() + +# if defined(NS_FREE_PERMANENT_DATA) + // WMF Shutdown requires this or it will badly crash + UtilityProcessImpl::LoadLibraryOrCrash(L"ole32.dll"); +# endif // defined(NS_FREE_PERMANENT_DATA) + + auto rv = wmf::MediaFoundationInitializer::HasInitialized(); + if (!rv) { + NS_WARNING("Failed to init Media Foundation in the Utility process"); + return; + } +#endif // defined(MOZ_SANDBOX) && defined(XP_WIN) +} + +void UtilityAudioDecoderParent::Start( + Endpoint<PUtilityAudioDecoderParent>&& aEndpoint) { + MOZ_ASSERT(NS_IsMainThread()); + + DebugOnly<bool> ok = std::move(aEndpoint).Bind(this); + MOZ_ASSERT(ok); + +#ifdef MOZ_WIDGET_ANDROID + if (StaticPrefs::media_utility_android_media_codec_enabled()) { + AndroidDecoderModule::SetSupportedMimeTypes(); + } +#endif + + auto supported = PDMFactory::Supported(); + Unused << SendUpdateMediaCodecsSupported(GetRemoteDecodeInFromKind(mKind), + supported); + PROFILER_MARKER_UNTYPED("UtilityAudioDecoderParent::Start", IPC, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom( + mAudioDecoderParentStart))); +} + +mozilla::ipc::IPCResult +UtilityAudioDecoderParent::RecvNewContentRemoteDecoderManager( + Endpoint<PRemoteDecoderManagerParent>&& aEndpoint, + const ContentParentId& aParentId) { + MOZ_ASSERT(NS_IsMainThread()); + if (!RemoteDecoderManagerParent::CreateForContent(std::move(aEndpoint), + aParentId)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +#ifdef MOZ_WMF_MEDIA_ENGINE +mozilla::ipc::IPCResult UtilityAudioDecoderParent::RecvInitVideoBridge( + Endpoint<PVideoBridgeChild>&& aEndpoint, + nsTArray<gfx::GfxVarUpdate>&& aUpdates, + const ContentDeviceData& aContentDeviceData) { + MOZ_ASSERT(mKind == SandboxingKind::MF_MEDIA_ENGINE_CDM); + if (!RemoteDecoderManagerParent::CreateVideoBridgeToOtherProcess( + std::move(aEndpoint))) { + return IPC_FAIL_NO_REASON(this); + } + + for (const auto& update : aUpdates) { + gfx::gfxVars::ApplyUpdate(update); + } + + gfx::gfxConfig::Inherit( + { + gfx::Feature::HW_COMPOSITING, + gfx::Feature::D3D11_COMPOSITING, + gfx::Feature::OPENGL_COMPOSITING, + gfx::Feature::DIRECT2D, + }, + aContentDeviceData.prefs()); + + if (gfx::gfxConfig::IsEnabled(gfx::Feature::D3D11_COMPOSITING)) { + if (auto* devmgr = gfx::DeviceManagerDx::Get()) { + devmgr->ImportDeviceInfo(aContentDeviceData.d3d11()); + } + } + + Unused << SendCompleteCreatedVideoBridge(); + return IPC_OK(); +} + +IPCResult UtilityAudioDecoderParent::RecvUpdateVar( + const GfxVarUpdate& aUpdate) { + MOZ_ASSERT(mKind == SandboxingKind::MF_MEDIA_ENGINE_CDM); + gfx::gfxVars::ApplyUpdate(aUpdate); + return IPC_OK(); +} +#endif + +#ifdef MOZ_WMF_CDM +IPCResult UtilityAudioDecoderParent::RecvGetKeySystemCapabilities( + GetKeySystemCapabilitiesResolver&& aResolver) { + MOZ_ASSERT(mKind == SandboxingKind::MF_MEDIA_ENGINE_CDM); + MFCDMParent::GetAllKeySystemsCapabilities()->Then( + GetCurrentSerialEventTarget(), __func__, + [aResolver](CopyableTArray<MFCDMCapabilitiesIPDL>&& aCapabilities) { + aResolver(std::move(aCapabilities)); + }, + [aResolver](nsresult) { + aResolver(CopyableTArray<MFCDMCapabilitiesIPDL>()); + }); + return IPC_OK(); +} + +IPCResult UtilityAudioDecoderParent::RecvUpdateWidevineL1Path( + const nsString& aPath) { + MFCDMParent::SetWidevineL1Path(NS_ConvertUTF16toUTF8(aPath).get()); + return IPC_OK(); +} +#endif + +} // namespace mozilla::ipc diff --git a/ipc/glue/UtilityAudioDecoderParent.h b/ipc/glue/UtilityAudioDecoderParent.h new file mode 100644 index 0000000000..6996fa0538 --- /dev/null +++ b/ipc/glue/UtilityAudioDecoderParent.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 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/. */ +#ifndef _include_ipc_glue_UtilityAudioDecoderParent_h_ +#define _include_ipc_glue_UtilityAudioDecoderParent_h_ + +#include "mozilla/PRemoteDecoderManagerParent.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/UniquePtr.h" + +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/PUtilityAudioDecoderParent.h" + +#include "mozilla/ipc/UtilityProcessSandboxing.h" + +#include "nsThreadManager.h" + +namespace mozilla::ipc { + +// This is in charge of handling the utility child process side to perform +// audio decoding +class UtilityAudioDecoderParent final : public PUtilityAudioDecoderParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityAudioDecoderParent, override); + + UtilityAudioDecoderParent(); + + static void GenericPreloadForSandbox(); + static void WMFPreloadForSandbox(); + + void Start(Endpoint<PUtilityAudioDecoderParent>&& aEndpoint); + + mozilla::ipc::IPCResult RecvNewContentRemoteDecoderManager( + Endpoint<PRemoteDecoderManagerParent>&& aEndpoint, + const ContentParentId& aParentId); + +#ifdef MOZ_WMF_MEDIA_ENGINE + mozilla::ipc::IPCResult RecvInitVideoBridge( + Endpoint<PVideoBridgeChild>&& aEndpoint, + nsTArray<mozilla::gfx::GfxVarUpdate>&& aUpdates, + const ContentDeviceData& aContentDeviceData); + + IPCResult RecvUpdateVar(const mozilla::gfx::GfxVarUpdate& aUpdate); +#endif + +#ifdef MOZ_WMF_CDM + IPCResult RecvGetKeySystemCapabilities( + GetKeySystemCapabilitiesResolver&& aResolver); + + IPCResult RecvUpdateWidevineL1Path(const nsString& aPath); +#endif + + private: + ~UtilityAudioDecoderParent(); + + const SandboxingKind mKind; + TimeStamp mAudioDecoderParentStart; +}; + +} // namespace mozilla::ipc + +#endif // _include_ipc_glue_UtilityAudioDecoderParent_h_ diff --git a/ipc/glue/UtilityProcessChild.cpp b/ipc/glue/UtilityProcessChild.cpp new file mode 100644 index 0000000000..cda3dbc817 --- /dev/null +++ b/ipc/glue/UtilityProcessChild.cpp @@ -0,0 +1,401 @@ +/* -*- 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 "UtilityProcessChild.h" + +#include "mozilla/AppShutdown.h" +#include "mozilla/Logging.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/JSOracleChild.h" +#include "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/UtilityProcessManager.h" +#include "mozilla/ipc/UtilityProcessSandboxing.h" +#include "mozilla/Preferences.h" +#include "mozilla/RemoteDecoderManagerParent.h" + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.h" +#endif + +#if defined(XP_OPENBSD) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +#endif + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +# include "mozilla/SandboxTestingChild.h" +#endif + +#include "mozilla/Telemetry.h" + +#if defined(XP_WIN) +# include "mozilla/WinDllServices.h" +# include "mozilla/dom/WindowsUtilsChild.h" +# include "mozilla/widget/filedialog/WinFileDialogChild.h" +#endif + +#include "nsDebugImpl.h" +#include "nsIXULRuntime.h" +#include "nsThreadManager.h" +#include "GeckoProfiler.h" + +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/FOGIPC.h" +#include "mozilla/glean/GleanMetrics.h" + +#include "mozilla/Services.h" + +namespace mozilla::ipc { + +using namespace layers; + +static StaticMutex sUtilityProcessChildMutex; +static StaticRefPtr<UtilityProcessChild> sUtilityProcessChild + MOZ_GUARDED_BY(sUtilityProcessChildMutex); + +UtilityProcessChild::UtilityProcessChild() : mChildStartTime(TimeStamp::Now()) { + nsDebugImpl::SetMultiprocessMode("Utility"); +} + +UtilityProcessChild::~UtilityProcessChild() = default; + +/* static */ +RefPtr<UtilityProcessChild> UtilityProcessChild::GetSingleton() { + MOZ_ASSERT(XRE_IsUtilityProcess()); + if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) { + return nullptr; + } + StaticMutexAutoLock lock(sUtilityProcessChildMutex); + if (!sUtilityProcessChild) { + sUtilityProcessChild = new UtilityProcessChild(); + } + return sUtilityProcessChild; +} + +/* static */ +RefPtr<UtilityProcessChild> UtilityProcessChild::Get() { + StaticMutexAutoLock lock(sUtilityProcessChildMutex); + return sUtilityProcessChild; +} + +bool UtilityProcessChild::Init(mozilla::ipc::UntypedEndpoint&& aEndpoint, + const nsCString& aParentBuildID, + uint64_t aSandboxingKind) { + MOZ_ASSERT(NS_IsMainThread()); + + // Initialize the thread manager before starting IPC. Otherwise, messages + // may be posted to the main thread and we won't be able to process them. + if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) { + return false; + } + + // Now it's safe to start IPC. + if (NS_WARN_IF(!aEndpoint.Bind(this))) { + return false; + } + + // This must be checked before any IPDL message, which may hit sentinel + // errors due to parent and content processes having different + // versions. + MessageChannel* channel = GetIPCChannel(); + if (channel && !channel->SendBuildIDsMatchMessage(aParentBuildID.get())) { + // We need to quit this process if the buildID doesn't match the parent's. + // This can occur when an update occurred in the background. + ipc::ProcessChild::QuickExit(); + } + + // Init crash reporter support. + ipc::CrashReporterClient::InitSingleton(this); + + if (NS_FAILED(NS_InitMinimalXPCOM())) { + return false; + } + + mSandbox = (SandboxingKind)aSandboxingKind; + + // At the moment, only ORB uses JSContext in the + // Utility Process and ORB uses GENERIC_UTILITY + if (mSandbox == SandboxingKind::GENERIC_UTILITY) { + if (!JS_FrontendOnlyInit()) { + return false; + } +#if defined(__OpenBSD__) && defined(MOZ_SANDBOX) + // Bug 1823458: delay pledge initialization, otherwise + // JS_FrontendOnlyInit triggers sysctl(KERN_PROC_ID) which isnt + // permitted with the current pledge.utility config + StartOpenBSDSandbox(GeckoProcessType_Utility, mSandbox); +#endif + } + + profiler_set_process_name(nsCString("Utility Process")); + + // Notify the parent process that we have finished our init and that it can + // now resolve the pending promise of process startup + SendInitCompleted(); + + PROFILER_MARKER_UNTYPED( + "UtilityProcessChild::SendInitCompleted", IPC, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom(mChildStartTime))); + + RunOnShutdown( + [sandboxKind = mSandbox] { + StaticMutexAutoLock lock(sUtilityProcessChildMutex); + sUtilityProcessChild = nullptr; + if (sandboxKind == SandboxingKind::GENERIC_UTILITY) { + JS_FrontendOnlyShutDown(); + } + }, + ShutdownPhase::XPCOMShutdownFinal); + + return true; +} + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +extern "C" { +void CGSShutdownServerConnections(); +}; +#endif + +mozilla::ipc::IPCResult UtilityProcessChild::RecvInit( + const Maybe<FileDescriptor>& aBrokerFd, + const bool& aCanRecordReleaseTelemetry, + const bool& aIsReadyForBackgroundProcessing) { + // Do this now (before closing WindowServer on macOS) to avoid risking + // blocking in GetCurrentProcess() called on that platform + mozilla::ipc::SetThisProcessName("Utility Process"); + +#if defined(MOZ_SANDBOX) +# if defined(XP_MACOSX) + // Close all current connections to the WindowServer. This ensures that the + // Activity Monitor will not label the content process as "Not responding" + // because it's not running a native event loop. See bug 1384336. + CGSShutdownServerConnections(); + +# elif defined(XP_LINUX) + int fd = -1; + if (aBrokerFd.isSome()) { + fd = aBrokerFd.value().ClonePlatformHandle().release(); + } + + SetUtilitySandbox(fd, mSandbox); + +# endif // XP_MACOSX/XP_LINUX +#endif // MOZ_SANDBOX + +#if defined(XP_WIN) + if (aCanRecordReleaseTelemetry) { + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->StartUntrustedModulesProcessor(aIsReadyForBackgroundProcessing); + } +#endif // defined(XP_WIN) + + PROFILER_MARKER_UNTYPED( + "UtilityProcessChild::RecvInit", IPC, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom(mChildStartTime))); + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessChild::RecvPreferenceUpdate( + const Pref& aPref) { + Preferences::SetPreference(aPref); + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessChild::RecvInitProfiler( + Endpoint<PProfilerChild>&& aEndpoint) { + mProfilerController = ChildProfilerController::Create(std::move(aEndpoint)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessChild::RecvRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, const Maybe<FileDescriptor>& aDMDFile, + const RequestMemoryReportResolver& aResolver) { + nsPrintfCString processName("Utility (pid %" PRIPID + ", sandboxingKind %" PRIu64 ")", + base::GetCurrentProcId(), mSandbox); + + mozilla::dom::MemoryReportRequestClient::Start( + aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, processName, + [&](const MemoryReport& aReport) { + Unused << GetSingleton()->SendAddMemoryReport(aReport); + }, + aResolver); + return IPC_OK(); +} + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +mozilla::ipc::IPCResult UtilityProcessChild::RecvInitSandboxTesting( + Endpoint<PSandboxTestingChild>&& aEndpoint) { + if (!SandboxTestingChild::Initialize(std::move(aEndpoint))) { + return IPC_FAIL( + this, "InitSandboxTesting failed to initialise the child process."); + } + return IPC_OK(); +} +#endif + +mozilla::ipc::IPCResult UtilityProcessChild::RecvFlushFOGData( + FlushFOGDataResolver&& aResolver) { + glean::FlushFOGData(std::move(aResolver)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessChild::RecvTestTriggerMetrics( + TestTriggerMetricsResolver&& aResolve) { + mozilla::glean::test_only_ipc::a_counter.Add( + nsIXULRuntime::PROCESS_TYPE_UTILITY); + aResolve(true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessChild::RecvTestTelemetryProbes() { + const uint32_t kExpectedUintValue = 42; + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UTILITY_ONLY_UINT, + kExpectedUintValue); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +UtilityProcessChild::RecvStartUtilityAudioDecoderService( + Endpoint<PUtilityAudioDecoderParent>&& aEndpoint) { + PROFILER_MARKER_UNTYPED( + "UtilityProcessChild::RecvStartUtilityAudioDecoderService", MEDIA, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom(mChildStartTime))); + mUtilityAudioDecoderInstance = new UtilityAudioDecoderParent(); + if (!mUtilityAudioDecoderInstance) { + return IPC_FAIL(this, "Failed to create UtilityAudioDecoderParent"); + } + + mUtilityAudioDecoderInstance->Start(std::move(aEndpoint)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessChild::RecvStartJSOracleService( + Endpoint<PJSOracleChild>&& aEndpoint) { + PROFILER_MARKER_UNTYPED( + "UtilityProcessChild::RecvStartJSOracleService", JS, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom(mChildStartTime))); + mJSOracleInstance = new mozilla::dom::JSOracleChild(); + if (!mJSOracleInstance) { + return IPC_FAIL(this, "Failed to create JSOracleParent"); + } + + mJSOracleInstance->Start(std::move(aEndpoint)); + return IPC_OK(); +} + +#if defined(XP_WIN) +mozilla::ipc::IPCResult UtilityProcessChild::RecvStartWindowsUtilsService( + Endpoint<dom::PWindowsUtilsChild>&& aEndpoint) { + PROFILER_MARKER_UNTYPED( + "UtilityProcessChild::RecvStartWindowsUtilsService", OTHER, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom(mChildStartTime))); + mWindowsUtilsInstance = new dom::WindowsUtilsChild(); + if (!mWindowsUtilsInstance) { + return IPC_FAIL(this, "Failed to create WindowsUtilsChild"); + } + + [[maybe_unused]] bool ok = std::move(aEndpoint).Bind(mWindowsUtilsInstance); + MOZ_ASSERT(ok); + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessChild::RecvStartWinFileDialogService( + Endpoint<widget::filedialog::PWinFileDialogChild>&& aEndpoint) { + PROFILER_MARKER_UNTYPED( + "UtilityProcessChild::RecvStartWinFileDialogService", OTHER, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom(mChildStartTime))); + + auto instance = MakeRefPtr<widget::filedialog::WinFileDialogChild>(); + if (!instance) { + return IPC_FAIL(this, "Failed to create WinFileDialogChild"); + } + + bool const ok = std::move(aEndpoint).Bind(instance.get()); + if (!ok) { + return IPC_FAIL(this, "Failed to bind created WinFileDialogChild"); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessChild::RecvGetUntrustedModulesData( + GetUntrustedModulesDataResolver&& aResolver) { + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->GetUntrustedModulesData()->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolver](Maybe<UntrustedModulesData>&& aData) { + aResolver(std::move(aData)); + }, + [aResolver](nsresult aReason) { aResolver(Nothing()); }); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +UtilityProcessChild::RecvUnblockUntrustedModulesThread() { + if (nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService()) { + obs->NotifyObservers(nullptr, "unblock-untrusted-modules-thread", nullptr); + } + return IPC_OK(); +} +#endif // defined(XP_WIN) + +void UtilityProcessChild::ActorDestroy(ActorDestroyReason aWhy) { + if (AbnormalShutdown == aWhy) { + NS_WARNING("Shutting down Utility process early due to a crash!"); + ipc::ProcessChild::QuickExit(); + } + + // Send the last bits of Glean data over to the main process. + glean::FlushFOGData( + [](ByteBuf&& aBuf) { glean::SendFOGData(std::move(aBuf)); }); + +#ifndef NS_FREE_PERMANENT_DATA + ProcessChild::QuickExit(); +#else + + if (mProfilerController) { + mProfilerController->Shutdown(); + mProfilerController = nullptr; + } + + uint32_t timeout = 0; + if (mUtilityAudioDecoderInstance) { + mUtilityAudioDecoderInstance = nullptr; + timeout = 10 * 1000; + } + + mJSOracleInstance = nullptr; + +# ifdef XP_WIN + mWindowsUtilsInstance = nullptr; +# endif + + // Wait until all RemoteDecoderManagerParent have closed. + // It is still possible some may not have clean up yet, and we might hit + // timeout. Our xpcom-shutdown listener should take care of cleaning the + // reference of our singleton. + // + // FIXME: Should move from using AsyncBlockers to proper + // nsIAsyncShutdownService once it is not JS, see bug 1760855 + mShutdownBlockers.WaitUntilClear(timeout)->Then( + GetCurrentSerialEventTarget(), __func__, [&]() { +# ifdef XP_WIN + { + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->DisableFull(); + } +# endif // defined(XP_WIN) + + ipc::CrashReporterClient::DestroySingleton(); + XRE_ShutdownChildProcess(); + }); +#endif // NS_FREE_PERMANENT_DATA +} + +} // namespace mozilla::ipc diff --git a/ipc/glue/UtilityProcessChild.h b/ipc/glue/UtilityProcessChild.h new file mode 100644 index 0000000000..db6db6bba6 --- /dev/null +++ b/ipc/glue/UtilityProcessChild.h @@ -0,0 +1,110 @@ +/* -*- 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/. */ +#ifndef _include_ipc_glue_UtilityProcessChild_h_ +#define _include_ipc_glue_UtilityProcessChild_h_ +#include "mozilla/ipc/PUtilityProcessChild.h" +#include "mozilla/ipc/UtilityProcessSandboxing.h" +#include "mozilla/ipc/UtilityAudioDecoderParent.h" +#include "mozilla/UniquePtr.h" +#include "ChildProfilerController.h" + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +# include "mozilla/PSandboxTestingChild.h" +#endif +#include "mozilla/PRemoteDecoderManagerParent.h" +#include "mozilla/ipc/AsyncBlockers.h" +#include "mozilla/dom/JSOracleChild.h" +#include "mozilla/ProfilerMarkers.h" + +namespace mozilla::dom { +class PJSOracleChild; +} // namespace mozilla::dom + +namespace mozilla::ipc { + +class UtilityProcessHost; + +class UtilityProcessChild final : public PUtilityProcessChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityProcessChild, override); + + UtilityProcessChild(); + + static RefPtr<UtilityProcessChild> GetSingleton(); + static RefPtr<UtilityProcessChild> Get(); + + SandboxingKind mSandbox{}; + + bool Init(mozilla::ipc::UntypedEndpoint&& aEndpoint, + const nsCString& aParentBuildID, uint64_t aSandboxingKind); + + mozilla::ipc::IPCResult RecvInit(const Maybe<ipc::FileDescriptor>& aBrokerFd, + const bool& aCanRecordReleaseTelemetry, + const bool& aIsReadyForBackgroundProcessing); + mozilla::ipc::IPCResult RecvInitProfiler( + Endpoint<PProfilerChild>&& aEndpoint); + + mozilla::ipc::IPCResult RecvPreferenceUpdate(const Pref& pref); + + mozilla::ipc::IPCResult RecvRequestMemoryReport( + const uint32_t& generation, const bool& anonymize, + const bool& minimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& DMDFile, + const RequestMemoryReportResolver& aResolver); + + mozilla::ipc::IPCResult RecvFlushFOGData(FlushFOGDataResolver&& aResolver); + + mozilla::ipc::IPCResult RecvTestTriggerMetrics( + TestTriggerMetricsResolver&& aResolve); + + mozilla::ipc::IPCResult RecvTestTelemetryProbes(); + + mozilla::ipc::IPCResult RecvStartUtilityAudioDecoderService( + Endpoint<PUtilityAudioDecoderParent>&& aEndpoint); + + mozilla::ipc::IPCResult RecvStartJSOracleService( + Endpoint<dom::PJSOracleChild>&& aEndpoint); + +#if defined(XP_WIN) + mozilla::ipc::IPCResult RecvStartWindowsUtilsService( + Endpoint<PWindowsUtilsChild>&& aEndpoint); + + mozilla::ipc::IPCResult RecvStartWinFileDialogService( + Endpoint<PWinFileDialogChild>&& aEndpoint); + + mozilla::ipc::IPCResult RecvGetUntrustedModulesData( + GetUntrustedModulesDataResolver&& aResolver); + mozilla::ipc::IPCResult RecvUnblockUntrustedModulesThread(); +#endif // defined(XP_WIN) + + AsyncBlockers& AsyncShutdownService() { return mShutdownBlockers; } + + void ActorDestroy(ActorDestroyReason aWhy) override; + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) + mozilla::ipc::IPCResult RecvInitSandboxTesting( + Endpoint<PSandboxTestingChild>&& aEndpoint); +#endif + + protected: + friend class UtilityProcessImpl; + ~UtilityProcessChild(); + + private: + TimeStamp mChildStartTime; + RefPtr<ChildProfilerController> mProfilerController; + RefPtr<UtilityAudioDecoderParent> mUtilityAudioDecoderInstance{}; + RefPtr<dom::JSOracleChild> mJSOracleInstance{}; +#ifdef XP_WIN + RefPtr<PWindowsUtilsChild> mWindowsUtilsInstance; +#endif + + AsyncBlockers mShutdownBlockers; +}; + +} // namespace mozilla::ipc + +#endif // _include_ipc_glue_UtilityProcessChild_h_ diff --git a/ipc/glue/UtilityProcessHost.cpp b/ipc/glue/UtilityProcessHost.cpp new file mode 100644 index 0000000000..6b8a84966e --- /dev/null +++ b/ipc/glue/UtilityProcessHost.cpp @@ -0,0 +1,431 @@ +/* -*- 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 "UtilityProcessHost.h" + +#include "mozilla/dom/ContentParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/UtilityProcessManager.h" +#include "mozilla/ipc/UtilityProcessSandboxing.h" +#include "mozilla/Telemetry.h" + +#include "chrome/common/process_watcher.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_general.h" + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.h" +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxBrokerPolicyFactory.h" +#endif + +#if defined(XP_WIN) +# include "mozilla/WinDllServices.h" +#endif // defined(XP_WIN) + +#if defined(MOZ_WMF_CDM) && defined(MOZ_SANDBOX) && !defined(MOZ_ASAN) +# define MOZ_WMF_CDM_LPAC_SANDBOX true +#endif + +#ifdef MOZ_WMF_CDM_LPAC_SANDBOX +# include "GMPServiceParent.h" +# include "mozilla/dom/KeySystemNames.h" +# include "mozilla/GeckoArgs.h" +# include "mozilla/MFMediaEngineUtils.h" +# include "mozilla/StaticPrefs_media.h" +# include "nsIFile.h" +# include "sandboxBroker.h" +#endif + +#include "ProfilerParent.h" +#include "mozilla/PProfilerChild.h" + +namespace mozilla::ipc { + +LazyLogModule gUtilityProcessLog("utilityproc"); +#define LOGD(...) MOZ_LOG(gUtilityProcessLog, LogLevel::Debug, (__VA_ARGS__)) + +#ifdef MOZ_WMF_CDM_LPAC_SANDBOX +# define WMF_LOG(msg, ...) \ + MOZ_LOG(gMFMediaEngineLog, LogLevel::Debug, \ + ("UtilityProcessHost=%p, " msg, this, ##__VA_ARGS__)) +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +bool UtilityProcessHost::sLaunchWithMacSandbox = false; +#endif + +UtilityProcessHost::UtilityProcessHost(SandboxingKind aSandbox, + RefPtr<Listener> aListener) + : GeckoChildProcessHost(GeckoProcessType_Utility), + mListener(std::move(aListener)), + mLiveToken(new media::Refcountable<bool>(true)), + mLaunchPromise( + MakeRefPtr<GenericNonExclusivePromise::Private>(__func__)) { + MOZ_COUNT_CTOR(UtilityProcessHost); + LOGD("[%p] UtilityProcessHost::UtilityProcessHost sandboxingKind=%" PRIu64, + this, aSandbox); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + if (!sLaunchWithMacSandbox) { + sLaunchWithMacSandbox = IsUtilitySandboxEnabled(aSandbox); + } + mDisableOSActivityMode = sLaunchWithMacSandbox; +#endif +#if defined(MOZ_SANDBOX) + mSandbox = aSandbox; +#endif +} + +UtilityProcessHost::~UtilityProcessHost() { + MOZ_COUNT_DTOR(UtilityProcessHost); +#if defined(MOZ_SANDBOX) + LOGD("[%p] UtilityProcessHost::~UtilityProcessHost sandboxingKind=%" PRIu64, + this, mSandbox); +#else + LOGD("[%p] UtilityProcessHost::~UtilityProcessHost", this); +#endif +} + +bool UtilityProcessHost::Launch(StringVector aExtraOpts) { + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched); + MOZ_ASSERT(!mUtilityProcessParent); + + LOGD("[%p] UtilityProcessHost::Launch", this); + + mPrefSerializer = MakeUnique<ipc::SharedPreferenceSerializer>(); + if (!mPrefSerializer->SerializeToSharedMemory(GeckoProcessType_Utility, + /* remoteType */ ""_ns)) { + return false; + } + mPrefSerializer->AddSharedPrefCmdLineArgs(*this, aExtraOpts); + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + mSandboxLevel = Preferences::GetInt("security.sandbox.utility.level"); +#endif + +#ifdef MOZ_WMF_CDM_LPAC_SANDBOX + EnsureWidevineL1PathForSandbox(aExtraOpts); +#endif + +#if defined(MOZ_WMF_CDM) && defined(MOZ_SANDBOX) + EnanbleMFCDMTelemetryEventIfNeeded(); +#endif + + mLaunchPhase = LaunchPhase::Waiting; + + if (!GeckoChildProcessHost::AsyncLaunch(aExtraOpts)) { + NS_WARNING("UtilityProcess AsyncLaunch failed, aborting."); + mLaunchPhase = LaunchPhase::Complete; + mPrefSerializer = nullptr; + return false; + } + LOGD("[%p] UtilityProcessHost::Launch launching async", this); + return true; +} + +RefPtr<GenericNonExclusivePromise> UtilityProcessHost::LaunchPromise() { + MOZ_ASSERT(NS_IsMainThread()); + + if (mLaunchPromiseLaunched) { + return mLaunchPromise; + } + + WhenProcessHandleReady()->Then( + GetCurrentSerialEventTarget(), __func__, + [this, liveToken = mLiveToken]( + const ipc::ProcessHandlePromise::ResolveOrRejectValue& aResult) { + if (!*liveToken) { + // The UtilityProcessHost got deleted. Abort. The promise would have + // already been rejected. + return; + } + if (mLaunchCompleted) { + return; + } + mLaunchCompleted = true; + if (aResult.IsReject()) { + RejectPromise(); + } + // If aResult.IsResolve() then we have succeeded in launching the + // Utility process. The promise will be resolved once the channel has + // connected (or failed to) later. + }); + + mLaunchPromiseLaunched = true; + return mLaunchPromise; +} + +void UtilityProcessHost::OnChannelConnected(base::ProcessId peer_pid) { + MOZ_ASSERT(!NS_IsMainThread()); + LOGD("[%p] UtilityProcessHost::OnChannelConnected", this); + + GeckoChildProcessHost::OnChannelConnected(peer_pid); + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "UtilityProcessHost::OnChannelConnected", + [this, liveToken = mLiveToken]() { + if (*liveToken && mLaunchPhase == LaunchPhase::Waiting) { + InitAfterConnect(true); + } + })); +} + +void UtilityProcessHost::InitAfterConnect(bool aSucceeded) { + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting); + MOZ_ASSERT(!mUtilityProcessParent); + + mLaunchPhase = LaunchPhase::Complete; + + if (!aSucceeded) { + RejectPromise(); + return; + } + + mUtilityProcessParent = MakeRefPtr<UtilityProcessParent>(this); + DebugOnly<bool> rv = TakeInitialEndpoint().Bind(mUtilityProcessParent.get()); + MOZ_ASSERT(rv); + + // Only clear mPrefSerializer in the success case to avoid a + // possible race in the case case of a timeout on Windows launch. + // See Bug 1555076 comment 7: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1555076#c7 + mPrefSerializer = nullptr; + + Maybe<FileDescriptor> brokerFd; + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + UniquePtr<SandboxBroker::Policy> policy; + switch (mSandbox) { + case SandboxingKind::GENERIC_UTILITY: + policy = SandboxBrokerPolicyFactory::GetUtilityProcessPolicy( + GetActor()->OtherPid()); + break; + + default: + MOZ_ASSERT(false, "Invalid SandboxingKind"); + break; + } + if (policy != nullptr) { + brokerFd = Some(FileDescriptor()); + mSandboxBroker = SandboxBroker::Create( + std::move(policy), GetActor()->OtherPid(), brokerFd.ref()); + // This is unlikely to fail and probably indicates OS resource + // exhaustion, but we can at least try to recover. + Unused << NS_WARN_IF(mSandboxBroker == nullptr); + MOZ_ASSERT(brokerFd.ref().IsValid()); + } +#endif // XP_LINUX && MOZ_SANDBOX + + bool isReadyForBackgroundProcessing = false; +#if defined(XP_WIN) + RefPtr<DllServices> dllSvc(DllServices::Get()); + isReadyForBackgroundProcessing = dllSvc->IsReadyForBackgroundProcessing(); +#endif + + Unused << GetActor()->SendInit(brokerFd, Telemetry::CanRecordReleaseData(), + isReadyForBackgroundProcessing); + + Unused << GetActor()->SendInitProfiler( + ProfilerParent::CreateForProcess(GetActor()->OtherPid())); + + LOGD("[%p] UtilityProcessHost::InitAfterConnect succeeded", this); + + // Promise will be resolved later, from UtilityProcessParent when the child + // will send the InitCompleted message. +} + +void UtilityProcessHost::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mShutdownRequested); + LOGD("[%p] UtilityProcessHost::Shutdown", this); + + RejectPromise(); + + if (mUtilityProcessParent) { + LOGD("[%p] UtilityProcessHost::Shutdown not destroying utility process.", + this); + + // OnChannelClosed uses this to check if the shutdown was expected or + // unexpected. + mShutdownRequested = true; + + // The channel might already be closed if we got here unexpectedly. + if (mUtilityProcessParent->CanSend()) { + mUtilityProcessParent->Close(); + } + +#ifndef NS_FREE_PERMANENT_DATA + // No need to communicate shutdown, the Utility process doesn't need to + // communicate anything back. + KillHard("NormalShutdown"); +#endif + + // If we're shutting down unexpectedly, we're in the middle of handling an + // ActorDestroy for PUtilityProcessParent, which is still on the stack. + // We'll return back to OnChannelClosed. + // + // Otherwise, we'll wait for OnChannelClose to be called whenever + // PUtilityProcessParent acknowledges shutdown. + return; + } + + DestroyProcess(); +} + +void UtilityProcessHost::OnChannelClosed() { + MOZ_ASSERT(NS_IsMainThread()); + LOGD("[%p] UtilityProcessHost::OnChannelClosed", this); + + RejectPromise(); + + if (!mShutdownRequested && mListener) { + // This is an unclean shutdown. Notify our listener that we're going away. + mListener->OnProcessUnexpectedShutdown(this); + } + + DestroyProcess(); + + // Release the actor. + UtilityProcessParent::Destroy(std::move(mUtilityProcessParent)); +} + +void UtilityProcessHost::KillHard(const char* aReason) { + MOZ_ASSERT(NS_IsMainThread()); + LOGD("[%p] UtilityProcessHost::KillHard", this); + + ProcessHandle handle = GetChildProcessHandle(); + if (!base::KillProcess(handle, base::PROCESS_END_KILLED_BY_USER)) { + NS_WARNING("failed to kill subprocess!"); + } + + SetAlreadyDead(); +} + +void UtilityProcessHost::DestroyProcess() { + MOZ_ASSERT(NS_IsMainThread()); + LOGD("[%p] UtilityProcessHost::DestroyProcess", this); + + RejectPromise(); + + // Any pending tasks will be cancelled from now on. + *mLiveToken = false; + + NS_DispatchToMainThread( + NS_NewRunnableFunction("DestroyProcessRunnable", [this] { Destroy(); })); +} + +void UtilityProcessHost::ResolvePromise() { + MOZ_ASSERT(NS_IsMainThread()); + LOGD("[%p] UtilityProcessHost connected - resolving launch promise", this); + + if (!mLaunchPromiseSettled) { + mLaunchPromise->Resolve(true, __func__); + mLaunchPromiseSettled = true; + } + + mLaunchCompleted = true; +} + +void UtilityProcessHost::RejectPromise() { + MOZ_ASSERT(NS_IsMainThread()); + LOGD("[%p] UtilityProcessHost connection failed - rejecting launch promise", + this); + + if (!mLaunchPromiseSettled) { + mLaunchPromise->Reject(NS_ERROR_FAILURE, __func__); + mLaunchPromiseSettled = true; + } + + mLaunchCompleted = true; +} + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +bool UtilityProcessHost::FillMacSandboxInfo(MacSandboxInfo& aInfo) { + GeckoChildProcessHost::FillMacSandboxInfo(aInfo); + if (!aInfo.shouldLog && PR_GetEnv("MOZ_SANDBOX_UTILITY_LOGGING")) { + aInfo.shouldLog = true; + } + return true; +} + +/* static */ +MacSandboxType UtilityProcessHost::GetMacSandboxType() { + return MacSandboxType_Utility; +} +#endif + +#ifdef MOZ_WMF_CDM_LPAC_SANDBOX +void UtilityProcessHost::EnsureWidevineL1PathForSandbox( + StringVector& aExtraOpts) { + if (mSandbox != SandboxingKind::MF_MEDIA_ENGINE_CDM) { + return; + } + + RefPtr<mozilla::gmp::GeckoMediaPluginServiceParent> gmps = + mozilla::gmp::GeckoMediaPluginServiceParent::GetSingleton(); + if (NS_WARN_IF(!gmps)) { + WMF_LOG("Failed to get GeckoMediaPluginServiceParent!"); + return; + } + + if (!StaticPrefs::media_eme_widevine_experiment_enabled()) { + return; + } + + // If Widevine L1 is installed after the MFCDM process starts, we will set it + // path later via MFCDMService::UpdateWideivineL1Path(). + nsString widevineL1Path; + nsCOMPtr<nsIFile> pluginFile; + if (NS_WARN_IF(NS_FAILED(gmps->FindPluginDirectoryForAPI( + nsCString(kWidevineExperimentAPIName), + {nsCString(kWidevineExperimentKeySystemName)}, + getter_AddRefs(pluginFile))))) { + WMF_LOG("Widevine L1 is not installed yet"); + return; + } + + if (!pluginFile) { + WMF_LOG("No plugin file found!"); + return; + } + + if (NS_WARN_IF(NS_FAILED(pluginFile->GetTarget(widevineL1Path)))) { + WMF_LOG("Failed to get L1 path!"); + return; + } + + WMF_LOG("Set Widevine L1 path=%s", + NS_ConvertUTF16toUTF8(widevineL1Path).get()); + geckoargs::sPluginPath.Put(NS_ConvertUTF16toUTF8(widevineL1Path).get(), + aExtraOpts); + SandboxBroker::EnsureLpacPermsissionsOnDir(widevineL1Path); +} + +# undef WMF_LOG + +#endif + +#if defined(MOZ_WMF_CDM) && defined(MOZ_SANDBOX) +void UtilityProcessHost::EnanbleMFCDMTelemetryEventIfNeeded() const { + if (mSandbox != SandboxingKind::MF_MEDIA_ENGINE_CDM) { + return; + } + static bool sTelemetryEventEnabled = false; + if (!sTelemetryEventEnabled) { + sTelemetryEventEnabled = true; + Telemetry::SetEventRecordingEnabled("mfcdm"_ns, true); + } +} +#endif + +} // namespace mozilla::ipc diff --git a/ipc/glue/UtilityProcessHost.h b/ipc/glue/UtilityProcessHost.h new file mode 100644 index 0000000000..bd4ffabe75 --- /dev/null +++ b/ipc/glue/UtilityProcessHost.h @@ -0,0 +1,165 @@ +/* -*- 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/. */ + +#ifndef _include_ipc_glue_UtilityProcessHost_h_ +#define _include_ipc_glue_UtilityProcessHost_h_ + +#include "mozilla/UniquePtr.h" +#include "mozilla/ipc/UtilityProcessParent.h" +#include "mozilla/ipc/UtilityProcessSandboxing.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/media/MediaUtils.h" +#include "mozilla/ipc/ProcessUtils.h" + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxBroker.h" +#endif + +namespace mozilla::ipc { + +class UtilityProcessParent; + +// UtilityProcessHost is the "parent process" container for a subprocess handle +// and IPC connection. It owns the parent process IPDL actor, which in this +// case, is a UtilityChild. +// +// UtilityProcessHosts are allocated and managed by UtilityProcessManager. +class UtilityProcessHost final : public mozilla::ipc::GeckoChildProcessHost { + friend class UtilityProcessParent; + + public: + class Listener { + protected: + virtual ~Listener() = default; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityProcessHost::Listener); + + // The UtilityProcessHost has unexpectedly shutdown or had its connection + // severed. This is not called if an error occurs after calling + // Shutdown(). + virtual void OnProcessUnexpectedShutdown(UtilityProcessHost* aHost) {} + }; + + explicit UtilityProcessHost(SandboxingKind aSandbox, + RefPtr<Listener> listener); + + // Launch the subprocess asynchronously. On failure, false is returned. + // Otherwise, true is returned. If succeeded, a follow-up call should be made + // to LaunchPromise() which will return a promise that will be resolved once + // the Utility process has launched and a channel has been established. + // + // @param aExtraOpts (StringVector) + // Extra options to pass to the subprocess. + bool Launch(StringVector aExtraOpts); + + // Return a promise that will be resolved once the process has completed its + // launch. The promise will be immediately resolved if the launch has already + // succeeded. + RefPtr<GenericNonExclusivePromise> LaunchPromise(); + + // Inform the process that it should clean up its resources and shut + // down. This initiates an asynchronous shutdown sequence. After this + // method returns, it is safe for the caller to forget its pointer to + // the UtilityProcessHost. + // + // After this returns, the attached Listener is no longer used. + void Shutdown(); + + // Return the actor for the top-level actor of the process. If the process + // has not connected yet, this returns null. + RefPtr<UtilityProcessParent> GetActor() const { + MOZ_ASSERT(NS_IsMainThread()); + return mUtilityProcessParent; + } + + bool IsConnected() const { + MOZ_ASSERT(NS_IsMainThread()); + return bool(mUtilityProcessParent); + } + + // Called on the IO thread. + void OnChannelConnected(base::ProcessId peer_pid) override; + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + // Return the sandbox type to be used with this process type. + static MacSandboxType GetMacSandboxType(); +#endif + + private: + ~UtilityProcessHost(); + + // Called on the main thread with true after a connection has been established + // or false if it failed (including if it failed before the timeout kicked in) + void InitAfterConnect(bool aSucceeded); + + // Called on the main thread when the mUtilityProcessParent actor is shutting + // down. + void OnChannelClosed(); + + // Kill the remote process, triggering IPC shutdown. + void KillHard(const char* aReason); + + void DestroyProcess(); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + static bool sLaunchWithMacSandbox; + + // Sandbox the Utility process at launch for all instances + bool IsMacSandboxLaunchEnabled() override { return sLaunchWithMacSandbox; } + + // Override so we can turn on Utility process-specific sandbox logging + bool FillMacSandboxInfo(MacSandboxInfo& aInfo) override; +#endif + + DISALLOW_COPY_AND_ASSIGN(UtilityProcessHost); + + RefPtr<Listener> mListener; + + // All members below are only ever accessed on the main thread. + enum class LaunchPhase { Unlaunched, Waiting, Complete }; + LaunchPhase mLaunchPhase = LaunchPhase::Unlaunched; + + RefPtr<UtilityProcessParent> mUtilityProcessParent; + + UniquePtr<ipc::SharedPreferenceSerializer> mPrefSerializer{}; + + bool mShutdownRequested = false; + + void RejectPromise(); + void ResolvePromise(); + +#if defined(MOZ_WMF_CDM) && defined(MOZ_SANDBOX) && !defined(MOZ_ASAN) + void EnsureWidevineL1PathForSandbox(StringVector& aExtraOpts); +#endif + +#if defined(MOZ_WMF_CDM) && defined(MOZ_SANDBOX) + void EnanbleMFCDMTelemetryEventIfNeeded() const; +#endif + + // Set to true on construction and to false just prior deletion. + // The UtilityProcessHost isn't refcounted; so we can capture this by value in + // lambdas along with a strong reference to mLiveToken and check if that value + // is true before accessing "this". + // While a reference to mLiveToken can be taken on any thread; its value can + // only be read or written on the main thread. + const RefPtr<media::Refcountable<bool>> mLiveToken; + + RefPtr<GenericNonExclusivePromise::Private> mLaunchPromise{}; + bool mLaunchPromiseSettled = false; + bool mLaunchPromiseLaunched = false; + // Will be set to true if the Utility process as successfully started. + bool mLaunchCompleted = false; + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + UniquePtr<SandboxBroker> mSandboxBroker{}; +#endif +}; + +} // namespace mozilla::ipc + +#endif // _include_ipc_glue_UtilityProcessHost_h_ diff --git a/ipc/glue/UtilityProcessImpl.cpp b/ipc/glue/UtilityProcessImpl.cpp new file mode 100644 index 0000000000..b1d8f96cbd --- /dev/null +++ b/ipc/glue/UtilityProcessImpl.cpp @@ -0,0 +1,147 @@ +/* -*- 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 "UtilityProcessImpl.h" + +#include "mozilla/ipc/IOThreadChild.h" +#include "mozilla/GeckoArgs.h" +#include "mozilla/ProcInfo.h" + +#if defined(XP_WIN) +# include "nsExceptionHandler.h" +#endif + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +# include "mozilla/sandboxTarget.h" +# include "WMF.h" +# include "WMFDecoderModule.h" +#endif + +#if defined(XP_OPENBSD) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +#endif + +#if defined(MOZ_WMF_CDM) && defined(MOZ_SANDBOX) +# include "mozilla/MFCDMParent.h" +#endif + +namespace mozilla::ipc { + +UtilityProcessImpl::~UtilityProcessImpl() = default; + +#if defined(XP_WIN) +/* static */ +void UtilityProcessImpl::LoadLibraryOrCrash(LPCWSTR aLib) { + // re-try a few times depending on the error we get ; inspired by both our + // results on content process allocations as well as msys2: + // https://github.com/git-for-windows/msys2-runtime/blob/b4fed42af089ab955286343835a97e287496b3f8/winsup/cygwin/autoload.cc#L323-L339 + + const int kMaxRetries = 10; + DWORD err; + + for (int i = 0; i < kMaxRetries; i++) { + HMODULE module = ::LoadLibraryW(aLib); + if (module) { + return; + } + + err = ::GetLastError(); + + if (err != ERROR_NOACCESS && err != ERROR_DLL_INIT_FAILED) { + break; + } + + PR_Sleep(0); + } + + switch (err) { + /* case ERROR_ACCESS_DENIED: */ + /* case ERROR_BAD_EXE_FORMAT: */ + /* case ERROR_SHARING_VIOLATION: */ + case ERROR_MOD_NOT_FOUND: + case ERROR_COMMITMENT_LIMIT: + // We want to make it explicit in telemetry that this was in fact an + // OOM condition, even though we could not detect it on our own + CrashReporter::AnnotateOOMAllocationSize(1); + break; + + default: + break; + } + + MOZ_CRASH_UNSAFE_PRINTF("Unable to preload module: 0x%lx", err); +} +#endif // defined(XP_WIN) + +bool UtilityProcessImpl::Init(int aArgc, char* aArgv[]) { + Maybe<uint64_t> sandboxingKind = geckoargs::sSandboxingKind.Get(aArgc, aArgv); + if (sandboxingKind.isNothing()) { + return false; + } + + if (*sandboxingKind >= SandboxingKind::COUNT) { + return false; + } + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + // We delay load winmm.dll so that its dependencies don't interfere with COM + // initialization when win32k is locked down. We need to load it before we + // lower the sandbox in processes where the policy will prevent loading. + LoadLibraryOrCrash(L"winmm.dll"); + + // Call this once before enabling the sandbox, it will cache its result + // in a static variable. + GetCpuFrequencyMHz(); + + if (*sandboxingKind == SandboxingKind::GENERIC_UTILITY) { + // Preload audio generic libraries required for ffmpeg only + UtilityAudioDecoderParent::GenericPreloadForSandbox(); + } + + if (*sandboxingKind == SandboxingKind::UTILITY_AUDIO_DECODING_WMF +# ifdef MOZ_WMF_MEDIA_ENGINE + || *sandboxingKind == SandboxingKind::MF_MEDIA_ENGINE_CDM +# endif + ) { + UtilityAudioDecoderParent::WMFPreloadForSandbox(); + } + + // Go for it + mozilla::SandboxTarget::Instance()->StartSandbox(); +#elif defined(__OpenBSD__) && defined(MOZ_SANDBOX) + if (*sandboxingKind != SandboxingKind::GENERIC_UTILITY) { + StartOpenBSDSandbox(GeckoProcessType_Utility, + (SandboxingKind)*sandboxingKind); + } +#endif + +#if defined(MOZ_WMF_CDM) && defined(MOZ_SANDBOX) + if (*sandboxingKind == MF_MEDIA_ENGINE_CDM) { + Maybe<const char*> pluginPath = geckoargs::sPluginPath.Get(aArgc, aArgv); + if (pluginPath) { + MFCDMParent::SetWidevineL1Path(*pluginPath); + } else { + NS_WARNING("No Widevine L1 plugin for the utility process!"); + } + } +#endif + + Maybe<const char*> parentBuildID = + geckoargs::sParentBuildID.Get(aArgc, aArgv); + if (parentBuildID.isNothing()) { + return false; + } + + if (!ProcessChild::InitPrefs(aArgc, aArgv)) { + return false; + } + + return mUtility->Init(TakeInitialEndpoint(), nsCString(*parentBuildID), + *sandboxingKind); +} + +void UtilityProcessImpl::CleanUp() { NS_ShutdownXPCOM(nullptr); } + +} // namespace mozilla::ipc diff --git a/ipc/glue/UtilityProcessImpl.h b/ipc/glue/UtilityProcessImpl.h new file mode 100644 index 0000000000..9b42cd5f74 --- /dev/null +++ b/ipc/glue/UtilityProcessImpl.h @@ -0,0 +1,43 @@ +/* -*- 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/. */ +#ifndef _include_ipc_glue_UtilityProcessImpl_h__ +#define _include_ipc_glue_UtilityProcessImpl_h__ +#include "mozilla/ipc/ProcessChild.h" + +#if defined(XP_WIN) +# include "mozilla/mscom/ProcessRuntime.h" +#endif + +#include "mozilla/ipc/UtilityProcessChild.h" + +namespace mozilla::ipc { + +// This class owns the subprocess instance of a PUtilityProcess - which in this +// case, is a UtilityProcessParent. It is instantiated as a singleton in +// XRE_InitChildProcess. +class UtilityProcessImpl final : public ipc::ProcessChild { + public: + using ipc::ProcessChild::ProcessChild; + ~UtilityProcessImpl(); + + bool Init(int aArgc, char* aArgv[]) override; + void CleanUp() override; + +#if defined(XP_WIN) + static void LoadLibraryOrCrash(LPCWSTR aLib); +#endif // defined(XP_WIN) + + private: + RefPtr<UtilityProcessChild> mUtility = UtilityProcessChild::GetSingleton(); + +#if defined(XP_WIN) + mozilla::mscom::ProcessRuntime mCOMRuntime; +#endif +}; + +} // namespace mozilla::ipc + +#endif // _include_ipc_glue_UtilityProcessImpl_h__ diff --git a/ipc/glue/UtilityProcessManager.cpp b/ipc/glue/UtilityProcessManager.cpp new file mode 100644 index 0000000000..e24fed476b --- /dev/null +++ b/ipc/glue/UtilityProcessManager.cpp @@ -0,0 +1,660 @@ +/* -*- 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 "UtilityProcessManager.h" + +#include "JSOracleParent.h" +#include "mozilla/ipc/UtilityProcessHost.h" +#include "mozilla/MemoryReportingProcess.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/SyncRunnable.h" // for LaunchUtilityProcess +#include "mozilla/ipc/UtilityProcessParent.h" +#include "mozilla/ipc/UtilityAudioDecoderChild.h" +#include "mozilla/ipc/UtilityAudioDecoderParent.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/UtilityProcessSandboxing.h" +#include "mozilla/ipc/ProcessChild.h" +#include "nsAppRunner.h" +#include "nsContentUtils.h" + +#ifdef XP_WIN +# include "mozilla/dom/WindowsUtilsParent.h" +# include "mozilla/widget/filedialog/WinFileDialogParent.h" +#endif + +#include "mozilla/GeckoArgs.h" + +namespace mozilla::ipc { + +extern LazyLogModule gUtilityProcessLog; +#define LOGD(...) MOZ_LOG(gUtilityProcessLog, LogLevel::Debug, (__VA_ARGS__)) + +static StaticRefPtr<UtilityProcessManager> sSingleton; + +static bool sXPCOMShutdown = false; + +bool UtilityProcessManager::IsShutdown() const { + MOZ_ASSERT(NS_IsMainThread()); + return sXPCOMShutdown || !sSingleton; +} + +RefPtr<UtilityProcessManager> UtilityProcessManager::GetSingleton() { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + if (!sXPCOMShutdown && sSingleton == nullptr) { + sSingleton = new UtilityProcessManager(); + sSingleton->Init(); + } + return sSingleton; +} + +RefPtr<UtilityProcessManager> UtilityProcessManager::GetIfExists() { + MOZ_ASSERT(NS_IsMainThread()); + return sSingleton; +} + +UtilityProcessManager::UtilityProcessManager() { + LOGD("[%p] UtilityProcessManager::UtilityProcessManager", this); +} + +void UtilityProcessManager::Init() { + // Start listening for pref changes so we can + // forward them to the process once it is running. + mObserver = new Observer(this); + nsContentUtils::RegisterShutdownObserver(mObserver); + Preferences::AddStrongObserver(mObserver, ""); +} + +UtilityProcessManager::~UtilityProcessManager() { + LOGD("[%p] UtilityProcessManager::~UtilityProcessManager", this); + + // The Utility process should ALL have already been shut down. + MOZ_ASSERT(NoMoreProcesses()); +} + +NS_IMPL_ISUPPORTS(UtilityProcessManager::Observer, nsIObserver); + +UtilityProcessManager::Observer::Observer(UtilityProcessManager* aManager) + : mManager(aManager) {} + +NS_IMETHODIMP +UtilityProcessManager::Observer::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + mManager->OnXPCOMShutdown(); + } else if (!strcmp(aTopic, "nsPref:changed")) { + mManager->OnPreferenceChange(aData); + } + return NS_OK; +} + +void UtilityProcessManager::OnXPCOMShutdown() { + LOGD("[%p] UtilityProcessManager::OnXPCOMShutdown", this); + + MOZ_ASSERT(NS_IsMainThread()); + sXPCOMShutdown = true; + nsContentUtils::UnregisterShutdownObserver(mObserver); + CleanShutdownAllProcesses(); +} + +void UtilityProcessManager::OnPreferenceChange(const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + if (NoMoreProcesses()) { + // Process hasn't been launched yet + return; + } + // We know prefs are ASCII here. + NS_LossyConvertUTF16toASCII strData(aData); + + mozilla::dom::Pref pref(strData, /* isLocked */ false, + /* isSanitized */ false, Nothing(), Nothing()); + Preferences::GetPreference(&pref, GeckoProcessType_Utility, + /* remoteType */ ""_ns); + + for (auto& p : mProcesses) { + if (!p) { + continue; + } + + if (p->mProcessParent) { + Unused << p->mProcessParent->SendPreferenceUpdate(pref); + } else if (IsProcessLaunching(p->mSandbox)) { + p->mQueuedPrefs.AppendElement(pref); + } + } +} + +RefPtr<UtilityProcessManager::ProcessFields> UtilityProcessManager::GetProcess( + SandboxingKind aSandbox) { + if (!mProcesses[aSandbox]) { + return nullptr; + } + + return mProcesses[aSandbox]; +} + +RefPtr<GenericNonExclusivePromise> UtilityProcessManager::LaunchProcess( + SandboxingKind aSandbox) { + LOGD("[%p] UtilityProcessManager::LaunchProcess SandboxingKind=%" PRIu64, + this, aSandbox); + + MOZ_ASSERT(NS_IsMainThread()); + + if (IsShutdown()) { + NS_WARNING("Reject early LaunchProcess() for Shutdown"); + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + } + + RefPtr<ProcessFields> p = GetProcess(aSandbox); + if (p && p->mNumProcessAttempts) { + // We failed to start the Utility process earlier, abort now. + NS_WARNING("Reject LaunchProcess() for earlier mNumProcessAttempts"); + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + } + + if (p && p->mLaunchPromise && p->mProcess) { + return p->mLaunchPromise; + } + + if (!p) { + p = new ProcessFields(aSandbox); + mProcesses[aSandbox] = p; + } + + std::vector<std::string> extraArgs; + ProcessChild::AddPlatformBuildID(extraArgs); + geckoargs::sSandboxingKind.Put(aSandbox, extraArgs); + + // The subprocess is launched asynchronously, so we + // wait for the promise to be resolved to acquire the IPDL actor. + p->mProcess = new UtilityProcessHost(aSandbox, this); + if (!p->mProcess->Launch(extraArgs)) { + p->mNumProcessAttempts++; + DestroyProcess(aSandbox); + NS_WARNING("Reject LaunchProcess() for mNumProcessAttempts++"); + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, + __func__); + } + + RefPtr<UtilityProcessManager> self = this; + p->mLaunchPromise = p->mProcess->LaunchPromise()->Then( + GetMainThreadSerialEventTarget(), __func__, + [self, p, aSandbox](bool) { + if (self->IsShutdown()) { + NS_WARNING( + "Reject LaunchProcess() after LaunchPromise() for Shutdown"); + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_NOT_AVAILABLE, __func__); + } + + if (self->IsProcessDestroyed(aSandbox)) { + NS_WARNING( + "Reject LaunchProcess() after LaunchPromise() for destroyed " + "process"); + return GenericNonExclusivePromise::CreateAndReject( + NS_ERROR_NOT_AVAILABLE, __func__); + } + + p->mProcessParent = p->mProcess->GetActor(); + + // Flush any pref updates that happened during + // launch and weren't included in the blobs set + // up in LaunchUtilityProcess. + for (const mozilla::dom::Pref& pref : p->mQueuedPrefs) { + Unused << NS_WARN_IF(!p->mProcessParent->SendPreferenceUpdate(pref)); + } + p->mQueuedPrefs.Clear(); + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::UtilityProcessStatus, "Running"_ns); + + return GenericNonExclusivePromise::CreateAndResolve(true, __func__); + }, + [self, p, aSandbox](nsresult aError) { + if (GetSingleton()) { + p->mNumProcessAttempts++; + self->DestroyProcess(aSandbox); + } + NS_WARNING("Reject LaunchProcess() for LaunchPromise() rejection"); + return GenericNonExclusivePromise::CreateAndReject(aError, __func__); + }); + + return p->mLaunchPromise; +} + +template <typename Actor> +RefPtr<GenericNonExclusivePromise> UtilityProcessManager::StartUtility( + RefPtr<Actor> aActor, SandboxingKind aSandbox) { + LOGD( + "[%p] UtilityProcessManager::StartUtility actor=%p " + "SandboxingKind=%" PRIu64, + this, aActor.get(), aSandbox); + + TimeStamp utilityStart = TimeStamp::Now(); + + if (!aActor) { + MOZ_ASSERT(false, "Actor singleton failure"); + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + } + + if (aActor->CanSend()) { + // Actor has already been setup, so we: + // - know the process has been launched + // - the ipc actors are ready + PROFILER_MARKER_TEXT( + "UtilityProcessManager::StartUtility", IPC, + MarkerOptions(MarkerTiming::InstantNow()), + nsPrintfCString("SandboxingKind=%" PRIu64 " aActor->CanSend()", + aSandbox)); + return GenericNonExclusivePromise::CreateAndResolve(true, __func__); + } + + RefPtr<UtilityProcessManager> self = this; + return LaunchProcess(aSandbox)->Then( + GetMainThreadSerialEventTarget(), __func__, + [self, aActor, aSandbox, utilityStart]() { + RefPtr<UtilityProcessParent> utilityParent = + self->GetProcessParent(aSandbox); + if (!utilityParent) { + NS_WARNING("Missing parent in StartUtility"); + return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + } + + // It is possible if multiple processes concurrently request a utility + // actor that the previous CanSend() check returned false for both but + // that by the time we have started our process for real, one of them + // has already been able to establish the IPC connection and thus we + // would perform more than one Open() call. + // + // The tests within browser_utility_multipleAudio.js should be able to + // catch that behavior. + if (!aActor->CanSend()) { + nsresult rv = aActor->BindToUtilityProcess(utilityParent); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Protocol endpoints failure"); + return GenericNonExclusivePromise::CreateAndReject(rv, __func__); + } + + MOZ_DIAGNOSTIC_ASSERT(aActor->CanSend(), "IPC established for actor"); + self->RegisterActor(utilityParent, aActor->GetActorName()); + } + + PROFILER_MARKER_TEXT( + "UtilityProcessManager::StartUtility", IPC, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom(utilityStart)), + nsPrintfCString("SandboxingKind=%" PRIu64 " Resolve", aSandbox)); + return GenericNonExclusivePromise::CreateAndResolve(true, __func__); + }, + [self, aSandbox, utilityStart](nsresult aError) { + NS_WARNING("Reject StartUtility() for LaunchProcess() rejection"); + if (!self->IsShutdown()) { + NS_WARNING("Reject StartUtility() when !IsShutdown()"); + } + PROFILER_MARKER_TEXT( + "UtilityProcessManager::StartUtility", IPC, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom(utilityStart)), + nsPrintfCString("SandboxingKind=%" PRIu64 " Reject", aSandbox)); + return GenericNonExclusivePromise::CreateAndReject(aError, __func__); + }); +} + +RefPtr<UtilityProcessManager::StartRemoteDecodingUtilityPromise> +UtilityProcessManager::StartProcessForRemoteMediaDecoding( + base::ProcessId aOtherProcess, dom::ContentParentId aChildId, + SandboxingKind aSandbox) { + // Not supported kinds. + if (aSandbox != SandboxingKind::GENERIC_UTILITY +#ifdef MOZ_APPLEMEDIA + && aSandbox != SandboxingKind::UTILITY_AUDIO_DECODING_APPLE_MEDIA +#endif +#ifdef XP_WIN + && aSandbox != SandboxingKind::UTILITY_AUDIO_DECODING_WMF +#endif +#ifdef MOZ_WMF_MEDIA_ENGINE + && aSandbox != SandboxingKind::MF_MEDIA_ENGINE_CDM +#endif + ) { + return StartRemoteDecodingUtilityPromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + } + TimeStamp remoteDecodingStart = TimeStamp::Now(); + + RefPtr<UtilityProcessManager> self = this; + RefPtr<UtilityAudioDecoderChild> uadc = + UtilityAudioDecoderChild::GetSingleton(aSandbox); + MOZ_ASSERT(uadc, "Unable to get a singleton for UtilityAudioDecoderChild"); + return StartUtility(uadc, aSandbox) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [self, uadc, aOtherProcess, aChildId, aSandbox, + remoteDecodingStart]() { + RefPtr<UtilityProcessParent> parent = + self->GetProcessParent(aSandbox); + if (!parent) { + NS_WARNING("UtilityAudioDecoderParent lost in the middle"); + return StartRemoteDecodingUtilityPromise::CreateAndReject( + NS_ERROR_FAILURE, __func__); + } + + if (!uadc->CanSend()) { + NS_WARNING("UtilityAudioDecoderChild lost in the middle"); + return StartRemoteDecodingUtilityPromise::CreateAndReject( + NS_ERROR_FAILURE, __func__); + } + + base::ProcessId process = parent->OtherPid(); + + Endpoint<PRemoteDecoderManagerChild> childPipe; + Endpoint<PRemoteDecoderManagerParent> parentPipe; + nsresult rv = PRemoteDecoderManager::CreateEndpoints( + process, aOtherProcess, &parentPipe, &childPipe); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Could not create content remote decoder"); + return StartRemoteDecodingUtilityPromise::CreateAndReject( + rv, __func__); + } + + if (!uadc->SendNewContentRemoteDecoderManager(std::move(parentPipe), + aChildId)) { + MOZ_ASSERT(false, "SendNewContentRemoteDecoderManager failure"); + return StartRemoteDecodingUtilityPromise::CreateAndReject( + NS_ERROR_FAILURE, __func__); + } + +#ifdef MOZ_WMF_MEDIA_ENGINE + if (aSandbox == SandboxingKind::MF_MEDIA_ENGINE_CDM && + !uadc->CreateVideoBridge()) { + MOZ_ASSERT(false, "Failed to create video bridge"); + return StartRemoteDecodingUtilityPromise::CreateAndReject( + NS_ERROR_FAILURE, __func__); + } +#endif + PROFILER_MARKER_TEXT( + "UtilityProcessManager::StartProcessForRemoteMediaDecoding", + MEDIA, + MarkerOptions( + MarkerTiming::IntervalUntilNowFrom(remoteDecodingStart)), + "Resolve"_ns); + return StartRemoteDecodingUtilityPromise::CreateAndResolve( + std::move(childPipe), __func__); + }, + [self, remoteDecodingStart](nsresult aError) { + NS_WARNING( + "Reject StartProcessForRemoteMediaDecoding() for " + "StartUtility() rejection"); + PROFILER_MARKER_TEXT( + "UtilityProcessManager::StartProcessForRemoteMediaDecoding", + MEDIA, + MarkerOptions( + MarkerTiming::IntervalUntilNowFrom(remoteDecodingStart)), + "Reject"_ns); + return StartRemoteDecodingUtilityPromise::CreateAndReject(aError, + __func__); + }); +} + +RefPtr<UtilityProcessManager::JSOraclePromise> +UtilityProcessManager::StartJSOracle(dom::JSOracleParent* aParent) { + return StartUtility(RefPtr{aParent}, SandboxingKind::GENERIC_UTILITY); +} + +#ifdef XP_WIN + +// Windows Utils + +RefPtr<UtilityProcessManager::WindowsUtilsPromise> +UtilityProcessManager::GetWindowsUtilsPromise() { + TimeStamp windowsUtilsStart = TimeStamp::Now(); + RefPtr<UtilityProcessManager> self = this; + if (!mWindowsUtils) { + mWindowsUtils = new dom::WindowsUtilsParent(); + } + + RefPtr<dom::WindowsUtilsParent> wup = mWindowsUtils; + MOZ_ASSERT(wup, "Unable to get a singleton for WindowsUtils"); + return StartUtility(wup, SandboxingKind::WINDOWS_UTILS) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [self, wup, windowsUtilsStart]() { + if (!wup->CanSend()) { + MOZ_ASSERT(false, "WindowsUtilsParent can't send"); + return WindowsUtilsPromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + } + PROFILER_MARKER_TEXT( + "UtilityProcessManager::GetWindowsUtilsPromise", OTHER, + MarkerOptions( + MarkerTiming::IntervalUntilNowFrom(windowsUtilsStart)), + "Resolve"_ns); + return WindowsUtilsPromise::CreateAndResolve(wup, __func__); + }, + [self, windowsUtilsStart](nsresult aError) { + NS_WARNING("StartUtility rejected promise for PWindowsUtils"); + PROFILER_MARKER_TEXT( + "UtilityProcessManager::GetWindowsUtilsPromise", OTHER, + MarkerOptions( + MarkerTiming::IntervalUntilNowFrom(windowsUtilsStart)), + "Reject"_ns); + return WindowsUtilsPromise::CreateAndReject(aError, __func__); + }); +} + +void UtilityProcessManager::ReleaseWindowsUtils() { mWindowsUtils = nullptr; } + +RefPtr<UtilityProcessManager::WinFileDialogPromise> +UtilityProcessManager::CreateWinFileDialogActor() { + using Promise = WinFileDialogPromise; + TimeStamp startTime = TimeStamp::Now(); + auto wfdp = MakeRefPtr<widget::filedialog::WinFileDialogParent>(); + + return StartUtility(wfdp, SandboxingKind::WINDOWS_FILE_DIALOG) + ->Then( + GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__, + [wfdp, startTime]() mutable { + LOGD("CreateWinFileDialogAsync() resolve: wfdp = [%p]", wfdp.get()); + if (!wfdp->CanSend()) { + MOZ_ASSERT(false, "WinFileDialogParent can't send"); + return Promise::CreateAndReject(NS_ERROR_FAILURE, + __PRETTY_FUNCTION__); + } + PROFILER_MARKER_TEXT( + "UtilityProcessManager::CreateWinFileDialogAsync", OTHER, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom(startTime)), + "Resolve"_ns); + + return Promise::CreateAndResolve( + widget::filedialog::ProcessProxy(std::move(wfdp)), + __PRETTY_FUNCTION__); + }, + [self = RefPtr(this), startTime](nsresult error) { + LOGD("CreateWinFileDialogAsync() reject"); + if (!self->IsShutdown()) { + MOZ_ASSERT_UNREACHABLE("failure when starting file-dialog actor"); + } + PROFILER_MARKER_TEXT( + "UtilityProcessManager::CreateWinFileDialogAsync", OTHER, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom(startTime)), + "Reject"_ns); + + return Promise::CreateAndReject(error, __PRETTY_FUNCTION__); + }); +} + +#endif // XP_WIN + +bool UtilityProcessManager::IsProcessLaunching(SandboxingKind aSandbox) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<ProcessFields> p = GetProcess(aSandbox); + if (!p) { + MOZ_CRASH("Cannot check process launching with no process"); + return false; + } + + return p->mProcess && !(p->mProcessParent); +} + +bool UtilityProcessManager::IsProcessDestroyed(SandboxingKind aSandbox) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<ProcessFields> p = GetProcess(aSandbox); + if (!p) { + MOZ_CRASH("Cannot check process destroyed with no process"); + return false; + } + return !p->mProcess && !p->mProcessParent; +} + +void UtilityProcessManager::OnProcessUnexpectedShutdown( + UtilityProcessHost* aHost) { + MOZ_ASSERT(NS_IsMainThread()); + + for (auto& it : mProcesses) { + if (it && it->mProcess && it->mProcess == aHost) { + it->mNumUnexpectedCrashes++; + DestroyProcess(it->mSandbox); + return; + } + } + + MOZ_CRASH( + "Called UtilityProcessManager::OnProcessUnexpectedShutdown with invalid " + "aHost"); +} + +void UtilityProcessManager::CleanShutdownAllProcesses() { + LOGD("[%p] UtilityProcessManager::CleanShutdownAllProcesses", this); + + for (auto& it : mProcesses) { + if (it) { + DestroyProcess(it->mSandbox); + } + } +} + +void UtilityProcessManager::CleanShutdown(SandboxingKind aSandbox) { + LOGD("[%p] UtilityProcessManager::CleanShutdown SandboxingKind=%" PRIu64, + this, aSandbox); + + DestroyProcess(aSandbox); +} + +uint16_t UtilityProcessManager::AliveProcesses() { + uint16_t alive = 0; + for (auto& p : mProcesses) { + if (p != nullptr) { + alive++; + } + } + return alive; +} + +bool UtilityProcessManager::NoMoreProcesses() { return AliveProcesses() == 0; } + +void UtilityProcessManager::DestroyProcess(SandboxingKind aSandbox) { + LOGD("[%p] UtilityProcessManager::DestroyProcess SandboxingKind=%" PRIu64, + this, aSandbox); + + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + if (AliveProcesses() <= 1) { + if (mObserver) { + Preferences::RemoveObserver(mObserver, ""); + } + + mObserver = nullptr; + } + + RefPtr<ProcessFields> p = GetProcess(aSandbox); + if (!p) { + return; + } + + p->mQueuedPrefs.Clear(); + p->mProcessParent = nullptr; + + if (!p->mProcess) { + return; + } + + p->mProcess->Shutdown(); + p->mProcess = nullptr; + + mProcesses[aSandbox] = nullptr; + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::UtilityProcessStatus, "Destroyed"_ns); + + if (NoMoreProcesses()) { + sSingleton = nullptr; + } +} + +Maybe<base::ProcessId> UtilityProcessManager::ProcessPid( + SandboxingKind aSandbox) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<ProcessFields> p = GetProcess(aSandbox); + if (!p) { + return Nothing(); + } + if (p->mProcessParent) { + return Some(p->mProcessParent->OtherPid()); + } + return Nothing(); +} + +class UtilityMemoryReporter : public MemoryReportingProcess { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityMemoryReporter, override) + + explicit UtilityMemoryReporter(UtilityProcessParent* aParent) { + mParent = aParent; + } + + bool IsAlive() const override { return bool(GetParent()); } + + bool SendRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile) override { + RefPtr<UtilityProcessParent> parent = GetParent(); + if (!parent) { + return false; + } + + return parent->SendRequestMemoryReport(aGeneration, aAnonymize, + aMinimizeMemoryUsage, aDMDFile); + } + + int32_t Pid() const override { + if (RefPtr<UtilityProcessParent> parent = GetParent()) { + return (int32_t)parent->OtherPid(); + } + return 0; + } + + private: + RefPtr<UtilityProcessParent> GetParent() const { return mParent; } + + RefPtr<UtilityProcessParent> mParent = nullptr; + + protected: + ~UtilityMemoryReporter() = default; +}; + +RefPtr<MemoryReportingProcess> UtilityProcessManager::GetProcessMemoryReporter( + UtilityProcessParent* parent) { + return new UtilityMemoryReporter(parent); +} + +} // namespace mozilla::ipc diff --git a/ipc/glue/UtilityProcessManager.h b/ipc/glue/UtilityProcessManager.h new file mode 100644 index 0000000000..60cea016c9 --- /dev/null +++ b/ipc/glue/UtilityProcessManager.h @@ -0,0 +1,246 @@ +/* -*- 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/. */ +#ifndef _include_ipc_glue_UtilityProcessManager_h_ +#define _include_ipc_glue_UtilityProcessManager_h_ +#include "mozilla/MozPromise.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/ipc/UtilityProcessHost.h" +#include "mozilla/EnumeratedArray.h" +#include "mozilla/ProcInfo.h" +#include "nsIObserver.h" +#include "nsTArray.h" + +#include "mozilla/PRemoteDecoderManagerChild.h" + +namespace mozilla { + +class MemoryReportingProcess; + +namespace dom { +class JSOracleParent; +class WindowsUtilsParent; +} // namespace dom + +namespace widget::filedialog { +class ProcessProxy; +} // namespace widget::filedialog + +namespace ipc { + +class UtilityProcessParent; + +// The UtilityProcessManager is a singleton responsible for creating +// Utility-bound objects that may live in another process. Currently, it +// provides access to the Utility process via ContentParent. +class UtilityProcessManager final : public UtilityProcessHost::Listener { + friend class UtilityProcessParent; + + public: + template <typename T> + using Promise = MozPromise<T, nsresult, true>; + + using StartRemoteDecodingUtilityPromise = + Promise<Endpoint<PRemoteDecoderManagerChild>>; + using JSOraclePromise = GenericNonExclusivePromise; + +#ifdef XP_WIN + using WindowsUtilsPromise = Promise<RefPtr<dom::WindowsUtilsParent>>; + using WinFileDialogPromise = Promise<widget::filedialog::ProcessProxy>; +#endif + + static RefPtr<UtilityProcessManager> GetSingleton(); + + static RefPtr<UtilityProcessManager> GetIfExists(); + + // Launch a new Utility process asynchronously + RefPtr<GenericNonExclusivePromise> LaunchProcess(SandboxingKind aSandbox); + + template <typename Actor> + RefPtr<GenericNonExclusivePromise> StartUtility(RefPtr<Actor> aActor, + SandboxingKind aSandbox); + + RefPtr<StartRemoteDecodingUtilityPromise> StartProcessForRemoteMediaDecoding( + base::ProcessId aOtherProcess, dom::ContentParentId aChildId, + SandboxingKind aSandbox); + + RefPtr<JSOraclePromise> StartJSOracle(mozilla::dom::JSOracleParent* aParent); + +#ifdef XP_WIN + // Get the (possibly already resolved) promise for the Windows utility + // process actor. Creates the process if it is not running. + RefPtr<WindowsUtilsPromise> GetWindowsUtilsPromise(); + // Releases the WindowsUtils actor so that it can be destroyed. + // Subsequent attempts to use WindowsUtils will create a new process. + void ReleaseWindowsUtils(); + + // Get a new Windows file-dialog utility-process actor. These are never + // reused; this will always return a fresh actor. + RefPtr<WinFileDialogPromise> CreateWinFileDialogActor(); +#endif + + void OnProcessUnexpectedShutdown(UtilityProcessHost* aHost); + + // Returns the platform pid for this utility sandbox process. + Maybe<base::ProcessId> ProcessPid(SandboxingKind aSandbox); + + // Create a MemoryReportingProcess object for this utility process + RefPtr<MemoryReportingProcess> GetProcessMemoryReporter( + UtilityProcessParent* parent); + + // Returns access to the PUtility protocol if a Utility process for that + // sandbox is present. + RefPtr<UtilityProcessParent> GetProcessParent(SandboxingKind aSandbox) { + RefPtr<ProcessFields> p = GetProcess(aSandbox); + if (!p) { + return nullptr; + } + return p->mProcessParent; + } + + // Get a list of all valid utility process parent references + nsTArray<RefPtr<UtilityProcessParent>> GetAllProcessesProcessParent() { + nsTArray<RefPtr<UtilityProcessParent>> rv; + for (auto& p : mProcesses) { + if (p && p->mProcessParent) { + rv.AppendElement(p->mProcessParent); + } + } + return rv; + } + + // Returns the Utility Process for that sandbox + UtilityProcessHost* Process(SandboxingKind aSandbox) { + RefPtr<ProcessFields> p = GetProcess(aSandbox); + if (!p) { + return nullptr; + } + return p->mProcess; + } + + void RegisterActor(const RefPtr<UtilityProcessParent>& aParent, + UtilityActorName aActorName) { + for (auto& p : mProcesses) { + if (p && p->mProcessParent && p->mProcessParent == aParent) { + p->mActors.AppendElement(aActorName); + return; + } + } + } + + Span<const UtilityActorName> GetActors( + const RefPtr<UtilityProcessParent>& aParent) { + for (auto& p : mProcesses) { + if (p && p->mProcessParent && p->mProcessParent == aParent) { + return p->mActors; + } + } + return {}; + } + + Span<const UtilityActorName> GetActors(GeckoChildProcessHost* aHost) { + for (auto& p : mProcesses) { + if (p && p->mProcess == aHost) { + return p->mActors; + } + } + return {}; + } + + Span<const UtilityActorName> GetActors(SandboxingKind aSbKind) { + auto proc = GetProcess(aSbKind); + if (!proc) { + return {}; + } + return proc->mActors; + } + + // Shutdown the Utility process for that sandbox. + void CleanShutdown(SandboxingKind aSandbox); + + // Shutdown all utility processes + void CleanShutdownAllProcesses(); + + uint16_t AliveProcesses(); + + private: + ~UtilityProcessManager(); + + bool IsProcessLaunching(SandboxingKind aSandbox); + bool IsProcessDestroyed(SandboxingKind aSandbox); + + // Called from our xpcom-shutdown observer. + void OnXPCOMShutdown(); + void OnPreferenceChange(const char16_t* aData); + + UtilityProcessManager(); + + void Init(); + + void DestroyProcess(SandboxingKind aSandbox); + + bool IsShutdown() const; + + class Observer final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + explicit Observer(UtilityProcessManager* aManager); + + protected: + ~Observer() = default; + + RefPtr<UtilityProcessManager> mManager; + }; + friend class Observer; + + RefPtr<Observer> mObserver; + + class ProcessFields final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProcessFields); + + explicit ProcessFields(SandboxingKind aSandbox) : mSandbox(aSandbox){}; + + // Promise will be resolved when this Utility process has been fully started + // and configured. Only accessed on the main thread. + RefPtr<GenericNonExclusivePromise> mLaunchPromise; + + uint32_t mNumProcessAttempts = 0; + uint32_t mNumUnexpectedCrashes = 0; + + // Fields that are associated with the current Utility process. + UtilityProcessHost* mProcess = nullptr; + RefPtr<UtilityProcessParent> mProcessParent = nullptr; + + // Collects any pref changes that occur during process launch (after + // the initial map is passed in command-line arguments) to be sent + // when the process can receive IPC messages. + nsTArray<dom::Pref> mQueuedPrefs; + + nsTArray<UtilityActorName> mActors; + + SandboxingKind mSandbox = SandboxingKind::COUNT; + + protected: + ~ProcessFields() = default; + }; + + EnumeratedArray<SandboxingKind, SandboxingKind::COUNT, RefPtr<ProcessFields>> + mProcesses; + + RefPtr<ProcessFields> GetProcess(SandboxingKind); + bool NoMoreProcesses(); + +#ifdef XP_WIN + RefPtr<dom::WindowsUtilsParent> mWindowsUtils; +#endif // XP_WIN +}; + +} // namespace ipc + +} // namespace mozilla + +#endif // _include_ipc_glue_UtilityProcessManager_h_ diff --git a/ipc/glue/UtilityProcessParent.cpp b/ipc/glue/UtilityProcessParent.cpp new file mode 100644 index 0000000000..2860b4704b --- /dev/null +++ b/ipc/glue/UtilityProcessParent.cpp @@ -0,0 +1,203 @@ +/* -*- 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 "mozilla/ipc/UtilityProcessParent.h" +#include "mozilla/ipc/UtilityProcessManager.h" + +#if defined(XP_WIN) +# include <dwrite.h> +# include <process.h> +# include "mozilla/WinDllServices.h" +#endif + +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/FOGIPC.h" + +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryIPC.h" + +#include "nsHashPropertyBag.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" + +namespace mozilla::ipc { + +UtilityProcessParent::UtilityProcessParent(UtilityProcessHost* aHost) + : mHost(aHost) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mHost); +} + +UtilityProcessParent::~UtilityProcessParent() = default; + +bool UtilityProcessParent::SendRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, const Maybe<FileDescriptor>& aDMDFile) { + mMemoryReportRequest = MakeUnique<MemoryReportRequestHost>(aGeneration); + + PUtilityProcessParent::SendRequestMemoryReport( + aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, + [self = RefPtr{this}](const uint32_t& aGeneration2) { + if (self->mMemoryReportRequest) { + self->mMemoryReportRequest->Finish(aGeneration2); + self->mMemoryReportRequest = nullptr; + } + }, + [self = RefPtr{this}](mozilla::ipc::ResponseRejectReason) { + self->mMemoryReportRequest = nullptr; + }); + + return true; +} + +mozilla::ipc::IPCResult UtilityProcessParent::RecvAddMemoryReport( + const MemoryReport& aReport) { + if (mMemoryReportRequest) { + mMemoryReportRequest->RecvReport(aReport); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessParent::RecvFOGData(ByteBuf&& aBuf) { + glean::FOGData(std::move(aBuf)); + return IPC_OK(); +} + +#if defined(XP_WIN) +mozilla::ipc::IPCResult UtilityProcessParent::RecvGetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority, + GetModulesTrustResolver&& aResolver) { + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->GetModulesTrust(std::move(aModPaths), aRunAtNormalPriority) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolver](ModulesMapResult&& aResult) { + aResolver(Some(ModulesMapResult(std::move(aResult)))); + }, + [aResolver](nsresult aRv) { aResolver(Nothing()); }); + return IPC_OK(); +} +#endif // defined(XP_WIN) + +mozilla::ipc::IPCResult UtilityProcessParent::RecvAccumulateChildHistograms( + nsTArray<HistogramAccumulation>&& aAccumulations) { + TelemetryIPC::AccumulateChildHistograms(Telemetry::ProcessID::Utility, + aAccumulations); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +UtilityProcessParent::RecvAccumulateChildKeyedHistograms( + nsTArray<KeyedHistogramAccumulation>&& aAccumulations) { + TelemetryIPC::AccumulateChildKeyedHistograms(Telemetry::ProcessID::Utility, + aAccumulations); + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessParent::RecvUpdateChildScalars( + nsTArray<ScalarAction>&& aScalarActions) { + TelemetryIPC::UpdateChildScalars(Telemetry::ProcessID::Utility, + aScalarActions); + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessParent::RecvUpdateChildKeyedScalars( + nsTArray<KeyedScalarAction>&& aScalarActions) { + TelemetryIPC::UpdateChildKeyedScalars(Telemetry::ProcessID::Utility, + aScalarActions); + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessParent::RecvRecordChildEvents( + nsTArray<mozilla::Telemetry::ChildEventData>&& aEvents) { + TelemetryIPC::RecordChildEvents(Telemetry::ProcessID::Utility, aEvents); + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessParent::RecvRecordDiscardedData( + const mozilla::Telemetry::DiscardedData& aDiscardedData) { + TelemetryIPC::RecordDiscardedData(Telemetry::ProcessID::Utility, + aDiscardedData); + return IPC_OK(); +} + +mozilla::ipc::IPCResult UtilityProcessParent::RecvInitCompleted() { + MOZ_ASSERT(mHost); + mHost->ResolvePromise(); + return IPC_OK(); +} + +void UtilityProcessParent::ActorDestroy(ActorDestroyReason aWhy) { + RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag(); + + if (aWhy == AbnormalShutdown) { + nsAutoString dumpID; + + if (mCrashReporter) { +#if defined(MOZ_SANDBOX) + RefPtr<mozilla::ipc::UtilityProcessManager> upm = + mozilla::ipc::UtilityProcessManager::GetSingleton(); + if (upm) { + Span<const UtilityActorName> actors = upm->GetActors(this); + nsAutoCString actorsName; + if (!actors.IsEmpty()) { + actorsName += GetUtilityActorName(actors.First<1>()[0]); + for (const auto& actor : actors.From(1)) { + actorsName += ", "_ns + GetUtilityActorName(actor); + } + } + mCrashReporter->AddAnnotation( + CrashReporter::Annotation::UtilityActorsName, actorsName); + } +#endif + } + + GenerateCrashReport(OtherPid(), &dumpID); + + // It's okay for dumpID to be empty if there was no minidump generated + // tests like ipc/glue/test/browser/browser_utility_crashReporter.js are + // there to verify this + if (!dumpID.IsEmpty()) { + props->SetPropertyAsAString(u"dumpID"_ns, dumpID); + } + + MaybeTerminateProcess(); + } + + nsAutoString pid; + pid.AppendInt(static_cast<uint64_t>(OtherPid())); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers((nsIPropertyBag2*)props, "ipc:utility-shutdown", + pid.get()); + } else { + NS_WARNING("Could not get a nsIObserverService, ipc:utility-shutdown skip"); + } + + mHost->OnChannelClosed(); +} + +// To ensure that IPDL is finished before UtilityParent gets deleted. +class DeferredDeleteUtilityProcessParent : public Runnable { + public: + explicit DeferredDeleteUtilityProcessParent( + RefPtr<UtilityProcessParent> aParent) + : Runnable("ipc::glue::DeferredDeleteUtilityProcessParent"), + mParent(std::move(aParent)) {} + + NS_IMETHODIMP Run() override { return NS_OK; } + + private: + RefPtr<UtilityProcessParent> mParent; +}; + +/* static */ +void UtilityProcessParent::Destroy(RefPtr<UtilityProcessParent> aParent) { + NS_DispatchToMainThread( + new DeferredDeleteUtilityProcessParent(std::move(aParent))); +} + +} // namespace mozilla::ipc diff --git a/ipc/glue/UtilityProcessParent.h b/ipc/glue/UtilityProcessParent.h new file mode 100644 index 0000000000..6ff907583f --- /dev/null +++ b/ipc/glue/UtilityProcessParent.h @@ -0,0 +1,77 @@ +/* -*- 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/. */ +#ifndef _include_ipc_glue_UtilityProcessParent_h__ +#define _include_ipc_glue_UtilityProcessParent_h__ +#include "mozilla/ipc/PUtilityProcessParent.h" +#include "mozilla/ipc/CrashReporterHelper.h" +#include "mozilla/ipc/UtilityProcessHost.h" +#include "mozilla/dom/MemoryReportRequest.h" + +#include "mozilla/RefPtr.h" + +namespace mozilla { + +namespace ipc { + +class UtilityProcessHost; + +class UtilityProcessParent final + : public PUtilityProcessParent, + public ipc::CrashReporterHelper<GeckoProcessType_Utility> { + typedef mozilla::dom::MemoryReportRequestHost MemoryReportRequestHost; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UtilityProcessParent, override); + friend class UtilityProcessHost; + + explicit UtilityProcessParent(UtilityProcessHost* aHost); + + mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport); + + bool SendRequestMemoryReport(const uint32_t& aGeneration, + const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile); + + mozilla::ipc::IPCResult RecvFOGData(ByteBuf&& aBuf); + +#if defined(XP_WIN) + mozilla::ipc::IPCResult RecvGetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority, + GetModulesTrustResolver&& aResolver); +#endif // defined(XP_WIN) + + mozilla::ipc::IPCResult RecvAccumulateChildHistograms( + nsTArray<HistogramAccumulation>&& aAccumulations); + mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistograms( + nsTArray<KeyedHistogramAccumulation>&& aAccumulations); + mozilla::ipc::IPCResult RecvUpdateChildScalars( + nsTArray<ScalarAction>&& aScalarActions); + mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars( + nsTArray<KeyedScalarAction>&& aScalarActions); + mozilla::ipc::IPCResult RecvRecordChildEvents( + nsTArray<ChildEventData>&& events); + mozilla::ipc::IPCResult RecvRecordDiscardedData( + const DiscardedData& aDiscardedData); + + mozilla::ipc::IPCResult RecvInitCompleted(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + private: + UtilityProcessHost* mHost; + UniquePtr<MemoryReportRequestHost> mMemoryReportRequest{}; + + ~UtilityProcessParent(); + + static void Destroy(RefPtr<UtilityProcessParent> aParent); +}; + +} // namespace ipc + +} // namespace mozilla + +#endif // _include_ipc_glue_UtilityProcessParent_h__ diff --git a/ipc/glue/UtilityProcessSandboxing.cpp b/ipc/glue/UtilityProcessSandboxing.cpp new file mode 100644 index 0000000000..0c333fdeca --- /dev/null +++ b/ipc/glue/UtilityProcessSandboxing.cpp @@ -0,0 +1,70 @@ +/* -*- 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 "UtilityProcessSandboxing.h" + +#include <vector> +#include <string> + +#include "prenv.h" + +namespace mozilla::ipc { + +std::vector<std::string> split(const std::string& str, char s) { + std::vector<std::string> rv; + size_t last = 0; + size_t i; + size_t c = str.size(); + for (i = 0; i <= c; ++i) { + if (i == c || str[i] == s) { + rv.push_back(str.substr(last, i - last)); + last = i + 1; + } + } + return rv; +} + +bool IsUtilitySandboxEnabled(const char* envVar, SandboxingKind aKind) { +#ifdef XP_WIN + // Sandboxing the Windows file dialog is probably not useful. + // + // (Additionally, it causes failures in our test environments: when running + // tests on windows-11-2009-qr machines, sandboxed child processes can't see + // or interact with any other process's windows -- which means they can't + // select a window from the parent process as the file dialog's parent. This + // occurs regardless of the sandbox preferences, which is why we disable + // sandboxing entirely rather than use a maximally permissive preference-set. + // This behavior has not been seen in user-facing environments.) + if (aKind == SandboxingKind::WINDOWS_FILE_DIALOG) { + return false; + } +#endif + + if (envVar == nullptr) { + return true; + } + + const std::string disableUtility(envVar); + if (disableUtility == "1") { + return false; + } + + std::vector<std::string> components = split(disableUtility, ','); + const std::string thisKind = "utility:" + std::to_string(aKind); + for (const std::string& thisOne : components) { + if (thisOne == thisKind) { + return false; + } + } + + return true; +} + +bool IsUtilitySandboxEnabled(SandboxingKind aKind) { + return IsUtilitySandboxEnabled(PR_GetEnv("MOZ_DISABLE_UTILITY_SANDBOX"), + aKind); +} + +} // namespace mozilla::ipc diff --git a/ipc/glue/UtilityProcessSandboxing.h b/ipc/glue/UtilityProcessSandboxing.h new file mode 100644 index 0000000000..461bbec7c4 --- /dev/null +++ b/ipc/glue/UtilityProcessSandboxing.h @@ -0,0 +1,46 @@ +/* -*- 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/. */ +#ifndef _include_ipc_glue_UtilityProcessSandboxing_h_ +#define _include_ipc_glue_UtilityProcessSandboxing_h_ + +#include <stdint.h> + +namespace mozilla { + +namespace ipc { + +// When adding a new value, the checks within UtilityProcessImpl::Init() needs +// to be updated as well. +enum SandboxingKind : uint64_t { + + GENERIC_UTILITY, + +#ifdef MOZ_APPLEMEDIA + UTILITY_AUDIO_DECODING_APPLE_MEDIA, +#endif +#ifdef XP_WIN + UTILITY_AUDIO_DECODING_WMF, +#endif +#ifdef MOZ_WMF_MEDIA_ENGINE + MF_MEDIA_ENGINE_CDM, +#endif +#ifdef XP_WIN + WINDOWS_UTILS, + WINDOWS_FILE_DIALOG, +#endif + + COUNT, + +}; + +bool IsUtilitySandboxEnabled(const char* envVar, SandboxingKind aKind); +bool IsUtilitySandboxEnabled(SandboxingKind aKind); + +} // namespace ipc + +} // namespace mozilla + +#endif // _include_ipc_glue_UtilityProcessSandboxing_h_ diff --git a/ipc/glue/WindowsMessageLoop.cpp b/ipc/glue/WindowsMessageLoop.cpp new file mode 100644 index 0000000000..1305ecdea1 --- /dev/null +++ b/ipc/glue/WindowsMessageLoop.cpp @@ -0,0 +1,1192 @@ +/* -*- 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 "mozilla/DebugOnly.h" + +#include "WindowsMessageLoop.h" +#include "Neutering.h" +#include "MessageChannel.h" + +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "WinUtils.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/JSExecutionManager.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/mscom/Utils.h" +#include "mozilla/PaintTracker.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WindowsProcessMitigations.h" + +using namespace mozilla; +using namespace mozilla::ipc; +using namespace mozilla::ipc::windows; + +/** + * The Windows-only code below exists to solve a general problem with deadlocks + * that we experience when sending synchronous IPC messages to processes that + * contain native windows (i.e. HWNDs). Windows (the OS) sends synchronous + * messages between parent and child HWNDs in multiple circumstances (e.g. + * WM_PARENTNOTIFY, WM_NCACTIVATE, etc.), even when those HWNDs are controlled + * by different threads or different processes. Thus we can very easily end up + * in a deadlock by a call stack like the following: + * + * Process A: + * - CreateWindow(...) creates a "parent" HWND. + * - SendCreateChildWidget(HWND) is a sync IPC message that sends the "parent" + * HWND over to Process B. Process A blocks until a response is received + * from Process B. + * + * Process B: + * - RecvCreateWidget(HWND) gets the "parent" HWND from Process A. + * - CreateWindow(..., HWND) creates a "child" HWND with the parent from + * process A. + * - Windows (the OS) generates a WM_PARENTNOTIFY message that is sent + * synchronously to Process A. Process B blocks until a response is + * received from Process A. Process A, however, is blocked and cannot + * process the message. Both processes are deadlocked. + * + * The example above has a few different workarounds (e.g. setting the + * WS_EX_NOPARENTNOTIFY style on the child window) but the general problem is + * persists. Once two HWNDs are parented we must not block their owning + * threads when manipulating either HWND. + * + * Windows requires any application that hosts native HWNDs to always process + * messages or risk deadlock. Given our architecture the only way to meet + * Windows' requirement and allow for synchronous IPC messages is to pump a + * miniature message loop during a sync IPC call. We avoid processing any + * queued messages during the loop (with one exception, see below), but + * "nonqueued" messages (see + * http://msdn.microsoft.com/en-us/library/ms644927(VS.85).aspx under the + * section "Nonqueued messages") cannot be avoided. Those messages are trapped + * in a special window procedure where we can either ignore the message or + * process it in some fashion. + * + * Queued and "non-queued" messages will be processed during Interrupt calls if + * modal UI related api calls block an Interrupt in-call in the child. To + * prevent windows from freezing, and to allow concurrent processing of critical + * events (such as painting), we spin a native event dispatch loop while + * these in-calls are blocked. + */ + +#if defined(ACCESSIBILITY) +// pulled from accessibility's win utils +extern const wchar_t* kPropNameTabContent; +#endif + +// widget related message id constants we need to defer, see nsAppShell. +extern UINT sAppShellGeckoMsgId; + +namespace { + +const wchar_t kOldWndProcProp[] = L"MozillaIPCOldWndProc"; + +// This isn't defined before Windows XP. +enum { WM_XP_THEMECHANGED = 0x031A }; + +static StaticAutoPtr<AutoTArray<HWND, 20>> gNeuteredWindows; + +typedef nsTArray<UniquePtr<DeferredMessage>> DeferredMessageArray; +DeferredMessageArray* gDeferredMessages = nullptr; + +HHOOK gDeferredGetMsgHook = nullptr; +HHOOK gDeferredCallWndProcHook = nullptr; + +DWORD gUIThreadId = 0; +HWND gCOMWindow = 0; +// Once initialized, gWinEventHook is never unhooked. We save the handle so +// that we can check whether or not the hook is initialized. +HWINEVENTHOOK gWinEventHook = nullptr; +const wchar_t kCOMWindowClassName[] = L"OleMainThreadWndClass"; + +// WM_GETOBJECT id pulled from uia headers +#define MOZOBJID_UIAROOT -25 + +HWND FindCOMWindow() { + MOZ_ASSERT(gUIThreadId); + + HWND last = 0; + while ( + (last = FindWindowExW(HWND_MESSAGE, last, kCOMWindowClassName, NULL))) { + if (GetWindowThreadProcessId(last, NULL) == gUIThreadId) { + return last; + } + } + + return (HWND)0; +} + +void CALLBACK WinEventHook(HWINEVENTHOOK aWinEventHook, DWORD aEvent, + HWND aHwnd, LONG aIdObject, LONG aIdChild, + DWORD aEventThread, DWORD aMsEventTime) { + MOZ_ASSERT(aWinEventHook == gWinEventHook); + MOZ_ASSERT(gUIThreadId == aEventThread); + switch (aEvent) { + case EVENT_OBJECT_CREATE: { + if (aIdObject != OBJID_WINDOW || aIdChild != CHILDID_SELF) { + // Not an event we're interested in + return; + } + wchar_t classBuf[256] = {0}; + int result = ::GetClassNameW(aHwnd, classBuf, MOZ_ARRAY_LENGTH(classBuf)); + if (result != (MOZ_ARRAY_LENGTH(kCOMWindowClassName) - 1) || + wcsncmp(kCOMWindowClassName, classBuf, result)) { + // Not a class we're interested in + return; + } + MOZ_ASSERT(FindCOMWindow() == aHwnd); + gCOMWindow = aHwnd; + break; + } + case EVENT_OBJECT_DESTROY: { + if (aHwnd == gCOMWindow && aIdObject == OBJID_WINDOW) { + MOZ_ASSERT(aIdChild == CHILDID_SELF); + gCOMWindow = 0; + } + break; + } + default: { + return; + } + } +} + +LRESULT CALLBACK DeferredMessageHook(int nCode, WPARAM wParam, LPARAM lParam) { + // XXX This function is called for *both* the WH_CALLWNDPROC hook and the + // WH_GETMESSAGE hook, but they have different parameters. We don't + // use any of them except nCode which has the same meaning. + + // Only run deferred messages if all of these conditions are met: + // 1. The |nCode| indicates that this hook should do something. + // 2. We have deferred messages to run. + // 3. We're not being called from the PeekMessage within the WaitFor*Notify + // function (indicated with MessageChannel::IsPumpingMessages). We really + // only want to run after returning to the main event loop. + if (nCode >= 0 && gDeferredMessages && !MessageChannel::IsPumpingMessages()) { + NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook, + "These hooks must be set if we're being called!"); + NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!"); + + // Unset hooks first, in case we reenter below. + UnhookWindowsHookEx(gDeferredGetMsgHook); + UnhookWindowsHookEx(gDeferredCallWndProcHook); + gDeferredGetMsgHook = 0; + gDeferredCallWndProcHook = 0; + + // Unset the global and make sure we delete it when we're done here. + auto messages = WrapUnique(gDeferredMessages); + gDeferredMessages = nullptr; + + // Run all the deferred messages in order. + uint32_t count = messages->Length(); + for (uint32_t index = 0; index < count; index++) { + messages->ElementAt(index)->Run(); + } + } + + // Always call the next hook. + return CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +void ScheduleDeferredMessageRun() { + if (gDeferredMessages && !(gDeferredGetMsgHook && gDeferredCallWndProcHook)) { + NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!"); + + gDeferredGetMsgHook = ::SetWindowsHookEx(WH_GETMESSAGE, DeferredMessageHook, + nullptr, gUIThreadId); + gDeferredCallWndProcHook = ::SetWindowsHookEx( + WH_CALLWNDPROC, DeferredMessageHook, nullptr, gUIThreadId); + NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook, + "Failed to set hooks!"); + } +} + +static void DumpNeuteredMessage(HWND hwnd, UINT uMsg) { +#ifdef DEBUG + nsAutoCString log("Received \"nonqueued\" "); + // classify messages + if (uMsg < WM_USER) { + const char* msgText = mozilla::widget::WinUtils::WinEventToEventName(uMsg); + if (msgText) { + log.AppendPrintf("ui message \"%s\"", msgText); + } else { + log.AppendPrintf("ui message (0x%X)", uMsg); + } + } else if (uMsg >= WM_USER && uMsg < WM_APP) { + log.AppendPrintf("WM_USER message (0x%X)", uMsg); + } else if (uMsg >= WM_APP && uMsg < 0xC000) { + log.AppendPrintf("WM_APP message (0x%X)", uMsg); + } else if (uMsg >= 0xC000 && uMsg < 0x10000) { + log.AppendPrintf("registered windows message (0x%X)", uMsg); + } else { + log.AppendPrintf("system message (0x%X)", uMsg); + } + + log.AppendLiteral(" during a synchronous IPC message for window "); + log.AppendPrintf("0x%p", hwnd); + + wchar_t className[256] = {0}; + if (GetClassNameW(hwnd, className, sizeof(className) - 1) > 0) { + log.AppendLiteral(" (\""); + log.Append(NS_ConvertUTF16toUTF8((char16_t*)className)); + log.AppendLiteral("\")"); + } + + log.AppendLiteral( + ", sending it to DefWindowProc instead of the normal " + "window procedure."); + NS_ERROR(log.get()); +#endif +} + +LRESULT +ProcessOrDeferMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + UniquePtr<DeferredMessage> deferred; + + // Most messages ask for 0 to be returned if the message is processed. + LRESULT res = 0; + + switch (uMsg) { + // Messages that can be deferred as-is. These must not contain pointers in + // their wParam or lParam arguments! + case WM_ACTIVATE: + case WM_ACTIVATEAPP: + case WM_CANCELMODE: + case WM_CAPTURECHANGED: + case WM_CHILDACTIVATE: + case WM_DESTROY: + case WM_ENABLE: + case WM_IME_NOTIFY: + case WM_IME_SETCONTEXT: + case WM_KILLFOCUS: + case WM_MOUSEWHEEL: + case WM_NCDESTROY: + case WM_PARENTNOTIFY: + case WM_SETFOCUS: + case WM_SYSCOMMAND: + case WM_DISPLAYCHANGE: + case WM_SHOWWINDOW: // Intentional fall-through. + case WM_XP_THEMECHANGED: { + deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam); + break; + } + + case WM_DEVICECHANGE: + case WM_POWERBROADCAST: + case WM_NCACTIVATE: // Intentional fall-through. + case WM_SETCURSOR: { + // Friggin unconventional return value... + res = TRUE; + deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam); + break; + } + + case WM_MOUSEACTIVATE: { + res = MA_NOACTIVATE; + deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam); + break; + } + + // These messages need to use the RedrawWindow function to generate the + // right kind of message. We can't simply fake them as the MSDN docs say + // explicitly that paint messages should not be sent by an application. + case WM_ERASEBKGND: { + UINT flags = RDW_INVALIDATE | RDW_ERASE | RDW_NOINTERNALPAINT | + RDW_NOFRAME | RDW_NOCHILDREN | RDW_ERASENOW; + deferred = MakeUnique<DeferredRedrawMessage>(hwnd, flags); + break; + } + + // This message will generate a WM_PAINT message if there are invalid + // areas. + case WM_PAINT: { + deferred = MakeUnique<DeferredUpdateMessage>(hwnd); + break; + } + + // This message holds a string in its lParam that we must copy. + case WM_SETTINGCHANGE: { + deferred = + MakeUnique<DeferredSettingChangeMessage>(hwnd, uMsg, wParam, lParam); + break; + } + + // These messages are faked via a call to SetWindowPos. + case WM_WINDOWPOSCHANGED: { + deferred = MakeUnique<DeferredWindowPosMessage>(hwnd, lParam); + break; + } + case WM_NCCALCSIZE: { + deferred = + MakeUnique<DeferredWindowPosMessage>(hwnd, lParam, true, wParam); + break; + } + + case WM_COPYDATA: { + deferred = + MakeUnique<DeferredCopyDataMessage>(hwnd, uMsg, wParam, lParam); + res = TRUE; + break; + } + + case WM_STYLECHANGED: { + deferred = MakeUnique<DeferredStyleChangeMessage>(hwnd, wParam, lParam); + break; + } + + case WM_SETICON: { + deferred = MakeUnique<DeferredSetIconMessage>(hwnd, uMsg, wParam, lParam); + break; + } + + // Messages that are safe to pass to DefWindowProc go here. + case WM_ENTERIDLE: + case WM_GETICON: + case WM_NCPAINT: // (never trap nc paint events) + case WM_GETMINMAXINFO: + case WM_GETTEXT: + case WM_NCHITTEST: + case WM_STYLECHANGING: // Intentional fall-through. + case WM_WINDOWPOSCHANGING: + case WM_GETTEXTLENGTH: { + return DefWindowProc(hwnd, uMsg, wParam, lParam); + } + + // Just return, prevents DefWindowProc from messaging the window + // syncronously with other events, which may be deferred. Prevents + // random shutdown of aero composition on the window. + case WM_SYNCPAINT: + return 0; + + // This message causes QuickTime to make re-entrant calls. + // Simply discarding it doesn't seem to hurt anything. + case WM_APP - 1: + return 0; + + // We only support a query for our IAccessible or UIA pointers. + // This should be safe, and needs to be sync. +#if defined(ACCESSIBILITY) + case WM_GETOBJECT: { + LONG objId = static_cast<LONG>(lParam); + if (objId == OBJID_CLIENT || objId == MOZOBJID_UIAROOT) { + WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp); + if (oldWndProc) { + return CallWindowProcW(oldWndProc, hwnd, uMsg, wParam, lParam); + } + } + return DefWindowProc(hwnd, uMsg, wParam, lParam); + } +#endif // ACCESSIBILITY + + default: { + // Unknown messages only are logged in debug builds and sent to + // DefWindowProc. + if (uMsg && uMsg == sAppShellGeckoMsgId) { + // Widget's registered native event callback + deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam); + } + } + } + + // No deferred message was created and we land here, this is an + // unhandled message. + if (!deferred) { + DumpNeuteredMessage(hwnd, uMsg); + return DefWindowProc(hwnd, uMsg, wParam, lParam); + } + + // Create the deferred message array if it doesn't exist already. + if (!gDeferredMessages) { + gDeferredMessages = new DeferredMessageArray(20); + } + + // Save for later. The array takes ownership of |deferred|. + gDeferredMessages->AppendElement(std::move(deferred)); + return res; +} + +} // namespace + +LRESULT CALLBACK NeuteredWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) { + WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp); + if (!oldWndProc) { + // We should really never ever get here. + NS_ERROR("No old wndproc!"); + return DefWindowProc(hwnd, uMsg, wParam, lParam); + } + + // See if we care about this message. We may either ignore it, send it to + // DefWindowProc, or defer it for later. + return ProcessOrDeferMessage(hwnd, uMsg, wParam, lParam); +} + +namespace { + +static bool WindowIsDeferredWindow(HWND hWnd) { + if (!IsWindow(hWnd)) { + NS_WARNING("Window has died!"); + return false; + } + + char16_t buffer[256] = {0}; + int length = GetClassNameW(hWnd, (wchar_t*)buffer, sizeof(buffer) - 1); + if (length <= 0) { + NS_WARNING("Failed to get class name!"); + return false; + } + +#if defined(ACCESSIBILITY) + // Tab content creates a window that responds to accessible WM_GETOBJECT + // calls. This window can safely be ignored. + if (::GetPropW(hWnd, kPropNameTabContent)) { + return false; + } +#endif + + // Common mozilla windows we must defer messages to. + nsDependentString className(buffer, length); + if (StringBeginsWith(className, u"Mozilla"_ns) || + StringBeginsWith(className, u"Gecko"_ns) || + className.EqualsLiteral("nsToolkitClass") || + className.EqualsLiteral("nsAppShell:EventWindowClass")) { + return true; + } + + return false; +} + +bool NeuterWindowProcedure(HWND hWnd) { + if (!WindowIsDeferredWindow(hWnd)) { + // Some other kind of window, skip. + return false; + } + + NS_ASSERTION(!GetProp(hWnd, kOldWndProcProp), "This should always be null!"); + + // It's possible to get nullptr out of SetWindowLongPtr, and the only way to + // know if that's a valid old value is to use GetLastError. Clear the error + // here so we can tell. + SetLastError(ERROR_SUCCESS); + + LONG_PTR currentWndProc = + SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)NeuteredWindowProc); + if (!currentWndProc) { + if (ERROR_SUCCESS == GetLastError()) { + // No error, so we set something and must therefore reset it. + SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc); + } + return false; + } + + NS_ASSERTION(currentWndProc != (LONG_PTR)NeuteredWindowProc, + "This shouldn't be possible!"); + + if (!SetProp(hWnd, kOldWndProcProp, (HANDLE)currentWndProc)) { + // Cleanup + NS_WARNING("SetProp failed!"); + SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc); + RemovePropW(hWnd, kOldWndProcProp); + return false; + } + + return true; +} + +void RestoreWindowProcedure(HWND hWnd) { + NS_ASSERTION(WindowIsDeferredWindow(hWnd), + "Not a deferred window, this shouldn't be in our list!"); + LONG_PTR oldWndProc = (LONG_PTR)GetProp(hWnd, kOldWndProcProp); + if (oldWndProc) { + NS_ASSERTION(oldWndProc != (LONG_PTR)NeuteredWindowProc, + "This shouldn't be possible!"); + + DebugOnly<LONG_PTR> currentWndProc = + SetWindowLongPtr(hWnd, GWLP_WNDPROC, oldWndProc); + NS_ASSERTION(currentWndProc == (LONG_PTR)NeuteredWindowProc, + "This should never be switched out from under us!"); + } + RemovePropW(hWnd, kOldWndProcProp); +} + +LRESULT CALLBACK CallWindowProcedureHook(int nCode, WPARAM wParam, + LPARAM lParam) { + if (nCode >= 0) { + NS_ASSERTION(gNeuteredWindows, "This should never be null!"); + + HWND hWnd = reinterpret_cast<CWPSTRUCT*>(lParam)->hwnd; + + if (!gNeuteredWindows->Contains(hWnd) && + !SuppressedNeuteringRegion::IsNeuteringSuppressed() && + NeuterWindowProcedure(hWnd)) { + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + gNeuteredWindows->AppendElement(hWnd); + } + } + return CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +inline void AssertWindowIsNotNeutered(HWND hWnd) { +#ifdef DEBUG + // Make sure our neutered window hook isn't still in place. + LONG_PTR wndproc = GetWindowLongPtr(hWnd, GWLP_WNDPROC); + NS_ASSERTION(wndproc != (LONG_PTR)NeuteredWindowProc, "Window is neutered!"); +#endif +} + +void UnhookNeuteredWindows() { + if (!gNeuteredWindows) return; + uint32_t count = gNeuteredWindows->Length(); + for (uint32_t index = 0; index < count; index++) { + RestoreWindowProcedure(gNeuteredWindows->ElementAt(index)); + } + gNeuteredWindows->Clear(); +} + +// This timeout stuff assumes a sane value of mTimeoutMs (less than the overflow +// value for GetTickCount(), which is something like 50 days). It uses the +// cheapest (and least accurate) method supported by Windows 2000. + +struct TimeoutData { + DWORD startTicks; + DWORD targetTicks; +}; + +void InitTimeoutData(TimeoutData* aData, int32_t aTimeoutMs) { + aData->startTicks = GetTickCount(); + if (!aData->startTicks) { + // How unlikely is this! + aData->startTicks++; + } + aData->targetTicks = aData->startTicks + aTimeoutMs; +} + +bool TimeoutHasExpired(const TimeoutData& aData) { + if (!aData.startTicks) { + return false; + } + + DWORD now = GetTickCount(); + + if (aData.targetTicks < aData.startTicks) { + // Overflow + return now < aData.startTicks && now >= aData.targetTicks; + } + return now >= aData.targetTicks; +} + +} // namespace + +namespace mozilla { +namespace ipc { +namespace windows { + +void InitUIThread() { + if (!XRE_UseNativeEventProcessing()) { + return; + } + // If we aren't setup before a call to NotifyWorkerThread, we'll hang + // on startup. + if (!gUIThreadId) { + gUIThreadId = GetCurrentThreadId(); + } + + MOZ_ASSERT(gUIThreadId); + MOZ_ASSERT(gUIThreadId == GetCurrentThreadId(), + "Called InitUIThread multiple times on different threads!"); + + if (!gWinEventHook && !mscom::IsCurrentThreadMTA()) { + gWinEventHook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY, + NULL, &WinEventHook, GetCurrentProcessId(), + gUIThreadId, WINEVENT_OUTOFCONTEXT); + MOZ_ASSERT(gWinEventHook); + + // We need to execute this after setting the hook in case the OLE window + // already existed. + gCOMWindow = FindCOMWindow(); + } +} + +} // namespace windows +} // namespace ipc +} // namespace mozilla + +// See SpinInternalEventLoop below +MessageChannel::SyncStackFrame::SyncStackFrame(MessageChannel* channel) + : mSpinNestedEvents(false), + mListenerNotified(false), + mChannel(channel), + mPrev(mChannel->mTopFrame), + mStaticPrev(sStaticTopFrame) { + // Only track stack frames when Windows message deferral behavior + // is request for the channel. + if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) { + return; + } + + mChannel->mTopFrame = this; + sStaticTopFrame = this; + + if (!mStaticPrev) { + NS_ASSERTION(!gNeuteredWindows, "Should only set this once!"); + gNeuteredWindows = new AutoTArray<HWND, 20>(); + } +} + +MessageChannel::SyncStackFrame::~SyncStackFrame() { + if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) { + return; + } + + NS_ASSERTION(this == mChannel->mTopFrame, + "Mismatched interrupt stack frames"); + NS_ASSERTION(this == sStaticTopFrame, + "Mismatched static Interrupt stack frames"); + + mChannel->mTopFrame = mPrev; + sStaticTopFrame = mStaticPrev; + + if (!mStaticPrev) { + NS_ASSERTION(gNeuteredWindows, "Bad pointer!"); + gNeuteredWindows = nullptr; + } +} + +MessageChannel::SyncStackFrame* MessageChannel::sStaticTopFrame; + +// nsAppShell's notification that gecko events are being processed. +// If we are here and there is an Interrupt Incall active, we are spinning +// a nested gecko event loop. In which case the remote process needs +// to know about it. +void /* static */ +MessageChannel::NotifyGeckoEventDispatch() { + // sStaticTopFrame is only valid for Interrupt channels + if (!sStaticTopFrame || sStaticTopFrame->mListenerNotified) return; + + sStaticTopFrame->mListenerNotified = true; + MessageChannel* channel = + static_cast<MessageChannel*>(sStaticTopFrame->mChannel); + channel->Listener()->ProcessRemoteNativeEventsInInterruptCall(); +} + +// invoked by the module that receives the spin event loop +// message. +void MessageChannel::ProcessNativeEventsInInterruptCall() { + NS_ASSERTION(GetCurrentThreadId() == gUIThreadId, + "Shouldn't be on a non-main thread in here!"); + if (!mTopFrame) { + NS_ERROR("Spin logic error: no Interrupt frame"); + return; + } + + mTopFrame->mSpinNestedEvents = true; +} + +// Spin loop is called in place of WaitFor*Notify when modal ui is being shown +// in a child. There are some intricacies in using it however. Spin loop is +// enabled for a particular Interrupt frame by the client calling +// MessageChannel::ProcessNativeEventsInInterrupt(). +// This call can be nested for multiple Interrupt frames in a single plugin or +// multiple unrelated plugins. +void MessageChannel::SpinInternalEventLoop() { + if (mozilla::PaintTracker::IsPainting()) { + MOZ_CRASH("Don't spin an event loop while painting."); + } + + NS_ASSERTION(mTopFrame && mTopFrame->mSpinNestedEvents, + "Spinning incorrectly"); + + // Nested windows event loop we trigger when the child enters into modal + // event loops. + + // Note, when we return, we always reset the notify worker event. So there's + // no need to reset it on return here. + + do { + MSG msg = {0}; + + // Don't get wrapped up in here if the child connection dies. + { + MonitorAutoLock lock(*mMonitor); + if (!Connected()) { + return; + } + } + + // Retrieve window or thread messages + if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { + // The child UI should have been destroyed before the app is closed, in + // which case, we should never get this here. + if (msg.message == WM_QUIT) { + NS_ERROR("WM_QUIT received in SpinInternalEventLoop!"); + } else { + TranslateMessage(&msg); + ::DispatchMessageW(&msg); + return; + } + } + + // Note, give dispatching windows events priority over checking if + // mEvent is signaled, otherwise heavy ipc traffic can cause jittery + // playback of video. We'll exit out on each disaptch above, so ipc + // won't get starved. + + // Wait for UI events or a signal from the io thread. + DWORD result = + MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, QS_ALLINPUT); + if (result == WAIT_OBJECT_0) { + // Our NotifyWorkerThread event was signaled + return; + } + } while (true); +} + +static HHOOK gWindowHook; + +static inline void StartNeutering() { + if (!gUIThreadId) { + mozilla::ipc::windows::InitUIThread(); + } + MOZ_ASSERT(gUIThreadId); + MOZ_ASSERT(!gWindowHook); + NS_ASSERTION(!MessageChannel::IsPumpingMessages(), + "Shouldn't be pumping already!"); + MessageChannel::SetIsPumpingMessages(true); + gWindowHook = ::SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook, + nullptr, gUIThreadId); + NS_ASSERTION(gWindowHook, "Failed to set hook!"); +} + +static void StopNeutering() { + MOZ_ASSERT(MessageChannel::IsPumpingMessages()); + ::UnhookWindowsHookEx(gWindowHook); + gWindowHook = NULL; + ::UnhookNeuteredWindows(); + // Before returning we need to set a hook to run any deferred messages that + // we received during the IPC call. The hook will unset itself as soon as + // someone else calls GetMessage, PeekMessage, or runs code that generates + // a "nonqueued" message. + ::ScheduleDeferredMessageRun(); + MessageChannel::SetIsPumpingMessages(false); +} + +NeuteredWindowRegion::NeuteredWindowRegion(bool aDoNeuter) + : mNeuteredByThis(!gWindowHook && aDoNeuter && + XRE_UseNativeEventProcessing()) { + if (mNeuteredByThis) { + StartNeutering(); + } +} + +NeuteredWindowRegion::~NeuteredWindowRegion() { + if (gWindowHook && mNeuteredByThis) { + StopNeutering(); + } +} + +void NeuteredWindowRegion::PumpOnce() { + if (!gWindowHook) { + // This should be a no-op if nothing has been neutered. + return; + } + + MSG msg = {0}; + // Pump any COM messages so that we don't hang due to STA marshaling. + if (gCOMWindow && ::PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) { + ::TranslateMessage(&msg); + ::DispatchMessageW(&msg); + } + // Expunge any nonqueued messages on the current thread. + ::PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE); +} + +DeneuteredWindowRegion::DeneuteredWindowRegion() + : mReneuter(gWindowHook != NULL) { + if (mReneuter) { + StopNeutering(); + } +} + +DeneuteredWindowRegion::~DeneuteredWindowRegion() { + if (mReneuter) { + StartNeutering(); + } +} + +SuppressedNeuteringRegion::SuppressedNeuteringRegion() + : mReenable(::gUIThreadId == ::GetCurrentThreadId() && ::gWindowHook) { + if (mReenable) { + MOZ_ASSERT(!sSuppressNeutering); + sSuppressNeutering = true; + } +} + +SuppressedNeuteringRegion::~SuppressedNeuteringRegion() { + if (mReenable) { + MOZ_ASSERT(sSuppressNeutering); + sSuppressNeutering = false; + } +} + +bool SuppressedNeuteringRegion::sSuppressNeutering = false; + +bool MessageChannel::WaitForSyncNotify() { + mMonitor->AssertCurrentThreadOwns(); + + if (!gUIThreadId) { + mozilla::ipc::windows::InitUIThread(); + } + + // Use a blocking wait if this channel does not require + // Windows message deferral behavior. + if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) { + TimeDuration timeout = (kNoTimeout == mTimeoutMs) + ? TimeDuration::Forever() + : TimeDuration::FromMilliseconds(mTimeoutMs); + + MOZ_ASSERT(!mIsSyncWaitingOnNonMainThread); + mIsSyncWaitingOnNonMainThread = true; + + CVStatus status = mMonitor->Wait(timeout); + + MOZ_ASSERT(mIsSyncWaitingOnNonMainThread); + mIsSyncWaitingOnNonMainThread = false; + + // If the timeout didn't expire, we know we received an event. The + // converse is not true. + return WaitResponse(status == CVStatus::Timeout); + } + + NS_ASSERTION( + mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION, + "Shouldn't be here for channels that don't use message deferral!"); + NS_ASSERTION(mTopFrame, "No top frame!"); + + MonitorAutoUnlock unlock(*mMonitor); + + bool timedout = false; + + UINT_PTR timerId = 0; + TimeoutData timeoutData = {0}; + + if (mTimeoutMs != kNoTimeout) { + InitTimeoutData(&timeoutData, mTimeoutMs); + + // We only do this to ensure that we won't get stuck in + // MsgWaitForMultipleObjects below. + timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr); + NS_ASSERTION(timerId, "SetTimer failed!"); + } + + NeuteredWindowRegion neuteredRgn(true); + + { + while (1) { + MSG msg = {0}; + // Don't get wrapped up in here if the child connection dies. + { + MonitorAutoLock lock(*mMonitor); + if (!Connected()) { + break; + } + } + + // Wait until we have a message in the queue. MSDN docs are a bit unclear + // but it seems that windows from two different threads (and it should be + // noted that a thread in another process counts as a "different thread") + // will implicitly have their message queues attached if they are parented + // to one another. This wait call, then, will return for a message + // delivered to *either* thread. + DWORD result = + MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, QS_ALLINPUT); + if (result == WAIT_OBJECT_0) { + // Our NotifyWorkerThread event was signaled + BOOL success = ResetEvent(mEvent); + if (!success) { + gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle) + << "WindowsMessageChannel::WaitForSyncNotify failed to reset " + "event. GetLastError: " + << GetLastError(); + } + break; + } else if (result != (WAIT_OBJECT_0 + 1)) { + NS_ERROR("Wait failed!"); + break; + } + + if (TimeoutHasExpired(timeoutData)) { + // A timeout was specified and we've passed it. Break out. + timedout = true; + break; + } + + // The only way to know on which thread the message was delivered is to + // use some logic on the return values of GetQueueStatus and PeekMessage. + // PeekMessage will return false if there are no "queued" messages, but it + // will run all "nonqueued" messages before returning. So if PeekMessage + // returns false and there are no "nonqueued" messages that were run then + // we know that the message we woke for was intended for a window on + // another thread. + bool haveSentMessagesPending = + (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0; + + // Either of the PeekMessage calls below will actually process all + // "nonqueued" messages that are pending before returning. If we have + // "nonqueued" messages pending then we should have switched out all the + // window procedures above. In that case this PeekMessage call won't + // actually cause any mozilla code (or plugin code) to run. + + // We have to manually pump all COM messages *after* looking at the queue + // queue status but before yielding our thread below. + if (gCOMWindow) { + if (PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + ::DispatchMessageW(&msg); + } + } + + // If the following PeekMessage call fails to return a message for us (and + // returns false) and we didn't run any "nonqueued" messages then we must + // have woken up for a message designated for a window in another thread. + // If we loop immediately then we could enter a tight loop, so we'll give + // up our time slice here to let the child process its message. + if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) && + !haveSentMessagesPending) { + // Message was for child, we should wait a bit. + SwitchToThread(); + } + } + } + + if (timerId) { + KillTimer(nullptr, timerId); + timerId = 0; + } + + return WaitResponse(timedout); +} + +void MessageChannel::NotifyWorkerThread() { + mMonitor->AssertCurrentThreadOwns(); + + if (mIsSyncWaitingOnNonMainThread) { + mMonitor->Notify(); + return; + } + + MOZ_RELEASE_ASSERT(mEvent, "No signal event to set, this is really bad!"); + if (!SetEvent(mEvent)) { + NS_WARNING("Failed to set NotifyWorkerThread event!"); + gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle) + << "WindowsMessageChannel failed to SetEvent. GetLastError: " + << GetLastError(); + } +} + +void DeferredSendMessage::Run() { + AssertWindowIsNotNeutered(hWnd); + if (!IsWindow(hWnd)) { + NS_ERROR("Invalid window!"); + return; + } + + WNDPROC wndproc = + reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + if (!wndproc) { + NS_ERROR("Invalid window procedure!"); + return; + } + + CallWindowProc(wndproc, hWnd, message, wParam, lParam); +} + +void DeferredRedrawMessage::Run() { + AssertWindowIsNotNeutered(hWnd); + if (!IsWindow(hWnd)) { + NS_ERROR("Invalid window!"); + return; + } + +#ifdef DEBUG + BOOL ret = +#endif + RedrawWindow(hWnd, nullptr, nullptr, flags); + NS_ASSERTION(ret, "RedrawWindow failed!"); +} + +DeferredUpdateMessage::DeferredUpdateMessage(HWND aHWnd) { + mWnd = aHWnd; + if (!GetUpdateRect(mWnd, &mUpdateRect, FALSE)) { + memset(&mUpdateRect, 0, sizeof(RECT)); + return; + } + ValidateRect(mWnd, &mUpdateRect); +} + +void DeferredUpdateMessage::Run() { + AssertWindowIsNotNeutered(mWnd); + if (!IsWindow(mWnd)) { + NS_ERROR("Invalid window!"); + return; + } + + InvalidateRect(mWnd, &mUpdateRect, FALSE); +#ifdef DEBUG + BOOL ret = +#endif + UpdateWindow(mWnd); + NS_ASSERTION(ret, "UpdateWindow failed!"); +} + +DeferredSettingChangeMessage::DeferredSettingChangeMessage(HWND aHWnd, + UINT aMessage, + WPARAM aWParam, + LPARAM aLParam) + : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) { + NS_ASSERTION(aMessage == WM_SETTINGCHANGE, "Wrong message type!"); + if (aLParam) { + lParamString = _wcsdup(reinterpret_cast<const wchar_t*>(aLParam)); + lParam = reinterpret_cast<LPARAM>(lParamString); + } else { + lParamString = nullptr; + lParam = 0; + } +} + +DeferredSettingChangeMessage::~DeferredSettingChangeMessage() { + free(lParamString); +} + +DeferredWindowPosMessage::DeferredWindowPosMessage(HWND aHWnd, LPARAM aLParam, + bool aForCalcSize, + WPARAM aWParam) { + if (aForCalcSize) { + if (aWParam) { + NCCALCSIZE_PARAMS* arg = reinterpret_cast<NCCALCSIZE_PARAMS*>(aLParam); + memcpy(&windowPos, arg->lppos, sizeof(windowPos)); + + NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!"); + } else { + RECT* arg = reinterpret_cast<RECT*>(aLParam); + windowPos.hwnd = aHWnd; + windowPos.hwndInsertAfter = nullptr; + windowPos.x = arg->left; + windowPos.y = arg->top; + windowPos.cx = arg->right - arg->left; + windowPos.cy = arg->bottom - arg->top; + + NS_ASSERTION(arg->right >= arg->left && arg->bottom >= arg->top, + "Negative width or height!"); + } + windowPos.flags = SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOOWNERZORDER | + SWP_NOZORDER | SWP_DEFERERASE | SWP_NOSENDCHANGING; + } else { + // Not for WM_NCCALCSIZE + WINDOWPOS* arg = reinterpret_cast<WINDOWPOS*>(aLParam); + memcpy(&windowPos, arg, sizeof(windowPos)); + + NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!"); + + // Windows sends in some private flags sometimes that we can't simply copy. + // Filter here. + UINT mask = SWP_ASYNCWINDOWPOS | SWP_DEFERERASE | SWP_DRAWFRAME | + SWP_FRAMECHANGED | SWP_HIDEWINDOW | SWP_NOACTIVATE | + SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW | + SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE | + SWP_NOZORDER | SWP_SHOWWINDOW; + windowPos.flags &= mask; + } +} + +void DeferredWindowPosMessage::Run() { + AssertWindowIsNotNeutered(windowPos.hwnd); + if (!IsWindow(windowPos.hwnd)) { + NS_ERROR("Invalid window!"); + return; + } + + if (!IsWindow(windowPos.hwndInsertAfter)) { + NS_WARNING("ZOrder change cannot be honored"); + windowPos.hwndInsertAfter = 0; + windowPos.flags |= SWP_NOZORDER; + } + +#ifdef DEBUG + BOOL ret = +#endif + SetWindowPos(windowPos.hwnd, windowPos.hwndInsertAfter, windowPos.x, + windowPos.y, windowPos.cx, windowPos.cy, windowPos.flags); + NS_ASSERTION(ret, "SetWindowPos failed!"); +} + +DeferredCopyDataMessage::DeferredCopyDataMessage(HWND aHWnd, UINT aMessage, + WPARAM aWParam, LPARAM aLParam) + : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) { + NS_ASSERTION(IsWindow(reinterpret_cast<HWND>(aWParam)), "Bad window!"); + + COPYDATASTRUCT* source = reinterpret_cast<COPYDATASTRUCT*>(aLParam); + NS_ASSERTION(source, "Should never be null!"); + + copyData.dwData = source->dwData; + copyData.cbData = source->cbData; + + if (source->cbData) { + copyData.lpData = malloc(source->cbData); + if (copyData.lpData) { + memcpy(copyData.lpData, source->lpData, source->cbData); + } else { + NS_ERROR("Out of memory?!"); + copyData.cbData = 0; + } + } else { + copyData.lpData = nullptr; + } + + lParam = reinterpret_cast<LPARAM>(©Data); +} + +DeferredCopyDataMessage::~DeferredCopyDataMessage() { free(copyData.lpData); } + +DeferredStyleChangeMessage::DeferredStyleChangeMessage(HWND aHWnd, + WPARAM aWParam, + LPARAM aLParam) + : hWnd(aHWnd) { + index = static_cast<int>(aWParam); + style = reinterpret_cast<STYLESTRUCT*>(aLParam)->styleNew; +} + +void DeferredStyleChangeMessage::Run() { SetWindowLongPtr(hWnd, index, style); } + +DeferredSetIconMessage::DeferredSetIconMessage(HWND aHWnd, UINT aMessage, + WPARAM aWParam, LPARAM aLParam) + : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) { + NS_ASSERTION(aMessage == WM_SETICON, "Wrong message type!"); +} + +void DeferredSetIconMessage::Run() { + AssertWindowIsNotNeutered(hWnd); + if (!IsWindow(hWnd)) { + NS_ERROR("Invalid window!"); + return; + } + + WNDPROC wndproc = + reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); + if (!wndproc) { + NS_ERROR("Invalid window procedure!"); + return; + } + + HICON hOld = reinterpret_cast<HICON>( + CallWindowProc(wndproc, hWnd, message, wParam, lParam)); + if (hOld) { + DestroyIcon(hOld); + } +} diff --git a/ipc/glue/WindowsMessageLoop.h b/ipc/glue/WindowsMessageLoop.h new file mode 100644 index 0000000000..7fa614279b --- /dev/null +++ b/ipc/glue/WindowsMessageLoop.h @@ -0,0 +1,134 @@ +/* -*- 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/. */ + +#ifndef IPC_GLUE_WINDOWSMESSAGELOOP_H +#define IPC_GLUE_WINDOWSMESSAGELOOP_H + +// This file is only meant to compile on windows +#include <windows.h> + +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace ipc { +namespace windows { + +void InitUIThread(); + +class DeferredMessage { + public: + MOZ_COUNTED_DEFAULT_CTOR(DeferredMessage) + + MOZ_COUNTED_DTOR_VIRTUAL(DeferredMessage) + + virtual void Run() = 0; +}; + +// Uses CallWndProc to deliver a message at a later time. Messages faked with +// this class must not have pointers in their wParam or lParam members as they +// may be invalid by the time the message actually runs. +class DeferredSendMessage : public DeferredMessage { + public: + DeferredSendMessage(HWND aHWnd, UINT aMessage, WPARAM aWParam, LPARAM aLParam) + : hWnd(aHWnd), message(aMessage), wParam(aWParam), lParam(aLParam) {} + + virtual void Run(); + + protected: + HWND hWnd; + UINT message; + WPARAM wParam; + LPARAM lParam; +}; + +// Uses RedrawWindow to fake several painting-related messages. Flags passed +// to the constructor go directly to RedrawWindow. +class DeferredRedrawMessage : public DeferredMessage { + public: + DeferredRedrawMessage(HWND aHWnd, UINT aFlags) : hWnd(aHWnd), flags(aFlags) {} + + virtual void Run(); + + private: + HWND hWnd; + UINT flags; +}; + +// Uses UpdateWindow to generate a WM_PAINT message if needed. +class DeferredUpdateMessage : public DeferredMessage { + public: + explicit DeferredUpdateMessage(HWND aHWnd); + + virtual void Run(); + + private: + HWND mWnd; + RECT mUpdateRect; +}; + +// This class duplicates a string that may exist in the lParam member of the +// message. +class DeferredSettingChangeMessage : public DeferredSendMessage { + public: + DeferredSettingChangeMessage(HWND aHWnd, UINT aMessage, WPARAM aWParam, + LPARAM aLParam); + + ~DeferredSettingChangeMessage(); + + private: + wchar_t* lParamString; +}; + +// This class uses SetWindowPos to fake various size-related messages. Flags +// passed to the constructor go straight through to SetWindowPos. +class DeferredWindowPosMessage : public DeferredMessage { + public: + DeferredWindowPosMessage(HWND aHWnd, LPARAM aLParam, + bool aForCalcSize = false, WPARAM aWParam = 0); + + virtual void Run(); + + private: + WINDOWPOS windowPos; +}; + +// This class duplicates a data buffer for a WM_COPYDATA message. +class DeferredCopyDataMessage : public DeferredSendMessage { + public: + DeferredCopyDataMessage(HWND aHWnd, UINT aMessage, WPARAM aWParam, + LPARAM aLParam); + + ~DeferredCopyDataMessage(); + + private: + COPYDATASTRUCT copyData; +}; + +class DeferredStyleChangeMessage : public DeferredMessage { + public: + DeferredStyleChangeMessage(HWND aHWnd, WPARAM aWParam, LPARAM aLParam); + + virtual void Run(); + + private: + HWND hWnd; + int index; + LONG_PTR style; +}; + +class DeferredSetIconMessage : public DeferredSendMessage { + public: + DeferredSetIconMessage(HWND aHWnd, UINT aMessage, WPARAM aWParam, + LPARAM aLParam); + + virtual void Run(); +}; + +} /* namespace windows */ +} /* namespace ipc */ +} /* namespace mozilla */ + +#endif /* IPC_GLUE_WINDOWSMESSAGELOOP_H */ diff --git a/ipc/glue/components.conf b/ipc/glue/components.conf new file mode 100644 index 0000000000..34048dbbfa --- /dev/null +++ b/ipc/glue/components.conf @@ -0,0 +1,22 @@ +# -*- 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/. + +Headers = [ + 'mozilla/ipc/ForkServer.h', +] + +Classes = [ + { + 'cid': '{cdb4757f-f51b-40c0-8b38-66d12c3bff7b}', + 'contract_ids': ['@mozilla.org/fork-server-launcher;1'], + 'singleton': True, + 'type': 'mozilla::ipc::ForkServerLauncher', + 'headers': ['mozilla/ipc/ForkServiceChild.h'], + 'constructor': 'mozilla::ipc::ForkServerLauncher::Create', + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + 'categories': {'xpcom-startup': 'Fork Server Launcher'}, + }, +] diff --git a/ipc/glue/moz.build b/ipc/glue/moz.build new file mode 100644 index 0000000000..bb23b29a45 --- /dev/null +++ b/ipc/glue/moz.build @@ -0,0 +1,313 @@ +# -*- 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/. +include("/dom/media/webrtc/third_party_build/webrtc.mozbuild") + +EXPORTS += [ + "nsIIPCSerializableInputStream.h", +] + +EXPORTS.mozilla.ipc += [ + "AsyncBlockers.h", + "BackgroundChild.h", + "BackgroundParent.h", + "BackgroundStarterChild.h", + "BackgroundStarterParent.h", + "BackgroundUtils.h", + "BigBuffer.h", + "BrowserProcessSubThread.h", + "ByteBuf.h", + "ByteBufUtils.h", + "CrashReporterClient.h", + "CrashReporterHelper.h", + "CrashReporterHost.h", + "CrossProcessMutex.h", + "CrossProcessSemaphore.h", + "DataPipe.h", + "Endpoint.h", + "EnvironmentMap.h", + "FileDescriptor.h", + "FileDescriptorUtils.h", + "GeckoChildProcessHost.h", + "IdleSchedulerChild.h", + "IdleSchedulerParent.h", + "InputStreamUtils.h", + "IOThreadChild.h", + "IPCCore.h", + "IPCForwards.h", + "IPCStreamUtils.h", + "IPCTypes.h", + "IPDLParamTraits.h", + "IPDLStructMember.h", + "LaunchError.h", + "MessageChannel.h", + "MessageLink.h", + "MessagePump.h", + "Neutering.h", + "NodeChannel.h", + "NodeController.h", + "ProcessChild.h", + "ProcessUtils.h", + "ProtocolMessageUtils.h", + "ProtocolUtils.h", + "RandomAccessStreamUtils.h", + "RawShmem.h", + "ScopedPort.h", + "SerializedStructuredCloneBuffer.h", + "SharedMemory.h", + "SharedMemoryBasic.h", + "Shmem.h", + "ShmemMessageUtils.h", + "SideVariant.h", + "TaintingIPCUtils.h", + "TaskFactory.h", + "ToplevelActorHolder.h", + "TransportSecurityInfoUtils.h", + "URIUtils.h", + "UtilityAudioDecoder.h", + "UtilityAudioDecoderChild.h", + "UtilityAudioDecoderParent.h", + "UtilityProcessChild.h", + "UtilityProcessHost.h", + "UtilityProcessImpl.h", + "UtilityProcessManager.h", + "UtilityProcessParent.h", + "UtilityProcessSandboxing.h", + "WindowsMessageLoop.h", +] + +if CONFIG["OS_ARCH"] == "WINNT": + SOURCES += [ + "SharedMemory_windows.cpp", + "WindowsMessageLoop.cpp", + ] +else: + UNIFIED_SOURCES += [ + "SharedMemory_posix.cpp", + ] + +if CONFIG["OS_ARCH"] == "WINNT": + SOURCES += [ + "CrossProcessMutex_windows.cpp", + ] +elif not CONFIG["OS_ARCH"] in ("NetBSD", "OpenBSD"): + UNIFIED_SOURCES += [ + "CrossProcessMutex_posix.cpp", + ] +else: + UNIFIED_SOURCES += [ + "CrossProcessMutex_unimplemented.cpp", + ] + +if CONFIG["OS_ARCH"] == "WINNT": + SOURCES += [ + "CrossProcessSemaphore_windows.cpp", + ] +elif CONFIG["OS_ARCH"] == "Darwin": + SOURCES += [ + "CrossProcessSemaphore_mach.cpp", + ] +else: + UNIFIED_SOURCES += [ + "CrossProcessSemaphore_posix.cpp", + ] + +if CONFIG["OS_ARCH"] == "Darwin": + EXPORTS.mozilla.ipc += ["SharedMemoryBasic_mach.h"] + SOURCES += [ + "SharedMemoryBasic_mach.mm", + ] +else: + EXPORTS.mozilla.ipc += ["SharedMemoryBasic_chromium.h"] + +if CONFIG["OS_ARCH"] == "Linux": + UNIFIED_SOURCES += [ + "ProcessUtils_linux.cpp", + "SetProcessTitle.cpp", + ] + EXPORTS.mozilla.ipc += [ + "SetProcessTitle.h", + ] +elif CONFIG["OS_ARCH"] in ("DragonFly", "FreeBSD", "NetBSD", "OpenBSD"): + UNIFIED_SOURCES += [ + "ProcessUtils_bsd.cpp", + "SetProcessTitle.cpp", + ] + EXPORTS.mozilla.ipc += [ + "SetProcessTitle.h", + ] +elif CONFIG["OS_ARCH"] == "Darwin": + UNIFIED_SOURCES += ["ProcessUtils_mac.mm"] +else: + UNIFIED_SOURCES += [ + "ProcessUtils_none.cpp", + ] + +if CONFIG["OS_ARCH"] != "WINNT": + EXPORTS.mozilla.ipc += [ + "FileDescriptorShuffle.h", + ] + UNIFIED_SOURCES += [ + "FileDescriptorShuffle.cpp", + ] + +EXPORTS.ipc += [ + "EnumSerializer.h", + "IPCMessageUtils.h", + "IPCMessageUtilsSpecializations.h", +] + +UNIFIED_SOURCES += [ + "BackgroundImpl.cpp", + "BackgroundUtils.cpp", + "BigBuffer.cpp", + "BrowserProcessSubThread.cpp", + "CrashReporterClient.cpp", + "CrashReporterHost.cpp", + "DataPipe.cpp", + "Endpoint.cpp", + "FileDescriptor.cpp", + "FileDescriptorUtils.cpp", + "IdleSchedulerChild.cpp", + "IdleSchedulerParent.cpp", + "InputStreamUtils.cpp", + "IPCMessageUtilsSpecializations.cpp", + "IPCStreamUtils.cpp", + "LaunchError.cpp", + "MessageChannel.cpp", + "MessageLink.cpp", + "MessagePump.cpp", + "NodeChannel.cpp", + "NodeController.cpp", + "ProcessChild.cpp", + "ProcessUtils_common.cpp", + "ProtocolUtils.cpp", + "RandomAccessStreamUtils.cpp", + "RawShmem.cpp", + "ScopedPort.cpp", + "SerializedStructuredCloneBuffer.cpp", + "SharedMemory.cpp", + "Shmem.cpp", + "StringUtil.cpp", + "TransportSecurityInfoUtils.cpp", + "URIUtils.cpp", + "UtilityAudioDecoder.cpp", + "UtilityAudioDecoderChild.cpp", + "UtilityAudioDecoderParent.cpp", + "UtilityProcessChild.cpp", + "UtilityProcessHost.cpp", + "UtilityProcessImpl.cpp", + "UtilityProcessManager.cpp", + "UtilityProcessParent.cpp", + "UtilityProcessSandboxing.cpp", +] + +SOURCES += [ + "BackgroundChildImpl.cpp", + "BackgroundParentImpl.cpp", +] + +if CONFIG["OS_ARCH"] == "Darwin": + # GeckoChildProcessHost.cpp cannot be built unified due to OSX header + # clashes with TextRange. + SOURCES += [ + "GeckoChildProcessHost.cpp", + ] +else: + UNIFIED_SOURCES += [ + "GeckoChildProcessHost.cpp", + ] + +if CONFIG["OS_ARCH"] == "WINNT": + UNIFIED_SOURCES += ["MessagePump_windows.cpp"] +elif CONFIG["OS_ARCH"] == "Darwin": + UNIFIED_SOURCES += ["MessagePump_mac.mm"] +elif CONFIG["OS_TARGET"] == "Android": + UNIFIED_SOURCES += ["MessagePump_android.cpp"] + +LOCAL_INCLUDES += [ + "/caps", + "/dom/broadcastchannel", + "/dom/indexedDB", + "/dom/storage", + "/netwerk/base", + "/third_party/libwebrtc", + "/third_party/libwebrtc/third_party/abseil-cpp", + "/tools/fuzzing/ipc", + "/xpcom/build", +] + +PREPROCESSED_IPDL_SOURCES = [ + "PUtilityAudioDecoder.ipdl", + "PUtilityProcess.ipdl", +] + +IPDL_SOURCES = [ + "InputStreamParams.ipdlh", + "IPCStream.ipdlh", + "PBackground.ipdl", + "PBackgroundSharedTypes.ipdlh", + "PBackgroundStarter.ipdl", + "PBackgroundTest.ipdl", + "PIdleScheduler.ipdl", + "ProtocolTypes.ipdlh", + "RandomAccessStreamParams.ipdlh", + "URIParams.ipdlh", +] + +if CONFIG["MOZ_ENABLE_FORKSERVER"]: + EXPORTS.mozilla.ipc += [ + "ForkServer.h", + "ForkServiceChild.h", + "MiniTransceiver.h", + ] + UNIFIED_SOURCES += [ + "ForkServer.cpp", + "ForkServiceChild.cpp", + "MiniTransceiver.cpp", + ] + XPCOM_MANIFESTS += [ + "components.conf", + ] + +LOCAL_INCLUDES += [ + "/dom/ipc", + "/toolkit/crashreporter", + "/toolkit/xre", + "/xpcom/base", + "/xpcom/threads", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +if CONFIG["OS_ARCH"] == "Darwin": + OS_LIBS += ["bsm"] # for audit_token_to_pid + +for var in ( + "MOZ_CHILD_PROCESS_NAME", + "MOZ_CHILD_PROCESS_BUNDLENAME", + "MOZ_EME_PROCESS_NAME_BRANDED", + "MOZ_EME_PROCESS_BUNDLENAME", +): + DEFINES[var] = '"%s"' % CONFIG[var] + +if CONFIG["MOZ_SANDBOX"] and CONFIG["OS_ARCH"] == "WINNT": + LOCAL_INCLUDES += [ + "/security/sandbox/chromium", + "/security/sandbox/chromium-shim", + "/security/sandbox/win/src/sandboxbroker", + ] + +if CONFIG["ENABLE_TESTS"]: + DIRS += [ + "test/gtest", + "test/utility_process_xpcom", + "test/browser", + ] + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") diff --git a/ipc/glue/nsIIPCSerializableInputStream.h b/ipc/glue/nsIIPCSerializableInputStream.h new file mode 100644 index 0000000000..acc7594f66 --- /dev/null +++ b/ipc/glue/nsIIPCSerializableInputStream.h @@ -0,0 +1,104 @@ +/* -*- 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/. */ + +#ifndef mozilla_ipc_nsIIPCSerializableInputStream_h +#define mozilla_ipc_nsIIPCSerializableInputStream_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "nsISupports.h" +#include "nsTArrayForwardDeclare.h" + +namespace mozilla { +namespace ipc { + +class FileDescriptor; +class InputStreamParams; + +} // namespace ipc + +} // namespace mozilla + +#define NS_IIPCSERIALIZABLEINPUTSTREAM_IID \ + { \ + 0xb0211b14, 0xea6d, 0x40d4, { \ + 0x87, 0xb5, 0x7b, 0xe3, 0xdf, 0xac, 0x09, 0xd1 \ + } \ + } + +class NS_NO_VTABLE nsIIPCSerializableInputStream : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIPCSERIALIZABLEINPUTSTREAM_IID) + + // Determine the serialized complexity of this input stream, initializing + // `*aSizeUsed`, `*aPipes` and `*aTransferables` to the number of inline + // bytes/pipes/transferable resources which would be used. This will be used + // by other `Serialize` implementations to potentially simplify the resulting + // stream, reducing the number of pipes or file descriptors required. + // + // Each outparameter corresponds to a type of resource which will be included + // in the serialized message, as follows: + // + // *aSizeUsed: + // Raw bytes to be included inline in the message's payload, usually in the + // form of a nsCString for a StringInputStreamParams. This must be less + // than or equal to `aMaxSize`. Larger payloads should instead be + // serialized using SerializeInputStreamAsPipe. + // *aPipes: + // New pipes, created using SerializeInputStreamAsPipe, which will be used + // to asynchronously transfer part of the pipe over IPC. Callers such as + // nsMultiplexInputStream may choose to serialize themselves as a DataPipe + // if they contain DataPipes themselves, so existing DataPipe instances + // which are cheaply transferred should be counted as transferrables. + // *aTransferables: + // Existing objects which can be more cheaply transferred over IPC than by + // serializing them inline in a payload or transferring them through a new + // DataPipe. This includes RemoteLazyInputStreams, FileDescriptors, and + // existing DataPipeReceiver instances. + // + // Callers of this method must have initialized all of `*aSizeUsed`, + // `*aPipes`, and `*aTransferables` to 0, so implementations are not required + // to initialize all outparameters. The outparameters must not be null. + virtual void SerializedComplexity(uint32_t aMaxSize, uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) = 0; + + virtual void Serialize(mozilla::ipc::InputStreamParams& aParams, + uint32_t aMaxSize, uint32_t* aSizeUsed) = 0; + + virtual bool Deserialize(const mozilla::ipc::InputStreamParams& aParams) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIIPCSerializableInputStream, + NS_IIPCSERIALIZABLEINPUTSTREAM_IID) + +#define NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM \ + virtual void SerializedComplexity(uint32_t aMaxSize, uint32_t* aSizeUsed, \ + uint32_t* aPipes, \ + uint32_t* aTransferrables) override; \ + virtual void Serialize(mozilla::ipc::InputStreamParams&, uint32_t, \ + uint32_t*) override; \ + \ + virtual bool Deserialize(const mozilla::ipc::InputStreamParams&) override; + +#define NS_FORWARD_NSIIPCSERIALIZABLEINPUTSTREAM(_to) \ + virtual void SerializedComplexity(uint32_t aMaxSize, uint32_t* aSizeUsed, \ + uint32_t* aPipes, \ + uint32_t* aTransferables) override { \ + _to SerializedComplexity(aMaxSize, aSizeUsed, aPipes, aTransferables); \ + }; \ + \ + virtual void Serialize(mozilla::ipc::InputStreamParams& aParams, \ + uint32_t aMaxSize, uint32_t* aSizeUsed) override { \ + _to Serialize(aParams, aMaxSize, aSizeUsed); \ + } \ + \ + virtual bool Deserialize(const mozilla::ipc::InputStreamParams& aParams) \ + override { \ + return _to Deserialize(aParams); \ + } + +#endif // mozilla_ipc_nsIIPCSerializableInputStream_h diff --git a/ipc/glue/test/browser/browser.toml b/ipc/glue/test/browser/browser.toml new file mode 100644 index 0000000000..8f94bc130d --- /dev/null +++ b/ipc/glue/test/browser/browser.toml @@ -0,0 +1,94 @@ +[DEFAULT] +support-files = ["head.js"] +# Set this since we want to continue monitoring the disabling of pref since we +# still allow it a little bit. +environment = "MOZ_DONT_LOCK_UTILITY_PLZ_FILE_A_BUG=1" + +["browser_audio_telemetry_content.js"] +skip-if = ["os == 'win'"] # gfx blocks us because media.rdd-process.enabled=false disables PDMFactory::AllDecodersAreRemote() +support-files = [ + "head-telemetry.js", + "../../../../dom/media/test/small-shot.ogg", + "../../../../dom/media/test/small-shot.mp3", + "../../../../dom/media/test/small-shot.m4a", + "../../../../dom/media/test/small-shot.flac" +] + +["browser_audio_telemetry_rdd.js"] +support-files = [ + "head-telemetry.js", + "../../../../dom/media/test/small-shot.ogg", + "../../../../dom/media/test/small-shot.mp3", + "../../../../dom/media/test/small-shot.m4a", + "../../../../dom/media/test/small-shot.flac" +] + +["browser_audio_telemetry_utility.js"] +support-files = [ + "head-telemetry.js", + "../../../../dom/media/test/small-shot.ogg", + "../../../../dom/media/test/small-shot.mp3", + "../../../../dom/media/test/small-shot.m4a", + "../../../../dom/media/test/small-shot.flac" +] + +["browser_audio_telemetry_utility_EME.js"] +support-files = [ + "head-telemetry.js", + "../../../../dom/media/test/eme_standalone.js", + "../../../../dom/media/test/short-aac-encrypted-audio.mp4" +] + +["browser_utility_audioDecodeCrash.js"] +support-files = [ + "../../../../dom/media/test/small-shot.ogg", + "../../../../dom/media/test/small-shot.mp3", + "../../../../dom/media/test/small-shot.m4a", + "../../../../dom/media/test/small-shot.flac" +] +skip-if = [ + "!crashreporter", + "ccov", +] + +["browser_utility_crashReporter.js"] +skip-if = [ + "!crashreporter", + "ccov", +] + +["browser_utility_filepicker_crashed.js"] +run-if = ["os == 'win'"] +skip-if = [ + "!crashreporter", + "ccov", +] + +["browser_utility_geolocation_crashed.js"] +run-if = ["os == 'win'"] +skip-if = [ + "!crashreporter", + "ccov", +] + +["browser_utility_hard_kill.js"] + +["browser_utility_hard_kill_delayed.js"] # bug 1754572: we really want hard_kill to be rust before hard_kill_delayed + +["browser_utility_memoryReport.js"] +skip-if = ["tsan"] # bug 1754554 + +["browser_utility_multipleAudio.js"] +support-files = [ + "../../../../dom/media/test/small-shot.ogg", + "../../../../dom/media/test/small-shot.mp3", + "../../../../dom/media/test/small-shot.m4a", + "../../../../dom/media/test/small-shot.flac", + "head-multiple.js" +] + +["browser_utility_profiler.js"] +support-files = ["../../../../tools/profiler/tests/shared-head.js"] +skip-if = ["tsan"] # from tools/profiler/tests/browser/browser.ini, timing out on profiler tests? + +["browser_utility_start_clean_shutdown.js"] diff --git a/ipc/glue/test/browser/browser_audio_fallback.toml b/ipc/glue/test/browser/browser_audio_fallback.toml new file mode 100644 index 0000000000..0de2a1c9e7 --- /dev/null +++ b/ipc/glue/test/browser/browser_audio_fallback.toml @@ -0,0 +1,17 @@ +[DEFAULT] +support-files = [ + "head.js", + "head-multiple.js", +] +prefs = ["media.allow-audio-non-utility=true"] +# Set this since we want to continue monitoring the disabling of pref since we +# still allow it a little bit. +environment = "MOZ_DONT_LOCK_UTILITY_PLZ_FILE_A_BUG=1" + +["browser_utility_multipleAudio_fallback.js"] +support-files = [ + "../../../../dom/media/test/small-shot.ogg", + "../../../../dom/media/test/small-shot.mp3", + "../../../../dom/media/test/small-shot.m4a", + "../../../../dom/media/test/small-shot.flac", +] diff --git a/ipc/glue/test/browser/browser_audio_fallback_content.toml b/ipc/glue/test/browser/browser_audio_fallback_content.toml new file mode 100644 index 0000000000..3efc6409ac --- /dev/null +++ b/ipc/glue/test/browser/browser_audio_fallback_content.toml @@ -0,0 +1,17 @@ +[DEFAULT] +support-files = [ + "head.js", + "head-multiple.js", +] +prefs = [ + "media.allow-audio-non-utility=true", + "media.rdd-process.enabled=false", +] + +["browser_utility_multipleAudio_fallback_content.js"] +support-files = [ + "../../../../dom/media/test/small-shot.ogg", + "../../../../dom/media/test/small-shot.mp3", + "../../../../dom/media/test/small-shot.m4a", + "../../../../dom/media/test/small-shot.flac", +] diff --git a/ipc/glue/test/browser/browser_audio_locked.toml b/ipc/glue/test/browser/browser_audio_locked.toml new file mode 100644 index 0000000000..9f0607bf5f --- /dev/null +++ b/ipc/glue/test/browser/browser_audio_locked.toml @@ -0,0 +1,3 @@ +[DEFAULT] + +["browser_utility_audio_locked.js"] diff --git a/ipc/glue/test/browser/browser_audio_shutdown.toml b/ipc/glue/test/browser/browser_audio_shutdown.toml new file mode 100644 index 0000000000..f99fff7830 --- /dev/null +++ b/ipc/glue/test/browser/browser_audio_shutdown.toml @@ -0,0 +1,5 @@ +[DEFAULT] +support-files = ["head.js"] + +["browser_utility_audio_shutdown.js"] +support-files = ["../../../../dom/media/test/small-shot.ogg"] diff --git a/ipc/glue/test/browser/browser_audio_telemetry_content.js b/ipc/glue/test/browser/browser_audio_telemetry_content.js new file mode 100644 index 0000000000..89f5126794 --- /dev/null +++ b/ipc/glue/test/browser/browser_audio_telemetry_content.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head-telemetry.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/ipc/glue/test/browser/head-telemetry.js", + this +); + +add_setup(async function testNoTelemetry() { + await Telemetry.clearScalars(); + await SpecialPowers.pushPrefEnv({ + set: [["media.allow-audio-non-utility", true]], + }); +}); + +add_task(async function testAudioDecodingInContent() { + await runTest({ expectUtility: false, expectRDD: false }); +}); + +add_task(async function testContentTelemetry() { + const codecs = ["vorbis", "mp3", "aac", "flac"]; + const extraKey = getExtraKey({ + rddPref: false, + utilityPref: false, + allowNonUtility: true, + }); + await verifyTelemetryForProcess("tab", codecs, extraKey); + + const platform = Services.appinfo.OS; + for (let exp of utilityPerCodecs[platform]) { + await verifyNoTelemetryForProcess(exp.process, exp.codecs, extraKey); + } + + await verifyNoTelemetryForProcess("rdd", codecs, extraKey); +}); diff --git a/ipc/glue/test/browser/browser_audio_telemetry_rdd.js b/ipc/glue/test/browser/browser_audio_telemetry_rdd.js new file mode 100644 index 0000000000..ec0944303b --- /dev/null +++ b/ipc/glue/test/browser/browser_audio_telemetry_rdd.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head-telemetry.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/ipc/glue/test/browser/head-telemetry.js", + this +); + +add_setup(async function testNoTelemetry() { + await Telemetry.clearScalars(); + await SpecialPowers.pushPrefEnv({ + set: [["media.allow-audio-non-utility", true]], + }); +}); + +add_task(async function testAudioDecodingInRDD() { + await runTest({ expectUtility: false, expectRDD: true }); +}); + +add_task(async function testRDDTelemetry() { + const extraKey = getExtraKey({ + rddPref: true, + utilityPref: false, + allowNonUtility: true, + }); + const platform = Services.appinfo.OS; + for (let exp of utilityPerCodecs[platform]) { + await verifyNoTelemetryForProcess(exp.process, exp.codecs, extraKey); + } + const codecs = ["vorbis", "mp3", "aac", "flac"]; + await verifyTelemetryForProcess("rdd", codecs, extraKey); +}); diff --git a/ipc/glue/test/browser/browser_audio_telemetry_utility.js b/ipc/glue/test/browser/browser_audio_telemetry_utility.js new file mode 100644 index 0000000000..e121c89049 --- /dev/null +++ b/ipc/glue/test/browser/browser_audio_telemetry_utility.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head-telemetry.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/ipc/glue/test/browser/head-telemetry.js", + this +); + +add_setup(async function testNoTelemetry() { + await Telemetry.clearScalars(); +}); + +add_task(async function testAudioDecodingInUtility() { + await runTest({ expectUtility: true, expectRDD: true }); +}); + +add_task(async function testUtilityTelemetry() { + const platform = Services.appinfo.OS; + const extraKey = getExtraKey({ rddPref: true, utilityPref: true }); + for (let exp of utilityPerCodecs[platform]) { + await verifyTelemetryForProcess(exp.process, exp.codecs, extraKey); + } + await verifyNoTelemetryForProcess( + "rdd", + ["vorbis", "mp3", "aac", "flac"], + extraKey + ); +}); diff --git a/ipc/glue/test/browser/browser_audio_telemetry_utility_EME.js b/ipc/glue/test/browser/browser_audio_telemetry_utility_EME.js new file mode 100644 index 0000000000..7d2e9a4e78 --- /dev/null +++ b/ipc/glue/test/browser/browser_audio_telemetry_utility_EME.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head-telemetry.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/ipc/glue/test/browser/head-telemetry.js", + this +); + +SimpleTest.requestCompleteLog(); + +add_setup(async function testNoTelemetry() { + await Telemetry.clearScalars(); +}); + +add_task(async function testAudioDecodingInUtility() { + await runTestWithEME(); +}); + +add_task(async function testUtilityTelemetry() { + const platform = Services.appinfo.OS; + const extraKey = getExtraKey({ rddPref: true, utilityPref: true }); + for (let exp of utilityPerCodecs[platform]) { + if (exp.codecs.includes("aac")) { + await verifyTelemetryForProcess(exp.process, ["aac"], extraKey); + } + } +}); diff --git a/ipc/glue/test/browser/browser_child_hang.js b/ipc/glue/test/browser/browser_child_hang.js new file mode 100644 index 0000000000..cf890a6c61 --- /dev/null +++ b/ipc/glue/test/browser/browser_child_hang.js @@ -0,0 +1,37 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// +// Try to open a tab. This provides code coverage for a few things, +// although currently there's no automated functional test of correctness: +// +// * On opt builds, when the tab is closed and the process exits, it +// will hang for 3s and the parent will kill it after 2s. +// +// * On debug[*] builds, the parent process will wait until the +// process exits normally; but also, on browser shutdown, the +// preallocated content processes will block parent shutdown in +// WillDestroyCurrentMessageLoop. +// +// [*] Also sanitizer and code coverage builds. +// + +add_task(async function () { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "https://example.com/", + forceNewProcess: true, + }, + async function (browser) { + // browser.frameLoader.remoteTab.osPid is the child pid; once we + // have a way to get notifications about child process termination + // events, that could be useful. + ok(true, "Browser isn't broken"); + } + ); + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 4000)); + ok(true, "Still running after child process (hopefully) exited"); +}); diff --git a/ipc/glue/test/browser/browser_child_hang.toml b/ipc/glue/test/browser/browser_child_hang.toml new file mode 100644 index 0000000000..ddc8b95670 --- /dev/null +++ b/ipc/glue/test/browser/browser_child_hang.toml @@ -0,0 +1,7 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ +[DEFAULT] +tags = "ipc" +environment = "MOZ_TEST_CHILD_EXIT_HANG=3" + +["browser_child_hang.js"] diff --git a/ipc/glue/test/browser/browser_utility_audioDecodeCrash.js b/ipc/glue/test/browser/browser_utility_audioDecodeCrash.js new file mode 100644 index 0000000000..1c7551c623 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_audioDecodeCrash.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +async function getAudioDecoderPid(expectation) { + info("Finding a running AudioDecoder"); + + const actor = expectation.replace("Utility ", ""); + + let audioDecoderProcess = (await ChromeUtils.requestProcInfo()).children.find( + p => + p.type === "utility" && + p.utilityActors.find(a => a.actorName === `audioDecoder_${actor}`) + ); + ok( + audioDecoderProcess, + `Found the AudioDecoder ${actor} process at ${audioDecoderProcess.pid}` + ); + return audioDecoderProcess.pid; +} + +async function crashDecoder(expectation) { + const audioPid = await getAudioDecoderPid(expectation); + ok(audioPid > 0, `Found an audio decoder ${audioPid}`); + const actorIsAudioDecoder = actorNames => { + return actorNames + .split(",") + .some(actorName => actorName.trim().startsWith("audio-decoder-")); + }; + info(`Crashing audio decoder ${audioPid}`); + await crashSomeUtility(audioPid, actorIsAudioDecoder); +} + +async function runTest(src, withClose, expectation) { + info(`Add media tabs: ${src}`); + let tab = await addMediaTab(src); + + info("Play tab"); + await play(tab, expectation.process, expectation.decoder); + + info("Crash decoder"); + await crashDecoder(expectation.process); + + if (withClose) { + info("Stop tab"); + await stop(tab); + + info("Remove tab"); + await BrowserTestUtils.removeTab(tab); + + info("Create tab again"); + tab = await addMediaTab(src); + } + + info("Play tab again"); + await play(tab, expectation.process, expectation.decoder); + + info("Stop tab"); + await stop(tab); + + info("Remove tab"); + await BrowserTestUtils.removeTab(tab); +} + +add_setup(async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [["media.utility-process.enabled", true]], + }); +}); + +async function testAudioCrash(withClose) { + info(`Running tests for audio decoder process crashing: ${withClose}`); + + SimpleTest.expectChildProcessCrash(); + + const platform = Services.appinfo.OS; + + for (let { src, expectations } of audioTestData()) { + if (!(platform in expectations)) { + info(`Skipping ${src} for ${platform}`); + continue; + } + + await runTest(src, withClose, expectations[platform]); + } +} + +add_task(async function testAudioCrashSimple() { + await testAudioCrash(false); +}); + +add_task(async function testAudioCrashClose() { + await testAudioCrash(true); +}); diff --git a/ipc/glue/test/browser/browser_utility_audio_locked.js b/ipc/glue/test/browser/browser_utility_audio_locked.js new file mode 100644 index 0000000000..4be22de425 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_audio_locked.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head-multiple.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/ipc/glue/test/browser/head-multiple.js", + this +); + +add_setup(async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [["media.utility-process.enabled", false]], + }); +}); + +add_task(async function testAudioDecodingInUtility() { + // TODO: When getting rid of audio decoding on non utility at all, this + // should be removed + // We only lock the preference in Nightly builds so far, but on beta we expect + // audio decoding error + await runTest({ + expectUtility: isNightlyOnly(), + expectError: !isNightlyOnly(), + }); +}); diff --git a/ipc/glue/test/browser/browser_utility_audio_shutdown.js b/ipc/glue/test/browser/browser_utility_audio_shutdown.js new file mode 100644 index 0000000000..a0a4be63f6 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_audio_shutdown.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// The purpose of that test is to reproduce edge case behaviors that one can +// have while running whole ipc/glue/test/browser/ suite but that could this +// way be intermittent and hard to diagnose. By having such a test we make sure +// it is cleanly reproduced and wont regress somewhat silently. + +"use strict"; + +async function runTest(src, process, decoder) { + info(`Add media tabs: ${src}`); + let tab = await addMediaTab(src); + + info("Play tab"); + await play(tab, process, decoder); + + info("Stop tab"); + await stop(tab); + + info("Remove tab"); + await BrowserTestUtils.removeTab(tab); +} + +async function findGenericAudioDecoder() { + const audioDecoders = (await ChromeUtils.requestProcInfo()).children.filter( + p => { + return ( + p.type === "utility" && + p.utilityActors.find(a => a.actorName === "audioDecoder_Generic") + ); + } + ); + ok(audioDecoders.length === 1, "Only one audio decoder present"); + return audioDecoders[0].pid; +} + +add_setup(async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [["media.utility-process.enabled", true]], + }); +}); + +add_task(async function testKill() { + await runTest("small-shot.ogg", "Utility Generic", "ffvpx audio decoder"); + + await cleanUtilityProcessShutdown( + "audioDecoder_Generic", + true /* preferKill */ + ); + + info("Waiting 15s to trigger mShutdownBlockers assertions"); + await new Promise((resolve, reject) => { + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + setTimeout(resolve, 15 * 1000); + }); + + ok(true, "Waited 15s to trigger mShutdownBlockers assertions: over"); +}); + +add_task(async function testShutdown() { + await runTest("small-shot.ogg", "Utility Generic", "ffvpx audio decoder"); + + const audioDecoderPid = await findGenericAudioDecoder(); + ok(audioDecoderPid > 0, `Valid PID found: ${audioDecoderPid}`); + + await cleanUtilityProcessShutdown("audioDecoder_Generic"); + + info("Waiting 15s to trigger mShutdownBlockers assertions"); + await new Promise((resolve, reject) => { + /* eslint-disable mozilla/no-arbitrary-setTimeout */ + setTimeout(resolve, 15 * 1000); + }); + + ok(true, "Waited 15s to trigger mShutdownBlockers assertions: over"); +}); diff --git a/ipc/glue/test/browser/browser_utility_crashReporter.js b/ipc/glue/test/browser/browser_utility_crashReporter.js new file mode 100644 index 0000000000..73e6c6355a --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_crashReporter.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +async function startAndCrashUtility(numUnknownActors, actorsCheck) { + const actors = Array(numUnknownActors).fill("unknown"); + const utilityPid = await startUtilityProcess(actors); + await crashSomeUtility(utilityPid, actorsCheck); +} + +// When running full suite, previous tests may have left some utility +// processes running and this might interfere with our testing. +add_setup(async function ensureNoExistingProcess() { + await killUtilityProcesses(); +}); + +add_task(async function utilityNoActor() { + await startAndCrashUtility(0, actorNames => { + return actorNames === undefined; + }); +}); + +add_task(async function utilityOneActor() { + await startAndCrashUtility(1, actorNames => { + return actorNames === kGenericUtilityActor; + }); +}); + +add_task(async function utilityManyActors() { + await startAndCrashUtility(42, actorNames => { + return actorNames === Array(42).fill("unknown").join(", "); + }); +}); diff --git a/ipc/glue/test/browser/browser_utility_filepicker_crashed.js b/ipc/glue/test/browser/browser_utility_filepicker_crashed.js new file mode 100644 index 0000000000..e8eb83cf30 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_filepicker_crashed.js @@ -0,0 +1,170 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +SimpleTest.requestCompleteLog(); + +// Wait until the child process with the given PID has indeed been terminated. +// +// Note that `checkUtilityExists`, and other functions deriving from the output +// of `ChromeUtils.requestProcInfo()`, do not suffice for this purpose! It is an +// attested failure mode that the file-dialog utility process has been removed +// from the proc-info list, but is still live with the file-picker dialog still +// displayed. +function untilChildProcessDead(pid) { + return utilityProcessTest().untilChildProcessDead(pid); +} + +async function fileDialogProcessExists() { + return !!(await tryGetUtilityPid("windowsFileDialog")); +} + +// Poll for the creation of a file dialog process. +function untilFileDialogProcessExists(options = { maxTime: 2000 }) { + // milliseconds + const maxTime = options.maxTime ?? 2000, + pollTime = options.pollTime ?? 100; + const count = maxTime / pollTime; + + return TestUtils.waitForCondition( + () => tryGetUtilityPid("windowsFileDialog", { quiet: true }), + "waiting for file dialog process", + pollTime, // interval + count // maxTries + ); +} + +function openFileDialog() { + const process = (async () => { + await untilFileDialogProcessExists(); + let pid = await tryGetUtilityPid("windowsFileDialog"); + ok(pid, `pid should be acquired in openFileDialog::process (got ${pid})`); + // HACK: Wait briefly for the file dialog to open. + // + // If this is not done, we may attempt to crash the process while it's in + // the middle of creating and showing the file dialog window. There _should_ + // be no problem with this, but `::MiniDumpWriteDump()` occasionally fails + // with mysterious errors (`ERROR_BAD_LENGTH`) if we crashed the process + // while that was happening, yielding no minidump and therefore a failing + // test. + // + // Use of an arbitrary timeout could presumably be avoided by setting a + // window hook for the file dialog being shown and `await`ing on that. + // + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(res => setTimeout(res, 1000)); + return pid; + })(); + + const file = new Promise((resolve, reject) => { + info("Opening Windows file dialog"); + let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); + fp.init(window, "Test: browser_utility_filepicker_crashed.js", fp.modeOpen); + fp.open(result => { + ok( + result == fp.returnCancel, + "filepicker should resolve to cancellation" + ); + resolve(); + }); + }); + + return { process, file }; +} + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + // remote, no fallback + ["widget.windows.utility_process_file_picker", 2], + ], + }); +}); + +function makeTask(description, Describe, action) { + let task = async function () { + if (await fileDialogProcessExists()) { + // If this test proceeds, it will probably cause whatever other test has a + // file dialog open to fail. + // + // (We shouldn't be running two such tests in parallel on the same Fx + // instance, but that's not obvious at this level.) + ok(false, "another test has a file dialog open; aborting"); + return; + } + + const { process, file } = openFileDialog(); + const pid = await process; + const untilDead = untilChildProcessDead(pid); + + info(Describe + " the file-dialog utility process"); + await action(); + + // the file-picker's callback should have been promptly cancelled + const _before = Date.now(); + await file; + const _after = Date.now(); + const delta = _after - _before; + info(`file callback resolved after ${description} after ${delta}ms`); + + // depending on the test configuration, this may take some time while + // cleanup occurs + await untilDead; + }; + + // give this task a legible name + Object.defineProperty(task, "name", { + value: "testFileDialogProcess-" + Describe.replace(" ", ""), + }); + + return task; +} + +for (let [description, Describe, action] of [ + ["crash", "Crash", () => crashSomeUtilityActor("windowsFileDialog")], + [ + "being killed", + "Kill", + () => cleanUtilityProcessShutdown("windowsFileDialog", true), + ], + // Unfortunately, a controlled shutdown doesn't actually terminate the utility + // process; the file dialog remains open. (This is expected to be resolved with + // bug 1837008.) + /* [ + "shutdown", + "Shut down", + () => cleanUtilityProcessShutdown("windowsFileDialog"), + ] */ +]) { + add_task(makeTask(description, Describe, action)); + add_task(testCleanup); +} + +async function testCleanup() { + const killFileDialogProcess = async () => { + if (await tryGetUtilityPid("windowsFileDialog", { quiet: true })) { + await cleanUtilityProcessShutdown("windowsFileDialog", true); + return true; + } + return false; + }; + + // If a test failure occurred, the file dialog process may or may not already + // exist... + if (await killFileDialogProcess()) { + console.warn("File dialog process found and killed"); + return; + } + + // ... and if not, may or may not be pending creation. + info("Active file dialog process not found; waiting..."); + try { + await untilFileDialogProcessExists({ maxTime: 1000 }); + } catch (e) { + info("File dialog process not found during cleanup (as expected)"); + return; + } + await killFileDialogProcess(); + console.warn("Delayed file dialog process found and killed"); +} diff --git a/ipc/glue/test/browser/browser_utility_geolocation_crashed.js b/ipc/glue/test/browser/browser_utility_geolocation_crashed.js new file mode 100644 index 0000000000..b0c341b69f --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_geolocation_crashed.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +async function getGeolocation() { + info("Requesting geolocation"); + + let resolve; + let promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + }); + + navigator.geolocation.getCurrentPosition( + () => { + ok(true, "geolocation succeeded"); + resolve(undefined); + }, + () => { + ok(false, "geolocation failed"); + resolve(undefined); + } + ); + + return promise; +} + +add_setup(async function () { + // Avoid the permission doorhanger and cache that would trigger instead + // of re-requesting location. Setting geo.timeout to 0 causes it to + // retry the system geolocation (incl. recreating the utility process) + // instead of reusing the MLS geolocation fallback it found the first time. + await SpecialPowers.pushPrefEnv({ + set: [ + ["geo.prompt.testing", true], + ["geo.prompt.testing.allow", true], + ["geo.provider.network.debug.requestCache.enabled", false], + ["geo.provider.testing", false], + ["geo.timeout", 0], + ], + }); +}); + +add_task(async function testGeolocationProcessCrash() { + info("Start the Windows utility process"); + await getGeolocation(); + + info("Crash the utility process"); + await crashSomeUtilityActor("windowsUtils"); + + info("Restart the Windows utility process"); + await getGeolocation(); + + info("Confirm the restarted process"); + await checkUtilityExists("windowsUtils"); + + info("Kill the utility process"); + await cleanUtilityProcessShutdown("windowsUtils", true); + + info("Restart the Windows utility process again"); + await getGeolocation(); + + info("Confirm the restarted process"); + await checkUtilityExists("windowsUtils"); +}); + +add_task(async function testCleanup() { + info("Clean up to avoid confusing future tests"); + await cleanUtilityProcessShutdown("windowsUtils", true); +}); diff --git a/ipc/glue/test/browser/browser_utility_hard_kill.js b/ipc/glue/test/browser/browser_utility_hard_kill.js new file mode 100644 index 0000000000..516ef64045 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_hard_kill.js @@ -0,0 +1,13 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async () => { + await startUtilityProcess(["unknown"]); + + SimpleTest.expectChildProcessCrash(); + + info("Hard kill Utility Process"); + await cleanUtilityProcessShutdown("unknown", true /* preferKill */); +}); diff --git a/ipc/glue/test/browser/browser_utility_hard_kill_delayed.js b/ipc/glue/test/browser/browser_utility_hard_kill_delayed.js new file mode 100644 index 0000000000..ffda4d3988 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_hard_kill_delayed.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async () => { + const utilityPid = await startUtilityProcess(); + + SimpleTest.expectChildProcessCrash(); + + const utilityProcessGone = TestUtils.topicObserved("ipc:utility-shutdown"); + + info("Hard kill Utility Process"); + const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService( + Ci.nsIProcessToolsService + ); + + // Here we really want to exercise the fact that kill() might not be done + // right now but a bit later, and we should wait for the process to be dead + // before considering the test is finished. + // + // Without this, we get into bug 1754572 (where there was no setTimeout nor + // the wait) where the kill() operation ends up really killing the child a + // bit after the current test has been finished ; unfortunately, this happened + // right after the next test, browser_utility_memoryReport.js did start and + // even worse, after it thought it had started a new utility process. We were + // in fact re-using the one we started here, and when we wanted to query its + // pid in the browser_utility_memoryReport.js then the kill() happened, so + // no more process and the test intermittently failed. + // + // The timeout value of 50ms should be long enough to allow the test to finish + // and the next one to start and get a reference on the process we launched, + // and yet allow us to kill the process in the middle of the next test. Higher + // values would allow browser_utility_memoryReport.js to complete without + // reproducing the issue (both locally and on try). + // + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(() => { + ProcessTools.kill(utilityPid); + }, 50); + + info(`Waiting for utility process ${utilityPid} to go away.`); + let [subject, data] = await utilityProcessGone; + ok( + subject instanceof Ci.nsIPropertyBag2, + "Subject needs to be a nsIPropertyBag2 to clean up properly" + ); + is( + parseInt(data, 10), + utilityPid, + `Should match the crashed PID ${utilityPid} with ${data}` + ); + + // Make sure the process is dead, otherwise there is a risk of race for + // writing leak logs + utilityProcessTest().noteIntentionalCrash(utilityPid); +}); diff --git a/ipc/glue/test/browser/browser_utility_memoryReport.js b/ipc/glue/test/browser/browser_utility_memoryReport.js new file mode 100644 index 0000000000..8cec61b8be --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_memoryReport.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// When running full suite, previous audio decoding tests might have left some +// running and this might interfere with our testing +add_setup(async function ensureNoExistingProcess() { + await killUtilityProcesses(); +}); + +add_task(async () => { + const utilityPid = await startUtilityProcess(); + + const gMgr = Cc["@mozilla.org/memory-reporter-manager;1"].getService( + Ci.nsIMemoryReporterManager + ); + ok(utilityPid !== undefined, `Utility process is running as ${utilityPid}`); + + var utilityReports = []; + + const performCollection = new Promise((resolve, reject) => { + // Record the reports from the live memory reporters then process them. + let handleReport = function ( + aProcess, + aUnsafePath, + aKind, + aUnits, + aAmount, + aDescription + ) { + const expectedProcess = `Utility (pid ${utilityPid}, sandboxingKind ${kGenericUtilitySandbox})`; + if (aProcess !== expectedProcess) { + return; + } + + let report = { + process: aProcess, + path: aUnsafePath, + kind: aKind, + units: aUnits, + amount: aAmount, + description: aDescription, + }; + + utilityReports.push(report); + }; + + info("Memory report: Perform the call"); + gMgr.getReports(handleReport, null, resolve, null, false); + }); + + await performCollection; + + info( + `Collected ${utilityReports.length} reports from utility process ${utilityPid}` + ); + ok(!!utilityReports.length, "Collected some reports"); + ok( + utilityReports.filter(r => r.path === "vsize" && r.amount > 0).length === 1, + "Collected vsize report" + ); + ok( + utilityReports.filter(r => r.path === "resident" && r.amount > 0).length === + 1, + "Collected resident report" + ); + ok( + !!utilityReports.filter( + r => r.path.search(/^explicit\/.*/) >= 0 && r.amount > 0 + ).length, + "Collected some explicit/ report" + ); + + await cleanUtilityProcessShutdown(); +}); diff --git a/ipc/glue/test/browser/browser_utility_multipleAudio.js b/ipc/glue/test/browser/browser_utility_multipleAudio.js new file mode 100644 index 0000000000..107cd2e234 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_multipleAudio.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head-multiple.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/ipc/glue/test/browser/head-multiple.js", + this +); + +add_setup(async function checkAudioDecodingNonUtility() { + const isAudioDecodingNonUtilityAllowed = await SpecialPowers.getBoolPref( + "media.allow-audio-non-utility" + ); + ok( + !isAudioDecodingNonUtilityAllowed, + "Audio decoding should not be allowed on non utility processes by default" + ); +}); + +add_task(async function testAudioDecodingInUtility() { + await runTest({ expectUtility: true }); +}); + +add_task(async function testFailureAudioDecodingInRDD() { + await runTest({ expectUtility: false, expectError: true }); +}); + +add_task(async function testFailureAudioDecodingInContent() { + const platform = Services.appinfo.OS; + if (platform === "WINNT") { + ok( + true, + "Manually skipping on Windows because of gfx killing us, cf browser.ini" + ); + return; + } + + await SpecialPowers.pushPrefEnv({ + set: [["media.rdd-process.enabled", false]], + }); + await runTest({ expectUtility: false, expectRDD: false, expectError: true }); +}); diff --git a/ipc/glue/test/browser/browser_utility_multipleAudio_fallback.js b/ipc/glue/test/browser/browser_utility_multipleAudio_fallback.js new file mode 100644 index 0000000000..cbebd08287 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_multipleAudio_fallback.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head-multiple.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/ipc/glue/test/browser/head-multiple.js", + this +); + +add_setup(async function checkAudioDecodingNonUtility() { + const isAudioDecodingNonUtilityAllowed = await SpecialPowers.getBoolPref( + "media.allow-audio-non-utility" + ); + ok(isAudioDecodingNonUtilityAllowed, "Audio decoding has been allowed"); +}); + +add_task(async function testFallbackAudioDecodingInRDD() { + await runTest({ expectUtility: false, expectError: false }); +}); diff --git a/ipc/glue/test/browser/browser_utility_multipleAudio_fallback_content.js b/ipc/glue/test/browser/browser_utility_multipleAudio_fallback_content.js new file mode 100644 index 0000000000..f07a29985f --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_multipleAudio_fallback_content.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head-multiple.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/ipc/glue/test/browser/head-multiple.js", + this +); + +add_setup(async function checkAudioDecodingNonUtility() { + const isAudioDecodingNonUtilityAllowed = await SpecialPowers.getBoolPref( + "media.allow-audio-non-utility" + ); + ok(isAudioDecodingNonUtilityAllowed, "Audio decoding has been allowed"); +}); + +add_task(async function testFallbackAudioDecodingInContent() { + await runTest({ expectContent: true }); +}); diff --git a/ipc/glue/test/browser/browser_utility_profiler.js b/ipc/glue/test/browser/browser_utility_profiler.js new file mode 100644 index 0000000000..084cd67747 --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_profiler.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from /tools/profiler/tests/shared-head.js */ + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/tools/profiler/tests/browser/shared-head.js", + this +); + +// When running full suite, previous tests may have left some utility +// processes running and this might interfere with our testing. +add_setup(async function ensureNoExistingProcess() { + await killUtilityProcesses(); +}); + +add_task(async () => { + const utilityPid = await startUtilityProcess(); + + info("Start the profiler"); + await startProfiler(); + + let profile; + await TestUtils.waitForCondition(async () => { + profile = await Services.profiler.getProfileDataAsync(); + return ( + // Search for process name to not be disturbed by other types of utility + // e.g. Utility AudioDecoder + profile.processes.filter( + ps => ps.threads[0].processName === "Utility Process" + ).length === 1 + ); + }, "Give time for the profiler to start and collect some samples"); + + info(`Check that the utility process ${utilityPid} is present.`); + let utilityProcessIndex = profile.processes.findIndex( + p => p.threads[0].pid == utilityPid + ); + Assert.notEqual(utilityProcessIndex, -1, "Could find index of utility"); + + Assert.equal( + profile.processes[utilityProcessIndex].threads[0].processType, + "utility", + "Profile has processType utility" + ); + + Assert.equal( + profile.processes[utilityProcessIndex].threads[0].name, + "GeckoMain", + "Profile has correct main thread name" + ); + + Assert.equal( + profile.processes[utilityProcessIndex].threads[0].processName, + "Utility Process", + "Profile has correct process name" + ); + + Assert.greater( + profile.processes[utilityProcessIndex].threads.length, + 0, + "The utility process should have threads" + ); + + Assert.equal( + profile.threads.length, + 1, + "The parent process should have only one thread" + ); + + Services.profiler.StopProfiler(); + + await cleanUtilityProcessShutdown(); +}); diff --git a/ipc/glue/test/browser/browser_utility_start_clean_shutdown.js b/ipc/glue/test/browser/browser_utility_start_clean_shutdown.js new file mode 100644 index 0000000000..62a9e4065b --- /dev/null +++ b/ipc/glue/test/browser/browser_utility_start_clean_shutdown.js @@ -0,0 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async () => { + await startUtilityProcess(); + await cleanUtilityProcessShutdown(); +}); diff --git a/ipc/glue/test/browser/head-multiple.js b/ipc/glue/test/browser/head-multiple.js new file mode 100644 index 0000000000..0ab098448e --- /dev/null +++ b/ipc/glue/test/browser/head-multiple.js @@ -0,0 +1,78 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head.js */ + +async function runTest({ + expectUtility = false, + expectRDD = false, + expectContent = false, + expectError = false, +}) { + info(`Running tests with decoding from somewhere`); + info(` expectUtility: ${expectUtility}`); + info(` expectRDD: ${expectRDD}`); + info(` expectContent: ${expectContent}`); + + // Utility should now be the default, so dont toggle the pref unless we test + // RDD + if (!expectUtility) { + await SpecialPowers.pushPrefEnv({ + set: [["media.utility-process.enabled", expectUtility]], + }); + } + + const platform = Services.appinfo.OS; + + for (let { src, expectations } of audioTestData()) { + if (!(platform in expectations)) { + info(`Skipping ${src} for ${platform}`); + continue; + } + + const expectation = expectations[platform]; + + info(`Add media tabs: ${src}`); + let tabs = [await addMediaTab(src), await addMediaTab(src)]; + let playback = []; + + info("Play tabs"); + for (let tab of tabs) { + playback.push( + play( + tab, + expectUtility && !expectContent && !expectError + ? expectation.process + : "RDD", + expectation.decoder, + expectContent, + false, // expectJava + expectError + ) + ); + } + + info("Wait all playback"); + await Promise.all(playback); + + let allstop = []; + info("Stop tabs"); + for (let tab of tabs) { + allstop.push(stop(tab)); + } + + info("Wait all stop"); + await Promise.all(allstop); + + let remove = []; + info("Remove tabs"); + for (let tab of tabs) { + remove.push(BrowserTestUtils.removeTab(tab)); + } + + info("Wait all tabs to be removed"); + await Promise.all(remove); + } +} diff --git a/ipc/glue/test/browser/head-telemetry.js b/ipc/glue/test/browser/head-telemetry.js new file mode 100644 index 0000000000..46841347fa --- /dev/null +++ b/ipc/glue/test/browser/head-telemetry.js @@ -0,0 +1,269 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/* import-globals-from head.js */ + +const Telemetry = Services.telemetry; + +const { TelemetryController } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryController.sys.mjs" +); + +/* eslint-disable mozilla/no-redeclare-with-import-autofix */ +const { ContentTaskUtils } = ChromeUtils.importESModule( + "resource://testing-common/ContentTaskUtils.sys.mjs" +); + +const MEDIA_AUDIO_PROCESS = "media.audio_process_per_codec_name"; + +const utilityPerCodecs = { + Linux: [ + { + process: "utility+audioDecoder_Generic", + codecs: ["vorbis", "mp3", "aac", "flac"], + }, + ], + WINNT: [ + { + process: "utility+audioDecoder_Generic", + codecs: ["vorbis", "mp3", "flac"], + }, + { + process: "utility+audioDecoder_WMF", + codecs: ["aac"], + }, + ], + Darwin: [ + { + process: "utility+audioDecoder_Generic", + codecs: ["vorbis", "mp3", "flac"], + }, + { + process: "utility+audioDecoder_AppleMedia", + codecs: ["aac"], + }, + ], +}; + +const kInterval = 300; /* ms */ +const kRetries = 5; + +/** + * This function waits until utility scalars are reported into the + * scalar snapshot. + */ +async function waitForKeyedScalars(process) { + await ContentTaskUtils.waitForCondition( + () => { + const scalars = Telemetry.getSnapshotForKeyedScalars("main", false); + return Object.keys(scalars).includes("content"); + }, + `Waiting for ${process} scalars to have been set`, + kInterval, + kRetries + ); +} + +async function waitForValue(process, codecNames, extra = "") { + await ContentTaskUtils.waitForCondition( + () => { + const telemetry = Telemetry.getSnapshotForKeyedScalars( + "main", + false + ).content; + if (telemetry && MEDIA_AUDIO_PROCESS in telemetry) { + const keyProcMimeTypes = Object.keys(telemetry[MEDIA_AUDIO_PROCESS]); + const found = codecNames.every(item => + keyProcMimeTypes.includes(`${process},${item}${extra}`) + ); + return found; + } + return false; + }, + `Waiting for ${MEDIA_AUDIO_PROCESS} [${process}, ${codecNames}, ${extra}]`, + kInterval, + kRetries + ); +} + +async function runTest({ + expectUtility = false, + expectRDD = false, + expectError = false, +}) { + info( + `Running tests with decoding from Utility or RDD: expectUtility=${expectUtility} expectRDD=${expectRDD} expectError=${expectError}` + ); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.utility-process.enabled", expectUtility], + ["media.rdd-process.enabled", expectRDD], + ["toolkit.telemetry.ipcBatchTimeout", 0], + ], + }); + + const platform = Services.appinfo.OS; + + for (let { src, expectations } of audioTestData()) { + if (!(platform in expectations)) { + info(`Skipping ${src} for ${platform}`); + continue; + } + + const expectation = expectations[platform]; + + info(`Add media tab: ${src}`); + let tab = await addMediaTab(src); + + info("Play tab"); + await play( + tab, + expectUtility ? expectation.process : "RDD", + expectation.decoder, + !expectUtility && !expectRDD, + false, + expectError + ); + + info("Stop tab"); + await stop(tab); + + info("Remove tab"); + await BrowserTestUtils.removeTab(tab); + } +} + +async function runTestWithEME() { + info(`Running tests with decoding from Utility for EME`); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.utility-process.enabled", true], + ["toolkit.telemetry.ipcBatchTimeout", 0], + ], + }); + + const platform = Services.appinfo.OS; + + for (let { src, expectations } of audioTestDataEME()) { + if (!(platform in expectations)) { + info(`Skipping ${src} for ${platform}`); + continue; + } + + const expectation = expectations[platform]; + + info(`Add EME media tab`); + let tab = await addMediaTabWithEME(src.sourceBuffer, src.audioFile); + + info("Play tab"); + await play( + tab, + expectation.process, + expectation.decoder, + false, // expectContent + false, // expectJava + false, // expectError + true // withEME + ); + + info("Stop tab"); + await stop(tab); + + info("Remove tab"); + await BrowserTestUtils.removeTab(tab); + } +} + +function getTelemetry() { + const telemetry = Telemetry.getSnapshotForKeyedScalars("main", false).content; + return telemetry; +} + +async function verifyTelemetryForProcess(process, codecNames, extraKey = "") { + // Once scalars are set by the utility process, they don't immediately get + // sent to the parent process. Wait for the Telemetry IPC Timer to trigger + // and batch send the data back to the parent process. + await waitForKeyedScalars(process); + await waitForValue(process, codecNames, extraKey); + + const telemetry = getTelemetry(); + + // The amount here depends on how many times RemoteAudioDecoderParent::RemoteAudioDecoderParent + // gets called, which might be more than actual audio files being playback, e.g., we would get one for metadata loading, then one for playback etc. + // But we dont care really we just want to ensure 0 on RDD, Content and others + // in the wild.[${codecName}] + codecNames.forEach(codecName => { + Assert.equal( + telemetry[MEDIA_AUDIO_PROCESS][`${process},${codecName}${extraKey}`], + 1, + `${MEDIA_AUDIO_PROCESS} must have the correct value (${process}, ${codecName}).` + ); + }); +} + +async function verifyNoTelemetryForProcess(process, codecNames, extraKey = "") { + try { + await waitForKeyedScalars(process); + await waitForValue(process, codecNames, extraKey); + } catch (ex) { + if (ex.indexOf("timed out after") > 0) { + Assert.ok( + true, + `Expected timeout ${process}[${MEDIA_AUDIO_PROCESS}] for ${codecNames}` + ); + } else { + Assert.ok( + false, + `Unexpected exception on ${process}[${MEDIA_AUDIO_PROCESS}] for ${codecNames}: ${ex}` + ); + } + } + + const telemetry = getTelemetry(); + + // There could be races with telemetry for power usage coming up + codecNames.forEach(codecName => { + if (telemetry) { + if (telemetry && MEDIA_AUDIO_PROCESS in telemetry) { + Assert.ok( + !( + `${process},${codecName}${extraKey}` in + telemetry[MEDIA_AUDIO_PROCESS] + ), + `Some telemetry but no ${process}[${MEDIA_AUDIO_PROCESS}][${codecName}]` + ); + } else { + Assert.ok( + !(MEDIA_AUDIO_PROCESS in telemetry), + `No telemetry for ${process}[${MEDIA_AUDIO_PROCESS}][${codecName}]` + ); + } + } else { + Assert.equal( + undefined, + telemetry, + `No telemetry for ${process}[${MEDIA_AUDIO_PROCESS}][${codecName}]` + ); + } + }); +} + +function getExtraKey({ utilityPref, rddPref, allowNonUtility }) { + let extraKey = ""; + if (!rddPref) { + extraKey += ",rdd-disabled"; + } + if (!utilityPref) { + extraKey += ",utility-disabled"; + } + // TODO: This needs to be removed when getting rid of ability to decode on + // non utility at all + if (allowNonUtility) { + extraKey += ",allow-non-utility"; + } + return extraKey; +} diff --git a/ipc/glue/test/browser/head.js b/ipc/glue/test/browser/head.js new file mode 100644 index 0000000000..8acff88273 --- /dev/null +++ b/ipc/glue/test/browser/head.js @@ -0,0 +1,562 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const utilityProcessTest = () => { + return Cc["@mozilla.org/utility-process-test;1"].createInstance( + Ci.nsIUtilityProcessTest + ); +}; + +const kGenericUtilitySandbox = 0; +const kGenericUtilityActor = "unknown"; + +// Start a generic utility process with the given array of utility actor names +// registered. +async function startUtilityProcess(actors = []) { + info("Start a UtilityProcess"); + return utilityProcessTest().startProcess(actors); +} + +// Returns an array of process infos for utility processes of the given type +// or all utility processes if actor is not defined. +async function getUtilityProcesses(actor = undefined, options = {}) { + let procInfos = (await ChromeUtils.requestProcInfo()).children.filter(p => { + return ( + p.type === "utility" && + (actor == undefined || + p.utilityActors.find(a => a.actorName.startsWith(actor))) + ); + }); + + if (!options?.quiet) { + info(`Utility process infos = ${JSON.stringify(procInfos)}`); + } + return procInfos; +} + +async function tryGetUtilityPid(actor, options = {}) { + let process = await getUtilityProcesses(actor, options); + if (!options?.quiet) { + ok(process.length <= 1, `at most one ${actor} process exists`); + } + return process[0]?.pid; +} + +async function checkUtilityExists(actor) { + info(`Looking for a running ${actor} utility process`); + const utilityPid = await tryGetUtilityPid(actor); + ok(utilityPid > 0, `Found ${actor} utility process ${utilityPid}`); + return utilityPid; +} + +// "Cleanly stop" a utility process. This will never leave a crash dump file. +// preferKill will "kill" the process (e.g. SIGABRT) instead of using the +// UtilityProcessManager. +// To "crash" -- i.e. shutdown and generate a crash dump -- use +// crashSomeUtility(). +async function cleanUtilityProcessShutdown(actor, preferKill = false) { + info(`${preferKill ? "Kill" : "Clean shutdown"} Utility Process ${actor}`); + + const utilityPid = await tryGetUtilityPid(actor); + ok(utilityPid !== undefined, `Must have PID for ${actor} utility process`); + + const utilityProcessGone = TestUtils.topicObserved( + "ipc:utility-shutdown", + (subject, data) => parseInt(data, 10) === utilityPid + ); + + if (preferKill) { + SimpleTest.expectChildProcessCrash(); + info(`Kill Utility Process ${utilityPid}`); + const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService( + Ci.nsIProcessToolsService + ); + ProcessTools.kill(utilityPid); + } else { + info(`Stopping Utility Process ${utilityPid}`); + await utilityProcessTest().stopProcess(actor); + } + + let [subject, data] = await utilityProcessGone; + ok( + subject instanceof Ci.nsIPropertyBag2, + "Subject needs to be a nsIPropertyBag2 to clean up properly" + ); + is( + parseInt(data, 10), + utilityPid, + `Should match the crashed PID ${utilityPid} with ${data}` + ); + + // Make sure the process is dead, otherwise there is a risk of race for + // writing leak logs + utilityProcessTest().noteIntentionalCrash(utilityPid); + + ok(!subject.hasKey("dumpID"), "There should be no dumpID"); +} + +async function killUtilityProcesses() { + let utilityProcesses = await getUtilityProcesses(); + for (const utilityProcess of utilityProcesses) { + for (const actor of utilityProcess.utilityActors) { + info(`Stopping ${actor.actorName} utility process`); + await cleanUtilityProcessShutdown(actor.actorName, /* preferKill */ true); + } + } +} + +function audioTestData() { + return [ + { + src: "small-shot.ogg", + expectations: { + Android: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + Linux: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + WINNT: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + Darwin: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + }, + }, + { + src: "small-shot.mp3", + expectations: { + Android: { process: "Utility Generic", decoder: "ffvpx audio decoder" }, + Linux: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + WINNT: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + Darwin: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + }, + }, + { + src: "small-shot.m4a", + expectations: { + // Add Android after Bug 1771196 + Linux: { + process: "Utility Generic", + decoder: "ffmpeg audio decoder", + }, + WINNT: { + process: "Utility WMF", + decoder: "wmf audio decoder", + }, + Darwin: { + process: "Utility AppleMedia", + decoder: "apple coremedia decoder", + }, + }, + }, + { + src: "small-shot.flac", + expectations: { + Android: { process: "Utility Generic", decoder: "ffvpx audio decoder" }, + Linux: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + WINNT: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + Darwin: { + process: "Utility Generic", + decoder: "ffvpx audio decoder", + }, + }, + }, + ]; +} + +function audioTestDataEME() { + return [ + { + src: { + audioFile: + "https://example.com/browser/ipc/glue/test/browser/short-aac-encrypted-audio.mp4", + sourceBuffer: "audio/mp4", + }, + expectations: { + Linux: { + process: "Utility Generic", + decoder: "ffmpeg audio decoder", + }, + WINNT: { + process: "Utility WMF", + decoder: "wmf audio decoder", + }, + Darwin: { + process: "Utility AppleMedia", + decoder: "apple coremedia decoder", + }, + }, + }, + ]; +} + +async function addMediaTab(src) { + const tab = BrowserTestUtils.addTab(gBrowser, "about:blank", { + forceNewProcess: true, + }); + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + await SpecialPowers.spawn(browser, [src], createAudioElement); + return tab; +} + +async function addMediaTabWithEME(sourceBuffer, audioFile) { + const tab = BrowserTestUtils.addTab( + gBrowser, + "https://example.com/browser/", + { + forceNewProcess: true, + } + ); + const browser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(browser); + await SpecialPowers.spawn( + browser, + [sourceBuffer, audioFile], + createAudioElementEME + ); + return tab; +} + +async function play( + tab, + expectUtility, + expectDecoder, + expectContent = false, + expectJava = false, + expectError = false, + withEME = false +) { + let browser = tab.linkedBrowser; + return SpecialPowers.spawn( + browser, + [ + expectUtility, + expectDecoder, + expectContent, + expectJava, + expectError, + withEME, + ], + checkAudioDecoder + ); +} + +async function stop(tab) { + let browser = tab.linkedBrowser; + await SpecialPowers.spawn(browser, [], async function () { + let audio = content.document.querySelector("audio"); + audio.pause(); + }); +} + +async function createAudioElement(src) { + const doc = typeof content !== "undefined" ? content.document : document; + const ROOT = "https://example.com/browser/ipc/glue/test/browser"; + let audio = doc.createElement("audio"); + audio.setAttribute("controls", "true"); + audio.setAttribute("loop", true); + audio.src = `${ROOT}/${src}`; + doc.body.appendChild(audio); +} + +async function createAudioElementEME(sourceBuffer, audioFile) { + // Helper to clone data into content so the EME helper can use the data. + function cloneIntoContent(data) { + return Cu.cloneInto(data, content.wrappedJSObject); + } + + // Load the EME helper into content. + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/ipc/glue/test/browser/eme_standalone.js", + content + ); + + let audio = content.document.createElement("audio"); + audio.setAttribute("controls", "true"); + audio.setAttribute("loop", true); + audio.setAttribute("_sourceBufferType", sourceBuffer); + audio.setAttribute("_audioUrl", audioFile); + content.document.body.appendChild(audio); + + let emeHelper = new content.wrappedJSObject.EmeHelper(); + emeHelper.SetKeySystem( + content.wrappedJSObject.EmeHelper.GetClearkeyKeySystemString() + ); + emeHelper.SetInitDataTypes(cloneIntoContent(["keyids", "cenc"])); + emeHelper.SetAudioCapabilities( + cloneIntoContent([{ contentType: 'audio/mp4; codecs="mp4a.40.2"' }]) + ); + emeHelper.AddKeyIdAndKey( + "2cdb0ed6119853e7850671c3e9906c3c", + "808B9ADAC384DE1E4F56140F4AD76194" + ); + emeHelper.onerror = error => { + is(false, `Got unexpected error from EME helper: ${error}`); + }; + await emeHelper.ConfigureEme(audio); + // Done setting up EME. +} + +async function checkAudioDecoder( + expectedProcess, + expectedDecoder, + expectContent = false, + expectJava = false, + expectError = false, + withEME = false +) { + const doc = typeof content !== "undefined" ? content.document : document; + let audio = doc.querySelector("audio"); + const checkPromise = new Promise((resolve, reject) => { + const timeUpdateHandler = async ev => { + const debugInfo = await SpecialPowers.wrap(audio).mozRequestDebugInfo(); + const audioDecoderName = debugInfo.decoder.reader.audioDecoderName; + + const isExpectedDecoder = + audioDecoderName.indexOf(`${expectedDecoder}`) == 0; + ok( + isExpectedDecoder, + `playback ${audio.src} was from decoder '${audioDecoderName}', expected '${expectedDecoder}'` + ); + + const isExpectedProcess = + audioDecoderName.indexOf(`(${expectedProcess} remote)`) > 0; + const isJavaRemote = audioDecoderName.indexOf("(remote)") > 0; + const isOk = + (isExpectedProcess && !isJavaRemote && !expectContent && !expectJava) || // Running in Utility + (expectJava && !isExpectedProcess && isJavaRemote) || // Running in Java remote + (expectContent && !isExpectedProcess && !isJavaRemote); // Running in Content + + ok( + isOk, + `playback ${audio.src} was from process '${audioDecoderName}', expected '${expectedProcess}'` + ); + + if (isOk) { + resolve(); + } else { + reject(); + } + }; + + const startPlaybackHandler = async ev => { + ok( + await audio.play().then( + _ => true, + _ => false + ), + "audio started playing" + ); + + audio.addEventListener("timeupdate", timeUpdateHandler, { once: true }); + }; + + audio.addEventListener("error", async err => { + info( + `Received HTML media error: ${audio.error.code}: ${audio.error.message}` + ); + if (expectError) { + const w = typeof content !== "undefined" ? content.window : window; + ok( + audio.error.code === w.MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED || + w.MediaError.MEDIA_ERR_DECODE, + "Media supported but decoding failed" + ); + resolve(); + } else { + info(`Unexpected error`); + reject(); + } + }); + + audio.addEventListener("canplaythrough", startPlaybackHandler, { + once: true, + }); + }); + + if (!withEME) { + // We need to make sure the decoder is ready before play()ing otherwise we + // could get into bad situations + audio.load(); + } else { + // For EME we need to create and load content ourselves. We do this here + // because if we do it in createAudioElementEME() above then we end up + // with events fired before we get a chance to listen to them here + async function once(target, name) { + return new Promise(r => target.addEventListener(name, r, { once: true })); + } + + // Setup MSE. + const ms = new content.wrappedJSObject.MediaSource(); + audio.src = content.wrappedJSObject.URL.createObjectURL(ms); + await once(ms, "sourceopen"); + const sb = ms.addSourceBuffer(audio.getAttribute("_sourceBufferType")); + let fetchResponse = await content.fetch(audio.getAttribute("_audioUrl")); + let dataBuffer = await fetchResponse.arrayBuffer(); + sb.appendBuffer(dataBuffer); + await once(sb, "updateend"); + ms.endOfStream(); + await once(ms, "sourceended"); + } + + return checkPromise; +} + +async function runMochitestUtilityAudio( + src, + { + expectUtility, + expectDecoder, + expectContent = false, + expectJava = false, + expectError = false, + } = {} +) { + info(`Add media: ${src}`); + await createAudioElement(src); + let audio = document.querySelector("audio"); + ok(audio, "Found an audio element created"); + + info(`Play media: ${src}`); + await checkAudioDecoder( + expectUtility, + expectDecoder, + expectContent, + expectJava, + expectError + ); + + info(`Pause media: ${src}`); + await audio.pause(); + + info(`Remove media: ${src}`); + document.body.removeChild(audio); +} + +async function crashSomeUtility(utilityPid, actorsCheck) { + SimpleTest.expectChildProcessCrash(); + + const crashMan = Services.crashmanager; + const utilityProcessGone = TestUtils.topicObserved( + "ipc:utility-shutdown", + (subject, data) => { + info(`ipc:utility-shutdown: data=${data} subject=${subject}`); + return parseInt(data, 10) === utilityPid; + } + ); + + info("prune any previous crashes"); + const future = new Date(Date.now() + 1000 * 60 * 60 * 24); + await crashMan.pruneOldCrashes(future); + + info("crash Utility Process"); + const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService( + Ci.nsIProcessToolsService + ); + + info(`Crash Utility Process ${utilityPid}`); + ProcessTools.crash(utilityPid); + + info(`Waiting for utility process ${utilityPid} to go away.`); + let [subject, data] = await utilityProcessGone; + ok( + parseInt(data, 10) === utilityPid, + `Should match the crashed PID ${utilityPid} with ${data}` + ); + ok( + subject instanceof Ci.nsIPropertyBag2, + "Subject needs to be a nsIPropertyBag2 to clean up properly" + ); + + // Make sure the process is dead, otherwise there is a risk of race for + // writing leak logs + utilityProcessTest().noteIntentionalCrash(utilityPid); + + const dumpID = subject.getPropertyAsAString("dumpID"); + ok(dumpID, "There should be a dumpID"); + + await crashMan.ensureCrashIsPresent(dumpID); + await crashMan.getCrashes().then(crashes => { + is(crashes.length, 1, "There should be only one record"); + const crash = crashes[0]; + ok( + crash.isOfType( + crashMan.processTypes[Ci.nsIXULRuntime.PROCESS_TYPE_UTILITY], + crashMan.CRASH_TYPE_CRASH + ), + "Record should be a utility process crash" + ); + ok(crash.id === dumpID, "Record should have an ID"); + ok( + actorsCheck(crash.metadata.UtilityActorsName), + `Record should have the correct actors name for: ${crash.metadata.UtilityActorsName}` + ); + }); + + let minidumpDirectory = Services.dirsvc.get("ProfD", Ci.nsIFile); + minidumpDirectory.append("minidumps"); + + let dumpfile = minidumpDirectory.clone(); + dumpfile.append(dumpID + ".dmp"); + if (dumpfile.exists()) { + info(`Removal of ${dumpfile.path}`); + dumpfile.remove(false); + } + + let extrafile = minidumpDirectory.clone(); + extrafile.append(dumpID + ".extra"); + info(`Removal of ${extrafile.path}`); + if (extrafile.exists()) { + extrafile.remove(false); + } +} + +// Crash a utility process and generate a crash dump. To close a utility +// process (forcefully or not) without a generating a crash, use +// cleanUtilityProcessShutdown. +async function crashSomeUtilityActor( + actor, + actorsCheck = () => { + return true; + } +) { + // Get PID for utility type + const procInfos = await getUtilityProcesses(actor); + ok( + procInfos.length == 1, + `exactly one ${actor} utility process should be found` + ); + const utilityPid = procInfos[0].pid; + return crashSomeUtility(utilityPid, actorsCheck); +} + +function isNightlyOnly() { + const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" + ); + return AppConstants.NIGHTLY_BUILD; +} diff --git a/ipc/glue/test/browser/mochitest_audio_off.toml b/ipc/glue/test/browser/mochitest_audio_off.toml new file mode 100644 index 0000000000..d174ea3939 --- /dev/null +++ b/ipc/glue/test/browser/mochitest_audio_off.toml @@ -0,0 +1,12 @@ +[DEFAULT] +run-if = ["os == 'android' && !isolated_process"] # Bug 1771452 +support-files = [ + "head.js", + "../../../../dom/media/test/small-shot.ogg", + "../../../../dom/media/test/small-shot.mp3", + "../../../../dom/media/test/small-shot.m4a", + "../../../../dom/media/test/small-shot.flac", +] +prefs = ["media.utility-process.enabled=false"] + +["test_utility_audio_off.html"] diff --git a/ipc/glue/test/browser/mochitest_audio_on.toml b/ipc/glue/test/browser/mochitest_audio_on.toml new file mode 100644 index 0000000000..908f4005f1 --- /dev/null +++ b/ipc/glue/test/browser/mochitest_audio_on.toml @@ -0,0 +1,12 @@ +[DEFAULT] +run-if = ["os == 'android' && !isolated_process"] # Bug 1771452 +support-files = [ + "head.js", + "../../../../dom/media/test/small-shot.ogg", + "../../../../dom/media/test/small-shot.mp3", + "../../../../dom/media/test/small-shot.m4a", + "../../../../dom/media/test/small-shot.flac", +] +prefs = ["media.utility-process.enabled=true"] + +["test_utility_audio_on.html"] diff --git a/ipc/glue/test/browser/moz.build b/ipc/glue/test/browser/moz.build new file mode 100644 index 0000000000..671bdea5de --- /dev/null +++ b/ipc/glue/test/browser/moz.build @@ -0,0 +1,15 @@ +# -*- 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/. + +BROWSER_CHROME_MANIFESTS += [ + "browser.toml", + "browser_audio_fallback.toml", + "browser_audio_fallback_content.toml", + "browser_audio_locked.toml", + "browser_audio_shutdown.toml", + "browser_child_hang.toml", +] +MOCHITEST_MANIFESTS += ["mochitest_audio_off.toml", "mochitest_audio_on.toml"] diff --git a/ipc/glue/test/browser/test_utility_audio_off.html b/ipc/glue/test/browser/test_utility_audio_off.html new file mode 100644 index 0000000000..619cfaf11d --- /dev/null +++ b/ipc/glue/test/browser/test_utility_audio_off.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Audio decoder not in Utility process</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +(async function() { + const platform = SpecialPowers.Services.appinfo.OS; + for (let {src, expectations} of audioTestData()) { + if (!(platform in expectations)) { + info(`Skipping ${src} for ${platform}`); + continue; + } + + try { + await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: expectations[platform].decoder, expectContent: true, expectJava: false, expectError: true }); + } catch (ex) { + ok(false, "Failure"); + } + } + + for (let src of [ + "small-shot.m4a", + ]) { + try { + await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: "android decoder", expectContent: false, expectJava: true, expectError: false }); + } catch (ex) { + ok(false, `Failure ${ex}`); + } + } + + SimpleTest.finish(); +})(); +</script> +</pre> +</body> +</html> diff --git a/ipc/glue/test/browser/test_utility_audio_on.html b/ipc/glue/test/browser/test_utility_audio_on.html new file mode 100644 index 0000000000..f473527520 --- /dev/null +++ b/ipc/glue/test/browser/test_utility_audio_on.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Audio decoder in Utility process</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +(async function() { + const platform = SpecialPowers.Services.appinfo.OS; + for (let {src, expectations} of audioTestData()) { + if (!(platform in expectations)) { + info(`Skipping ${src} for ${platform}`); + continue; + } + + try { + await runMochitestUtilityAudio(src, { expectUtility: expectations[platform].process, expectDecoder: expectations[platform].decoder, expectContent: false, expectJava: false }); + } catch (ex) { + ok(false, "Failure"); + } + } + + // Remove all after Bug 1771196 + for (let src of [ + "small-shot.m4a", + ]) { + try { + await runMochitestUtilityAudio(src, { expectUtility: "", expectDecoder: "android decoder", expectContent: false, expectJava: true }); + } catch (ex) { + ok(false, `Failure ${ex}`); + } + } + + SimpleTest.finish(); +})(); +</script> +</pre> +</body> +</html> diff --git a/ipc/glue/test/gtest/TestAsyncBlockers.cpp b/ipc/glue/test/gtest/TestAsyncBlockers.cpp new file mode 100644 index 0000000000..6f8d298621 --- /dev/null +++ b/ipc/glue/test/gtest/TestAsyncBlockers.cpp @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/SpinEventLoopUntil.h" +#include "mozilla/ipc/AsyncBlockers.h" +#include "mozilla/gtest/MozHelpers.h" + +#include "nsCOMPtr.h" +#include "nsITimer.h" +#include "nsINamed.h" + +using namespace mozilla; +using namespace mozilla::ipc; + +#define PROCESS_EVENTS_UNTIL(_done) \ + SpinEventLoopUntil("TestAsyncBlockers"_ns, [&]() { return _done; }); + +class TestAsyncBlockers : public ::testing::Test { + protected: + void SetUp() override { + SAVE_GDB_SLEEP(mOldSleepDuration); + return; + } + + void TearDown() final { RESTORE_GDB_SLEEP(mOldSleepDuration); } + + private: +#if defined(HAS_GDB_SLEEP_DURATION) + unsigned int mOldSleepDuration = 0; +#endif // defined(HAS_GDB_SLEEP_DURATION) +}; + +class Blocker {}; + +TEST_F(TestAsyncBlockers, Register) { + AsyncBlockers blockers; + Blocker* blocker = new Blocker(); + blockers.Register(blocker); + EXPECT_TRUE(true); +} + +TEST_F(TestAsyncBlockers, Register_Deregister) { + AsyncBlockers blockers; + Blocker* blocker = new Blocker(); + blockers.Register(blocker); + blockers.Deregister(blocker); + EXPECT_TRUE(true); +} + +TEST_F(TestAsyncBlockers, Register_WaitUntilClear) { + AsyncBlockers blockers; + bool done = false; + + Blocker* blocker = new Blocker(); + blockers.Register(blocker); + + blockers.WaitUntilClear(5 * 1000)->Then(GetCurrentSerialEventTarget(), + __func__, [&]() { + EXPECT_TRUE(true); + done = true; + }); + + NS_ProcessPendingEvents(nullptr); + + blockers.Deregister(blocker); + + PROCESS_EVENTS_UNTIL(done); +} + +class AsyncBlockerTimerCallback : public nsITimerCallback, public nsINamed { + protected: + virtual ~AsyncBlockerTimerCallback(); + + public: + explicit AsyncBlockerTimerCallback() {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED +}; + +NS_IMPL_ISUPPORTS(AsyncBlockerTimerCallback, nsITimerCallback, nsINamed) + +AsyncBlockerTimerCallback::~AsyncBlockerTimerCallback() = default; + +NS_IMETHODIMP +AsyncBlockerTimerCallback::Notify(nsITimer* timer) { + // If we resolve through this, it means + // blockers.WaitUntilClear() started to wait for + // the completion of the timeout which is not + // good. + EXPECT_TRUE(false); + return NS_OK; +} + +NS_IMETHODIMP +AsyncBlockerTimerCallback::GetName(nsACString& aName) { + aName.AssignLiteral("AsyncBlockerTimerCallback"); + return NS_OK; +} + +TEST_F(TestAsyncBlockers, NoRegister_WaitUntilClear) { + AsyncBlockers blockers; + bool done = false; + + nsCOMPtr<nsITimer> timer = NS_NewTimer(); + ASSERT_TRUE(timer); + + RefPtr<AsyncBlockerTimerCallback> timerCb = new AsyncBlockerTimerCallback(); + timer->InitWithCallback(timerCb, 1 * 1000, nsITimer::TYPE_ONE_SHOT); + + blockers.WaitUntilClear(10 * 1000)->Then(GetCurrentSerialEventTarget(), + __func__, [&]() { + // If we resolve through this + // before the nsITimer it means we + // have been resolved before the 5s + // timeout + EXPECT_TRUE(true); + timer->Cancel(); + done = true; + }); + + PROCESS_EVENTS_UNTIL(done); +} + +TEST_F(TestAsyncBlockers, Register_WaitUntilClear_0s) { + AsyncBlockers blockers; + bool done = false; + + Blocker* blocker = new Blocker(); + blockers.Register(blocker); + + blockers.WaitUntilClear(0)->Then(GetCurrentSerialEventTarget(), __func__, + [&]() { + EXPECT_TRUE(true); + done = true; + }); + + NS_ProcessPendingEvents(nullptr); + + blockers.Deregister(blocker); + + PROCESS_EVENTS_UNTIL(done); +} + +#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && !defined(ANDROID) && \ + !(defined(XP_DARWIN) && !defined(MOZ_DEBUG)) +static void DeregisterEmpty_Test() { + mozilla::gtest::DisableCrashReporter(); + + AsyncBlockers blockers; + Blocker* blocker = new Blocker(); + blockers.Deregister(blocker); +} + +TEST_F(TestAsyncBlockers, DeregisterEmpty) { + ASSERT_DEATH_IF_SUPPORTED(DeregisterEmpty_Test(), ""); +} +#endif // defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && !defined(ANDROID) && + // !(defined(XP_DARWIN) && !defined(MOZ_DEBUG)) + +#undef PROCESS_EVENTS_UNTIL diff --git a/ipc/glue/test/gtest/TestUtilityProcess.cpp b/ipc/glue/test/gtest/TestUtilityProcess.cpp new file mode 100644 index 0000000000..c5d19c992f --- /dev/null +++ b/ipc/glue/test/gtest/TestUtilityProcess.cpp @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/SpinEventLoopUntil.h" + +#include "mozilla/ipc/UtilityProcessManager.h" + +#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) +# include "nsIAppShellService.h" +# include "nsServiceManagerUtils.h" +#endif // defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + +#if defined(XP_WIN) +# include "mozilla/gtest/MozHelpers.h" +# include "mozilla/ipc/UtilityProcessImpl.h" +#endif // defined(XP_WIN) + +#ifdef MOZ_WIDGET_ANDROID +# define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/widget/appshell/android;1" +#endif // MOZ_WIDGET_ANDROID + +#ifdef XP_MACOSX +# define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/widget/appshell/mac;1" +#endif // XP_MACOSX + +using namespace mozilla; +using namespace mozilla::ipc; + +#define WAIT_FOR_EVENTS \ + SpinEventLoopUntil("UtilityProcess::emptyUtil"_ns, [&]() { return done; }); + +bool setupDone = false; + +class UtilityProcess : public ::testing::Test { + protected: + void SetUp() override { + if (setupDone) { + return; + } + +#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + appShell = do_GetService(NS_APPSHELLSERVICE_CONTRACTID); +#endif // defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + mozilla::SandboxBroker::GeckoDependentInitialize(); +#endif // defined(XP_WIN) && defined(MOZ_SANDBOX) + + setupDone = true; + } + +#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) + nsCOMPtr<nsIAppShellService> appShell; +#endif // defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX) +}; + +TEST_F(UtilityProcess, ProcessManager) { + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + ASSERT_NE(utilityProc, nullptr); +} + +TEST_F(UtilityProcess, NoProcess) { + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + EXPECT_NE(utilityProc, nullptr); + + Maybe<int32_t> noPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + ASSERT_TRUE(noPid.isNothing()); +} + +TEST_F(UtilityProcess, LaunchProcess) { + bool done = false; + + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + EXPECT_NE(utilityProc, nullptr); + + int32_t thisPid = base::GetCurrentProcId(); + EXPECT_GE(thisPid, 1); + + utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&]() mutable { + EXPECT_TRUE(true); + + Maybe<int32_t> utilityPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + EXPECT_TRUE(utilityPid.isSome()); + EXPECT_GE(*utilityPid, 1); + EXPECT_NE(*utilityPid, thisPid); + + printf_stderr("UtilityProcess running as %d\n", *utilityPid); + + done = true; + }, + [&](nsresult aError) mutable { + EXPECT_TRUE(false); + done = true; + }); + + WAIT_FOR_EVENTS; +} + +TEST_F(UtilityProcess, DestroyProcess) { + bool done = false; + + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + + utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&]() { + Maybe<int32_t> utilityPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + EXPECT_TRUE(utilityPid.isSome()); + EXPECT_GE(*utilityPid, 1); + + utilityProc->CleanShutdown(SandboxingKind::GENERIC_UTILITY); + + utilityPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + EXPECT_TRUE(utilityPid.isNothing()); + + EXPECT_TRUE(true); + done = true; + }, + [&](nsresult aError) { + EXPECT_TRUE(false); + done = true; + }); + + WAIT_FOR_EVENTS; +} + +#if defined(XP_WIN) +static void LoadLibraryCrash_Test() { + mozilla::gtest::DisableCrashReporter(); + // Just a uuidgen name to have something random + UtilityProcessImpl::LoadLibraryOrCrash( + L"2b49036e-6ba3-400c-a297-38fa1f6c5255.dll"); +} + +TEST_F(UtilityProcess, LoadLibraryCrash) { + ASSERT_DEATH_IF_SUPPORTED(LoadLibraryCrash_Test(), ""); +} +#endif // defined(XP_WIN) + +#undef WAIT_FOR_EVENTS diff --git a/ipc/glue/test/gtest/TestUtilityProcessSandboxing.cpp b/ipc/glue/test/gtest/TestUtilityProcessSandboxing.cpp new file mode 100644 index 0000000000..fff17d63ef --- /dev/null +++ b/ipc/glue/test/gtest/TestUtilityProcessSandboxing.cpp @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/gtest/MozHelpers.h" +#include "mozilla/ipc/UtilityProcessSandboxing.h" + +using namespace mozilla; +using namespace mozilla::ipc; + +TEST(UtilityProcessSandboxing, ParseNoEnvVar) +{ EXPECT_TRUE(IsUtilitySandboxEnabled("", SandboxingKind::COUNT)); } + +TEST(UtilityProcessSandboxing, ParseEnvVar_DisableAll) +{ EXPECT_FALSE(IsUtilitySandboxEnabled("1", SandboxingKind::COUNT)); } + +TEST(UtilityProcessSandboxing, ParseEnvVar_DontDisableAll) +{ EXPECT_TRUE(IsUtilitySandboxEnabled("0", SandboxingKind::COUNT)); } + +TEST(UtilityProcessSandboxing, ParseEnvVar_DisableGenericOnly) +{ + EXPECT_FALSE( + IsUtilitySandboxEnabled("utility:0", SandboxingKind::GENERIC_UTILITY)); + EXPECT_TRUE(IsUtilitySandboxEnabled("utility:0", SandboxingKind::COUNT)); +} + +#if defined(XP_DARWIN) +TEST(UtilityProcessSandboxing, ParseEnvVar_DisableAppleAudioOnly) +{ + EXPECT_FALSE(IsUtilitySandboxEnabled( + "utility:1", SandboxingKind::UTILITY_AUDIO_DECODING_APPLE_MEDIA)); + EXPECT_TRUE( + IsUtilitySandboxEnabled("utility:1", SandboxingKind::GENERIC_UTILITY)); +} +#endif // defined(XP_DARWIN) + +#if defined(XP_WIN) +TEST(UtilityProcessSandboxing, ParseEnvVar_DisableWMFOnly) +{ + EXPECT_FALSE(IsUtilitySandboxEnabled( + "utility:1", SandboxingKind::UTILITY_AUDIO_DECODING_WMF)); + EXPECT_TRUE( + IsUtilitySandboxEnabled("utility:1", SandboxingKind::GENERIC_UTILITY)); +} +#endif // defined(XP_WIN) + +TEST(UtilityProcessSandboxing, ParseEnvVar_DisableGenericOnly_Multiples) +{ + EXPECT_FALSE(IsUtilitySandboxEnabled("utility:1,utility:0,utility:2", + SandboxingKind::GENERIC_UTILITY)); +#if defined(XP_DARWIN) + EXPECT_FALSE(IsUtilitySandboxEnabled( + "utility:1,utility:0,utility:2", + SandboxingKind::UTILITY_AUDIO_DECODING_APPLE_MEDIA)); +#endif // XP_DARWIN +#if defined(XP_WIN) + EXPECT_FALSE( + IsUtilitySandboxEnabled("utility:1,utility:0,utility:2", + SandboxingKind::UTILITY_AUDIO_DECODING_WMF)); +#endif // XP_WIN + EXPECT_TRUE(IsUtilitySandboxEnabled("utility:8,utility:0,utility:6", + SandboxingKind::COUNT)); +} diff --git a/ipc/glue/test/gtest/moz.build b/ipc/glue/test/gtest/moz.build new file mode 100644 index 0000000000..b0af6d80cd --- /dev/null +++ b/ipc/glue/test/gtest/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; c-basic-offset: 4; 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/. + +Library("ipcgluetest") + +UNIFIED_SOURCES = [ + "TestAsyncBlockers.cpp", + "TestUtilityProcess.cpp", + "TestUtilityProcessSandboxing.cpp", +] + +LOCAL_INCLUDES += [ + "/widget", + "/widget/android", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" diff --git a/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp new file mode 100644 index 0000000000..6c084a3153 --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.cpp @@ -0,0 +1,280 @@ +/* -*- 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/. */ + +#if defined(ENABLE_TESTS) +# include "mozilla/ipc/UtilityProcessManager.h" +# include "mozilla/ipc/UtilityProcessTest.h" +# include "mozilla/dom/Promise.h" +# include "mozilla/ProcInfo.h" +# include "mozilla/IntentionalCrash.h" + +# ifdef XP_WIN +# include <handleapi.h> +# include <processthreadsapi.h> +# include <tlhelp32.h> + +# include "mozilla/WinHandleWatcher.h" +# include "nsISupports.h" +# include "nsWindowsHelpers.h" +# endif + +namespace mozilla::ipc { + +static UtilityActorName UtilityActorNameFromString( + const nsACString& aStringName) { + using namespace mozilla::dom; + + // We use WebIDLUtilityActorNames because UtilityActorNames is not designed + // for iteration. + for (size_t i = 0; i < WebIDLUtilityActorNameValues::Count; ++i) { + auto idlName = static_cast<UtilityActorName>(i); + const nsDependentCSubstring idlNameString( + WebIDLUtilityActorNameValues::GetString(idlName)); + if (idlNameString.Equals(aStringName)) { + return idlName; + } + } + MOZ_CRASH("Unknown utility actor name"); +} + +// Find the utility process with the given actor or any utility process if +// the actor is UtilityActorName::EndGuard_. +static SandboxingKind FindUtilityProcessWithActor(UtilityActorName aActorName) { + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + MOZ_ASSERT(utilityProc, "No UtilityprocessManager?"); + + for (size_t i = 0; i < SandboxingKind::COUNT; ++i) { + auto sbKind = static_cast<SandboxingKind>(i); + if (!utilityProc->Process(sbKind)) { + continue; + } + if (aActorName == UtilityActorName::EndGuard_) { + return sbKind; + } + for (auto actor : utilityProc->GetActors(sbKind)) { + if (actor == aActorName) { + return sbKind; + } + } + } + + return SandboxingKind::COUNT; +} + +# ifdef XP_WIN +namespace { +// Promise implementation for `UntilChildProcessDead`. +// +// Resolves the provided JS promise when the provided Windows HANDLE becomes +// signaled. +class WinHandlePromiseImpl final { + public: + NS_INLINE_DECL_REFCOUNTING(WinHandlePromiseImpl) + + using HandlePtr = mozilla::UniqueFileHandle; + + // Takes ownership of aHandle. + static void Create(mozilla::UniqueFileHandle handle, + RefPtr<mozilla::dom::Promise> promise) { + MOZ_ASSERT(handle); + MOZ_ASSERT(promise); + + RefPtr obj{new WinHandlePromiseImpl(std::move(handle), std::move(promise))}; + + // WARNING: This creates an owning-reference cycle: (self -> HandleWatcher + // -> Runnable -> self). `obj` will therefore only be destroyed when and + // if the HANDLE is signaled. + obj->watcher.Watch(obj->handle.get(), GetCurrentSerialEventTarget(), + NewRunnableMethod("WinHandlePromiseImpl::Resolve", obj, + &WinHandlePromiseImpl::Resolve)); + } + + private: + WinHandlePromiseImpl(mozilla::UniqueFileHandle handle, + RefPtr<mozilla::dom::Promise> promise) + : handle(std::move(handle)), promise(std::move(promise)) {} + + ~WinHandlePromiseImpl() { watcher.Stop(); } + + void Resolve() { promise->MaybeResolveWithUndefined(); } + + mozilla::UniqueFileHandle handle; + HandleWatcher watcher; + RefPtr<mozilla::dom::Promise> promise; +}; + +} // namespace +# endif + +NS_IMETHODIMP +UtilityProcessTest::StartProcess(const nsTArray<nsCString>& aActorsToRegister, + JSContext* aCx, + mozilla::dom::Promise** aOutPromise) { + NS_ENSURE_ARG(aOutPromise); + *aOutPromise = nullptr; + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + + ErrorResult erv; + RefPtr<dom::Promise> promise = dom::Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + MOZ_ASSERT(utilityProc, "No UtilityprocessManager?"); + + auto actors = aActorsToRegister.Clone(); + + utilityProc->LaunchProcess(SandboxingKind::GENERIC_UTILITY) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [promise, utilityProc, actors = std::move(actors)] { + RefPtr<UtilityProcessParent> utilityParent = + utilityProc->GetProcessParent(SandboxingKind::GENERIC_UTILITY); + Maybe<int32_t> utilityPid = + utilityProc->ProcessPid(SandboxingKind::GENERIC_UTILITY); + for (size_t i = 0; i < actors.Length(); ++i) { + auto uan = UtilityActorNameFromString(actors[i]); + utilityProc->RegisterActor(utilityParent, uan); + } + if (utilityPid.isSome()) { + promise->MaybeResolve(*utilityPid); + } else { + promise->MaybeReject(NS_ERROR_NOT_AVAILABLE); + } + }, + [promise](nsresult aError) { + MOZ_ASSERT_UNREACHABLE( + "UtilityProcessTest; failure to get Utility process"); + promise->MaybeReject(aError); + }); + + promise.forget(aOutPromise); + return NS_OK; +} + +NS_IMETHODIMP +UtilityProcessTest::NoteIntentionalCrash(uint32_t aPid) { + mozilla::NoteIntentionalCrash("utility", aPid); + return NS_OK; +} + +NS_IMETHODIMP +UtilityProcessTest::UntilChildProcessDead( + uint32_t pid, JSContext* cx, ::mozilla::dom::Promise** aOutPromise) { + NS_ENSURE_ARG(aOutPromise); + *aOutPromise = nullptr; + +# ifdef XP_WIN + if (pid == 0) { + return NS_ERROR_INVALID_ARG; + } + + nsIGlobalObject* global = xpc::CurrentNativeGlobal(cx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + + ErrorResult erv; + RefPtr<dom::Promise> promise = dom::Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + // Get a fresh handle to the child process with the specified PID. + mozilla::UniqueFileHandle handle; + { + bool failed = false; + GeckoChildProcessHost::GetAll([&](GeckoChildProcessHost* aProc) { + if (handle || failed) { + return; + } + if (aProc->GetChildProcessId() != pid) { + return; + } + + HANDLE handle_ = nullptr; + if (!::DuplicateHandle( + ::GetCurrentProcess(), aProc->GetChildProcessHandle(), + ::GetCurrentProcess(), &handle_, SYNCHRONIZE, FALSE, 0)) { + failed = true; + } else { + handle.reset(handle_); + } + }); + + if (failed || !handle) { + return NS_ERROR_FAILURE; + } + } + + // Create and attach the resolver for the promise, giving the handle over to + // it. + WinHandlePromiseImpl::Create(std::move(handle), promise); + + promise.forget(aOutPromise); + + return NS_OK; +# else // !defined(XP_WIN) + return NS_ERROR_NOT_IMPLEMENTED; +# endif +} + +NS_IMETHODIMP +UtilityProcessTest::StopProcess(const char* aActorName) { + using namespace mozilla::dom; + + SandboxingKind sbKind; + if (aActorName) { + const nsDependentCString actorStringName(aActorName); + UtilityActorName actorName = UtilityActorNameFromString(actorStringName); + sbKind = FindUtilityProcessWithActor(actorName); + } else { + sbKind = FindUtilityProcessWithActor(UtilityActorName::EndGuard_); + } + + if (sbKind == SandboxingKind::COUNT) { + MOZ_ASSERT_UNREACHABLE( + "Attempted to stop process for actor when no " + "such process exists"); + return NS_ERROR_FAILURE; + } + + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + MOZ_ASSERT(utilityProc, "No UtilityprocessManager?"); + + utilityProc->CleanShutdown(sbKind); + Maybe<int32_t> utilityPid = utilityProc->ProcessPid(sbKind); + MOZ_RELEASE_ASSERT(utilityPid.isNothing(), + "Should not have a utility process PID anymore"); + + return NS_OK; +} + +NS_IMETHODIMP +UtilityProcessTest::TestTelemetryProbes() { + RefPtr<UtilityProcessManager> utilityProc = + UtilityProcessManager::GetSingleton(); + MOZ_ASSERT(utilityProc, "No UtilityprocessManager?"); + + for (RefPtr<UtilityProcessParent>& parent : + utilityProc->GetAllProcessesProcessParent()) { + Unused << parent->SendTestTelemetryProbes(); + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(UtilityProcessTest, nsIUtilityProcessTest) + +} // namespace mozilla::ipc +#endif // defined(ENABLE_TESTS) diff --git a/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h new file mode 100644 index 0000000000..6c80fce71b --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/UtilityProcessTest.h @@ -0,0 +1,29 @@ +/* -*- 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/. */ +#ifndef _include_ipc_glue_UtilityProcessTest_h_ +#define _include_ipc_glue_UtilityProcessTest_h_ + +#if defined(ENABLE_TESTS) +# include "nsServiceManagerUtils.h" +# include "nsIUtilityProcessTest.h" + +namespace mozilla::ipc { + +class UtilityProcessTest final : public nsIUtilityProcessTest { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUTILITYPROCESSTEST + + UtilityProcessTest() = default; + + private: + ~UtilityProcessTest() = default; +}; + +} // namespace mozilla::ipc +#endif // defined(ENABLE_TESTS) + +#endif // _include_ipc_glue_UtilityProcessTest_h_ diff --git a/ipc/glue/test/utility_process_xpcom/components.conf b/ipc/glue/test/utility_process_xpcom/components.conf new file mode 100644 index 0000000000..25208ba7fc --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/components.conf @@ -0,0 +1,15 @@ +# -*- 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/. + +Classes = [ + { + 'cid': '{0a4478f4-c5ae-4fb1-8686-d5b09fb99afb}', + 'contract_ids': ['@mozilla.org/utility-process-test;1'], + 'type': 'mozilla::ipc::UtilityProcessTest', + 'headers': ['mozilla/ipc/UtilityProcessTest.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, +] diff --git a/ipc/glue/test/utility_process_xpcom/moz.build b/ipc/glue/test/utility_process_xpcom/moz.build new file mode 100644 index 0000000000..f04b436cbe --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/moz.build @@ -0,0 +1,21 @@ +# -*- 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/. + +EXPORTS.mozilla.ipc += ["UtilityProcessTest.h"] + +UNIFIED_SOURCES += ["UtilityProcessTest.cpp"] + +XPCOM_MANIFESTS += ["components.conf"] + +XPIDL_MODULE = "utility_process_xpcom_test" + +XPIDL_SOURCES += [ + "nsIUtilityProcessTest.idl", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl b/ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl new file mode 100644 index 0000000000..142be6f0e1 --- /dev/null +++ b/ipc/glue/test/utility_process_xpcom/nsIUtilityProcessTest.idl @@ -0,0 +1,56 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 "nsISupports.idl" + +[scriptable, uuid(0a4478f4-c5ae-4fb1-8686-d5b09fb99afb)] +interface nsIUtilityProcessTest : nsISupports +{ + /** + * ** Test-only Method ** + * + * Start a generic utility process from JS code. + * + * actorsToAdd: An array of actor names, taken from WebIDLUtilityActorName. + * Unlike normal utility processes, test processes launched this way do not + * have any associated actor names unless specified here. Empty by default. + */ + [implicit_jscontext] + Promise startProcess([optional] in Array<ACString> actorsToAdd); + + /** + * ** Test-only Method ** + * + * Report when a child process is actually dead (as opposed to merely having + * been removed from our internal list of child processes). Must be called + * while the process is still live. + * + * Only implemented on Windows. + */ + [implicit_jscontext] + Promise untilChildProcessDead(in uint32_t pid); + + /** + * ** Test-only Method ** + * + * Note that we are going to manually crash a process + */ + void noteIntentionalCrash(in unsigned long pid); + + /** + * ** Test-only Method ** + * + * Allowing to stop Utility Process from JS code. + * Default behavior is to stop any utility process. + */ + void stopProcess([optional] in string utilityActorName); + + /** + * ** Test-only Method ** + * + * Sending Telemetry probes + */ + void testTelemetryProbes(); +}; |