diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /ipc/glue | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/glue')
134 files changed, 30378 insertions, 0 deletions
diff --git a/ipc/glue/BackgroundChild.h b/ipc/glue/BackgroundChild.h new file mode 100644 index 0000000000..c1206ece22 --- /dev/null +++ b/ipc/glue/BackgroundChild.h @@ -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/. */ + +#ifndef mozilla_ipc_backgroundchild_h__ +#define mozilla_ipc_backgroundchild_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/ipc/Transport.h" + +class nsIEventTarget; + +namespace mozilla { +namespace dom { + +class BlobImpl; +class ContentChild; +class ContentParent; + +} // namespace dom + +namespace net { + +class SocketProcessImpl; +class SocketProcessChild; + +} // namespace net + +namespace ipc { + +class PBackgroundChild; + +// 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. +// +// GetOrCreateSocketActorForCurrentThread, which is like +// GetOrCreateForCurrentThread, is used to get or create PBackground actor +// between child process and socket process. +// +// 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. +class BackgroundChild final { + friend class mozilla::dom::ContentChild; + friend class mozilla::dom::ContentParent; + friend class mozilla::net::SocketProcessImpl; + friend class mozilla::net::SocketProcessChild; + + typedef mozilla::ipc::Transport Transport; + + public: + // See above. + static PBackgroundChild* GetForCurrentThread(); + + // See above. + static PBackgroundChild* GetOrCreateForCurrentThread( + nsIEventTarget* aMainEventTarget = nullptr); + + // See above. + static void CloseForCurrentThread(); + + // See above. + static PBackgroundChild* GetOrCreateSocketActorForCurrentThread( + nsIEventTarget* aMainEventTarget = nullptr); + + // See above. + static PBackgroundChild* GetOrCreateForSocketParentBridgeForCurrentThread( + nsIEventTarget* aMainEventTarget = nullptr); + + private: + // Only called by ContentChild or ContentParent. + 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..e10c90b530 --- /dev/null +++ b/ipc/glue/BackgroundChildImpl.cpp @@ -0,0 +1,708 @@ +/* -*- 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 "ActorsChild.h" // IndexedDB +#include "BroadcastChannelChild.h" +#include "FileDescriptorSetChild.h" +#ifdef MOZ_WEBRTC +# include "CamerasChild.h" +#endif +#include "mozilla/media/MediaChild.h" +#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/FileSystemTaskBase.h" +#include "mozilla/dom/PMediaTransportChild.h" +#include "mozilla/dom/TemporaryIPCBlobChild.h" +#include "mozilla/dom/cache/ActorUtils.h" +#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.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/GamepadEventChannelChild.h" +#include "mozilla/dom/GamepadTestChannelChild.h" +#include "mozilla/dom/LocalStorage.h" +#include "mozilla/dom/MessagePortChild.h" +#include "mozilla/dom/ServiceWorkerActors.h" +#include "mozilla/dom/ServiceWorkerContainerChild.h" +#include "mozilla/dom/ServiceWorkerManagerChild.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/VsyncChild.h" +#include "mozilla/ipc/IPCStreamAlloc.h" +#include "mozilla/ipc/PBackgroundTestChild.h" +#include "mozilla/ipc/PChildToParentStreamChild.h" +#include "mozilla/ipc/PParentToChildStreamChild.h" +#include "mozilla/net/HttpBackgroundChannelChild.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 "mozilla/RemoteLazyInputStreamChild.h" +#include "nsID.h" +#include "nsTraceRefcnt.h" + +namespace { + +class TestChild final : public mozilla::ipc::PBackgroundTestChild { + friend class mozilla::ipc::BackgroundChildImpl; + + nsCString mTestArg; + + explicit TestChild(const nsCString& aTestArg) : mTestArg(aTestArg) { + MOZ_COUNT_CTOR(TestChild); + } + + protected: + ~TestChild() override { MOZ_COUNT_DTOR(TestChild); } + + public: + mozilla::ipc::IPCResult Recv__delete__(const nsCString& aTestArg) override; +}; + +} // namespace + +namespace mozilla::ipc { + +using mozilla::dom::UDPSocketChild; +using mozilla::net::PUDPSocketChild; + +using mozilla::dom::PServiceWorkerChild; +using mozilla::dom::PServiceWorkerContainerChild; +using mozilla::dom::PServiceWorkerRegistrationChild; +using mozilla::dom::StorageDBChild; +using mozilla::dom::cache::PCacheChild; +using mozilla::dom::cache::PCacheStorageChild; +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!"); + } + + MOZ_CRASH_UNSAFE_PRINTF("%s: %s", abortMessage.get(), aReason); +} + +void BackgroundChildImpl::ActorDestroy(ActorDestroyReason aWhy) { + // May happen on any thread! +} + +PBackgroundTestChild* BackgroundChildImpl::AllocPBackgroundTestChild( + const nsCString& 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::PBackgroundSDBConnectionChild* +BackgroundChildImpl::AllocPBackgroundSDBConnectionChild( + const PersistenceType& aPersistenceType, + const PrincipalInfo& aPrincipalInfo) { + MOZ_CRASH( + "PBackgroundSDBConnectionChild actor should be manually " + "constructed!"); +} + +bool BackgroundChildImpl::DeallocPBackgroundSDBConnectionChild( + PBackgroundSDBConnectionChild* aActor) { + MOZ_ASSERT(aActor); + + delete aActor; + return true; +} + +BackgroundChildImpl::PBackgroundLSDatabaseChild* +BackgroundChildImpl::AllocPBackgroundLSDatabaseChild( + const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId, + const uint64_t& aDatastoreId) { + MOZ_CRASH("PBackgroundLSDatabaseChild actor should be manually constructed!"); +} + +bool BackgroundChildImpl::DeallocPBackgroundLSDatabaseChild( + PBackgroundLSDatabaseChild* 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 nsCString& 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 nsString& 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; +} + +dom::PRemoteWorkerChild* BackgroundChildImpl::AllocPRemoteWorkerChild( + const RemoteWorkerData& aData) { + RefPtr<dom::RemoteWorkerChild> agent = new dom::RemoteWorkerChild(aData); + return agent.forget().take(); +} + +IPCResult BackgroundChildImpl::RecvPRemoteWorkerConstructor( + PRemoteWorkerChild* aActor, const RemoteWorkerData& aData) { + dom::RemoteWorkerChild* actor = static_cast<dom::RemoteWorkerChild*>(aActor); + actor->ExecWorker(aData); + return IPC_OK(); +} + +bool BackgroundChildImpl::DeallocPRemoteWorkerChild( + dom::PRemoteWorkerChild* aActor) { + RefPtr<dom::RemoteWorkerChild> actor = + dont_AddRef(static_cast<dom::RemoteWorkerChild*>(aActor)); + return true; +} + +dom::PRemoteWorkerControllerChild* +BackgroundChildImpl::AllocPRemoteWorkerControllerChild( + const dom::RemoteWorkerData& aRemoteWorkerData) { + MOZ_CRASH( + "PRemoteWorkerControllerChild actors must be manually constructed!"); + return nullptr; +} + +bool BackgroundChildImpl::DeallocPRemoteWorkerControllerChild( + dom::PRemoteWorkerControllerChild* aActor) { + MOZ_ASSERT(aActor); + + RefPtr<dom::RemoteWorkerControllerChild> actor = + dont_AddRef(static_cast<dom::RemoteWorkerControllerChild*>(aActor)); + return true; +} + +dom::PRemoteWorkerServiceChild* +BackgroundChildImpl::AllocPRemoteWorkerServiceChild() { + RefPtr<dom::RemoteWorkerServiceChild> agent = + new dom::RemoteWorkerServiceChild(); + return agent.forget().take(); +} + +bool BackgroundChildImpl::DeallocPRemoteWorkerServiceChild( + dom::PRemoteWorkerServiceChild* aActor) { + RefPtr<dom::RemoteWorkerServiceChild> actor = + dont_AddRef(static_cast<dom::RemoteWorkerServiceChild*>(aActor)); + return true; +} + +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 nsString& aFullPath, const nsString& aType, const nsString& 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; +} + +already_AddRefed<PRemoteLazyInputStreamChild> +BackgroundChildImpl::AllocPRemoteLazyInputStreamChild(const nsID& aID, + const uint64_t& aSize) { + RefPtr<RemoteLazyInputStreamChild> actor = + new RemoteLazyInputStreamChild(aID, aSize); + return actor.forget(); +} + +PFileDescriptorSetChild* BackgroundChildImpl::AllocPFileDescriptorSetChild( + const FileDescriptor& aFileDescriptor) { + return new FileDescriptorSetChild(aFileDescriptor); +} + +bool BackgroundChildImpl::DeallocPFileDescriptorSetChild( + PFileDescriptorSetChild* aActor) { + MOZ_ASSERT(aActor); + + delete static_cast<FileDescriptorSetChild*>(aActor); + return true; +} + +dom::PVsyncChild* BackgroundChildImpl::AllocPVsyncChild() { + RefPtr<dom::VsyncChild> actor = new dom::VsyncChild(); + // There still has one ref-count after return, and it will be released in + // DeallocPVsyncChild(). + return actor.forget().take(); +} + +bool BackgroundChildImpl::DeallocPVsyncChild(PVsyncChild* aActor) { + MOZ_ASSERT(aActor); + + // This actor already has one ref-count. Please check AllocPVsyncChild(). + RefPtr<dom::VsyncChild> actor = + dont_AddRef(static_cast<dom::VsyncChild*>(aActor)); + return true; +} + +PUDPSocketChild* BackgroundChildImpl::AllocPUDPSocketChild( + const Maybe<PrincipalInfo>& aPrincipalInfo, const nsCString& 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 nsCString& aOrigin, + const nsString& 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 +// ----------------------------------------------------------------------------- + +PCacheStorageChild* BackgroundChildImpl::AllocPCacheStorageChild( + const Namespace& aNamespace, const PrincipalInfo& aPrincipalInfo) { + MOZ_CRASH("CacheStorageChild actor must be provided to PBackground manager"); + return nullptr; +} + +bool BackgroundChildImpl::DeallocPCacheStorageChild( + PCacheStorageChild* aActor) { + dom::cache::DeallocPCacheStorageChild(aActor); + return true; +} + +PCacheChild* BackgroundChildImpl::AllocPCacheChild() { + return dom::cache::AllocPCacheChild(); +} + +bool BackgroundChildImpl::DeallocPCacheChild(PCacheChild* aActor) { + dom::cache::DeallocPCacheChild(aActor); + return true; +} + +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; +} + +PChildToParentStreamChild* +BackgroundChildImpl::AllocPChildToParentStreamChild() { + MOZ_CRASH("PChildToParentStreamChild actors should be manually constructed!"); +} + +bool BackgroundChildImpl::DeallocPChildToParentStreamChild( + PChildToParentStreamChild* aActor) { + delete aActor; + return true; +} + +PParentToChildStreamChild* +BackgroundChildImpl::AllocPParentToChildStreamChild() { + return mozilla::ipc::AllocPParentToChildStreamChild(); +} + +bool BackgroundChildImpl::DeallocPParentToChildStreamChild( + PParentToChildStreamChild* aActor) { + delete aActor; + return true; +} + +BackgroundChildImpl::PQuotaChild* BackgroundChildImpl::AllocPQuotaChild() { + MOZ_CRASH("PQuotaChild actor should be manually constructed!"); +} + +bool BackgroundChildImpl::DeallocPQuotaChild(PQuotaChild* aActor) { + MOZ_ASSERT(aActor); + delete aActor; + return true; +} + +// ----------------------------------------------------------------------------- +// WebMIDI API +// ----------------------------------------------------------------------------- + +PMIDIPortChild* BackgroundChildImpl::AllocPMIDIPortChild( + const MIDIPortInfo& aPortInfo, const bool& aSysexEnabled) { + MOZ_CRASH("Should be created manually"); + return nullptr; +} + +bool BackgroundChildImpl::DeallocPMIDIPortChild(PMIDIPortChild* aActor) { + MOZ_ASSERT(aActor); + // The reference is increased in dom/midi/MIDIPort.cpp. We should + // decrease it after IPC. + RefPtr<dom::MIDIPortChild> child = + dont_AddRef(static_cast<dom::MIDIPortChild*>(aActor)); + child->Teardown(); + return true; +} + +PMIDIManagerChild* BackgroundChildImpl::AllocPMIDIManagerChild() { + MOZ_CRASH("Should be created manually"); + return nullptr; +} + +bool BackgroundChildImpl::DeallocPMIDIManagerChild(PMIDIManagerChild* aActor) { + MOZ_ASSERT(aActor); + // The reference is increased in dom/midi/MIDIAccessManager.cpp. We should + // decrease it after IPC. + RefPtr<dom::MIDIManagerChild> child = + dont_AddRef(static_cast<dom::MIDIManagerChild*>(aActor)); + return true; +} + +mozilla::dom::PClientManagerChild* +BackgroundChildImpl::AllocPClientManagerChild() { + return mozilla::dom::AllocClientManagerChild(); +} + +bool BackgroundChildImpl::DeallocPClientManagerChild( + mozilla::dom::PClientManagerChild* aActor) { + return mozilla::dom::DeallocClientManagerChild(aActor); +} + +#ifdef EARLY_BETA_OR_EARLIER +void BackgroundChildImpl::OnChannelReceivedMessage(const Message& aMsg) { + if (aMsg.type() == dom::PVsync::MessageType::Msg_Notify__ID) { + // Not really necessary to look at the message payload, it will be + // <0.5ms away from TimeStamp::Now() + SchedulerGroup::MarkVsyncReceived(); + } +} +#endif + +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 nsString& aGroupName, const PrincipalInfo& aPrincipalInfo) { + return new dom::EndpointForReportChild(); +} + +bool BackgroundChildImpl::DeallocPEndpointForReportChild( + PEndpointForReportChild* aActor) { + MOZ_ASSERT(aActor); + delete static_cast<dom::EndpointForReportChild*>(aActor); + return true; +} + +dom::PMediaTransportChild* BackgroundChildImpl::AllocPMediaTransportChild() { + // We don't allocate here: MediaTransportHandlerIPC is in charge of that, + // so we don't need to know the implementation particulars here. + MOZ_ASSERT_UNREACHABLE( + "The only thing that ought to be creating a PMediaTransportChild is " + "MediaTransportHandlerIPC!"); + return nullptr; +} + +bool BackgroundChildImpl::DeallocPMediaTransportChild( + dom::PMediaTransportChild* aActor) { + delete aActor; + return true; +} + +PChildToParentStreamChild* +BackgroundChildImpl::SendPChildToParentStreamConstructor( + PChildToParentStreamChild* aActor) { + return PBackgroundChild::SendPChildToParentStreamConstructor(aActor); +} + +PFileDescriptorSetChild* BackgroundChildImpl::SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) { + return PBackgroundChild::SendPFileDescriptorSetConstructor(aFD); +} + +} // namespace mozilla::ipc + +mozilla::ipc::IPCResult TestChild::Recv__delete__(const nsCString& 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..22e740e55c --- /dev/null +++ b/ipc/glue/BackgroundChildImpl.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 http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_backgroundchildimpl_h__ +#define mozilla_ipc_backgroundchildimpl_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/UniquePtr.h" +#include "nsRefPtrHashtable.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 ChildToParentStreamActorManager { + 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(); + + PChildToParentStreamChild* SendPChildToParentStreamConstructor( + PChildToParentStreamChild* aActor) override; + PFileDescriptorSetChild* SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) override; + + protected: + BackgroundChildImpl(); + virtual ~BackgroundChildImpl(); + + virtual void ProcessingError(Result aCode, const char* aReason) override; + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual PBackgroundTestChild* AllocPBackgroundTestChild( + const nsCString& aTestArg) override; + + virtual bool DeallocPBackgroundTestChild( + PBackgroundTestChild* aActor) override; + + virtual PBackgroundIndexedDBUtilsChild* AllocPBackgroundIndexedDBUtilsChild() + override; + + virtual bool DeallocPBackgroundIndexedDBUtilsChild( + PBackgroundIndexedDBUtilsChild* aActor) override; + + virtual PBackgroundSDBConnectionChild* AllocPBackgroundSDBConnectionChild( + const PersistenceType& aPersistenceType, + const PrincipalInfo& aPrincipalInfo) override; + + virtual bool DeallocPBackgroundSDBConnectionChild( + PBackgroundSDBConnectionChild* aActor) override; + + virtual PBackgroundLSDatabaseChild* AllocPBackgroundLSDatabaseChild( + const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId, + const uint64_t& aDatastoreId) override; + + virtual bool DeallocPBackgroundLSDatabaseChild( + PBackgroundLSDatabaseChild* 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 nsCString& aOriginKey, + const uint32_t& aPrivateBrowsingId) override; + + virtual bool DeallocPBackgroundLocalStorageCacheChild( + PBackgroundLocalStorageCacheChild* aActor) override; + + virtual PBackgroundStorageChild* AllocPBackgroundStorageChild( + const nsString& aProfilePath, + const uint32_t& aPrivateBrowsingId) override; + + virtual bool DeallocPBackgroundStorageChild( + PBackgroundStorageChild* aActor) override; + + virtual already_AddRefed<PRemoteLazyInputStreamChild> + AllocPRemoteLazyInputStreamChild(const nsID& aID, + const uint64_t& aSize) override; + + virtual PTemporaryIPCBlobChild* AllocPTemporaryIPCBlobChild() override; + + virtual bool DeallocPTemporaryIPCBlobChild( + PTemporaryIPCBlobChild* aActor) override; + + virtual PFileCreatorChild* AllocPFileCreatorChild( + const nsString& aFullPath, const nsString& aType, const nsString& aName, + const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck, + const bool& aIsFromNsIFile) override; + + virtual bool DeallocPFileCreatorChild(PFileCreatorChild* aActor) override; + + virtual mozilla::dom::PRemoteWorkerChild* AllocPRemoteWorkerChild( + const RemoteWorkerData& aData) override; + + virtual mozilla::ipc::IPCResult RecvPRemoteWorkerConstructor( + PRemoteWorkerChild* aActor, const RemoteWorkerData& aData) override; + + virtual bool DeallocPRemoteWorkerChild( + mozilla::dom::PRemoteWorkerChild* aActor) override; + + virtual mozilla::dom::PRemoteWorkerControllerChild* + AllocPRemoteWorkerControllerChild( + const mozilla::dom::RemoteWorkerData& aRemoteWorkerData) override; + + virtual bool DeallocPRemoteWorkerControllerChild( + mozilla::dom::PRemoteWorkerControllerChild* aActor) override; + + virtual mozilla::dom::PRemoteWorkerServiceChild* + AllocPRemoteWorkerServiceChild() override; + + virtual bool DeallocPRemoteWorkerServiceChild( + mozilla::dom::PRemoteWorkerServiceChild* aActor) 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 PFileDescriptorSetChild* AllocPFileDescriptorSetChild( + const FileDescriptor& aFileDescriptor) override; + + virtual bool DeallocPFileDescriptorSetChild( + PFileDescriptorSetChild* aActor) override; + + virtual PCamerasChild* AllocPCamerasChild() override; + + virtual bool DeallocPCamerasChild(PCamerasChild* aActor) override; + + virtual PVsyncChild* AllocPVsyncChild() override; + + virtual bool DeallocPVsyncChild(PVsyncChild* aActor) override; + + virtual PUDPSocketChild* AllocPUDPSocketChild( + const Maybe<PrincipalInfo>& aPrincipalInfo, + const nsCString& aFilter) override; + virtual bool DeallocPUDPSocketChild(PUDPSocketChild* aActor) override; + + virtual PBroadcastChannelChild* AllocPBroadcastChannelChild( + const PrincipalInfo& aPrincipalInfo, const nsCString& aOrigin, + const nsString& aChannel) override; + + virtual bool DeallocPBroadcastChannelChild( + PBroadcastChannelChild* aActor) override; + + virtual PServiceWorkerManagerChild* AllocPServiceWorkerManagerChild() + override; + + virtual bool DeallocPServiceWorkerManagerChild( + PServiceWorkerManagerChild* aActor) override; + + virtual dom::cache::PCacheStorageChild* AllocPCacheStorageChild( + const dom::cache::Namespace& aNamespace, + const PrincipalInfo& aPrincipalInfo) override; + + virtual bool DeallocPCacheStorageChild( + dom::cache::PCacheStorageChild* aActor) override; + + virtual dom::cache::PCacheChild* AllocPCacheChild() override; + + virtual bool DeallocPCacheChild(dom::cache::PCacheChild* aActor) 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 PChildToParentStreamChild* AllocPChildToParentStreamChild() override; + + virtual bool DeallocPChildToParentStreamChild( + PChildToParentStreamChild* aActor) override; + + virtual PParentToChildStreamChild* AllocPParentToChildStreamChild() override; + + virtual bool DeallocPParentToChildStreamChild( + PParentToChildStreamChild* aActor) override; + + virtual PQuotaChild* AllocPQuotaChild() override; + + virtual bool DeallocPQuotaChild(PQuotaChild* aActor) override; + + virtual PClientManagerChild* AllocPClientManagerChild() override; + + virtual bool DeallocPClientManagerChild(PClientManagerChild* aActor) override; + +#ifdef EARLY_BETA_OR_EARLIER + virtual void OnChannelReceivedMessage(const Message& aMsg) override; +#endif + + virtual PWebAuthnTransactionChild* AllocPWebAuthnTransactionChild() override; + + virtual bool DeallocPWebAuthnTransactionChild( + PWebAuthnTransactionChild* aActor) override; + + virtual PMIDIPortChild* AllocPMIDIPortChild( + const MIDIPortInfo& aPortInfo, const bool& aSysexEnabled) override; + virtual bool DeallocPMIDIPortChild(PMIDIPortChild*) override; + + virtual PMIDIManagerChild* AllocPMIDIManagerChild() override; + virtual bool DeallocPMIDIManagerChild(PMIDIManagerChild*) override; + + already_AddRefed<PServiceWorkerChild> AllocPServiceWorkerChild( + const IPCServiceWorkerDescriptor&); + + already_AddRefed<PServiceWorkerContainerChild> + AllocPServiceWorkerContainerChild(); + + already_AddRefed<PServiceWorkerRegistrationChild> + AllocPServiceWorkerRegistrationChild( + const IPCServiceWorkerRegistrationDescriptor&); + + virtual PEndpointForReportChild* AllocPEndpointForReportChild( + const nsString& aGroupName, const PrincipalInfo& aPrincipalInfo) override; + + virtual bool DeallocPEndpointForReportChild( + PEndpointForReportChild* aActor) override; + + virtual dom::PMediaTransportChild* AllocPMediaTransportChild() override; + + virtual bool DeallocPMediaTransportChild( + dom::PMediaTransportChild* 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..6d142e5350 --- /dev/null +++ b/ipc/glue/BackgroundImpl.cpp @@ -0,0 +1,1739 @@ +/* -*- 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 "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/Endpoint.h" +#include "mozilla/ipc/ProtocolTypes.h" +#include "mozilla/net/SocketProcessChild.h" +#include "mozilla/net/SocketProcessBridgeChild.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 AssertIsInMainProcess() { MOZ_ASSERT(XRE_IsParentProcess()); } + +void AssertIsInMainOrSocketProcess() { + MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess()); +} + +void AssertIsOnMainThread() { THREADSAFETY_ASSERT(NS_IsMainThread()); } + +void AssertIsNotOnMainThread() { THREADSAFETY_ASSERT(!NS_IsMainThread()); } + +// ----------------------------------------------------------------------------- +// ParentImpl Declaration +// ----------------------------------------------------------------------------- + +class ParentImpl final : public BackgroundParentImpl { + friend class mozilla::ipc::BackgroundParent; + + private: + class ShutdownObserver; + class CreateActorHelper; + + struct MOZ_STACK_CLASS TimerCallbackClosure { + nsIThread* mThread; + nsTArray<ParentImpl*>* mLiveActors; + + TimerCallbackClosure(nsIThread* aThread, nsTArray<ParentImpl*>* aLiveActors) + : mThread(aThread), mLiveActors(aLiveActors) { + AssertIsInMainOrSocketProcess(); + 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<ParentImpl*>* 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; + + // This is only modified on the main thread. It maintains a count of live + // actors so that the background thread can be shut down when it is no longer + // needed. + static 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; + + // Only touched on the main thread, null if this is a same-process actor. + RefPtr<ContentParent> mContent; + + // Set when the actor is opened successfully and used to handle shutdown + // hangs. Only touched on the background thread. + nsTArray<ParentImpl*>* 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 already_AddRefed<ChildImpl> CreateActorForSameProcess( + nsIEventTarget* aMainEventTarget); + + static bool IsOnBackgroundThread() { + return PR_GetCurrentThread() == sBackgroundPRThread; + } + + static void AssertIsOnBackgroundThread() { + THREADSAFETY_ASSERT(IsOnBackgroundThread()); + } + + // `ParentImpl` instances are created and 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 already_AddRefed<ContentParent> GetContentParent( + PBackgroundParent* aBackgroundActor); + + // Forwarded from BackgroundParent. + static intptr_t GetRawContentParentForComparison( + PBackgroundParent* aBackgroundActor); + + // Forwarded from BackgroundParent. + static uint64_t GetChildID(PBackgroundParent* aBackgroundActor); + + // Forwarded from BackgroundParent. + static bool GetLiveActorArray(PBackgroundParent* aBackgroundActor, + nsTArray<PBackgroundParent*>& aLiveActorArray); + + // Forwarded from BackgroundParent. + static bool Alloc(ContentParent* aContent, + Endpoint<PBackgroundParent>&& aEndpoint); + + static bool CreateBackgroundThread(); + + static void ShutdownBackgroundThread(); + + static void ShutdownTimerCallback(nsITimer* aTimer, void* aClosure); + + // For same-process actors. + ParentImpl() + : mLiveActorArray(nullptr), + mIsOtherProcessActor(false), + mActorDestroyed(false) { + AssertIsInMainProcess(); + AssertIsOnMainThread(); + } + + // For other-process actors. + // NOTE: ParentImpl could be used in 3 cases below. + // 1. Between parent process and content process. + // 2. Between socket process and content process. + // 3. Between parent process and socket process. + // |mContent| should be not null for case 1. For case 2 and 3, it's null. + explicit ParentImpl(ContentParent* aContent) + : mContent(aContent), + mLiveActorArray(nullptr), + mIsOtherProcessActor(true), + mActorDestroyed(false) { + MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess()); + AssertIsOnMainThread(); + } + + ~ParentImpl() { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(!mContent); + } + + void MainThreadActorDestroy(); + + void SetLiveActorArray(nsTArray<ParentImpl*>* aLiveActorArray) { + AssertIsInMainOrSocketProcess(); + 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; + + typedef base::ProcessId ProcessId; + typedef mozilla::ipc::Transport Transport; + + class ShutdownObserver; + + public: + class SendInitBackgroundRunnable; + + struct ThreadLocalInfo { + ThreadLocalInfo() +#ifdef DEBUG + : mClosed(false) +#endif + { + } + + RefPtr<ChildImpl> mActor; + RefPtr<SendInitBackgroundRunnable> mSendInitBackgroundRunnable; + 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**); + + constexpr explicit ThreadInfoWrapper(ActorCreateFunc aFunc) + : mThreadLocalIndex(kBadThreadLocalIndex), + mMainThreadInfo(nullptr), + mCreateActorFunc(aFunc) {} + + 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; + } + + 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; + } + } + + 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( + nsIEventTarget* aMainEventTarget) { + MOZ_ASSERT_IF(NS_IsMainThread(), !aMainEventTarget); + + MOZ_ASSERT(mThreadLocalIndex != kBadThreadLocalIndex, + "BackgroundChild::Startup() was never called!"); + + 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(); + } + + PBackgroundChild* bgChild = + GetFromThreadInfo(aMainEventTarget, threadLocalInfo); + if (bgChild) { + return bgChild; + } + + RefPtr<ChildImpl> actor; + mCreateActorFunc(threadLocalInfo, mThreadLocalIndex, aMainEventTarget, + getter_AddRefs(actor)); + return actor; + } + + private: + // 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; + + // 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; + ActorCreateFunc mCreateActorFunc; + }; + + // For PBackground between parent and content process. + static ThreadInfoWrapper sParentAndContentProcessThreadInfo; + + // For PBackground between socket and content process. + static ThreadInfoWrapper sSocketAndContentProcessThreadInfo; + + // For PBackground between socket and parent process. + static ThreadInfoWrapper sSocketAndParentProcessThreadInfo; + + // 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(); + + // Helper function for getting PBackgroundChild from thread info. + static PBackgroundChild* GetFromThreadInfo(nsIEventTarget* aMainEventTarget, + ThreadLocalInfo* aThreadLocalInfo); + + // Forwarded from BackgroundChild. + static PBackgroundChild* GetOrCreateForCurrentThread( + nsIEventTarget* aMainEventTarget); + + // Forwarded from BackgroundChild. + static PBackgroundChild* GetOrCreateSocketActorForCurrentThread( + nsIEventTarget* aMainEventTarget); + + // Forwarded from BackgroundChild. + static PBackgroundChild* GetOrCreateForSocketParentBridgeForCurrentThread( + nsIEventTarget* aMainEventTarget); + + static void CloseForCurrentThread(); + + // Forwarded from BackgroundChildImpl. + static BackgroundChildImpl::ThreadLocal* GetThreadLocalForCurrentThread(); + + 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(); } +}; + +class ParentImpl::CreateActorHelper final : public Runnable { + mozilla::Monitor mMonitor; + RefPtr<ParentImpl> mParentActor; + nsCOMPtr<nsIThread> mThread; + nsresult mMainThreadResultCode; + bool mWaiting; + + public: + explicit CreateActorHelper() + : Runnable("Background::ParentImpl::CreateActorHelper"), + mMonitor("CreateActorHelper::mMonitor"), + mMainThreadResultCode(NS_OK), + mWaiting(true) { + AssertIsInMainOrSocketProcess(); + AssertIsNotOnMainThread(); + } + + nsresult BlockAndGetResults(nsIEventTarget* aMainEventTarget, + RefPtr<ParentImpl>& aParentActor, + nsCOMPtr<nsIThread>& aThread); + + private: + ~CreateActorHelper() { AssertIsInMainOrSocketProcess(); } + + nsresult RunOnMainThread(); + + NS_DECL_NSIRUNNABLE +}; + +// ----------------------------------------------------------------------------- +// ChildImpl Helper Declarations +// ----------------------------------------------------------------------------- + +class ChildImpl::ShutdownObserver final : public nsIObserver { + public: + ShutdownObserver() { AssertIsOnMainThread(); } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + private: + ~ShutdownObserver() { AssertIsOnMainThread(); } +}; + +class ChildImpl::SendInitBackgroundRunnable final : public DiscardableRunnable { + nsCOMPtr<nsISerialEventTarget> mOwningEventTarget; + RefPtr<StrongWorkerRef> mWorkerRef; + Endpoint<PBackgroundParent> mParent; + mozilla::Mutex mMutex; + bool mSentInitBackground; + std::function<void(Endpoint<PBackgroundParent>&& aParent)> mSendInitfunc; + unsigned int mThreadLocalIndex; + + public: + static already_AddRefed<SendInitBackgroundRunnable> Create( + Endpoint<PBackgroundParent>&& aParent, + std::function<void(Endpoint<PBackgroundParent>&& aParent)>&& aFunc, + unsigned int aThreadLocalIndex); + + void ClearEventTarget() { + mWorkerRef = nullptr; + + mozilla::MutexAutoLock lock(mMutex); + mOwningEventTarget = nullptr; + } + + private: + explicit SendInitBackgroundRunnable( + Endpoint<PBackgroundParent>&& aParent, + std::function<void(Endpoint<PBackgroundParent>&& aParent)>&& aFunc, + unsigned int aThreadLocalIndex) + : DiscardableRunnable( + "Background::ChildImpl::SendInitBackgroundRunnable"), + mOwningEventTarget(GetCurrentSerialEventTarget()), + mParent(std::move(aParent)), + mMutex("SendInitBackgroundRunnable::mMutex"), + mSentInitBackground(false), + mSendInitfunc(std::move(aFunc)), + mThreadLocalIndex(aThreadLocalIndex) {} + + ~SendInitBackgroundRunnable() = default; + + NS_DECL_NSIRUNNABLE +}; + +} // 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 +already_AddRefed<ContentParent> BackgroundParent::GetContentParent( + PBackgroundParent* aBackgroundActor) { + return ParentImpl::GetContentParent(aBackgroundActor); +} + +// static +intptr_t BackgroundParent::GetRawContentParentForComparison( + PBackgroundParent* aBackgroundActor) { + return ParentImpl::GetRawContentParentForComparison(aBackgroundActor); +} + +// static +uint64_t BackgroundParent::GetChildID(PBackgroundParent* aBackgroundActor) { + return ParentImpl::GetChildID(aBackgroundActor); +} + +// static +bool BackgroundParent::GetLiveActorArray( + PBackgroundParent* aBackgroundActor, + nsTArray<PBackgroundParent*>& aLiveActorArray) { + return ParentImpl::GetLiveActorArray(aBackgroundActor, aLiveActorArray); +} + +// static +bool BackgroundParent::Alloc(ContentParent* aContent, + Endpoint<PBackgroundParent>&& aEndpoint) { + return ParentImpl::Alloc(aContent, std::move(aEndpoint)); +} + +// ----------------------------------------------------------------------------- +// BackgroundChild Public Methods +// ----------------------------------------------------------------------------- + +// static +void BackgroundChild::Startup() { ChildImpl::Startup(); } + +// static +PBackgroundChild* BackgroundChild::GetForCurrentThread() { + return ChildImpl::GetForCurrentThread(); +} + +// static +PBackgroundChild* BackgroundChild::GetOrCreateForCurrentThread( + nsIEventTarget* aMainEventTarget) { + return ChildImpl::GetOrCreateForCurrentThread(aMainEventTarget); +} + +// static +PBackgroundChild* BackgroundChild::GetOrCreateSocketActorForCurrentThread( + nsIEventTarget* aMainEventTarget) { + return ChildImpl::GetOrCreateSocketActorForCurrentThread(aMainEventTarget); +} + +// static +PBackgroundChild* +BackgroundChild::GetOrCreateForSocketParentBridgeForCurrentThread( + nsIEventTarget* aMainEventTarget) { + return ChildImpl::GetOrCreateForSocketParentBridgeForCurrentThread( + aMainEventTarget); +} + +// static +void BackgroundChild::CloseForCurrentThread() { + ChildImpl::CloseForCurrentThread(); +} + +// ----------------------------------------------------------------------------- +// BackgroundChildImpl Public Methods +// ----------------------------------------------------------------------------- + +// static +BackgroundChildImpl::ThreadLocal* +BackgroundChildImpl::GetThreadLocalForCurrentThread() { + return ChildImpl::GetThreadLocalForCurrentThread(); +} + +// ----------------------------------------------------------------------------- +// ParentImpl Static Members +// ----------------------------------------------------------------------------- + +StaticRefPtr<nsIThread> ParentImpl::sBackgroundThread; + +nsTArray<ParentImpl*>* ParentImpl::sLiveActorsForBackgroundThread; + +StaticRefPtr<nsITimer> ParentImpl::sShutdownTimer; + +Atomic<PRThread*> ParentImpl::sBackgroundPRThread; + +uint64_t ParentImpl::sLiveActorCount = 0; + +bool ParentImpl::sShutdownObserverRegistered = false; + +bool ParentImpl::sShutdownHasStarted = false; + +// ----------------------------------------------------------------------------- +// ChildImpl Static Members +// ----------------------------------------------------------------------------- + +static void ParentContentActorCreateFunc( + ChildImpl::ThreadLocalInfo* aThreadLocalInfo, + unsigned int aThreadLocalIndex, nsIEventTarget* aMainEventTarget, + ChildImpl** aOutput) { + if (XRE_IsParentProcess()) { + RefPtr<ChildImpl> strongActor = + ParentImpl::CreateActorForSameProcess(aMainEventTarget); + if (NS_WARN_IF(!strongActor)) { + return; + } + + aThreadLocalInfo->mActor = strongActor; + strongActor.forget(aOutput); + return; + } + + RefPtr<ContentChild> content = ContentChild::GetSingleton(); + MOZ_ASSERT(content); + + if (content->IsShuttingDown()) { + // The transport for ContentChild is shut down and can't be used to open + // PBackground. + return; + } + + Endpoint<PBackgroundParent> parent; + Endpoint<PBackgroundChild> child; + nsresult rv; + rv = PBackground::CreateEndpoints(content->OtherPid(), + base::GetCurrentProcId(), &parent, &child); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create top level actor!"); + return; + } + + RefPtr<ChildImpl::SendInitBackgroundRunnable> runnable; + if (!NS_IsMainThread()) { + runnable = ChildImpl::SendInitBackgroundRunnable::Create( + std::move(parent), + [](Endpoint<PBackgroundParent>&& aParent) { + RefPtr<ContentChild> content = ContentChild::GetSingleton(); + MOZ_ASSERT(content); + + if (!content->SendInitBackground(std::move(aParent))) { + NS_WARNING("Failed to create top level actor!"); + } + }, + aThreadLocalIndex); + if (!runnable) { + return; + } + } + + RefPtr<ChildImpl> strongActor = new ChildImpl(); + + if (!child.Bind(strongActor)) { + CRASH_IN_CHILD_PROCESS("Failed to bind ChildImpl!"); + + return; + } + + strongActor->SetActorAlive(); + + if (NS_IsMainThread()) { + if (!content->SendInitBackground(std::move(parent))) { + NS_WARNING("Failed to create top level actor!"); + return; + } + } else { + if (aMainEventTarget) { + MOZ_ALWAYS_SUCCEEDS( + aMainEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)); + } else { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable)); + } + + aThreadLocalInfo->mSendInitBackgroundRunnable = runnable; + } + + aThreadLocalInfo->mActor = strongActor; + strongActor.forget(aOutput); +} + +ChildImpl::ThreadInfoWrapper ChildImpl::sParentAndContentProcessThreadInfo( + ParentContentActorCreateFunc); + +static void SocketContentActorCreateFunc( + ChildImpl::ThreadLocalInfo* aThreadLocalInfo, + unsigned int aThreadLocalIndex, nsIEventTarget* aMainEventTarget, + ChildImpl** aOutput) { + RefPtr<SocketProcessBridgeChild> bridgeChild = + SocketProcessBridgeChild::GetSingleton(); + + if (!bridgeChild || bridgeChild->IsShuttingDown()) { + // The transport for SocketProcessBridgeChild is shut down + // and can't be used to open PBackground. + return; + } + + Endpoint<PBackgroundParent> parent; + Endpoint<PBackgroundChild> child; + nsresult rv; + rv = PBackground::CreateEndpoints(bridgeChild->SocketProcessPid(), + base::GetCurrentProcId(), &parent, &child); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create top level actor!"); + return; + } + + RefPtr<ChildImpl::SendInitBackgroundRunnable> runnable; + if (!NS_IsMainThread()) { + runnable = ChildImpl::SendInitBackgroundRunnable::Create( + std::move(parent), + [](Endpoint<PBackgroundParent>&& aParent) { + RefPtr<SocketProcessBridgeChild> bridgeChild = + SocketProcessBridgeChild::GetSingleton(); + + if (!bridgeChild->SendInitBackground(std::move(aParent))) { + NS_WARNING("Failed to create top level actor!"); + } + }, + aThreadLocalIndex); + if (!runnable) { + return; + } + } + + RefPtr<ChildImpl> strongActor = new ChildImpl(); + + if (!child.Bind(strongActor)) { + CRASH_IN_CHILD_PROCESS("Failed to bind ChildImpl!"); + + return; + } + + strongActor->SetActorAlive(); + + if (NS_IsMainThread()) { + if (!bridgeChild->SendInitBackground(std::move(parent))) { + NS_WARNING("Failed to create top level actor!"); + // Need to close the IPC channel before ChildImpl getting deleted. + strongActor->Close(); + strongActor->AssertActorDestroyed(); + return; + } + } else { + if (aMainEventTarget) { + MOZ_ALWAYS_SUCCEEDS( + aMainEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)); + } else { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable)); + } + + aThreadLocalInfo->mSendInitBackgroundRunnable = runnable; + } + + aThreadLocalInfo->mActor = strongActor; + strongActor.forget(aOutput); +} + +ChildImpl::ThreadInfoWrapper ChildImpl::sSocketAndContentProcessThreadInfo( + SocketContentActorCreateFunc); + +static void SocketParentActorCreateFunc( + ChildImpl::ThreadLocalInfo* aThreadLocalInfo, + unsigned int aThreadLocalIndex, nsIEventTarget* aMainEventTarget, + ChildImpl** aOutput) { + SocketProcessChild* socketChild = SocketProcessChild::GetSingleton(); + + if (!socketChild || socketChild->IsShuttingDown()) { + return; + } + + Endpoint<PBackgroundParent> parent; + Endpoint<PBackgroundChild> child; + nsresult rv; + rv = PBackground::CreateEndpoints(socketChild->OtherPid(), + base::GetCurrentProcId(), &parent, &child); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create top level actor!"); + return; + } + + RefPtr<ChildImpl::SendInitBackgroundRunnable> runnable; + if (!NS_IsMainThread()) { + runnable = ChildImpl::SendInitBackgroundRunnable::Create( + std::move(parent), + [](Endpoint<PBackgroundParent>&& aParent) { + SocketProcessChild* socketChild = SocketProcessChild::GetSingleton(); + MOZ_ASSERT(socketChild); + + if (!socketChild->SendInitBackground(std::move(aParent))) { + MOZ_CRASH("Failed to create top level actor!"); + } + }, + aThreadLocalIndex); + if (!runnable) { + return; + } + } + + RefPtr<ChildImpl> strongActor = new ChildImpl(); + + if (!child.Bind(strongActor)) { + CRASH_IN_CHILD_PROCESS("Failed to bind ChildImpl!"); + return; + } + + strongActor->SetActorAlive(); + + if (NS_IsMainThread()) { + if (!socketChild->SendInitBackground(std::move(parent))) { + NS_WARNING("Failed to create top level actor!"); + return; + } + } else { + if (aMainEventTarget) { + MOZ_ALWAYS_SUCCEEDS( + aMainEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)); + } else { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable)); + } + + aThreadLocalInfo->mSendInitBackgroundRunnable = runnable; + } + + aThreadLocalInfo->mActor = strongActor; + strongActor.forget(aOutput); +} + +ChildImpl::ThreadInfoWrapper ChildImpl::sSocketAndParentProcessThreadInfo( + SocketParentActorCreateFunc); + +bool ChildImpl::sShutdownHasStarted = false; + +// ----------------------------------------------------------------------------- +// ParentImpl Implementation +// ----------------------------------------------------------------------------- + +// static +bool ParentImpl::IsOtherProcessActor(PBackgroundParent* aBackgroundActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + + return static_cast<ParentImpl*>(aBackgroundActor)->mIsOtherProcessActor; +} + +// static +already_AddRefed<ContentParent> ParentImpl::GetContentParent( + PBackgroundParent* aBackgroundActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + + auto actor = static_cast<ParentImpl*>(aBackgroundActor); + if (actor->mActorDestroyed) { + MOZ_ASSERT(false, "GetContentParent called after ActorDestroy was called!"); + return nullptr; + } + + if (actor->mContent) { + // We need to hand out a reference to our ContentParent but we also need to + // keep the one we have. We can't call AddRef here because ContentParent is + // not threadsafe so instead we dispatch a runnable to the main thread to do + // it for us. This is safe since we are guaranteed that our AddRef runnable + // will run before the reference we hand out can be released, and the + // ContentParent can't die as long as the existing reference is maintained. + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(NewNonOwningRunnableMethod( + "ContentParent::AddRef", actor->mContent, &ContentParent::AddRef))); + } + + return already_AddRefed<ContentParent>(actor->mContent.get()); +} + +// static +intptr_t ParentImpl::GetRawContentParentForComparison( + PBackgroundParent* aBackgroundActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + + auto actor = static_cast<ParentImpl*>(aBackgroundActor); + if (actor->mActorDestroyed) { + MOZ_ASSERT(false, + "GetRawContentParentForComparison called after ActorDestroy was " + "called!"); + return intptr_t(-1); + } + + return intptr_t(static_cast<ContentParent*>(actor->mContent.get())); +} + +// static +uint64_t ParentImpl::GetChildID(PBackgroundParent* aBackgroundActor) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + + auto actor = static_cast<ParentImpl*>(aBackgroundActor); + if (actor->mActorDestroyed) { + MOZ_ASSERT(false, "GetContentParent called after ActorDestroy was called!"); + return 0; + } + + if (actor->mContent) { + return actor->mContent->ChildID(); + } + + return 0; +} + +// static +bool ParentImpl::GetLiveActorArray( + PBackgroundParent* aBackgroundActor, + nsTArray<PBackgroundParent*>& aLiveActorArray) { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aBackgroundActor); + MOZ_ASSERT(aLiveActorArray.IsEmpty()); + + auto actor = static_cast<ParentImpl*>(aBackgroundActor); + if (actor->mActorDestroyed) { + MOZ_ASSERT(false, + "GetLiveActorArray called after ActorDestroy was called!"); + return false; + } + + if (!actor->mLiveActorArray) { + return true; + } + + for (ParentImpl* liveActor : *actor->mLiveActorArray) { + aLiveActorArray.AppendElement(liveActor); + } + + return true; +} + +// static +bool ParentImpl::Alloc(ContentParent* aContent, + Endpoint<PBackgroundParent>&& aEndpoint) { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT(aEndpoint.IsValid()); + + if (!sBackgroundThread && !CreateBackgroundThread()) { + NS_WARNING("Failed to create background thread!"); + return false; + } + + MOZ_ASSERT(sLiveActorsForBackgroundThread); + + sLiveActorCount++; + + RefPtr<ParentImpl> actor = new ParentImpl(aContent); + + if (NS_FAILED(sBackgroundThread->Dispatch(NS_NewRunnableFunction( + "Background::ParentImpl::ConnectActorRunnable", + [actor = std::move(actor), endpoint = std::move(aEndpoint), + liveActorArray = sLiveActorsForBackgroundThread]() mutable { + MOZ_ASSERT(endpoint.IsValid()); + MOZ_ASSERT(liveActorArray); + // Transfer ownership to this thread. If Open() fails then we will + // release this reference in Destroy. + ParentImpl* actorTmp; + actor.forget(&actorTmp); + + if (!endpoint.Bind(actorTmp)) { + actorTmp->Destroy(); + return; + } + + actorTmp->SetLiveActorArray(liveActorArray); + })))) { + NS_WARNING("Failed to dispatch connect runnable!"); + + MOZ_ASSERT(sLiveActorCount); + sLiveActorCount--; + } + + return true; +} + +// static +already_AddRefed<ChildImpl> ParentImpl::CreateActorForSameProcess( + nsIEventTarget* aMainEventTarget) { + AssertIsInMainProcess(); + + RefPtr<ParentImpl> parentActor; + nsCOMPtr<nsIThread> backgroundThread; + + if (NS_IsMainThread()) { + if (!sBackgroundThread && !CreateBackgroundThread()) { + NS_WARNING("Failed to create background thread!"); + return nullptr; + } + + MOZ_ASSERT(!sShutdownHasStarted); + + sLiveActorCount++; + + parentActor = new ParentImpl(); + backgroundThread = sBackgroundThread.get(); + } else { + RefPtr<CreateActorHelper> helper = new CreateActorHelper(); + + nsresult rv = helper->BlockAndGetResults(aMainEventTarget, parentActor, + backgroundThread); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + } + + RefPtr<ChildImpl> childActor = new ChildImpl(); + + MessageChannel* parentChannel = parentActor->GetIPCChannel(); + MOZ_ASSERT(parentChannel); + + if (!childActor->Open(parentChannel, backgroundThread, ChildSide)) { + NS_WARNING("Failed to open ChildImpl!"); + + // Can't release it here, we will release this reference in Destroy. + ParentImpl* actor; + parentActor.forget(&actor); + + actor->Destroy(); + + return nullptr; + } + + childActor->SetActorAlive(); + + // Make sure the parent knows it is same process. + parentActor->SetOtherProcessId(base::GetCurrentProcId()); + + // Now that Open() has succeeded transfer the ownership of the actors to IPDL. + Unused << parentActor.forget(); + + return childActor.forget(); +} + +// static +bool ParentImpl::CreateBackgroundThread() { + AssertIsInMainOrSocketProcess(); + 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<ParentImpl*>(1); + + if (!sShutdownTimer) { + MOZ_ASSERT(newShutdownTimer); + sShutdownTimer = newShutdownTimer; + } + + return true; +} + +// static +void ParentImpl::ShutdownBackgroundThread() { + AssertIsInMainOrSocketProcess(); + 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<ParentImpl*>> 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([&]() { 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) { + AssertIsInMainOrSocketProcess(); + 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<ParentImpl*> actorsToClose(liveActors->Clone()); + for (ParentImpl* 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! + + AssertIsInMainOrSocketProcess(); + + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( + NewNonOwningRunnableMethod("ParentImpl::MainThreadActorDestroy", this, + &ParentImpl::MainThreadActorDestroy))); +} + +void ParentImpl::MainThreadActorDestroy() { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + MOZ_ASSERT_IF(!mIsOtherProcessActor, !mContent); + + mContent = nullptr; + + MOZ_ASSERT(sLiveActorCount); + sLiveActorCount--; + + // This may be the last reference! + Release(); +} + +void ParentImpl::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsInMainOrSocketProcess(); + 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) { + AssertIsInMainOrSocketProcess(); + 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; +} + +nsresult ParentImpl::CreateActorHelper::BlockAndGetResults( + nsIEventTarget* aMainEventTarget, RefPtr<ParentImpl>& aParentActor, + nsCOMPtr<nsIThread>& aThread) { + AssertIsNotOnMainThread(); + + if (aMainEventTarget) { + MOZ_ALWAYS_SUCCEEDS(aMainEventTarget->Dispatch(this, NS_DISPATCH_NORMAL)); + } else { + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this)); + } + + mozilla::MonitorAutoLock lock(mMonitor); + while (mWaiting) { + lock.Wait(); + } + + if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) { + return mMainThreadResultCode; + } + + aParentActor = std::move(mParentActor); + aThread = std::move(mThread); + return NS_OK; +} + +nsresult ParentImpl::CreateActorHelper::RunOnMainThread() { + AssertIsOnMainThread(); + + if (!sBackgroundThread && !CreateBackgroundThread()) { + NS_WARNING("Failed to create background thread!"); + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(!sShutdownHasStarted); + + sLiveActorCount++; + + mParentActor = new ParentImpl(); + mThread = sBackgroundThread; + + return NS_OK; +} + +NS_IMETHODIMP +ParentImpl::CreateActorHelper::Run() { + AssertIsOnMainThread(); + + nsresult rv = RunOnMainThread(); + if (NS_WARN_IF(NS_FAILED(rv))) { + mMainThreadResultCode = rv; + } + + mozilla::MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(mWaiting); + + mWaiting = false; + lock.Notify(); + + return NS_OK; +} + +// ----------------------------------------------------------------------------- +// 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(); + sSocketAndContentProcessThreadInfo.Startup(); + sSocketAndParentProcessThreadInfo.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)); +} + +// static +void ChildImpl::Shutdown() { + AssertIsOnMainThread(); + + sParentAndContentProcessThreadInfo.Shutdown(); + sSocketAndContentProcessThreadInfo.Shutdown(); + sSocketAndParentProcessThreadInfo.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::GetFromThreadInfo( + nsIEventTarget* aMainEventTarget, ThreadLocalInfo* aThreadLocalInfo) { + MOZ_ASSERT(aThreadLocalInfo); + + if (aThreadLocalInfo->mActor) { + RefPtr<SendInitBackgroundRunnable>& runnable = + aThreadLocalInfo->mSendInitBackgroundRunnable; + + if (aMainEventTarget && runnable) { + // The SendInitBackgroundRunnable was already dispatched to the main + // thread to finish initialization of a new background child actor. + // However, the caller passed a custom main event target which indicates + // that synchronous blocking of the main thread is happening (done by + // creating a nested event target and spinning the event loop). + // It can happen that the SendInitBackgroundRunnable didn't have a chance + // to run before the synchronous blocking has occured. Unblocking of the + // main thread can depend on an IPC message received on this thread, so + // we have to dispatch the SendInitBackgroundRunnable to the custom main + // event target too, otherwise IPC will be only queueing messages on this + // thread. The runnable will run twice in the end, but that's a harmless + // race between the main and nested event queue of the main thread. + // There's a guard in the runnable implementation for calling + // SendInitBackground only once. + + MOZ_ALWAYS_SUCCEEDS( + aMainEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL)); + } + + return aThreadLocalInfo->mActor; + } + + return nullptr; +} + +/* static */ +PBackgroundChild* ChildImpl::GetOrCreateForCurrentThread( + nsIEventTarget* aMainEventTarget) { + return sParentAndContentProcessThreadInfo.GetOrCreateForCurrentThread( + aMainEventTarget); +} + +/* static */ +PBackgroundChild* ChildImpl::GetOrCreateSocketActorForCurrentThread( + nsIEventTarget* aMainEventTarget) { + return sSocketAndContentProcessThreadInfo.GetOrCreateForCurrentThread( + aMainEventTarget); +} + +/* static */ +PBackgroundChild* ChildImpl::GetOrCreateForSocketParentBridgeForCurrentThread( + nsIEventTarget* aMainEventTarget) { + return sSocketAndParentProcessThreadInfo.GetOrCreateForCurrentThread( + aMainEventTarget); +} + +// static +void ChildImpl::CloseForCurrentThread() { + MOZ_ASSERT(!NS_IsMainThread(), + "PBackground for the main thread should be shut down via " + "ChildImpl::Shutdown()."); + + sParentAndContentProcessThreadInfo.CloseForCurrentThread(); + sSocketAndContentProcessThreadInfo.CloseForCurrentThread(); + sSocketAndParentProcessThreadInfo.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::ThreadLocalDestructor(void* aThreadLocal) { + auto threadLocalInfo = static_cast<ThreadLocalInfo*>(aThreadLocal); + + if (threadLocalInfo) { + MOZ_ASSERT(threadLocalInfo->mClosed); + + if (threadLocalInfo->mActor) { + threadLocalInfo->mActor->Close(); + threadLocalInfo->mActor->AssertActorDestroyed(); + } + + if (threadLocalInfo->mSendInitBackgroundRunnable) { + threadLocalInfo->mSendInitBackgroundRunnable->ClearEventTarget(); + } + + 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; +} + +// static +already_AddRefed<ChildImpl::SendInitBackgroundRunnable> +ChildImpl::SendInitBackgroundRunnable::Create( + Endpoint<PBackgroundParent>&& aParent, + std::function<void(Endpoint<PBackgroundParent>&& aParent)>&& aFunc, + unsigned int aThreadLocalIndex) { + MOZ_ASSERT(!NS_IsMainThread()); + + RefPtr<SendInitBackgroundRunnable> runnable = new SendInitBackgroundRunnable( + std::move(aParent), std::move(aFunc), aThreadLocalIndex); + + WorkerPrivate* workerPrivate = mozilla::dom::GetCurrentThreadWorkerPrivate(); + if (!workerPrivate) { + return runnable.forget(); + } + + workerPrivate->AssertIsOnWorkerThread(); + + runnable->mWorkerRef = StrongWorkerRef::Create( + workerPrivate, "ChildImpl::SendInitBackgroundRunnable"); + if (NS_WARN_IF(!runnable->mWorkerRef)) { + return nullptr; + } + + return runnable.forget(); +} + +NS_IMETHODIMP +ChildImpl::SendInitBackgroundRunnable::Run() { + if (NS_IsMainThread()) { + if (mSentInitBackground) { + return NS_OK; + } + + mSentInitBackground = true; + + mSendInitfunc(std::move(mParent)); + + nsCOMPtr<nsISerialEventTarget> owningEventTarget; + { + mozilla::MutexAutoLock lock(mMutex); + owningEventTarget = mOwningEventTarget; + } + + if (!owningEventTarget) { + return NS_OK; + } + + nsresult rv = owningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + ClearEventTarget(); + + auto threadLocalInfo = + static_cast<ThreadLocalInfo*>(PR_GetThreadPrivate(mThreadLocalIndex)); + + if (!threadLocalInfo) { + return NS_OK; + } + + threadLocalInfo->mSendInitBackgroundRunnable = nullptr; + + return NS_OK; +} diff --git a/ipc/glue/BackgroundParent.h b/ipc/glue/BackgroundParent.h new file mode 100644 index 0000000000..1955aabcb6 --- /dev/null +++ b/ipc/glue/BackgroundParent.h @@ -0,0 +1,120 @@ +/* -*- 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/ipc/Transport.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 PBackgroundParent; + +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::dom::ContentParent; + + typedef base::ProcessId ProcessId; + typedef mozilla::dom::BlobImpl BlobImpl; + typedef mozilla::dom::ContentParent ContentParent; + typedef mozilla::ipc::Transport Transport; + friend class mozilla::net::SocketProcessBridgeParent; + friend class mozilla::net::SocketProcessParent; + + 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 the ContentParent associated with the parent actor if + // the parent actor corresponds to a child actor from another 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. However, + // ContentParent is not threadsafe and the returned pointer may not be used on + // any thread other than the main thread. Callers must take care to use (and + // release) the returned pointer appropriately. + static already_AddRefed<ContentParent> GetContentParent( + PBackgroundParent* aBackgroundActor); + + // Get a value that represents the ContentParent associated with the parent + // actor for comparison. The value is not guaranteed to uniquely identify the + // ContentParent after the ContentParent has died. This function may only be + // called on the background thread. + static intptr_t GetRawContentParentForComparison( + PBackgroundParent* aBackgroundActor); + + static uint64_t GetChildID(PBackgroundParent* aBackgroundActor); + + static bool GetLiveActorArray(PBackgroundParent* aBackgroundActor, + nsTArray<PBackgroundParent*>& aLiveActorArray); + + private: + // Only called by ContentParent for cross-process actors. + static bool Alloc(ContentParent* aContent, + Endpoint<PBackgroundParent>&& aEndpoint); + + // Called by SocketProcessBridgeParent and SocketProcessParent for + // cross-process actors. + static bool Alloc(Endpoint<PBackgroundParent>&& 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()); } + +inline void AssertIsInMainOrSocketProcess() { + MOZ_ASSERT(XRE_IsParentProcess() || XRE_IsSocketProcess()); +} + +} // 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..ec3b6d4fbe --- /dev/null +++ b/ipc/glue/BackgroundParentImpl.cpp @@ -0,0 +1,1411 @@ +/* -*- 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" +#include "FileDescriptorSetParent.h" +#ifdef MOZ_WEBRTC +# include "CamerasParent.h" +#endif +#include "mozilla/Assertions.h" +#include "mozilla/Preferences.h" +#include "mozilla/RDDProcessManager.h" +#include "mozilla/RefPtr.h" +#include "mozilla/RemoteLazyInputStreamParent.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/dom/ClientManagerActors.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/DOMTypes.h" +#include "mozilla/dom/EndpointForReportParent.h" +#include "mozilla/dom/FileCreatorParent.h" +#include "mozilla/dom/FileSystemBase.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/MediaTransportParent.h" +#include "mozilla/dom/MessagePortParent.h" +#include "mozilla/dom/PGamepadEventChannelParent.h" +#include "mozilla/dom/PGamepadTestChannelParent.h" +#include "mozilla/dom/RemoteWorkerControllerParent.h" +#include "mozilla/dom/RemoteWorkerParent.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/cache/ActorUtils.h" +#include "mozilla/dom/indexedDB/ActorsParent.h" +#include "mozilla/dom/localstorage/ActorsParent.h" +#include "mozilla/dom/network/UDPSocketParent.h" +#include "mozilla/dom/quota/ActorsParent.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/IPCStreamAlloc.h" +#include "mozilla/ipc/IdleSchedulerParent.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/ipc/PBackgroundTestParent.h" +#include "mozilla/ipc/PChildToParentStreamParent.h" +#include "mozilla/ipc/PParentToChildStreamParent.h" +#include "mozilla/media/MediaParent.h" +#include "mozilla/net/BackgroundDataBridgeParent.h" +#include "mozilla/net/HttpBackgroundChannelParent.h" +#include "mozilla/psm/VerifySSLServerCertParent.h" +#include "nsNetUtil.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "nsTraceRefcnt.h" +#include "nsXULAppAPI.h" + +#ifdef DISABLE_ASSERTS_FOR_FUZZING +# define ASSERT_UNLESS_FUZZING(...) \ + do { \ + } while (0) +#else +# define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false) +#endif + +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; + +BackgroundParentImpl::BackgroundParentImpl() { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + + MOZ_COUNT_CTOR(mozilla::ipc::BackgroundParentImpl); +} + +BackgroundParentImpl::~BackgroundParentImpl() { + AssertIsInMainOrSocketProcess(); + AssertIsOnMainThread(); + + MOZ_COUNT_DTOR(mozilla::ipc::BackgroundParentImpl); +} + +void BackgroundParentImpl::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); +} + +already_AddRefed<net::PBackgroundDataBridgeParent> +BackgroundParentImpl::AllocPBackgroundDataBridgeParent( + const uint64_t& aChannelID) { + MOZ_ASSERT(XRE_IsSocketProcess(), "Should be in socket process"); + AssertIsOnBackgroundThread(); + + RefPtr<net::BackgroundDataBridgeParent> actor = + new net::BackgroundDataBridgeParent(aChannelID); + return actor.forget(); +} + +BackgroundParentImpl::PBackgroundTestParent* +BackgroundParentImpl::AllocPBackgroundTestParent(const nsCString& aTestArg) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return new TestParent(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBackgroundTestConstructor( + PBackgroundTestParent* aActor, const nsCString& aTestArg) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!PBackgroundTestParent::Send__delete__(aActor, aTestArg)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPBackgroundTestParent( + PBackgroundTestParent* aActor) { + AssertIsInMainOrSocketProcess(); + 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; + + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return AllocPBackgroundIDBFactoryParent(aLoggingInfo); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundIDBFactoryConstructor( + PBackgroundIDBFactoryParent* aActor, const LoggingInfo& aLoggingInfo) { + using mozilla::dom::indexedDB::RecvPBackgroundIDBFactoryConstructor; + + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!RecvPBackgroundIDBFactoryConstructor(aActor, aLoggingInfo)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +auto BackgroundParentImpl::AllocPBackgroundIndexedDBUtilsParent() + -> PBackgroundIndexedDBUtilsParent* { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::indexedDB::AllocPBackgroundIndexedDBUtilsParent(); +} + +bool BackgroundParentImpl::DeallocPBackgroundIndexedDBUtilsParent( + PBackgroundIndexedDBUtilsParent* aActor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::indexedDB::DeallocPBackgroundIndexedDBUtilsParent( + aActor); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvFlushPendingFileDeletions() { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + if (!mozilla::dom::indexedDB::RecvFlushPendingFileDeletions()) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +BackgroundParentImpl::PBackgroundSDBConnectionParent* +BackgroundParentImpl::AllocPBackgroundSDBConnectionParent( + const PersistenceType& aPersistenceType, + const PrincipalInfo& aPrincipalInfo) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundSDBConnectionParent(aPersistenceType, + aPrincipalInfo); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundSDBConnectionConstructor( + PBackgroundSDBConnectionParent* aActor, + const PersistenceType& aPersistenceType, + const PrincipalInfo& aPrincipalInfo) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!mozilla::dom::RecvPBackgroundSDBConnectionConstructor( + aActor, aPersistenceType, aPrincipalInfo)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPBackgroundSDBConnectionParent( + PBackgroundSDBConnectionParent* aActor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPBackgroundSDBConnectionParent(aActor); +} + +BackgroundParentImpl::PBackgroundLSDatabaseParent* +BackgroundParentImpl::AllocPBackgroundLSDatabaseParent( + const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId, + const uint64_t& aDatastoreId) { + AssertIsInMainOrSocketProcess(); + 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) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!mozilla::dom::RecvPBackgroundLSDatabaseConstructor( + aActor, aPrincipalInfo, aPrivateBrowsingId, aDatastoreId)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPBackgroundLSDatabaseParent( + PBackgroundLSDatabaseParent* aActor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPBackgroundLSDatabaseParent(aActor); +} + +BackgroundParentImpl::PBackgroundLSObserverParent* +BackgroundParentImpl::AllocPBackgroundLSObserverParent( + const uint64_t& aObserverId) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundLSObserverParent(aObserverId); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundLSObserverConstructor( + PBackgroundLSObserverParent* aActor, const uint64_t& aObserverId) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!mozilla::dom::RecvPBackgroundLSObserverConstructor(aActor, + aObserverId)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPBackgroundLSObserverParent( + PBackgroundLSObserverParent* aActor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPBackgroundLSObserverParent(aActor); +} + +BackgroundParentImpl::PBackgroundLSRequestParent* +BackgroundParentImpl::AllocPBackgroundLSRequestParent( + const LSRequestParams& aParams) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundLSRequestParent(this, aParams); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundLSRequestConstructor( + PBackgroundLSRequestParent* aActor, const LSRequestParams& aParams) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!mozilla::dom::RecvPBackgroundLSRequestConstructor(aActor, aParams)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPBackgroundLSRequestParent( + PBackgroundLSRequestParent* aActor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPBackgroundLSRequestParent(aActor); +} + +BackgroundParentImpl::PBackgroundLSSimpleRequestParent* +BackgroundParentImpl::AllocPBackgroundLSSimpleRequestParent( + const LSSimpleRequestParams& aParams) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundLSSimpleRequestParent(this, aParams); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundLSSimpleRequestConstructor( + PBackgroundLSSimpleRequestParent* aActor, + const LSSimpleRequestParams& aParams) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + if (!mozilla::dom::RecvPBackgroundLSSimpleRequestConstructor(aActor, + aParams)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPBackgroundLSSimpleRequestParent( + PBackgroundLSSimpleRequestParent* aActor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPBackgroundLSSimpleRequestParent(aActor); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvLSClearPrivateBrowsing() { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + if (BackgroundParent::IsOtherProcessActor(this)) { + return IPC_FAIL_NO_REASON(this); + } + + if (!mozilla::dom::RecvLSClearPrivateBrowsing()) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +BackgroundParentImpl::PBackgroundLocalStorageCacheParent* +BackgroundParentImpl::AllocPBackgroundLocalStorageCacheParent( + const PrincipalInfo& aPrincipalInfo, const nsCString& aOriginKey, + const uint32_t& aPrivateBrowsingId) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundLocalStorageCacheParent( + aPrincipalInfo, aOriginKey, aPrivateBrowsingId); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPBackgroundLocalStorageCacheConstructor( + PBackgroundLocalStorageCacheParent* aActor, + const PrincipalInfo& aPrincipalInfo, const nsCString& aOriginKey, + const uint32_t& aPrivateBrowsingId) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::RecvPBackgroundLocalStorageCacheConstructor( + this, aActor, aPrincipalInfo, aOriginKey, aPrivateBrowsingId); +} + +bool BackgroundParentImpl::DeallocPBackgroundLocalStorageCacheParent( + PBackgroundLocalStorageCacheParent* aActor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPBackgroundLocalStorageCacheParent(aActor); +} + +auto BackgroundParentImpl::AllocPBackgroundStorageParent( + const nsString& aProfilePath, const uint32_t& aPrivateBrowsingId) + -> PBackgroundStorageParent* { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::AllocPBackgroundStorageParent(aProfilePath, + aPrivateBrowsingId); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBackgroundStorageConstructor( + PBackgroundStorageParent* aActor, const nsString& aProfilePath, + const uint32_t& aPrivateBrowsingId) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::RecvPBackgroundStorageConstructor(aActor, aProfilePath, + aPrivateBrowsingId); +} + +bool BackgroundParentImpl::DeallocPBackgroundStorageParent( + PBackgroundStorageParent* aActor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPBackgroundStorageParent(aActor); +} + +already_AddRefed<BackgroundParentImpl::PBackgroundSessionStorageManagerParent> +BackgroundParentImpl::AllocPBackgroundSessionStorageManagerParent( + const uint64_t& aTopContextId) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return dom::AllocPBackgroundSessionStorageManagerParent(aTopContextId); +} + +already_AddRefed<PIdleSchedulerParent> +BackgroundParentImpl::AllocPIdleSchedulerParent() { + AssertIsOnBackgroundThread(); + RefPtr<IdleSchedulerParent> actor = new IdleSchedulerParent(); + return actor.forget(); +} + +mozilla::dom::PRemoteWorkerParent* +BackgroundParentImpl::AllocPRemoteWorkerParent(const RemoteWorkerData& aData) { + RefPtr<dom::RemoteWorkerParent> agent = new dom::RemoteWorkerParent(); + return agent.forget().take(); +} + +bool BackgroundParentImpl::DeallocPRemoteWorkerParent( + mozilla::dom::PRemoteWorkerParent* aActor) { + RefPtr<mozilla::dom::RemoteWorkerParent> actor = + dont_AddRef(static_cast<mozilla::dom::RemoteWorkerParent*>(aActor)); + return true; +} + +dom::PRemoteWorkerControllerParent* +BackgroundParentImpl::AllocPRemoteWorkerControllerParent( + const dom::RemoteWorkerData& aRemoteWorkerData) { + RefPtr<dom::RemoteWorkerControllerParent> actor = + new dom::RemoteWorkerControllerParent(aRemoteWorkerData); + return actor.forget().take(); +} + +IPCResult BackgroundParentImpl::RecvPRemoteWorkerControllerConstructor( + dom::PRemoteWorkerControllerParent* aActor, + const dom::RemoteWorkerData& aRemoteWorkerData) { + MOZ_ASSERT(aActor); + + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPRemoteWorkerControllerParent( + dom::PRemoteWorkerControllerParent* aActor) { + RefPtr<dom::RemoteWorkerControllerParent> actor = + dont_AddRef(static_cast<dom::RemoteWorkerControllerParent*>(aActor)); + return true; +} + +mozilla::dom::PRemoteWorkerServiceParent* +BackgroundParentImpl::AllocPRemoteWorkerServiceParent() { + return new mozilla::dom::RemoteWorkerServiceParent(); +} + +IPCResult BackgroundParentImpl::RecvPRemoteWorkerServiceConstructor( + PRemoteWorkerServiceParent* aActor) { + mozilla::dom::RemoteWorkerServiceParent* actor = + static_cast<mozilla::dom::RemoteWorkerServiceParent*>(aActor); + + RefPtr<ContentParent> parent = BackgroundParent::GetContentParent(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()); + NS_ReleaseOnMainThread("ContentParent release", parent.forget()); + } + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPRemoteWorkerServiceParent( + mozilla::dom::PRemoteWorkerServiceParent* aActor) { + delete aActor; + return true; +} + +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 nsString& aFullPath, const nsString& aType, const nsString& 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 nsString& aFullPath, + const nsString& aType, const nsString& aName, + const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck, + const bool& aIsFromNsIFile) { + bool isFileRemoteType = false; + + // If the ContentParent is null we are dealing with a same-process actor. + RefPtr<ContentParent> parent = BackgroundParent::GetContentParent(this); + if (!parent) { + isFileRemoteType = true; + } else { + isFileRemoteType = parent->GetRemoteType() == FILE_REMOTE_TYPE; + NS_ReleaseOnMainThread("ContentParent release", parent.forget()); + } + + 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<PRemoteLazyInputStreamParent> +BackgroundParentImpl::AllocPRemoteLazyInputStreamParent(const nsID& aID, + const uint64_t& aSize) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<RemoteLazyInputStreamParent> actor = + RemoteLazyInputStreamParent::Create(aID, aSize, this); + return actor.forget(); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPRemoteLazyInputStreamConstructor( + PRemoteLazyInputStreamParent* aActor, const nsID& aID, + const uint64_t& aSize) { + if (!static_cast<RemoteLazyInputStreamParent*>(aActor)->HasValidStream()) { + return IPC_FAIL_NO_REASON(this); + } + + return IPC_OK(); +} + +PFileDescriptorSetParent* BackgroundParentImpl::AllocPFileDescriptorSetParent( + const FileDescriptor& aFileDescriptor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return new FileDescriptorSetParent(aFileDescriptor); +} + +bool BackgroundParentImpl::DeallocPFileDescriptorSetParent( + PFileDescriptorSetParent* aActor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + delete static_cast<FileDescriptorSetParent*>(aActor); + return true; +} + +PChildToParentStreamParent* +BackgroundParentImpl::AllocPChildToParentStreamParent() { + return mozilla::ipc::AllocPChildToParentStreamParent(); +} + +bool BackgroundParentImpl::DeallocPChildToParentStreamParent( + PChildToParentStreamParent* aActor) { + delete aActor; + return true; +} + +PParentToChildStreamParent* +BackgroundParentImpl::AllocPParentToChildStreamParent() { + MOZ_CRASH( + "PParentToChildStreamParent actors should be manually constructed!"); +} + +bool BackgroundParentImpl::DeallocPParentToChildStreamParent( + PParentToChildStreamParent* aActor) { + delete aActor; + return true; +} + +BackgroundParentImpl::PVsyncParent* BackgroundParentImpl::AllocPVsyncParent() { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<mozilla::dom::VsyncParent> actor = new mozilla::dom::VsyncParent(); + actor->UpdateVsyncSource(nullptr); + // There still has one ref-count after return, and it will be released in + // DeallocPVsyncParent(). + return actor.forget().take(); +} + +bool BackgroundParentImpl::DeallocPVsyncParent(PVsyncParent* aActor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + // This actor already has one ref-count. Please check AllocPVsyncParent(). + RefPtr<mozilla::dom::VsyncParent> actor = + dont_AddRef(static_cast<mozilla::dom::VsyncParent*>(aActor)); + return true; +} + +camera::PCamerasParent* BackgroundParentImpl::AllocPCamerasParent() { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + +#ifdef MOZ_WEBRTC + RefPtr<mozilla::camera::CamerasParent> actor = + mozilla::camera::CamerasParent::Create(); + return actor.forget().take(); +#else + return nullptr; +#endif +} + +bool BackgroundParentImpl::DeallocPCamerasParent( + camera::PCamerasParent* aActor) { + AssertIsInMainOrSocketProcess(); + 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 nsCString& /* unused */) + -> PUDPSocketParent* { + RefPtr<UDPSocketParent> p = new UDPSocketParent(this); + + return p.forget().take(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPUDPSocketConstructor( + PUDPSocketParent* aActor, const Maybe<PrincipalInfo>& aOptionalPrincipal, + const nsCString& aFilter) { + AssertIsInMainOrSocketProcess(); + 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; +} + +already_AddRefed<mozilla::psm::PVerifySSLServerCertParent> +BackgroundParentImpl::AllocPVerifySSLServerCertParent( + const ByteArray& aServerCert, const nsTArray<ByteArray>& aPeerCertChain, + const nsCString& aHostName, const int32_t& aPort, + const OriginAttributes& aOriginAttributes, + const Maybe<ByteArray>& aStapledOCSPResponse, + const Maybe<ByteArray>& aSctsFromTLSExtension, + const Maybe<DelegatedCredentialInfoArg>& aDcInfo, + const uint32_t& aProviderFlags, const uint32_t& aCertVerifierFlags) { + RefPtr<mozilla::psm::VerifySSLServerCertParent> parent = + new mozilla::psm::VerifySSLServerCertParent(); + return parent.forget(); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvPVerifySSLServerCertConstructor( + PVerifySSLServerCertParent* aActor, const ByteArray& aServerCert, + nsTArray<ByteArray>&& aPeerCertChain, const nsCString& aHostName, + const int32_t& aPort, const OriginAttributes& aOriginAttributes, + const Maybe<ByteArray>& aStapledOCSPResponse, + const Maybe<ByteArray>& aSctsFromTLSExtension, + const Maybe<DelegatedCredentialInfoArg>& aDcInfo, + const uint32_t& aProviderFlags, const uint32_t& aCertVerifierFlags) { + mozilla::psm::VerifySSLServerCertParent* authCert = + static_cast<mozilla::psm::VerifySSLServerCertParent*>(aActor); + if (!authCert->Dispatch(aServerCert, std::move(aPeerCertChain), aHostName, + aPort, aOriginAttributes, aStapledOCSPResponse, + aSctsFromTLSExtension, aDcInfo, aProviderFlags, + aCertVerifierFlags)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::dom::PBroadcastChannelParent* +BackgroundParentImpl::AllocPBroadcastChannelParent( + const PrincipalInfo& aPrincipalInfo, const nsCString& aOrigin, + const nsString& aChannel) { + AssertIsInMainOrSocketProcess(); + 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 { + +struct MOZ_STACK_CLASS NullifyContentParentRAII { + explicit NullifyContentParentRAII(RefPtr<ContentParent>& aContentParent) + : mContentParent(aContentParent) {} + + ~NullifyContentParentRAII() { mContentParent = nullptr; } + + RefPtr<ContentParent>& mContentParent; +}; + +class CheckPrincipalRunnable final : public Runnable { + public: + CheckPrincipalRunnable(already_AddRefed<ContentParent> aParent, + const PrincipalInfo& aPrincipalInfo, + const nsCString& aOrigin) + : Runnable("ipc::CheckPrincipalRunnable"), + mContentParent(aParent), + mPrincipalInfo(aPrincipalInfo), + mOrigin(aOrigin) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + MOZ_ASSERT(mContentParent); + } + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + + NullifyContentParentRAII raii(mContentParent); + + auto principalOrErr = PrincipalInfoToPrincipal(mPrincipalInfo); + if (NS_WARN_IF(principalOrErr.isErr())) { + mContentParent->KillHard( + "BroadcastChannel killed: PrincipalInfoToPrincipal failed."); + } + + nsAutoCString origin; + nsresult rv = principalOrErr.unwrap()->GetOrigin(origin); + if (NS_FAILED(rv)) { + mContentParent->KillHard( + "BroadcastChannel killed: principal::GetOrigin failed."); + return NS_OK; + } + + if (NS_WARN_IF(!mOrigin.Equals(origin))) { + mContentParent->KillHard( + "BroadcastChannel killed: origins do not match."); + return NS_OK; + } + + return NS_OK; + } + + private: + RefPtr<ContentParent> mContentParent; + PrincipalInfo mPrincipalInfo; + nsCString mOrigin; +}; + +} // namespace + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBroadcastChannelConstructor( + PBroadcastChannelParent* actor, const PrincipalInfo& aPrincipalInfo, + const nsCString& aOrigin, const nsString& aChannel) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<ContentParent> parent = BackgroundParent::GetContentParent(this); + + // If the ContentParent is null we are dealing with a same-process actor. + if (!parent) { + return IPC_OK(); + } + + RefPtr<CheckPrincipalRunnable> runnable = + new CheckPrincipalRunnable(parent.forget(), aPrincipalInfo, aOrigin); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable)); + + return IPC_OK(); +} + +bool BackgroundParentImpl::DeallocPBroadcastChannelParent( + PBroadcastChannelParent* aActor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + delete static_cast<BroadcastChannelParent*>(aActor); + return true; +} + +mozilla::dom::PServiceWorkerManagerParent* +BackgroundParentImpl::AllocPServiceWorkerManagerParent() { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<dom::ServiceWorkerManagerParent> agent = + new dom::ServiceWorkerManagerParent(); + return agent.forget().take(); +} + +bool BackgroundParentImpl::DeallocPServiceWorkerManagerParent( + PServiceWorkerManagerParent* aActor) { + AssertIsInMainOrSocketProcess(); + 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() { + AssertIsInMainOrSocketProcess(); + 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(); +} + +PCacheStorageParent* BackgroundParentImpl::AllocPCacheStorageParent( + const Namespace& aNamespace, const PrincipalInfo& aPrincipalInfo) { + return dom::cache::AllocPCacheStorageParent(this, aNamespace, aPrincipalInfo); +} + +bool BackgroundParentImpl::DeallocPCacheStorageParent( + PCacheStorageParent* aActor) { + dom::cache::DeallocPCacheStorageParent(aActor); + return true; +} + +PCacheParent* BackgroundParentImpl::AllocPCacheParent() { + MOZ_CRASH("CacheParent actor must be provided to PBackground manager"); + return nullptr; +} + +bool BackgroundParentImpl::DeallocPCacheParent(PCacheParent* aActor) { + dom::cache::DeallocPCacheParent(aActor); + return true; +} + +already_AddRefed<PCacheStreamControlParent> +BackgroundParentImpl::AllocPCacheStreamControlParent() { + MOZ_CRASH( + "CacheStreamControlParent actor must be provided to PBackground manager"); + return nullptr; +} + +PMessagePortParent* BackgroundParentImpl::AllocPMessagePortParent( + const nsID& aUUID, const nsID& aDestinationUUID, + const uint32_t& aSequenceID) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return new MessagePortParent(aUUID); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPMessagePortConstructor( + PMessagePortParent* aActor, const nsID& aUUID, const nsID& aDestinationUUID, + const uint32_t& aSequenceID) { + AssertIsInMainOrSocketProcess(); + 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) { + AssertIsInMainOrSocketProcess(); + 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) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + if (!MessagePortParent::ForceClose(aUUID, aDestinationUUID, aSequenceID)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +BackgroundParentImpl::PQuotaParent* BackgroundParentImpl::AllocPQuotaParent() { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + return mozilla::dom::quota::AllocPQuotaParent(); +} + +bool BackgroundParentImpl::DeallocPQuotaParent(PQuotaParent* aActor) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + return mozilla::dom::quota::DeallocPQuotaParent(aActor); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvShutdownQuotaManager() { + AssertIsInMainOrSocketProcess(); + 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() { + AssertIsInMainOrSocketProcess(); + 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) { + AssertIsInMainOrSocketProcess(); + 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) { + AssertIsInMainOrSocketProcess(); + 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(); +} + +already_AddRefed<dom::PFileSystemRequestParent> +BackgroundParentImpl::AllocPFileSystemRequestParent( + const FileSystemParams& aParams) { + AssertIsInMainOrSocketProcess(); + 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) { + AssertIsInMainOrSocketProcess(); + 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); + AssertIsInMainOrSocketProcess(); + 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(); +} + +PMIDIPortParent* BackgroundParentImpl::AllocPMIDIPortParent( + const MIDIPortInfo& aPortInfo, const bool& aSysexEnabled) { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<MIDIPortParent> result = new MIDIPortParent(aPortInfo, aSysexEnabled); + return result.forget().take(); +} + +bool BackgroundParentImpl::DeallocPMIDIPortParent(PMIDIPortParent* aActor) { + MOZ_ASSERT(aActor); + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<MIDIPortParent> parent = + dont_AddRef(static_cast<MIDIPortParent*>(aActor)); + parent->Teardown(); + return true; +} + +PMIDIManagerParent* BackgroundParentImpl::AllocPMIDIManagerParent() { + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<MIDIManagerParent> result = new MIDIManagerParent(); + MIDIPlatformService::Get()->AddManager(result); + return result.forget().take(); +} + +bool BackgroundParentImpl::DeallocPMIDIManagerParent( + PMIDIManagerParent* aActor) { + MOZ_ASSERT(aActor); + AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); + + RefPtr<MIDIManagerParent> parent = + dont_AddRef(static_cast<MIDIManagerParent*>(aActor)); + parent->Teardown(); + return true; +} + +mozilla::dom::PClientManagerParent* +BackgroundParentImpl::AllocPClientManagerParent() { + return mozilla::dom::AllocClientManagerParent(); +} + +bool BackgroundParentImpl::DeallocPClientManagerParent( + mozilla::dom::PClientManagerParent* aActor) { + return mozilla::dom::DeallocClientManagerParent(aActor); +} + +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(); +} + +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 nsString& aGroupName, const PrincipalInfo& aPrincipalInfo) { + RefPtr<dom::EndpointForReportParent> actor = + new dom::EndpointForReportParent(); + return actor.forget().take(); +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvPEndpointForReportConstructor( + PEndpointForReportParent* aActor, const nsString& aGroupName, + const PrincipalInfo& aPrincipalInfo) { + static_cast<dom::EndpointForReportParent*>(aActor)->Run(aGroupName, + aPrincipalInfo); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +BackgroundParentImpl::RecvEnsureRDDProcessAndCreateBridge( + EnsureRDDProcessAndCreateBridgeResolver&& aResolver) { + RDDProcessManager* rdd = RDDProcessManager::Get(); + using Type = + Tuple<const nsresult&, Endpoint<mozilla::PRemoteDecoderManagerChild>&&>; + if (!rdd) { + aResolver( + Type(NS_ERROR_NOT_AVAILABLE, Endpoint<PRemoteDecoderManagerChild>())); + } else { + rdd->EnsureRDDProcessAndCreateBridge(OtherPid()) + ->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(); +} + +bool BackgroundParentImpl::DeallocPEndpointForReportParent( + PEndpointForReportParent* aActor) { + RefPtr<dom::EndpointForReportParent> actor = + dont_AddRef(static_cast<dom::EndpointForReportParent*>(aActor)); + return true; +} + +mozilla::ipc::IPCResult BackgroundParentImpl::RecvRemoveEndpoint( + const nsString& aGroupName, const nsCString& aEndpointURL, + const PrincipalInfo& aPrincipalInfo) { + NS_DispatchToMainThread( + NS_NewRunnableFunction("BackgroundParentImpl::RecvRemoveEndpoint(", + [aGroupName, aEndpointURL, aPrincipalInfo]() { + dom::ReportingHeader::RemoveEndpoint( + aGroupName, aEndpointURL, aPrincipalInfo); + })); + + return IPC_OK(); +} + +dom::PMediaTransportParent* BackgroundParentImpl::AllocPMediaTransportParent() { +#ifdef MOZ_WEBRTC + return new MediaTransportParent; +#else + return nullptr; +#endif +} + +bool BackgroundParentImpl::DeallocPMediaTransportParent( + dom::PMediaTransportParent* aActor) { +#ifdef MOZ_WEBRTC + delete aActor; +#endif + return true; +} + +PParentToChildStreamParent* +BackgroundParentImpl::SendPParentToChildStreamConstructor( + PParentToChildStreamParent* aActor) { + return PBackgroundParent::SendPParentToChildStreamConstructor(aActor); +} + +PFileDescriptorSetParent* +BackgroundParentImpl::SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) { + return PBackgroundParent::SendPFileDescriptorSetConstructor(aFD); +} + +} // namespace mozilla::ipc + +void TestParent::ActorDestroy(ActorDestroyReason aWhy) { + mozilla::ipc::AssertIsInMainOrSocketProcess(); + AssertIsOnBackgroundThread(); +} diff --git a/ipc/glue/BackgroundParentImpl.h b/ipc/glue/BackgroundParentImpl.h new file mode 100644 index 0000000000..ecd9fb493e --- /dev/null +++ b/ipc/glue/BackgroundParentImpl.h @@ -0,0 +1,411 @@ +/* -*- 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/Attributes.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/PBackgroundParent.h" + +namespace mozilla { +namespace ipc { + +// Instances of this class should never be created directly. This class is meant +// to be inherited in BackgroundImpl. +class BackgroundParentImpl : public PBackgroundParent, + public ParentToChildStreamActorManager { + public: + PParentToChildStreamParent* SendPParentToChildStreamConstructor( + PParentToChildStreamParent* aActor) override; + PFileDescriptorSetParent* SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) override; + + protected: + BackgroundParentImpl(); + virtual ~BackgroundParentImpl(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + PBackgroundTestParent* AllocPBackgroundTestParent( + const nsCString& aTestArg) override; + + mozilla::ipc::IPCResult RecvPBackgroundTestConstructor( + PBackgroundTestParent* aActor, const nsCString& aTestArg) override; + + bool DeallocPBackgroundTestParent(PBackgroundTestParent* aActor) override; + + already_AddRefed<PBackgroundIDBFactoryParent> + AllocPBackgroundIDBFactoryParent(const LoggingInfo& aLoggingInfo) override; + + already_AddRefed<net::PBackgroundDataBridgeParent> + AllocPBackgroundDataBridgeParent(const uint64_t& aChannelID) override; + + mozilla::ipc::IPCResult RecvPBackgroundIDBFactoryConstructor( + PBackgroundIDBFactoryParent* aActor, + const LoggingInfo& aLoggingInfo) override; + + PBackgroundIndexedDBUtilsParent* AllocPBackgroundIndexedDBUtilsParent() + override; + + bool DeallocPBackgroundIndexedDBUtilsParent( + PBackgroundIndexedDBUtilsParent* aActor) override; + + mozilla::ipc::IPCResult RecvFlushPendingFileDeletions() override; + + PBackgroundSDBConnectionParent* AllocPBackgroundSDBConnectionParent( + const PersistenceType& aPersistenceType, + const PrincipalInfo& aPrincipalInfo) override; + + mozilla::ipc::IPCResult RecvPBackgroundSDBConnectionConstructor( + PBackgroundSDBConnectionParent* aActor, + const PersistenceType& aPersistenceType, + const PrincipalInfo& aPrincipalInfo) override; + + bool DeallocPBackgroundSDBConnectionParent( + PBackgroundSDBConnectionParent* aActor) override; + + 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; + + bool DeallocPBackgroundLSDatabaseParent( + PBackgroundLSDatabaseParent* aActor) 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; + + mozilla::ipc::IPCResult RecvLSClearPrivateBrowsing() override; + + PBackgroundLocalStorageCacheParent* AllocPBackgroundLocalStorageCacheParent( + const PrincipalInfo& aPrincipalInfo, const nsCString& aOriginKey, + const uint32_t& aPrivateBrowsingId) override; + + mozilla::ipc::IPCResult RecvPBackgroundLocalStorageCacheConstructor( + PBackgroundLocalStorageCacheParent* aActor, + const PrincipalInfo& aPrincipalInfo, const nsCString& aOriginKey, + const uint32_t& aPrivateBrowsingId) override; + + bool DeallocPBackgroundLocalStorageCacheParent( + PBackgroundLocalStorageCacheParent* aActor) override; + + PBackgroundStorageParent* AllocPBackgroundStorageParent( + const nsString& aProfilePath, + const uint32_t& aPrivateBrowsingId) override; + + mozilla::ipc::IPCResult RecvPBackgroundStorageConstructor( + PBackgroundStorageParent* aActor, const nsString& aProfilePath, + const uint32_t& aPrivateBrowsingId) override; + + bool DeallocPBackgroundStorageParent( + PBackgroundStorageParent* aActor) override; + + already_AddRefed<PBackgroundSessionStorageManagerParent> + AllocPBackgroundSessionStorageManagerParent( + const uint64_t& aTopContextId) override; + + already_AddRefed<PIdleSchedulerParent> AllocPIdleSchedulerParent() override; + + already_AddRefed<PRemoteLazyInputStreamParent> + AllocPRemoteLazyInputStreamParent(const nsID& aID, + const uint64_t& aSize) override; + + mozilla::ipc::IPCResult RecvPRemoteLazyInputStreamConstructor( + PRemoteLazyInputStreamParent* aActor, const nsID& aID, + const uint64_t& aSize) override; + + PTemporaryIPCBlobParent* AllocPTemporaryIPCBlobParent() override; + + mozilla::ipc::IPCResult RecvPTemporaryIPCBlobConstructor( + PTemporaryIPCBlobParent* actor) override; + + bool DeallocPTemporaryIPCBlobParent(PTemporaryIPCBlobParent* aActor) override; + + PFileCreatorParent* AllocPFileCreatorParent( + const nsString& aFullPath, const nsString& aType, const nsString& aName, + const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck, + const bool& aIsFromNsIFile) override; + + mozilla::ipc::IPCResult RecvPFileCreatorConstructor( + PFileCreatorParent* actor, const nsString& aFullPath, + const nsString& aType, const nsString& aName, + const Maybe<int64_t>& aLastModified, const bool& aExistenceCheck, + const bool& aIsFromNsIFile) override; + + bool DeallocPFileCreatorParent(PFileCreatorParent* aActor) override; + + mozilla::dom::PRemoteWorkerParent* AllocPRemoteWorkerParent( + const RemoteWorkerData& aData) override; + + bool DeallocPRemoteWorkerParent(PRemoteWorkerParent* aActor) override; + + mozilla::dom::PRemoteWorkerControllerParent* + AllocPRemoteWorkerControllerParent( + const mozilla::dom::RemoteWorkerData& aRemoteWorkerData) override; + + mozilla::ipc::IPCResult RecvPRemoteWorkerControllerConstructor( + mozilla::dom::PRemoteWorkerControllerParent* aActor, + const mozilla::dom::RemoteWorkerData& aRemoteWorkerData) override; + + bool DeallocPRemoteWorkerControllerParent( + mozilla::dom::PRemoteWorkerControllerParent* aActor) override; + + mozilla::dom::PRemoteWorkerServiceParent* AllocPRemoteWorkerServiceParent() + override; + + mozilla::ipc::IPCResult RecvPRemoteWorkerServiceConstructor( + PRemoteWorkerServiceParent* aActor) override; + + bool DeallocPRemoteWorkerServiceParent( + 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; + + PFileDescriptorSetParent* AllocPFileDescriptorSetParent( + const FileDescriptor& aFileDescriptor) override; + + bool DeallocPFileDescriptorSetParent( + PFileDescriptorSetParent* aActor) override; + + PVsyncParent* AllocPVsyncParent() override; + + bool DeallocPVsyncParent(PVsyncParent* aActor) override; + + already_AddRefed<mozilla::psm::PVerifySSLServerCertParent> + AllocPVerifySSLServerCertParent( + const ByteArray& aServerCert, const nsTArray<ByteArray>& aPeerCertChain, + const nsCString& aHostName, const int32_t& aPort, + const OriginAttributes& aOriginAttributes, + const Maybe<ByteArray>& aStapledOCSPResponse, + const Maybe<ByteArray>& aSctsFromTLSExtension, + const Maybe<DelegatedCredentialInfoArg>& aDcInfo, + const uint32_t& aProviderFlags, + const uint32_t& aCertVerifierFlags) override; + + mozilla::ipc::IPCResult RecvPVerifySSLServerCertConstructor( + PVerifySSLServerCertParent* aActor, const ByteArray& aServerCert, + nsTArray<ByteArray>&& aPeerCertChain, const nsCString& aHostName, + const int32_t& aPort, const OriginAttributes& aOriginAttributes, + const Maybe<ByteArray>& aStapledOCSPResponse, + const Maybe<ByteArray>& aSctsFromTLSExtension, + const Maybe<DelegatedCredentialInfoArg>& aDcInfo, + const uint32_t& aProviderFlags, + const uint32_t& aCertVerifierFlags) override; + + PBroadcastChannelParent* AllocPBroadcastChannelParent( + const PrincipalInfo& aPrincipalInfo, const nsCString& aOrigin, + const nsString& aChannel) override; + + mozilla::ipc::IPCResult RecvPBroadcastChannelConstructor( + PBroadcastChannelParent* actor, const PrincipalInfo& aPrincipalInfo, + const nsCString& origin, const nsString& channel) override; + + bool DeallocPBroadcastChannelParent(PBroadcastChannelParent* aActor) override; + + PChildToParentStreamParent* AllocPChildToParentStreamParent() override; + + bool DeallocPChildToParentStreamParent( + PChildToParentStreamParent* aActor) override; + + PParentToChildStreamParent* AllocPParentToChildStreamParent() override; + + bool DeallocPParentToChildStreamParent( + PParentToChildStreamParent* aActor) override; + + PServiceWorkerManagerParent* AllocPServiceWorkerManagerParent() override; + + bool DeallocPServiceWorkerManagerParent( + PServiceWorkerManagerParent* aActor) override; + + PCamerasParent* AllocPCamerasParent() override; + + bool DeallocPCamerasParent(PCamerasParent* aActor) override; + + mozilla::ipc::IPCResult RecvShutdownServiceWorkerRegistrar() override; + + dom::cache::PCacheStorageParent* AllocPCacheStorageParent( + const dom::cache::Namespace& aNamespace, + const PrincipalInfo& aPrincipalInfo) override; + + bool DeallocPCacheStorageParent( + dom::cache::PCacheStorageParent* aActor) override; + + dom::cache::PCacheParent* AllocPCacheParent() override; + + bool DeallocPCacheParent(dom::cache::PCacheParent* aActor) override; + + already_AddRefed<dom::cache::PCacheStreamControlParent> + AllocPCacheStreamControlParent(); + + PUDPSocketParent* AllocPUDPSocketParent(const Maybe<PrincipalInfo>& pInfo, + const nsCString& aFilter) override; + mozilla::ipc::IPCResult RecvPUDPSocketConstructor( + PUDPSocketParent*, const Maybe<PrincipalInfo>& aPrincipalInfo, + const nsCString& 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; + + PQuotaParent* AllocPQuotaParent() override; + + bool DeallocPQuotaParent(PQuotaParent* aActor) 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; + + 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; + + PClientManagerParent* AllocPClientManagerParent() override; + + bool DeallocPClientManagerParent(PClientManagerParent* aActor) override; + + mozilla::ipc::IPCResult RecvPClientManagerConstructor( + PClientManagerParent* aActor) override; + + PMIDIPortParent* AllocPMIDIPortParent(const MIDIPortInfo& aPortInfo, + const bool& aSysexEnabled) override; + + bool DeallocPMIDIPortParent(PMIDIPortParent* aActor) override; + + PMIDIManagerParent* AllocPMIDIManagerParent() override; + + bool DeallocPMIDIManagerParent(PMIDIManagerParent* aActor) override; + + mozilla::ipc::IPCResult RecvStorageActivity( + const PrincipalInfo& aPrincipalInfo) override; + + already_AddRefed<PServiceWorkerParent> AllocPServiceWorkerParent( + const IPCServiceWorkerDescriptor&) final; + + 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 nsString& aGroupName, const PrincipalInfo& aPrincipalInfo) override; + + mozilla::ipc::IPCResult RecvPEndpointForReportConstructor( + PEndpointForReportParent* actor, const nsString& aGroupName, + const PrincipalInfo& aPrincipalInfo) override; + + mozilla::ipc::IPCResult RecvEnsureRDDProcessAndCreateBridge( + EnsureRDDProcessAndCreateBridgeResolver&& aResolver) override; + + bool DeallocPEndpointForReportParent( + PEndpointForReportParent* aActor) override; + + mozilla::ipc::IPCResult RecvRemoveEndpoint( + const nsString& aGroupName, const nsCString& aEndpointURL, + const PrincipalInfo& aPrincipalInfo) override; + + dom::PMediaTransportParent* AllocPMediaTransportParent() override; + bool DeallocPMediaTransportParent( + dom::PMediaTransportParent* aActor) override; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_backgroundparentimpl_h__ diff --git a/ipc/glue/BackgroundUtils.cpp b/ipc/glue/BackgroundUtils.cpp new file mode 100644 index 0000000000..5f9c066713 --- /dev/null +++ b/ipc/glue/BackgroundUtils.cpp @@ -0,0 +1,1018 @@ +/* -*- 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/ipc/PBackgroundSharedTypes.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/net/CookieJarSettings.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 "URIUtils.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" + +namespace mozilla { + +using mozilla::BasePrincipal; +using mozilla::Maybe; +using mozilla::dom::BrowsingContext; +using mozilla::dom::ServiceWorkerDescriptor; +using namespace mozilla::net; + +namespace ipc { + +Result<nsCOMPtr<nsIPrincipal>, nsresult> PrincipalInfoToPrincipal( + const PrincipalInfo& aPrincipalInfo) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPrincipalInfo.type() != PrincipalInfo::T__None); + + nsCOMPtr<nsIScriptSecurityManager> secMan = + nsContentUtils::GetSecurityManager(); + if (!secMan) { + return Err(NS_ERROR_NULL_POINTER); + } + + nsCOMPtr<nsIPrincipal> principal; + nsresult rv; + + switch (aPrincipalInfo.type()) { + case PrincipalInfo::TSystemPrincipalInfo: { + rv = secMan->GetSystemPrincipal(getter_AddRefs(principal)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Err(rv); + } + + 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); + } + + 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); + } + + principal = BasePrincipal::CreateContentPrincipal(uri, info.attrs()); + 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.domain()) { + nsCOMPtr<nsIURI> domain; + rv = NS_NewURI(getter_AddRefs(domain), *info.domain()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Err(rv); + } + + rv = principal->SetDomain(domain); + if (NS_WARN_IF(NS_FAILED(rv))) { + return Err(rv); + } + } + + 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); + } + return Err(NS_ERROR_FAILURE); +} + +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(NS_IsMainThread()); + 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, + Maybe<LoadInfoArgs>* aOptionalLoadInfoArgs) { + 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> sandboxedLoadingPrincipalInfo; + if (aLoadInfo->GetLoadingSandboxed()) { + sandboxedLoadingPrincipalInfo.emplace(); + rv = PrincipalToPrincipalInfo(aLoadInfo->GetSandboxedLoadingPrincipal(), + sandboxedLoadingPrincipalInfo.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<PrincipalInfo> topLevelStorageAreaPrincipalInfo; + if (nsIPrincipal* prin = aLoadInfo->GetTopLevelStorageAreaPrincipal()) { + topLevelStorageAreaPrincipalInfo.emplace(); + rv = PrincipalToPrincipalInfo(prin, topLevelStorageAreaPrincipalInfo.ptr()); + NS_ENSURE_SUCCESS(rv, rv); + } + + Maybe<URIParams> optionalResultPrincipalURI; + nsCOMPtr<nsIURI> resultPrincipalURI; + Unused << aLoadInfo->GetResultPrincipalURI( + getter_AddRefs(resultPrincipalURI)); + if (resultPrincipalURI) { + SerializeURI(resultPrincipalURI, optionalResultPrincipalURI); + } + + 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))); + + 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); + } + + *aOptionalLoadInfoArgs = Some(LoadInfoArgs( + loadingPrincipalInfo, triggeringPrincipalInfo, principalToInheritInfo, + sandboxedLoadingPrincipalInfo, topLevelPrincipalInfo, + topLevelStorageAreaPrincipalInfo, optionalResultPrincipalURI, + aLoadInfo->GetSecurityFlags(), aLoadInfo->GetSandboxFlags(), + aLoadInfo->GetTriggeringSandboxFlags(), + aLoadInfo->InternalContentPolicyType(), + static_cast<uint32_t>(aLoadInfo->GetTainting()), + aLoadInfo->GetBlockAllMixedContent(), + aLoadInfo->GetUpgradeInsecureRequests(), + aLoadInfo->GetBrowserUpgradeInsecureRequests(), + aLoadInfo->GetBrowserDidUpgradeInsecureRequests(), + aLoadInfo->GetBrowserWouldUpgradeInsecureRequests(), + aLoadInfo->GetForceAllowDataURI(), + aLoadInfo->GetAllowInsecureRedirectToDataURI(), + aLoadInfo->GetBypassCORSChecks(), + aLoadInfo->GetSkipContentPolicyCheckForWebRequest(), + aLoadInfo->GetOriginalFrameSrcLoad(), + aLoadInfo->GetForceInheritPrincipalDropped(), + aLoadInfo->GetInnerWindowID(), aLoadInfo->GetBrowsingContextID(), + aLoadInfo->GetFrameBrowsingContextID(), + aLoadInfo->GetInitialSecurityCheckDone(), + aLoadInfo->GetIsInThirdPartyContext(), + aLoadInfo->GetIsThirdPartyContextToTopWindow(), + aLoadInfo->GetIsFormSubmission(), aLoadInfo->GetSendCSPViolationEvents(), + aLoadInfo->GetOriginAttributes(), redirectChainIncludingInternalRedirects, + redirectChain, ipcClientInfo, ipcReservedClientInfo, ipcInitialClientInfo, + ipcController, aLoadInfo->CorsUnsafeHeaders(), + aLoadInfo->GetForcePreflight(), aLoadInfo->GetIsPreflight(), + aLoadInfo->GetLoadTriggeredFromExternal(), + aLoadInfo->GetServiceWorkerTaintingSynthesized(), + aLoadInfo->GetDocumentHasUserInteracted(), + aLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain(), + cspNonce, aLoadInfo->GetSkipContentSniffing(), + aLoadInfo->GetHttpsOnlyStatus(), + aLoadInfo->GetHasValidUserGestureActivation(), + aLoadInfo->GetAllowDeprecatedSystemRequests(), + aLoadInfo->GetIsInDevToolsContext(), aLoadInfo->GetParserCreatedScript(), + aLoadInfo->GetIsFromProcessingFrameAttributes(), cookieJarSettingsArgs, + aLoadInfo->GetRequestBlockingReason(), maybeCspToInheritInfo, + aLoadInfo->GetHasStoragePermission(), + aLoadInfo->GetLoadingEmbedderPolicy())); + + return NS_OK; +} + +nsresult LoadInfoArgsToLoadInfo( + const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs, + nsILoadInfo** outLoadInfo) { + return LoadInfoArgsToLoadInfo(aOptionalLoadInfoArgs, nullptr, outLoadInfo); +} +nsresult LoadInfoArgsToLoadInfo( + const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs, + nsINode* aCspToInheritLoadingContext, nsILoadInfo** outLoadInfo) { + RefPtr<LoadInfo> loadInfo; + nsresult rv = + LoadInfoArgsToLoadInfo(aOptionalLoadInfoArgs, aCspToInheritLoadingContext, + getter_AddRefs(loadInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + loadInfo.forget(outLoadInfo); + return NS_OK; +} + +nsresult LoadInfoArgsToLoadInfo( + const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs, LoadInfo** outLoadInfo) { + return LoadInfoArgsToLoadInfo(aOptionalLoadInfoArgs, nullptr, outLoadInfo); +} +nsresult LoadInfoArgsToLoadInfo( + const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs, + nsINode* aCspToInheritLoadingContext, LoadInfo** outLoadInfo) { + if (aOptionalLoadInfoArgs.isNothing()) { + *outLoadInfo = nullptr; + return NS_OK; + } + + const LoadInfoArgs& loadInfoArgs = aOptionalLoadInfoArgs.ref(); + + 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)) { + nsCOMPtr<nsIPrincipal> originalTriggeringPrincipal; + nsCOMPtr<nsIPrincipal> originalPrincipalToInherit; + Tie(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> sandboxedLoadingPrincipal; + if (loadInfoArgs.sandboxedLoadingPrincipalInfo().isSome()) { + auto sandboxedLoadingPrincipalOrErr = PrincipalInfoToPrincipal( + loadInfoArgs.sandboxedLoadingPrincipalInfo().ref()); + if (NS_WARN_IF(sandboxedLoadingPrincipalOrErr.isErr())) { + return sandboxedLoadingPrincipalOrErr.unwrapErr(); + } + sandboxedLoadingPrincipal = sandboxedLoadingPrincipalOrErr.unwrap(); + } + + nsresult rv = NS_OK; + 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<nsIPrincipal> topLevelStorageAreaPrincipal; + if (loadInfoArgs.topLevelStorageAreaPrincipalInfo().isSome()) { + auto topLevelStorageAreaPrincipalOrErr = PrincipalInfoToPrincipal( + loadInfoArgs.topLevelStorageAreaPrincipalInfo().ref()); + if (NS_WARN_IF(topLevelStorageAreaPrincipalOrErr.isErr())) { + return topLevelStorageAreaPrincipalOrErr.unwrapErr(); + } + topLevelStorageAreaPrincipal = topLevelStorageAreaPrincipalOrErr.unwrap(); + } + + nsCOMPtr<nsIURI> resultPrincipalURI; + if (loadInfoArgs.resultPrincipalURI().isSome()) { + resultPrincipalURI = DeserializeURI(loadInfoArgs.resultPrincipalURI()); + NS_ENSURE_TRUE(resultPrincipalURI, NS_ERROR_UNEXPECTED); + } + + RedirectHistoryArray redirectChainIncludingInternalRedirects; + for (const RedirectHistoryEntryInfo& entryInfo : + loadInfoArgs.redirectChainIncludingInternalRedirects()) { + nsCOMPtr<nsIRedirectHistoryEntry> redirectHistoryEntry = + RHEntryInfoToRHEntry(entryInfo); + NS_ENSURE_SUCCESS(rv, rv); + redirectChainIncludingInternalRedirects.AppendElement( + redirectHistoryEntry.forget()); + } + + RedirectHistoryArray redirectChain; + for (const RedirectHistoryEntryInfo& entryInfo : + loadInfoArgs.redirectChain()) { + nsCOMPtr<nsIRedirectHistoryEntry> redirectHistoryEntry = + RHEntryInfoToRHEntry(entryInfo); + NS_ENSURE_SUCCESS(rv, rv); + 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)); + + 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(); + } + + RefPtr<mozilla::LoadInfo> loadInfo = new mozilla::LoadInfo( + loadingPrincipal, triggeringPrincipal, principalToInherit, + sandboxedLoadingPrincipal, topLevelPrincipal, + topLevelStorageAreaPrincipal, resultPrincipalURI, cookieJarSettings, + cspToInherit, clientInfo, reservedClientInfo, initialClientInfo, + controller, loadInfoArgs.securityFlags(), loadInfoArgs.sandboxFlags(), + loadInfoArgs.triggeringSandboxFlags(), loadInfoArgs.contentPolicyType(), + static_cast<LoadTainting>(loadInfoArgs.tainting()), + loadInfoArgs.blockAllMixedContent(), + loadInfoArgs.upgradeInsecureRequests(), + loadInfoArgs.browserUpgradeInsecureRequests(), + loadInfoArgs.browserDidUpgradeInsecureRequests(), + loadInfoArgs.browserWouldUpgradeInsecureRequests(), + loadInfoArgs.forceAllowDataURI(), + loadInfoArgs.allowInsecureRedirectToDataURI(), + loadInfoArgs.bypassCORSChecks(), + loadInfoArgs.skipContentPolicyCheckForWebRequest(), + loadInfoArgs.originalFrameSrcLoad(), + loadInfoArgs.forceInheritPrincipalDropped(), loadInfoArgs.innerWindowID(), + loadInfoArgs.browsingContextID(), loadInfoArgs.frameBrowsingContextID(), + loadInfoArgs.initialSecurityCheckDone(), + loadInfoArgs.isInThirdPartyContext(), + loadInfoArgs.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.cspNonce(), loadInfoArgs.skipContentSniffing(), + loadInfoArgs.httpsOnlyStatus(), + loadInfoArgs.hasValidUserGestureActivation(), + loadInfoArgs.allowDeprecatedSystemRequests(), + loadInfoArgs.isInDevToolsContext(), loadInfoArgs.parserCreatedScript(), + loadInfoArgs.hasStoragePermission(), loadInfoArgs.requestBlockingReason(), + loadingContext, loadInfoArgs.loadingEmbedderPolicy()); + + if (loadInfoArgs.isFromProcessingFrameAttributes()) { + loadInfo->SetIsFromProcessingFrameAttributes(); + } + + 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); + } + + *aForwarderArgsOut = ParentLoadInfoForwarderArgs( + aLoadInfo->GetAllowInsecureRedirectToDataURI(), + aLoadInfo->GetBypassCORSChecks(), ipcController, tainting, + aLoadInfo->GetSkipContentSniffing(), aLoadInfo->GetHttpsOnlyStatus(), + aLoadInfo->GetHasValidUserGestureActivation(), + aLoadInfo->GetAllowDeprecatedSystemRequests(), + aLoadInfo->GetIsInDevToolsContext(), aLoadInfo->GetParserCreatedScript(), + aLoadInfo->GetTriggeringSandboxFlags(), + aLoadInfo->GetServiceWorkerTaintingSynthesized(), + aLoadInfo->GetDocumentHasUserInteracted(), + aLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain(), + cookieJarSettingsArgs, aLoadInfo->GetRequestBlockingReason(), + aLoadInfo->GetHasStoragePermission(), + aLoadInfo->GetIsThirdPartyContextToTopWindow(), + aLoadInfo->GetIsInThirdPartyContext()); +} + +nsresult MergeParentLoadInfoForwarder( + ParentLoadInfoForwarderArgs const& aForwarderArgs, nsILoadInfo* aLoadInfo) { + nsresult rv; + + rv = aLoadInfo->SetAllowInsecureRedirectToDataURI( + aForwarderArgs.allowInsecureRedirectToDataURI()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetBypassCORSChecks(aForwarderArgs.bypassCORSChecks()); + 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->SetTriggeringSandboxFlags( + aForwarderArgs.triggeringSandboxFlags()); + 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->SetHasStoragePermission(aForwarderArgs.hasStoragePermission()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetIsThirdPartyContextToTopWindow( + aForwarderArgs.isThirdPartyContextToTopWindow()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aLoadInfo->SetIsInThirdPartyContext( + aForwarderArgs.isInThirdPartyContext()); + 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..8a9c4e2aa0 --- /dev/null +++ b/ipc/glue/BackgroundUtils.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_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(Message* aMsg, const paramType& aParam) { + nsAutoCString suffix; + aParam.CreateSuffix(suffix); + WriteParam(aMsg, suffix); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + nsAutoCString suffix; + return ReadParam(aMsg, aIter, &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); + +/** + * 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, + Maybe<mozilla::net::LoadInfoArgs>* outOptionalLoadInfoArgs); + +/** + * Convert LoadInfoArgs to a LoadInfo. + */ +nsresult LoadInfoArgsToLoadInfo( + const Maybe<mozilla::net::LoadInfoArgs>& aOptionalLoadInfoArgs, + nsILoadInfo** outLoadInfo); +nsresult LoadInfoArgsToLoadInfo( + const Maybe<mozilla::net::LoadInfoArgs>& aOptionalLoadInfoArgs, + nsINode* aCspToInheritLoadingContext, nsILoadInfo** outLoadInfo); +nsresult LoadInfoArgsToLoadInfo( + const Maybe<net::LoadInfoArgs>& aOptionalLoadInfoArgs, + mozilla::net::LoadInfo** outLoadInfo); +nsresult LoadInfoArgsToLoadInfo( + const Maybe<net::LoadInfoArgs>& aOptionalLoadInfoArgs, + 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/BrowserProcessSubThread.cpp b/ipc/glue/BrowserProcessSubThread.cpp new file mode 100644 index 0000000000..2a7947da9e --- /dev/null +++ b/ipc/glue/BrowserProcessSubThread.cpp @@ -0,0 +1,73 @@ +/* -*- 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" + +#if defined(OS_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(OS_WIN) + // Initializes the COM library on the current thread. + CoInitialize(nullptr); +#endif +} + +void BrowserProcessSubThread::CleanUp() { +#if defined(OS_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..03e56f617f --- /dev/null +++ b/ipc/glue/BrowserProcessSubThread.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_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]; +}; + +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..4ddca81210 --- /dev/null +++ b/ipc/glue/ByteBufUtils.h @@ -0,0 +1,52 @@ +/* -*- 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 "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(Message* aMsg, paramType&& aParam) { + WriteParam(aMsg, aParam.mLen); + // hand over ownership of the buffer to the Message + aMsg->WriteBytesZeroCopy(aParam.mData, aParam.mLen, aParam.mCapacity); + aParam.mData = nullptr; + aParam.mCapacity = 0; + aParam.mLen = 0; + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + // We make a copy from the BufferList so that we get a contigous result. + // For users the can handle a non-contiguous result using ExtractBuffers + // is an option, alternatively if the users don't need to take ownership of + // the data they can use the removed FlattenBytes (bug 1297981) + size_t length; + return ReadParam(aMsg, aIter, &length) && aResult->Allocate(length) && + aMsg->ReadBytesInto(aIter, aResult->mData, length); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(L"(byte buf)"); + } +}; + +} // 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..02dbfc7e90 --- /dev/null +++ b/ipc/glue/CrashReporterClient.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_CrashReporterClient_h +#define mozilla_ipc_CrashReporterClient_h + +#include "mozilla/Assertions.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.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; +}; + +} // 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..e749ef9a2c --- /dev/null +++ b/ipc/glue/CrashReporterHelper.h @@ -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/. */ + +#ifndef mozilla_ipc_CrashReporterHelper_h +#define mozilla_ipc_CrashReporterHelper_h + +#include "CrashReporterHost.h" +#include "mozilla/UniquePtr.h" +#include "nsExceptionHandler.h" +#include "nsICrashService.h" +#include "nsPrintfCString.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; + } + + 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 = %d 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..8c2581486a --- /dev/null +++ b/ipc/glue/CrashReporterHost.cpp @@ -0,0 +1,241 @@ +/* -*- 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 "nsICrashService.h" +#include "nsXULAppAPI.h" + +// Consistency checking for nsICrashService constants. We depend on the +// equivalence between nsICrashService values and GeckoProcessType values +// in the code below. Making them equal also ensures that if new process +// types are added, people will know they may need to add crash reporting +// support in various places because compilation errors will be triggered here. +static_assert(nsICrashService::PROCESS_TYPE_MAIN == + (int)GeckoProcessType_Default, + "GeckoProcessType enum is out of sync with nsICrashService!"); +static_assert(nsICrashService::PROCESS_TYPE_PLUGIN == + (int)GeckoProcessType_Plugin, + "GeckoProcessType enum is out of sync with nsICrashService!"); +static_assert(nsICrashService::PROCESS_TYPE_CONTENT == + (int)GeckoProcessType_Content, + "GeckoProcessType enum is out of sync with nsICrashService!"); +static_assert(nsICrashService::PROCESS_TYPE_IPDLUNITTEST == + (int)GeckoProcessType_IPDLUnitTest, + "GeckoProcessType enum is out of sync with nsICrashService!"); +static_assert(nsICrashService::PROCESS_TYPE_GMPLUGIN == + (int)GeckoProcessType_GMPlugin, + "GeckoProcessType enum is out of sync with nsICrashService!"); +static_assert(nsICrashService::PROCESS_TYPE_GPU == (int)GeckoProcessType_GPU, + "GeckoProcessType enum is out of sync with nsICrashService!"); +static_assert(nsICrashService::PROCESS_TYPE_VR == (int)GeckoProcessType_VR, + "GeckoProcessType enum is out of sync with nsICrashService!"); +static_assert(nsICrashService::PROCESS_TYPE_RDD == (int)GeckoProcessType_RDD, + "GeckoProcessType enum is out of sync with nsICrashService!"); +static_assert(nsICrashService::PROCESS_TYPE_SOCKET == + (int)GeckoProcessType_Socket, + "GeckoProcessType enum is out of sync with nsICrashService!"); +static_assert(nsICrashService::PROCESS_TYPE_SANDBOX_BROKER == + (int)GeckoProcessType_RemoteSandboxBroker, + "GeckoProcessType enum is out of sync with nsICrashService!"); +static_assert(nsICrashService::PROCESS_TYPE_FORKSERVER == + (int)GeckoProcessType_ForkServer, + "GeckoProcessType enum is out of sync with nsICrashService!"); +// Add new static asserts here if you add more process types. +// Update this static assert as well. +static_assert(nsICrashService::PROCESS_TYPE_FORKSERVER + 1 == + (int)GeckoProcessType_End, + "GeckoProcessType enum is out of sync with nsICrashService!"); + +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; + } + return FinalizeCrashReport(); +} + +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; +} + +int32_t CrashReporterHost::GetCrashType() { + if (mExtraAnnotations[CrashReporter::Annotation::PluginHang].EqualsLiteral( + "1")) { + return nsICrashService::CRASH_TYPE_HANG; + } + + return nsICrashService::CRASH_TYPE_CRASH; +} + +bool 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); + + RecordCrash(mProcessType, GetCrashType(), mDumpID); + + mFinalized = true; + return true; +} + +/* 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; + + if (aProcessType == GeckoProcessType_Plugin && + aCrashType == nsICrashService::CRASH_TYPE_HANG) { + key.AssignLiteral("pluginhang"); + } else { + switch (aProcessType) { +#define GECKO_PROCESS_TYPE(enum_name, string_name, xre_name, bin_type) \ + 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<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; +} + +bool CrashReporterHost::IsLikelyOOM() { + // The data is only populated during the call to `FinalizeCrashReport()`. + MOZ_ASSERT(mFinalized); + + // If `OOMAllocationSize` was set, we know that the crash happened + // because an allocation failed (`malloc` returned `nullptr`). + // + // As Unix systems generally allow `malloc` to return a non-null value + // even when no virtual memory is available, this doesn't cover all + // cases of OOM under Unix (far from it). + return mExtraAnnotations[CrashReporter::Annotation::OOMAllocationSize] + .Length() > 0; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/CrashReporterHost.h b/ipc/glue/CrashReporterHost.h new file mode 100644 index 0000000000..77182c0ba5 --- /dev/null +++ b/ipc/glue/CrashReporterHost.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_CrashReporterHost_h +#define mozilla_ipc_CrashReporterHost_h + +#include <functional> + +#include "mozilla/UniquePtr.h" +#include "base/process.h" +#include "nsExceptionHandler.h" +#include "nsThreadUtils.h" +#include "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. + bool FinalizeCrashReport(); + + // 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, + nsIFile* aMinidumpToPair, + const nsACString& aPairName) { + ScopedProcessHandle childHandle; +#ifdef XP_MACOSX + childHandle = aToplevelProtocol->Process()->GetChildTask(); +#else + if (!base::OpenPrivilegedProcessHandle(aToplevelProtocol->OtherPid(), + &childHandle.rwget())) { + NS_WARNING("Failed to open child process handle."); + return false; + } +#endif + + nsCOMPtr<nsIFile> targetDump; + if (!CrashReporter::CreateMinidumpsAndPair( + childHandle, mThreadId, aPairName, aMinidumpToPair, + 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]; + } + + // Return `true` if this crash reporter has been identified as a likely OOM. + // + // At the time of this writing, OOMs detection is considered reliable under + // Windows but other platforms quite often return false negatives. + // + // `CrashReporterHost::FinalizeCrashReport()` MUST have been called already. + bool IsLikelyOOM(); + + // 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..0ce23f4e28 --- /dev/null +++ b/ipc/glue/CrossProcessMutex.h @@ -0,0 +1,115 @@ +/* -*- 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(OS_WIN) && !defined(OS_NETBSD) && !defined(OS_OPENBSD) +# include <pthread.h> +# include "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(OS_WIN) +typedef HANDLE CrossProcessMutexHandle; +#elif !defined(OS_NETBSD) && !defined(OS_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(); + + /** + * ShareToProcess + * 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 ShareToProcess(base::ProcessId aTargetPid); + + private: + friend struct IPC::ParamTraits<CrossProcessMutex>; + + CrossProcessMutex(); + CrossProcessMutex(const CrossProcessMutex&); + CrossProcessMutex& operator=(const CrossProcessMutex&); + +#if defined(OS_WIN) + HANDLE mMutex; +#elif !defined(OS_NETBSD) && !defined(OS_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..f7f5c69536 --- /dev/null +++ b/ipc/glue/CrossProcessMutex_posix.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 "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(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::ShareToProcess( + base::ProcessId aTargetPid) { + CrossProcessMutexHandle result = ipc::SharedMemoryBasic::NULLHandle(); + + if (mSharedBuffer && !mSharedBuffer->ShareToProcess(aTargetPid, &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..165c3b256a --- /dev/null +++ b/ipc/glue/CrossProcessMutex_unimplemented.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 "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::ShareToProcess( + base::ProcessId aTargetPid) { + 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..b5ed066fde --- /dev/null +++ b/ipc/glue/CrossProcessMutex_windows.cpp @@ -0,0 +1,69 @@ +/* -*- 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, &flags)) { + MOZ_CRASH("Attempt to construct a mutex from an invalid handle!"); + } + mMutex = aHandle; + 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::ShareToProcess( + base::ProcessId aTargetPid) { + HANDLE newHandle; + bool succeeded = ipc::DuplicateHandle(mMutex, aTargetPid, &newHandle, 0, + DUPLICATE_SAME_ACCESS); + + if (!succeeded) { + return nullptr; + } + + return newHandle; +} + +} // namespace mozilla diff --git a/ipc/glue/CrossProcessSemaphore.h b/ipc/glue/CrossProcessSemaphore.h new file mode 100644 index 0000000000..1480946d7d --- /dev/null +++ b/ipc/glue/CrossProcessSemaphore.h @@ -0,0 +1,115 @@ +/* -*- 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(OS_WIN) && !defined(OS_MACOSX) +# include <pthread.h> +# include <semaphore.h> +# include "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(OS_WIN) +typedef HANDLE CrossProcessSemaphoreHandle; +#elif !defined(OS_MACOSX) +typedef mozilla::ipc::SharedMemoryBasic::Handle CrossProcessSemaphoreHandle; + +template <> +inline bool IsHandleValid<CrossProcessSemaphoreHandle>( + const CrossProcessSemaphoreHandle& handle) { + return !(handle == mozilla::ipc::SharedMemoryBasic::NULLHandle()); +} +#else +// Stub for other platforms. We can't use uintptr_t here since different +// processes could disagree on its size. +typedef uintptr_t CrossProcessSemaphoreHandle; +#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(); + + /** + * ShareToProcess + * 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 ShareToProcess(base::ProcessId aTargetPid); + + void CloseHandle(); + + private: + friend struct IPC::ParamTraits<CrossProcessSemaphore>; + + CrossProcessSemaphore(); + CrossProcessSemaphore(const CrossProcessSemaphore&); + CrossProcessSemaphore& operator=(const CrossProcessSemaphore&); + +#if defined(OS_WIN) + explicit CrossProcessSemaphore(HANDLE aSemaphore); + + HANDLE mSemaphore; +#elif !defined(OS_MACOSX) + RefPtr<mozilla::ipc::SharedMemoryBasic> mSharedBuffer; + sem_t* mSemaphore; + mozilla::Atomic<int32_t>* mRefCount; +#endif +}; + +} // namespace mozilla + +#endif diff --git a/ipc/glue/CrossProcessSemaphore_posix.cpp b/ipc/glue/CrossProcessSemaphore_posix.cpp new file mode 100644 index 0000000000..8c28d897fa --- /dev/null +++ b/ipc/glue/CrossProcessSemaphore_posix.cpp @@ -0,0 +1,161 @@ +/* -*- 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(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; + } + + ts.tv_nsec += (kNsPerMs * aWaitTime->ToMilliseconds()); + ts.tv_sec += ts.tv_nsec / kNsPerSec; + ts.tv_nsec %= 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::ShareToProcess( + base::ProcessId aTargetPid) { + CrossProcessSemaphoreHandle result = ipc::SharedMemoryBasic::NULLHandle(); + + if (mSharedBuffer && !mSharedBuffer->ShareToProcess(aTargetPid, &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..c8b88d5fbd --- /dev/null +++ b/ipc/glue/CrossProcessSemaphore_unimplemented.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 "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::ShareToProcess( + base::ProcessId aTargetPid) { + 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..66da54be61 --- /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, &flags)) { + return nullptr; + } + + return new CrossProcessSemaphore(aHandle); +} + +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::ShareToProcess( + base::ProcessId aTargetPid) { + HANDLE newHandle; + bool succeeded = ipc::DuplicateHandle(mSemaphore, aTargetPid, &newHandle, 0, + DUPLICATE_SAME_ACCESS); + + if (!succeeded) { + return nullptr; + } + + return newHandle; +} + +void CrossProcessSemaphore::CloseHandle() {} + +} // namespace mozilla diff --git a/ipc/glue/Endpoint.h b/ipc/glue/Endpoint.h new file mode 100644 index 0000000000..4a6a214d74 --- /dev/null +++ b/ipc/glue/Endpoint.h @@ -0,0 +1,239 @@ +/* -*- 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/Transport.h" +#include "nsXULAppAPI.h" +#include "nscore.h" + +namespace IPC { +template <class P> +struct ParamTraits; +} + +namespace mozilla { +namespace ipc { + +struct PrivateIPDLInterface {}; + +/** + * 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(parentPid, childPid, &parentEp, &childEp); + * + * You're required to pass in parentPid and childPid, which are the pids of the + * processes in which the parent and child endpoints will be used. + * + * 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. + */ +template <class PFooSide> +class Endpoint { + public: + typedef base::ProcessId ProcessId; + + Endpoint() + : mValid(false), + mMode(static_cast<mozilla::ipc::Transport::Mode>(0)), + mMyPid(0), + mOtherPid(0) {} + + Endpoint(const PrivateIPDLInterface&, mozilla::ipc::Transport::Mode aMode, + TransportDescriptor aTransport, ProcessId aMyPid, + ProcessId aOtherPid) + : mValid(true), + mMode(aMode), + mTransport(aTransport), + mMyPid(aMyPid), + mOtherPid(aOtherPid) {} + + Endpoint(Endpoint&& aOther) + : mValid(aOther.mValid), + mTransport(aOther.mTransport), + mMyPid(aOther.mMyPid), + mOtherPid(aOther.mOtherPid) { + if (aOther.mValid) { + mMode = aOther.mMode; + } + aOther.mValid = false; + } + + Endpoint& operator=(Endpoint&& aOther) { + mValid = aOther.mValid; + if (aOther.mValid) { + mMode = aOther.mMode; + } + mTransport = aOther.mTransport; + mMyPid = aOther.mMyPid; + mOtherPid = aOther.mOtherPid; + + aOther.mValid = false; + return *this; + } + + ~Endpoint() { + if (mValid) { + CloseDescriptor(mTransport); + } + } + + ProcessId OtherPid() const { 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. + bool Bind(PFooSide* aActor) { + MOZ_RELEASE_ASSERT(mValid); + MOZ_RELEASE_ASSERT(mMyPid == base::GetCurrentProcId()); + + UniquePtr<Transport> transport = + mozilla::ipc::OpenDescriptor(mTransport, mMode); + if (!transport) { + return false; + } + if (!aActor->Open( + std::move(transport), mOtherPid, XRE_GetIOMessageLoop(), + mMode == Transport::MODE_SERVER ? ParentSide : ChildSide)) { + return false; + } + mValid = false; + return true; + } + + bool IsValid() const { return mValid; } + + private: + friend struct IPC::ParamTraits<Endpoint<PFooSide>>; + + Endpoint(const Endpoint&) = delete; + Endpoint& operator=(const Endpoint&) = delete; + + bool mValid; + mozilla::ipc::Transport::Mode mMode; + TransportDescriptor mTransport; + ProcessId mMyPid, mOtherPid; +}; + +#if defined(XP_MACOSX) +void AnnotateCrashReportWithErrno(CrashReporter::Annotation tag, int error); +#else +static 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, + base::ProcessId aParentDestPid, + base::ProcessId aChildDestPid, + Endpoint<PFooParent>* aParentEndpoint, + Endpoint<PFooChild>* aChildEndpoint) { + MOZ_RELEASE_ASSERT(aParentDestPid); + MOZ_RELEASE_ASSERT(aChildDestPid); + + TransportDescriptor parentTransport, childTransport; + nsresult rv; + if (NS_FAILED(rv = CreateTransport(aParentDestPid, &parentTransport, + &childTransport))) { + AnnotateCrashReportWithErrno( + CrashReporter::Annotation::IpcCreateEndpointsNsresult, int(rv)); + return rv; + } + + *aParentEndpoint = + Endpoint<PFooParent>(aPrivate, mozilla::ipc::Transport::MODE_SERVER, + parentTransport, aParentDestPid, aChildDestPid); + + *aChildEndpoint = + Endpoint<PFooChild>(aPrivate, mozilla::ipc::Transport::MODE_CLIENT, + childTransport, aChildDestPid, aParentDestPid); + + return NS_OK; +} + +/** + * 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: + ManagedEndpoint() : mId(0) {} + + ManagedEndpoint(const PrivateIPDLInterface&, int32_t aId) : mId(aId) {} + + ManagedEndpoint(ManagedEndpoint&& aOther) : mId(aOther.mId) { + aOther.mId = 0; + } + + ManagedEndpoint& operator=(ManagedEndpoint&& aOther) { + mId = aOther.mId; + aOther.mId = 0; + return *this; + } + + bool IsValid() const { return mId != 0; } + + Maybe<int32_t> ActorId() const { return IsValid() ? Some(mId) : Nothing(); } + + bool operator==(const ManagedEndpoint& _o) const { return mId == _o.mId; } + + private: + friend struct IPC::ParamTraits<ManagedEndpoint<PFooSide>>; + + ManagedEndpoint(const ManagedEndpoint&) = delete; + ManagedEndpoint& operator=(const ManagedEndpoint&) = delete; + + // The routing ID for the to-be-created endpoint. + int32_t mId; + + // XXX(nika): Might be nice to have other info for assertions? + // e.g. mManagerId, mManagerType, etc. +}; + +} // 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..661acd7244 --- /dev/null +++ b/ipc/glue/EnumSerializer.h @@ -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/. */ + +#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; +} + +#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; + typedef typename mozilla::UnsignedStdintTypeForSize<sizeof(paramType)>::Type + uintParamType; + + static void Write(Message* aMsg, const paramType& aValue) { + MOZ_RELEASE_ASSERT(EnumValidator::IsLegalValue(aValue)); + WriteParam(aMsg, uintParamType(aValue)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uintParamType value; + if (!ReadParam(aMsg, aIter, &value)) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCReadErrorReason, "Bad iter"_ns); + return false; + } else if (!EnumValidator::IsLegalValue(paramType(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: + static bool IsLegalValue(E e) { + return IsLessThanOrEqual(MinLegal, e) && e < HighBound; + } +}; + +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: + static bool IsLegalValue(E e) { + return IsLessThanOrEqual(MinLegal, e) && e <= MaxLegal; + } +}; + +template <typename E, E AllBits> +struct BitFlagsEnumValidator { + static bool IsLegalValue(E e) { return (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..5cd0ef8c58 --- /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(OS_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); + +#elif defined(OS_POSIX) + +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..f411129de3 --- /dev/null +++ b/ipc/glue/FileDescriptor.cpp @@ -0,0 +1,163 @@ +/* -*- 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/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(const IPDLPrivate&, const PickleType& aPickle) { +#ifdef XP_WIN + mHandle.reset(aPickle); +#else + mHandle.reset(aPickle.fd); +#endif +} + +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; +} + +FileDescriptor::PickleType FileDescriptor::ShareTo( + const FileDescriptor::IPDLPrivate&, + FileDescriptor::ProcessId aTargetPid) const { + PlatformHandleType newHandle; +#ifdef XP_WIN + if (IsValid()) { + if (mozilla::ipc::DuplicateHandle(mHandle.get(), aTargetPid, &newHandle, 0, + DUPLICATE_SAME_ACCESS)) { + return newHandle; + } + NS_WARNING("Failed to duplicate file handle for other process!"); + } + return INVALID_HANDLE_VALUE; +#else // XP_WIN + if (IsValid()) { + newHandle = dup(mHandle.get()); + if (newHandle >= 0) { + return base::FileDescriptor(newHandle, /* auto_close */ true); + } + NS_WARNING("Failed to duplicate file handle for other process!"); + } + return base::FileDescriptor(); +#endif + + MOZ_CRASH("Must not get here!"); +} + +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::Message* aMsg, + IProtocol* aActor, + const FileDescriptor& aParam) { +#ifdef XP_WIN + FileDescriptor::PickleType pfd = + aParam.ShareTo(FileDescriptor::IPDLPrivate(), aActor->OtherPid()); +#else + // The pid returned by OtherPID() is only required for Windows to + // send file descriptors. For the use case of the fork server, + // aActor is always null. Since it is only for the special case of + // Windows, here we skip it for other platforms. + FileDescriptor::PickleType pfd = + aParam.ShareTo(FileDescriptor::IPDLPrivate(), 0); +#endif + WriteIPDLParam(aMsg, aActor, pfd); +} + +bool IPDLParamTraits<FileDescriptor>::Read(const IPC::Message* aMsg, + PickleIterator* aIter, + IProtocol* aActor, + FileDescriptor* aResult) { + FileDescriptor::PickleType pfd; + if (!ReadIPDLParam(aMsg, aIter, aActor, &pfd)) { + return false; + } + + *aResult = FileDescriptor(FileDescriptor::IPDLPrivate(), pfd); + 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..0bc5ae569f --- /dev/null +++ b/ipc/glue/FileDescriptor.h @@ -0,0 +1,102 @@ +/* -*- 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" + +#ifdef XP_UNIX +# include "base/file_descriptor_posix.h" +#endif + +namespace mozilla { +namespace ipc { + +// This class is used by IPDL to share file descriptors across processes. When +// sending a FileDescriptor IPDL will first duplicate a platform-specific file +// handle type ('PlatformHandleType') into a handle that is valid in the other +// process. Then IPDL will convert the duplicated handle into a type suitable +// for pickling ('PickleType') and then send that through the IPC pipe. In the +// receiving process the pickled data is converted into a platform-specific file +// handle and then returned to the receiver. +// +// To use this class add 'FileDescriptor' as an argument in the IPDL protocol +// and then pass a file descriptor from C++ to the Call/Send method. The +// Answer/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; + +#ifdef XP_WIN + typedef PlatformHandleType PickleType; +#else + typedef base::FileDescriptor PickleType; +#endif + + // 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); + + // This constructor WILL NOT duplicate the handle. + // FileDescriptor takes the ownership from IPC message. + FileDescriptor(const IPDLPrivate&, const PickleType& aPickle); + + ~FileDescriptor(); + + FileDescriptor& operator=(const FileDescriptor& aOther); + + FileDescriptor& operator=(FileDescriptor&& aOther); + + // Performs platform-specific actions to duplicate mHandle in the other + // process (e.g. dup() on POSIX, DuplicateHandle() on Windows). Returns a + // pickled value that can be passed to the other process via IPC. + PickleType ShareTo(const IPDLPrivate&, ProcessId aTargetPid) const; + + // 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/FileDescriptorSetChild.cpp b/ipc/glue/FileDescriptorSetChild.cpp new file mode 100644 index 0000000000..bc19d0d75a --- /dev/null +++ b/ipc/glue/FileDescriptorSetChild.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 "FileDescriptorSetChild.h" + +namespace mozilla::ipc { + +FileDescriptorSetChild::FileDescriptorSetChild( + const FileDescriptor& aFileDescriptor) { + mFileDescriptors.AppendElement(aFileDescriptor); +} + +FileDescriptorSetChild::~FileDescriptorSetChild() { + MOZ_ASSERT(mFileDescriptors.IsEmpty()); +} + +void FileDescriptorSetChild::ForgetFileDescriptors( + nsTArray<FileDescriptor>& aFileDescriptors) { + aFileDescriptors = std::move(mFileDescriptors); +} + +mozilla::ipc::IPCResult FileDescriptorSetChild::RecvAddFileDescriptor( + const FileDescriptor& aFileDescriptor) { + mFileDescriptors.AppendElement(aFileDescriptor); + return IPC_OK(); +} + +} // namespace mozilla::ipc diff --git a/ipc/glue/FileDescriptorSetChild.h b/ipc/glue/FileDescriptorSetChild.h new file mode 100644 index 0000000000..d3c8827751 --- /dev/null +++ b/ipc/glue/FileDescriptorSetChild.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/. */ + +#ifndef mozilla_ipc_FileDescriptorSetChild_h__ +#define mozilla_ipc_FileDescriptorSetChild_h__ + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/ipc/PFileDescriptorSetChild.h" +#include "nsTArray.h" + +namespace mozilla { + +namespace dom { + +class ContentChild; + +} // namespace dom + +namespace net { + +class SocketProcessChild; + +} // namespace net + +namespace ipc { + +class BackgroundChildImpl; +class FileDescriptor; + +class FileDescriptorSetChild final : public PFileDescriptorSetChild { + friend class BackgroundChildImpl; + friend class mozilla::dom::ContentChild; + friend class mozilla::net::SocketProcessChild; + friend class PFileDescriptorSetChild; + + nsTArray<FileDescriptor> mFileDescriptors; + + public: + void ForgetFileDescriptors(nsTArray<FileDescriptor>& aFileDescriptors); + + private: + explicit FileDescriptorSetChild(const FileDescriptor& aFileDescriptor); + ~FileDescriptorSetChild(); + + mozilla::ipc::IPCResult RecvAddFileDescriptor( + const FileDescriptor& aFileDescriptor); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_FileDescriptorSetChild_h__ diff --git a/ipc/glue/FileDescriptorSetParent.cpp b/ipc/glue/FileDescriptorSetParent.cpp new file mode 100644 index 0000000000..0cd7ea3ea3 --- /dev/null +++ b/ipc/glue/FileDescriptorSetParent.cpp @@ -0,0 +1,33 @@ +/* -*- 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 "FileDescriptorSetParent.h" + +namespace mozilla::ipc { + +FileDescriptorSetParent::FileDescriptorSetParent( + const FileDescriptor& aFileDescriptor) { + mFileDescriptors.AppendElement(aFileDescriptor); +} + +FileDescriptorSetParent::~FileDescriptorSetParent() = default; + +void FileDescriptorSetParent::ForgetFileDescriptors( + nsTArray<FileDescriptor>& aFileDescriptors) { + aFileDescriptors = std::move(mFileDescriptors); +} + +void FileDescriptorSetParent::ActorDestroy(ActorDestroyReason aWhy) { + // Implement me! Bug 1005157 +} + +mozilla::ipc::IPCResult FileDescriptorSetParent::RecvAddFileDescriptor( + const FileDescriptor& aFileDescriptor) { + mFileDescriptors.AppendElement(aFileDescriptor); + return IPC_OK(); +} + +} // namespace mozilla::ipc diff --git a/ipc/glue/FileDescriptorSetParent.h b/ipc/glue/FileDescriptorSetParent.h new file mode 100644 index 0000000000..0510f4982b --- /dev/null +++ b/ipc/glue/FileDescriptorSetParent.h @@ -0,0 +1,58 @@ +/* -*- 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_FileDescriptorSetParent_h__ +#define mozilla_ipc_FileDescriptorSetParent_h__ + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/ipc/PFileDescriptorSetParent.h" +#include "nsTArray.h" + +namespace mozilla { + +namespace dom { + +class ContentParent; + +} // namespace dom + +namespace net { + +class SocketProcessParent; + +} // namespace net + +namespace ipc { + +class BackgroundParentImpl; +class FileDescriptor; + +class FileDescriptorSetParent final : public PFileDescriptorSetParent { + friend class BackgroundParentImpl; + friend class mozilla::dom::ContentParent; + friend class mozilla::net::SocketProcessParent; + friend class PFileDescriptorSetParent; + + nsTArray<FileDescriptor> mFileDescriptors; + + public: + void ForgetFileDescriptors(nsTArray<FileDescriptor>& aFileDescriptors); + + private: + explicit FileDescriptorSetParent(const FileDescriptor& aFileDescriptor); + ~FileDescriptorSetParent(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + mozilla::ipc::IPCResult RecvAddFileDescriptor( + const FileDescriptor& aFileDescriptor); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_FileDescriptorSetParent_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..049353544d --- /dev/null +++ b/ipc/glue/ForkServer.cpp @@ -0,0 +1,304 @@ +/* -*- 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/ForkServer.h" +#include "mozilla/Logging.h" +#include "chrome/common/chrome_switches.h" +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/ipc/ProtocolMessageUtils.h" +#include "nsTraceRefcnt.h" + +#include <string.h> +#include <unistd.h> +#include <fcntl.h> + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxLaunch.h" +#endif + +#include <algorithm> + +namespace mozilla { +namespace ipc { + +static const int sClientFd = 3; + +LazyLogModule gForkServiceLog("ForkService"); + +ForkServer::ForkServer() {} + +/** + * Prepare an environment for running a fork server. + */ +void ForkServer::InitProcess(int* aArgc, char*** aArgv) { + base::InitForkServerProcess(); + + int fd = sClientFd; + int fd_flags = fcntl(sClientFd, F_GETFL, 0); + fcntl(fd, F_SETFL, fd_flags & ~O_NONBLOCK); + mTcver = MakeUnique<MiniTransceiver>(fd, DataBufferClear::AfterReceiving); +} + +/** + * Start providing the service at the IPC channel. + */ +bool ForkServer::HandleMessages() { + // |sClientFd| is created by an instance of |IPC::Channel|. + // It sends a HELLO automatically. + IPC::Message hello; + mTcver->RecvInfallible( + hello, "Expect to receive a HELLO message from the parent process!"); + MOZ_ASSERT(hello.type() == kHELLO_MESSAGE_TYPE); + + // Send it back + mTcver->SendInfallible(hello, "Fail to ack the received HELLO!"); + + while (true) { + 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 = Get<0>(elt); + nsCString& val = 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 = Get<0>(elt).ClonePlatformHandle().release(); + std::pair<int, int> fdmap(fd, Get<1>(elt)); + aOptions->fds_to_remap.push_back(fdmap); + MOZ_LOG(gForkServiceLog, LogLevel::Verbose, + ("\t%d => %d", fdmap.first, fdmap.second)); + } +} + +/** + * 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; + } + + PickleIterator iter(aMsg); + nsTArray<nsCString> argv_array; + nsTArray<EnvVar> env_map; + nsTArray<FdMapping> fds_remap; + + ReadIPDLParamInfallible(&aMsg, &iter, nullptr, &argv_array, + "Error deserializing 'nsCString[]'"); + ReadIPDLParamInfallible(&aMsg, &iter, nullptr, &env_map, + "Error deserializing 'EnvVar[]'"); + ReadIPDLParamInfallible(&aMsg, &iter, nullptr, &fds_remap, + "Error deserializing 'FdMapping[]'"); + aMsg.EndRead(iter, aMsg.type()); + + 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(IPC::Message&& message) { + IPC::Message msg(std::move(message)); + + std::vector<std::string> argv; + base::LaunchOptions options; + if (!ParseForkNewSubprocess(msg, argv, &options)) { + return; + } + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + mozilla::SandboxLaunchForkServerPrepare(argv, options); +#endif + + base::ProcessHandle child_pid = -1; + mAppProcBuilder = MakeUnique<base::AppProcessBuilder>(); + if (!mAppProcBuilder->ForkProcess(argv, 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); + WriteIPDLParam(&reply, 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(msg, 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 + + // 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); + 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")); + 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(); + + MOZ_ASSERT("tab"_ns == (*aArgv)[*aArgc - 1], "Only |tab| is allowed!"); + + // 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..e01e298f89 --- /dev/null +++ b/ipc/glue/ForkServer.h @@ -0,0 +1,44 @@ +/* -*- 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: + static const int kHELLO_MESSAGE_TYPE = 65535; + + ForkServer(); + ~ForkServer(){}; + + void InitProcess(int* aArgc, char*** aArgv); + bool HandleMessages(); + + // Called when a message is received. + void OnMessageReceived(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..a808e7389f --- /dev/null +++ b/ipc/glue/ForkServiceChild.cpp @@ -0,0 +1,182 @@ +/* -*- 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 <unistd.h> +#include <fcntl.h> + +namespace mozilla { +namespace ipc { + +extern LazyLogModule gForkServiceLog; + +mozilla::UniquePtr<ForkServiceChild> ForkServiceChild::sForkServiceChild; + +void ForkServiceChild::StartForkServer() { + std::vector<std::string> extraArgs; + + GeckoChildProcessHost* subprocess = + new GeckoChildProcessHost(GeckoProcessType_ForkServer, false); + subprocess->LaunchAndWaitForProcessHandle(std::move(extraArgs)); + + int fd = subprocess->GetChannel()->GetFileDescriptor(); + fd = dup(fd); // Dup it because the channel will close it. + int fs_flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, fs_flags & ~O_NONBLOCK); + int fd_flags = fcntl(fd, F_GETFD, 0); + fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC); + + sForkServiceChild = mozilla::MakeUnique<ForkServiceChild>(fd, subprocess); + + // Without doing this, IO thread may intercept messages since the + // IPC::Channel created by it is still open. + subprocess->GetChannel()->Close(); +} + +void ForkServiceChild::StopForkServer() { sForkServiceChild = nullptr; } + +ForkServiceChild::ForkServiceChild(int aFd, GeckoChildProcessHost* aProcess) + : mWaitForHello(true), mFailed(false), mProcess(aProcess) { + mTcver = MakeUnique<MiniTransceiver>(aFd); +} + +ForkServiceChild::~ForkServiceChild() { + mProcess->Destroy(); + close(mTcver->GetFD()); +} + +bool ForkServiceChild::SendForkNewSubprocess( + const nsTArray<nsCString>& aArgv, const nsTArray<EnvVar>& aEnvMap, + const nsTArray<FdMapping>& aFdsRemap, pid_t* aPid) { + if (mWaitForHello) { + // IPC::Channel created by the GeckoChildProcessHost has + // already send a HELLO. It is expected to receive a hello + // message from the fork server too. + IPC::Message hello; + mTcver->RecvInfallible(hello, "Fail to receive HELLO message"); + MOZ_ASSERT(hello.type() == ForkServer::kHELLO_MESSAGE_TYPE); + mWaitForHello = false; + } + + mRecvPid = -1; + IPC::Message msg(MSG_ROUTING_CONTROL, Msg_ForkNewSubprocess__ID); + + WriteIPDLParam(&msg, nullptr, aArgv); + WriteIPDLParam(&msg, nullptr, aEnvMap); + WriteIPDLParam(&msg, nullptr, aFdsRemap); + if (!mTcver->Send(msg)) { + MOZ_LOG(gForkServiceLog, LogLevel::Verbose, + ("the pipe to the fork server is closed or having errors")); + OnError(); + return false; + } + + 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 false; + } + OnMessageReceived(std::move(reply)); + + MOZ_ASSERT(mRecvPid != -1); + *aPid = mRecvPid; + return true; +} + +void ForkServiceChild::OnMessageReceived(IPC::Message&& message) { + if (message.type() != Reply_ForkNewSubprocess__ID) { + MOZ_LOG(gForkServiceLog, LogLevel::Verbose, + ("unknown reply type %d", message.type())); + return; + } + PickleIterator iter__(message); + + if (!ReadIPDLParam(&message, &iter__, nullptr, &mRecvPid)) { + MOZ_CRASH("Error deserializing 'pid_t'"); + } + message.EndRead(iter__, message.type()); +} + +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..d257d5e4dc --- /dev/null +++ b/ipc/glue/ForkServiceChild.h @@ -0,0 +1,103 @@ +/* -*- 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 "nsIObserver.h" +#include "nsString.h" +#include "mozilla/ipc/MiniTransceiver.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(); + + /** + * 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. + */ + bool SendForkNewSubprocess(const nsTArray<nsCString>& aArgv, + const nsTArray<EnvVar>& aEnvMap, + const nsTArray<FdMapping>& aFdsRemap, 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(IPC::Message&& message); + void OnError(); + + UniquePtr<MiniTransceiver> mTcver; + static UniquePtr<ForkServiceChild> sForkServiceChild; + pid_t mRecvPid; + bool mWaitForHello; + 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..14b78072e3 --- /dev/null +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -0,0 +1,1814 @@ +/* -*- 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/string_util.h" +#include "base/task.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/process_watcher.h" +#ifdef MOZ_WIDGET_COCOA +# include "SharedMemoryBasic.h" +# include "base/rand_util.h" +# include "chrome/common/mach_ipc_mac.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" + +#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/Omnijar.h" +#include "mozilla/RDDProcessHost.h" +#include "mozilla/Scoped.h" +#include "mozilla/Services.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Telemetry.h" +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/ipc/EnvironmentMap.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" +#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 "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsNativeCharsetUtils.h" +#include "nsTArray.h" +#include "nscore.h" // for NS_FREE_PERMANENT_DATA +#include "private/pprio.h" + +using mozilla::MonitorAutoLock; +using mozilla::Preferences; +using mozilla::StaticMutexAutoLock; + +namespace mozilla { +MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, + PR_Close) +} + +using mozilla::ScopedPRFileDesc; + +#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 { + +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), + 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, "%d", base::GetCurrentProcId()); + + // Compute the serial event target we'll use for launching. + nsCOMPtr<nsIEventTarget> threadOrPool = GetIPCLauncher(); + mLaunchThread = new TaskQueue(threadOrPool.forget()); + + 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); + + 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 bool DoSetup(); + virtual RefPtr<ProcessHandlePromise> DoLaunch() = 0; + virtual bool DoFinishLaunch() { return true; }; + + 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; + 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; + 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]; + + // Set during launch. + IPC::Channel* mChannel = nullptr; + IPC::Channel::ChannelId mChannelId; + ScopedPRFileDesc mCrashAnnotationReadPipe; + ScopedPRFileDesc mCrashAnnotationWritePipe; + nsCOMPtr<nsIFile> mAppDir; +}; + +#ifdef XP_WIN +class WindowsProcessLauncher : public BaseProcessLauncher { + public: + WindowsProcessLauncher(GeckoChildProcessHost* aHost, + std::vector<std::string>&& aExtraOpts) + : BaseProcessLauncher(aHost, std::move(aExtraOpts)), + mProfileDir(aHost->mProfileDir), + mCachedNtdllThunk(aHost->sCachedNtDllThunk) {} + + protected: + virtual bool DoSetup() override; + virtual RefPtr<ProcessHandlePromise> DoLaunch() override; + virtual bool DoFinishLaunch() override; + + mozilla::Maybe<CommandLine> mCmdLine; + bool mUseSandbox = false; + + nsCOMPtr<nsIFile> mProfileDir; + + const StaticAutoPtr<Buffer<IMAGE_THUNK_DATA>>& mCachedNtdllThunk; +}; +typedef WindowsProcessLauncher ProcessLauncher; +#endif // XP_WIN + +#ifdef OS_POSIX +class PosixProcessLauncher : public BaseProcessLauncher { + public: + PosixProcessLauncher(GeckoChildProcessHost* aHost, + std::vector<std::string>&& aExtraOpts) + : BaseProcessLauncher(aHost, std::move(aExtraOpts)), + mProfileDir(aHost->mProfileDir) {} + + protected: + virtual bool DoSetup() override; + virtual RefPtr<ProcessHandlePromise> DoLaunch() override; + virtual bool DoFinishLaunch() override; + + nsCOMPtr<nsIFile> mProfileDir; + + std::vector<std::string> mChildArgv; +}; + +# 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()))), + mParentRecvPort(mMachConnectionName.c_str()) {} + + protected: + virtual bool 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. + ReceivePort 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 bool DoSetup() override; +}; +typedef LinuxProcessLauncher ProcessLauncher; +# elif +# error "Unknown platform" +# endif +#endif // OS_POSIX + +using base::ProcessHandle; +using mozilla::ipc::BaseProcessLauncher; +using mozilla::ipc::ProcessLauncher; + +mozilla::StaticAutoPtr<mozilla::LinkedList<GeckoChildProcessHost>> + GeckoChildProcessHost::sGeckoChildProcessHosts; + +mozilla::StaticMutex GeckoChildProcessHost::sMutex; + +#ifdef XP_WIN +mozilla::StaticAutoPtr<Buffer<IMAGE_THUNK_DATA>> + GeckoChildProcessHost::sCachedNtDllThunk; + +// This static method initializes sCachedNtDllThunk. Because it's called in +// XREMain::XRE_main, which happens long before WindowsProcessLauncher's ctor +// accesses sCachedNtDllThunk, there is no race on sCachedNtDllThunk, thus +// no mutex is needed. +/* static */ +void GeckoChildProcessHost::CacheNtDllThunk() { + if (sCachedNtDllThunk) { + return; + } + + do { + nt::PEHeaders ourExeImage(::GetModuleHandleW(nullptr)); + if (!ourExeImage) { + break; + } + + nt::PEHeaders ntdllImage(::GetModuleHandleW(L"ntdll.dll")); + if (!ntdllImage) { + break; + } + + Maybe<Range<const uint8_t>> ntdllBoundaries = ntdllImage.GetBounds(); + if (!ntdllBoundaries) { + break; + } + + Maybe<Span<IMAGE_THUNK_DATA>> maybeNtDllThunks = + ourExeImage.GetIATThunksForModule("ntdll.dll", ntdllBoundaries.ptr()); + if (maybeNtDllThunks.isNothing()) { + break; + } + + sCachedNtDllThunk = new Buffer<IMAGE_THUNK_DATA>(maybeNtDllThunks.value()); + return; + } while (false); + + // Failed to cache IAT. Initializing the variable with nullptr. + sCachedNtDllThunk = new Buffer<IMAGE_THUNK_DATA>(); +} +#endif + +GeckoChildProcessHost::GeckoChildProcessHost(GeckoProcessType aProcessType, + bool aIsFileContent) + : mProcessType(aProcessType), + mIsFileContent(aIsFileContent), + mMonitor("mozilla.ipc.GeckChildProcessHost.mMonitor"), + mLaunchOptions(MakeUnique<base::LaunchOptions>()), + mProcessState(CREATING_CHANNEL), +#ifdef XP_WIN + mGroupId(u"-"), +#endif +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + mEnableSandboxLogging(false), + mSandboxLevel(0), +#endif + 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) + // The content process needs the content temp dir: + if (aProcessType == GeckoProcessType_Content) { + nsCOMPtr<nsIFile> contentTempDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR, + getter_AddRefs(contentTempDir)); + if (NS_SUCCEEDED(rv)) { + contentTempDir->GetNativePath(mTmpDirName); + } + } +#endif +#if defined(MOZ_ENABLE_FORKSERVER) + if (aProcessType == GeckoProcessType_Content && ForkServiceChild::Get()) { + mLaunchOptions->use_forkserver = true; + } +#endif +} + +GeckoChildProcessHost::~GeckoChildProcessHost() + +{ + AssertIOThread(); + MOZ_RELEASE_ASSERT(mDestroying); + + MOZ_COUNT_DTOR(GeckoChildProcessHost); + + if (mChildProcessHandle != 0) { +#if defined(MOZ_WIDGET_COCOA) + SharedMemoryBasic::CleanupForPidWithLock(mChildProcessHandle); +#endif + ProcessWatcher::EnsureProcessTerminated( + mChildProcessHandle +#ifdef NS_FREE_PERMANENT_DATA + // If we're doing leak logging, shutdown can be slow. + , + false // don't "force" +#endif + ); + } + +#if defined(MOZ_WIDGET_COCOA) + if (mChildTask != MACH_PORT_NULL) + mach_port_deallocate(mach_task_self(), mChildTask); +#endif + + if (mChildProcessHandle != 0) { +#if defined(XP_WIN) + CrashReporter::DeregisterChildCrashAnnotationFileDescriptor( + base::GetProcId(mChildProcessHandle)); +#else + CrashReporter::DeregisterChildCrashAnnotationFileDescriptor( + mChildProcessHandle); +#endif + } +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + if (mSandboxBroker) { + mSandboxBroker->Shutdown(); + mSandboxBroker = nullptr; + } +#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{}, __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(OS_WIN) + wchar_t exePathBuf[MAXPATHLEN]; + if (!::GetModuleFileNameW(nullptr, exePathBuf, MAXPATHLEN)) { + MOZ_CRASH("GetModuleFileNameW failed (FIXME)"); + } +# if defined(MOZ_SANDBOX) + // We need to start the child process using the real path, so that the + // sandbox policy rules will match for DLLs loaded from the bin dir after + // we have lowered the sandbox. + std::wstring exePathStr = exePathBuf; + if (widget::WinUtils::ResolveJunctionPointsAndSymLinks(exePathStr)) { + exePath = FilePath::FromWStringHack(exePathStr); + } else +# endif + { + exePath = FilePath::FromWStringHack(exePathBuf); + } +#elif defined(OS_POSIX) + exePath = FilePath(CommandLine::ForCurrentProcess()->argv()[0]); +#else +# error Sorry; target OS not supported yet. +#endif + return pathType; + } + + if (ShouldHaveDirectoryService()) { + MOZ_ASSERT(gGREBinPath); +#ifdef OS_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("plugin-container.app"_ns); + 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 OS_WIN + exePath = + FilePath::FromWStringHack(CommandLine::ForCurrentProcess()->program()); +#else + exePath = FilePath(CommandLine::ForCurrentProcess()->argv()[0]); +#endif + exePath = exePath.DirName(); + } + + exePath = exePath.AppendASCII(MOZ_CHILD_PROCESS_NAME); + + 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++; } + +void GeckoChildProcessHost::PrepareLaunch() { + if (CrashReporter::GetEnabled()) { + CrashReporter::OOPInit(); + } + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + SandboxLaunchPrepare(mProcessType, mLaunchOptions.get()); +#endif + +#ifdef XP_WIN + if (mProcessType == GeckoProcessType_Plugin) { + InitWindowsGroupID(); + } + +# if defined(MOZ_SANDBOX) + // We need to get the pref here as the process is launched off main thread. + if (mProcessType == GeckoProcessType_Content) { + 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()); + // Before resolving check if path ends with '\' as this indicates we + // want to give read access to a directory and so it needs a wildcard. + bool addWildcard = (resolvedPath.back() == L'\\'); + if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(resolvedPath)) { + NS_ERROR("Failed to resolve test read policy rule."); + continue; + } + + if (addWildcard) { + 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"); + + if (ShouldHaveDirectoryService() && mProcessType == GeckoProcessType_GPU) { + mozilla::Unused << NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(mProfileDir)); + } +# 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)); + + // 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](const LaunchResults& aResults) { + { + 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"); + } + +#ifdef XP_MACOSX + this->mChildTask = aResults.mChildTask; +#endif +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + this->mSandboxBroker = 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(aResults.mHandle, + __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))); + { + 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 || mChildProcessHandle); + + return mProcessState < PROCESS_ERROR; +} + +bool GeckoChildProcessHost::LaunchAndWaitForProcessHandle( + StringVector aExtraOpts) { + if (!AsyncLaunch(std::move(aExtraOpts))) { + return false; + } + return WaitForProcessHandle(); +} + +void GeckoChildProcessHost::InitializeChannel() { + CreateChannel(); + + MonitorAutoLock lock(mMonitor); + mProcessState = CHANNEL_INITIALIZED; + lock.Notify(); +} + +void GeckoChildProcessHost::Join() { + AssertIOThread(); + + if (!mChildProcessHandle) { + return; + } + + // If this fails, there's nothing we can do. + base::KillProcess(mChildProcessHandle, 0, /*wait*/ true); + SetAlreadyDead(); +} + +void GeckoChildProcessHost::SetAlreadyDead() { + if (mChildProcessHandle && mChildProcessHandle != 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))) { +# ifdef MOZ_SANDBOX + // We need to make sure the child log name doesn't contain any junction + // points or symlinks or the sandbox will reject rules to allow writing. + std::wstring resolvedPath(NS_ConvertUTF8toUTF16(absPath).get()); + if (widget::WinUtils::ResolveJunctionPointsAndSymLinks(resolvedPath)) { + AppendUTF16toUTF8( + Span(reinterpret_cast<const char16_t*>(resolvedPath.data()), + resolvedPath.size()), + buffer); + } else +# endif + { + 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(gChildCounter); +} + +// 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; + +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("-appdir")); + std::wstring wpath(path.get()); + aCmdLine.AppendLooseValue(wpath); +#else + nsAutoCString path; + MOZ_ALWAYS_SUCCEEDS(aAppDir->GetNativePath(path)); + aCmdLine.push_back("-appdir"); + aCmdLine.push_back(path.get()); +#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)); + aCmdLine.push_back("-profile"); + aCmdLine.push_back(path.get()); + } +#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() { + if (!DoSetup()) { + return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __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__); + }); +} + +bool BaseProcessLauncher::DoSetup() { +#if defined(MOZ_GECKO_PROFILER) || defined(MOZ_MEMORY) + RefPtr<BaseProcessLauncher> self = this; +# ifdef MOZ_GECKO_PROFILER + GetProfilerEnvVarsForChildProcess([self](const char* key, const char* value) { + self->mLaunchOptions->env_map[ENVIRONMENT_STRING(key)] = + ENVIRONMENT_STRING(value); + }); +# endif +# 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 +#endif + + MapChildLogging(); + + return PR_CreatePipe(&mCrashAnnotationReadPipe.rwget(), + &mCrashAnnotationWritePipe.rwget()) == PR_SUCCESS; +} + +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()); + } +} + +#if defined(MOZ_WIDGET_GTK) +bool LinuxProcessLauncher::DoSetup() { + if (!PosixProcessLauncher::DoSetup()) { + return false; + } + + 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 true; +} +#endif // MOZ_WIDGET_GTK + +#ifdef OS_POSIX +bool PosixProcessLauncher::DoSetup() { + if (!BaseProcessLauncher::DoSetup()) { + return false; + } + + // 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(OS_LINUX) || defined(OS_BSD) + const char* ld_library_path = PR_GetEnv("LD_LIBRARY_PATH"); + nsCString new_ld_lib_path(path.get()); + +# ifdef MOZ_WIDGET_GTK + if (mProcessType == GeckoProcessType_Plugin) { + new_ld_lib_path.AppendLiteral("/gtk2:"); + new_ld_lib_path.Append(path.get()); + } +# endif // MOZ_WIDGET_GTK + 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 OS_MACOSX // defined(OS_LINUX) || defined(OS_BSD) + mLaunchOptions->env_map["DYLD_LIBRARY_PATH"] = path.get(); + // XXX DYLD_INSERT_LIBRARIES should only be set when launching a plugin + // process, and has no effect on other subprocesses (the hooks in + // libplugin_child_interpose.dylib become noops). But currently it + // gets set when launching any kind of subprocess. + // + // Trigger "dyld interposing" for the dylib that contains + // plugin_child_interpose.mm. This allows us to hook OS calls in the + // plugin process (ones that don't work correctly in a background + // process). Don't break any other "dyld interposing" that has already + // been set up by whatever may have launched the browser. + const char* prevInterpose = PR_GetEnv("DYLD_INSERT_LIBRARIES"); + nsCString interpose; + if (prevInterpose && strlen(prevInterpose) > 0) { + interpose.Assign(prevInterpose); + interpose.Append(':'); + } + interpose.Append(path.get()); + interpose.AppendLiteral("/libplugin_child_interpose.dylib"); + mLaunchOptions->env_map["DYLD_INSERT_LIBRARIES"] = interpose.get(); + + // 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 // defined(OS_LINUX) || defined(OS_BSD) + } + + 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 + int srcChannelFd, dstChannelFd; + mChannel->GetClientFileDescriptorMapping(&srcChannelFd, &dstChannelFd); + mLaunchOptions->fds_to_remap.push_back( + std::pair<int, int>(srcChannelFd, dstChannelFd)); + + // 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_GMPlugin) { +# if defined(MOZ_WIDGET_ANDROID) + if (Omnijar::IsInitialized()) { + // Make sure that child processes can find the omnijar + // See XRE_InitCommandLine in nsAppRunner.cpp + nsAutoCString path; + nsCOMPtr<nsIFile> file = Omnijar::GetPath(Omnijar::GRE); + if (file && NS_SUCCEEDED(file->GetNativePath(path))) { + mChildArgv.push_back("-greomni"); + mChildArgv.push_back(path.get()); + } + } +# endif + // Add the application directory path (-appdir path) +# ifdef XP_MACOSX + AddAppDirToCommandLine(mChildArgv, mAppDir, mProfileDir); +# else + AddAppDirToCommandLine(mChildArgv, mAppDir, nullptr); +# endif + } + + mChildArgv.push_back(mPidString); + + if (!CrashReporter::IsDummy()) { +# if defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) + int childCrashFd, childCrashRemapFd; + if (!CrashReporter::CreateNotificationPipeForChild(&childCrashFd, + &childCrashRemapFd)) { + return false; + } + + 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"); + } +# elif defined(MOZ_WIDGET_COCOA) /* defined(OS_LINUX) || defined(OS_BSD) || \ + defined(OS_SOLARIS) */ + mChildArgv.push_back(CrashReporter::GetChildNotificationPipe()); +# endif // defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS) + } + + int fd = PR_FileDesc2NativeHandle(mCrashAnnotationWritePipe); + mLaunchOptions->fds_to_remap.push_back( + std::make_pair(fd, CrashReporter::GetAnnotationTimeCrashFd())); + +# ifdef MOZ_WIDGET_COCOA + mChildArgv.push_back( + static_cast<MacProcessLauncher*>(this)->mMachConnectionName.c_str()); +# endif // MOZ_WIDGET_COCOA + + mChildArgv.push_back(ChildProcessType()); + return true; +} +#endif // OS_POSIX + +#if defined(MOZ_WIDGET_ANDROID) +RefPtr<ProcessHandlePromise> AndroidProcessLauncher::DoLaunch() { + return LaunchAndroidService(mProcessType, mChildArgv, + mLaunchOptions->fds_to_remap); +} +#endif // MOZ_WIDGET_ANDROID + +#ifdef OS_POSIX +RefPtr<ProcessHandlePromise> PosixProcessLauncher::DoLaunch() { + ProcessHandle handle = 0; + if (!base::LaunchApp(mChildArgv, *mLaunchOptions, &handle)) { + return ProcessHandlePromise::CreateAndReject(LaunchError{}, __func__); + } + return ProcessHandlePromise::CreateAndResolve(handle, __func__); +} + +bool PosixProcessLauncher::DoFinishLaunch() { + if (!BaseProcessLauncher::DoFinishLaunch()) { + return false; + } + + // We're in the parent and the child was launched. Close the child FD in the + // parent as soon as possible, which will allow the parent to detect when the + // child closes its FD (either due to normal exit or due to crash). + mChannel->CloseClientFileDescriptor(); + + return true; +} +#endif // OS_POSIX + +#ifdef XP_MACOSX +bool MacProcessLauncher::DoFinishLaunch() { + if (!PosixProcessLauncher::DoFinishLaunch()) { + return false; + } + + // Wait for the child process to send us its 'task_t' data. + const int kTimeoutMs = 10000; + + MachReceiveMessage child_message; + kern_return_t err = + mParentRecvPort.WaitForMessage(&child_message, kTimeoutMs); + if (err != KERN_SUCCESS) { + std::string errString = + StringPrintf("0x%x %s", err, mach_error_string(err)); + CHROMIUM_LOG(ERROR) << "parent WaitForMessage() failed: " << errString; + return false; + } + + task_t child_task = child_message.GetTranslatedPort(0); + if (child_task == MACH_PORT_NULL) { + CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(0) failed."; + return false; + } + + if (child_message.GetTranslatedPort(1) == MACH_PORT_NULL) { + CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(1) failed."; + return false; + } + MachPortSender parent_sender(child_message.GetTranslatedPort(1)); + + if (child_message.GetTranslatedPort(2) == MACH_PORT_NULL) { + CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(2) failed."; + } + auto* parent_recv_port_memory_ack = + new MachPortSender(child_message.GetTranslatedPort(2)); + + if (child_message.GetTranslatedPort(3) == MACH_PORT_NULL) { + CHROMIUM_LOG(ERROR) << "parent GetTranslatedPort(3) failed."; + } + auto* parent_send_port_memory = + new MachPortSender(child_message.GetTranslatedPort(3)); + + MachSendMessage parent_message(/* id= */ 0); + if (!parent_message.AddDescriptor(MachMsgPortDescriptor(bootstrap_port))) { + CHROMIUM_LOG(ERROR) << "parent AddDescriptor(" << bootstrap_port + << ") failed."; + return false; + } + + auto* parent_recv_port_memory = new ReceivePort(); + if (!parent_message.AddDescriptor( + MachMsgPortDescriptor(parent_recv_port_memory->GetPort()))) { + CHROMIUM_LOG(ERROR) << "parent AddDescriptor(" + << parent_recv_port_memory->GetPort() << ") failed."; + return false; + } + + auto* parent_send_port_memory_ack = new ReceivePort(); + if (!parent_message.AddDescriptor( + MachMsgPortDescriptor(parent_send_port_memory_ack->GetPort()))) { + CHROMIUM_LOG(ERROR) << "parent AddDescriptor(" + << parent_send_port_memory_ack->GetPort() + << ") failed."; + return false; + } + + err = parent_sender.SendMessage(parent_message, kTimeoutMs); + if (err != KERN_SUCCESS) { + std::string errString = + StringPrintf("0x%x %s", err, mach_error_string(err)); + CHROMIUM_LOG(ERROR) << "parent SendMessage() failed: " << errString; + return false; + } + + SharedMemoryBasic::SetupMachMemory( + mResults.mHandle, parent_recv_port_memory, parent_recv_port_memory_ack, + parent_send_port_memory, parent_send_port_memory_ack, false); + + // NB: on OS X, we block much longer than we need to in order to + // reach this call, waiting for the child process's task_t. The + // best way to fix that is to refactor this file, hard. + mResults.mChildTask = child_task; + + return true; +} +#endif // XP_MACOSX + +#ifdef XP_WIN +bool WindowsProcessLauncher::DoSetup() { + if (!BaseProcessLauncher::DoSetup()) { + return false; + } + + 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_) + const bool isClearKey = isGMP && Contains(mExtraOpts, "gmp-clearkey"); + const bool isSandboxBroker = + mProcessType == GeckoProcessType_RemoteSandboxBroker; + if (isClearKey || isWidevine || isSandboxBroker) { + // 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()); + } +# endif // if defined(_ARM64_) +# endif // defined(MOZ_SANDBOX) || defined(_ARM64_) + + mCmdLine.emplace(exePath.ToWStringHack()); + + if (pathType == BinPathType::Self) { + mCmdLine->AppendLooseValue(UTF8ToWide("-contentproc")); + } + + mCmdLine->AppendSwitchWithValue(switches::kProcessChannelID, mChannelId); + + 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 (isClearKey || isWidevine) + mResults.mSandboxBroker = new RemoteSandboxBroker(); + 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_Plugin: + if (mSandboxLevel > 0 && !PR_GetEnv("MOZ_DISABLE_NPAPI_SANDBOX")) { + if (!mResults.mSandboxBroker->SetSecurityLevelForPluginProcess( + mSandboxLevel)) { + return false; + } + 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 (!mResults.mSandboxBroker->SetSecurityLevelForGMPlugin(level)) { + return false; + } + 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, + mProfileDir); + 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 (!mResults.mSandboxBroker->SetSecurityLevelForRDDProcess()) { + return false; + } + mUseSandbox = true; + } + break; + case GeckoProcessType_Socket: + if (!PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS_SANDBOX")) { + if (!mResults.mSandboxBroker->SetSecurityLevelForSocketProcess()) { + return false; + } + 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()); + } + } +# 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()); + + // Process id + mCmdLine->AppendLooseValue(UTF8ToWide(mPidString)); + + mCmdLine->AppendLooseValue( + UTF8ToWide(CrashReporter::GetChildNotificationPipe())); + + if (!CrashReporter::IsDummy()) { + PROsfd h = PR_FileDesc2NativeHandle(mCrashAnnotationWritePipe); + mLaunchOptions->handles_to_inherit.push_back(reinterpret_cast<HANDLE>(h)); + std::string hStr = std::to_string(h); + mCmdLine->AppendLooseValue(UTF8ToWide(hStr)); + } + + // 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 true; +} + +RefPtr<ProcessHandlePromise> WindowsProcessLauncher::DoLaunch() { + ProcessHandle handle = 0; +# ifdef MOZ_SANDBOX + if (mUseSandbox) { + const IMAGE_THUNK_DATA* cachedNtdllThunk = + mCachedNtdllThunk ? mCachedNtdllThunk->begin() : nullptr; + if (mResults.mSandboxBroker->LaunchApp( + mCmdLine->program().c_str(), + mCmdLine->command_line_string().c_str(), mLaunchOptions->env_map, + mProcessType, mEnableSandboxLogging, cachedNtdllThunk, &handle)) { + 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(LaunchError{}, __func__); + } +# endif // defined(MOZ_SANDBOX) + + if (!base::LaunchApp(mCmdLine.ref(), *mLaunchOptions, &handle)) { + return ProcessHandlePromise::CreateAndReject(LaunchError{}, __func__); + } + return ProcessHandlePromise::CreateAndResolve(handle, __func__); +} + +bool WindowsProcessLauncher::DoFinishLaunch() { + if (!BaseProcessLauncher::DoFinishLaunch()) { + return false; + } + +# ifdef MOZ_SANDBOX + if (!mUseSandbox) { + // We need to be able to duplicate handles to some types of non-sandboxed + // child processes. + switch (mProcessType) { + case GeckoProcessType_Default: + MOZ_CRASH("shouldn't be launching a parent process"); + case GeckoProcessType_Plugin: + case GeckoProcessType_IPDLUnitTest: + // No handle duplication necessary. + break; + default: + if (!SandboxBroker::AddTargetPeer(mResults.mHandle)) { + NS_WARNING("Failed to add child process as target peer."); + } + break; + } + } +# endif // MOZ_SANDBOX + + return true; +} +#endif // XP_WIN + +RefPtr<ProcessLaunchPromise> BaseProcessLauncher::FinishLaunch() { + if (!DoFinishLaunch()) { + return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__); + } + + MOZ_DIAGNOSTIC_ASSERT(mResults.mHandle); + + CrashReporter::RegisterChildCrashAnnotationFileDescriptor( + base::GetProcId(mResults.mHandle), mCrashAnnotationReadPipe.forget()); + + 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(int32_t peer_pid) { + if (!OpenPrivilegedHandle(peer_pid)) { + MOZ_CRASH("can't open handle to child process"); + } + MonitorAutoLock lock(mMonitor); + mProcessState = PROCESS_CONNECTED; + lock.Notify(); +} + +void GeckoChildProcessHost::OnMessageReceived(IPC::Message&& aMsg) { + // We never process messages ourself, just save them up for the next + // listener. + mQueue.push(std::move(aMsg)); +} + +void GeckoChildProcessHost::OnChannelError() { + // Update the process state to an error state if we have a channel + // error before we're connected. This fixes certain failures, + // but does not address the full range of possible issues described + // in the FIXME comment below. + MonitorAutoLock lock(mMonitor); + if (mProcessState < PROCESS_CONNECTED) { + mProcessState = PROCESS_ERROR; + lock.Notify(); + } + // FIXME/bug 773925: save up this error for the next listener. +} + +RefPtr<ProcessHandlePromise> GeckoChildProcessHost::WhenProcessHandleReady() { + MOZ_ASSERT(mHandlePromise != nullptr); + return mHandlePromise; +} + +void GeckoChildProcessHost::GetQueuedMessages(std::queue<IPC::Message>& queue) { + // If this is called off the IO thread, bad things will happen. + DCHECK(MessageLoopForIO::current()); + swap(queue, mQueue); + // We expect the next listener to take over processing of our queue. +} + +#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; + int32_t crashAnnotationFd = -1; + if (fds_to_remap.size() == 4) { + crashAnnotationFd = fds_to_remap[3].first; + } + if (fds_to_remap.size() == 5) { + crashFd = fds_to_remap[3].first; + crashAnnotationFd = fds_to_remap[4].first; + } + + auto type = java::GeckoProcessType::FromInt(aType); + auto genericResult = java::GeckoProcessManager::Start( + type, jargs, prefsFd, prefMapFd, ipcFd, crashFd, crashAnnotationFd); + 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 (!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; + 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); + for (GeckoChildProcessHost* gp = sGeckoChildProcessHosts->getFirst(); gp; + gp = static_cast<mozilla::LinkedListElement<GeckoChildProcessHost>*>(gp) + ->getNext()) { + aCallback(gp); + } +} + +RefPtr<ProcessLaunchPromise> BaseProcessLauncher::Launch( + GeckoChildProcessHost* aHost) { + AssertIOThread(); + + // Initializing the channel needs to happen on the I/O thread, but everything + // else can run on the launcher thread (or pool), to avoid blocking IPC + // messages. + // + // We avoid passing the host to the launcher thread to reduce the chances of + // data races with the IO thread (where e.g. OnChannelConnected may run + // concurrently). The pool currently needs access to the channel, which is not + // great. + // + // It's also unfortunate that we need to work with raw pointers to both the + // host and the channel. The assumption here is that the host (and therefore + // the channel) are never torn down until the return promise is resolved or + // rejected. + aHost->InitializeChannel(); + mChannel = aHost->GetChannel(); + if (!mChannel) { + return ProcessLaunchPromise::CreateAndReject(LaunchError{}, __func__); + } + mChannelId = aHost->GetChannelId(); + + 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..482c4d71a1 --- /dev/null +++ b/ipc/glue/GeckoChildProcessHost.h @@ -0,0 +1,299 @@ +/* -*- 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/child_process_host.h" +#include "chrome/common/ipc_message.h" + +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/Atomics.h" +#include "mozilla/Buffer.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Monitor.h" +#include "mozilla/MozPromise.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" + +#include "nsCOMPtr.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 + +struct _MacSandboxInfo; +typedef _MacSandboxInfo MacSandboxInfo; + +namespace mozilla { +namespace ipc { + +struct LaunchError {}; +typedef mozilla::MozPromise<base::ProcessHandle, LaunchError, false> + ProcessHandlePromise; + +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, false> + ProcessLaunchPromise; + +class GeckoChildProcessHost : public ChildProcessHost, + public LinkedListElement<GeckoChildProcessHost> { + protected: + typedef mozilla::Monitor Monitor; + typedef std::vector<std::string> StringVector; + + public: + typedef base::ProcessHandle 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(); + + // 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(int32_t peer_pid) override; + virtual void OnMessageReceived(IPC::Message&& aMsg) override; + virtual void OnChannelError() override; + virtual void GetQueuedMessages(std::queue<IPC::Message>& queue) override; + + // Resolves to the process handle when it's available (see + // LaunchAndWaitForProcessHandle); use with AsyncLaunch. + RefPtr<ProcessHandlePromise> WhenProcessHandleReady(); + + virtual void InitializeChannel(); + + virtual bool CanShutdown() override { return true; } + + using ChildProcessHost::TakeChannel; + IPC::Channel* GetChannel() { return channelp(); } + ChannelId GetChannelId() { return channel_id(); } + + // Returns a "borrowed" handle to the child process - the handle returned + // by this function must not be closed by the caller. + ProcessHandle GetChildProcessHandle() { return mChildProcessHandle; } + + GeckoProcessType GetProcessType() { return mProcessType; } + +#ifdef XP_MACOSX + task_t GetChildTask() { return mChildTask; } +#endif + +#ifdef XP_WIN + static void CacheNtDllThunk(); + + 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 + + /** + * Must run on the IO thread. Cause the OS process to exit and + * ensure its OS resources are cleaned up. + */ + void Join(); + + // For bug 943174: Skip the EnsureProcessTerminated call in the destructor. + void SetAlreadyDead(); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + // 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 + 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: + ~GeckoChildProcessHost(); + GeckoProcessType mProcessType; + bool mIsFileContent; + Monitor mMonitor; + FilePath mProcessPath; + // 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; + + // 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; + + 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 + + ProcessHandle mChildProcessHandle; +#if defined(OS_MACOSX) + task_t mChildTask; +#endif + RefPtr<ProcessHandlePromise> mHandlePromise; + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + bool mDisableOSActivityMode; +#endif + + bool OpenPrivilegedHandle(base::ProcessId aPid); + +#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(); + + // In between launching the subprocess and handing off its IPC + // channel, there's a small window of time in which *we* might still + // be the channel listener, and receive messages. That's bad + // because we have no idea what to do with those messages. So queue + // them here until we hand off the eventual listener. + // + // FIXME/cjones: this strongly indicates bad design. Shame on us. + std::queue<IPC::Message> mQueue; + + // 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; +#ifdef XP_WIN + static StaticAutoPtr<Buffer<IMAGE_THUNK_DATA>> sCachedNtDllThunk; +#endif + 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..edb1708b61 --- /dev/null +++ b/ipc/glue/IOThreadChild.h @@ -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/. */ + +#ifndef dom_plugins_IOThreadChild_h +#define dom_plugins_IOThreadChild_h 1 + +#include "chrome/common/child_thread.h" + +namespace mozilla { +namespace ipc { +//----------------------------------------------------------------------------- + +// The IOThreadChild class represents a background thread where the +// IPC IO MessageLoop lives. +class IOThreadChild : public ChildThread { + public: + IOThreadChild() + : ChildThread(base::Thread::Options(MessageLoop::TYPE_IO, + 0)) // stack size + {} + + ~IOThreadChild() = default; + + static MessageLoop* message_loop() { + return IOThreadChild::current()->Thread::message_loop(); + } + + static UniquePtr<IPC::Channel> TakeChannel() { + return IOThreadChild::current()->ChildThread::TakeChannel(); + } + + 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/IPCMessageUtils.cpp b/ipc/glue/IPCMessageUtils.cpp new file mode 100644 index 0000000000..97cf607b21 --- /dev/null +++ b/ipc/glue/IPCMessageUtils.cpp @@ -0,0 +1,24 @@ +/* -*- 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 "IPCMessageUtils.h" + +#include <cstddef> +#include "mozilla/CheckedInt.h" + +namespace IPC { + +bool ByteLengthIsValid(uint32_t aNumElements, size_t aElementSize, + int* aByteLength) { + auto length = mozilla::CheckedInt<int>(aNumElements) * aElementSize; + if (!length.isValid()) { + return false; + } + *aByteLength = length.value(); + return true; +} + +} // namespace IPC diff --git a/ipc/glue/IPCMessageUtils.h b/ipc/glue/IPCMessageUtils.h new file mode 100644 index 0000000000..afcc5305a7 --- /dev/null +++ b/ipc/glue/IPCMessageUtils.h @@ -0,0 +1,258 @@ +/* -*- 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 "build/build_config.h" +#include "chrome/common/ipc_message.h" +#include "chrome/common/ipc_message_utils.h" +#include "mozilla/MacroForEach.h" +#include "mozilla/ipc/IPCCore.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(OS_POSIX) +// 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(Message* aMsg, const paramType& aParam) { + aMsg->WriteBytes(&aParam, sizeof(aParam)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return aMsg->ReadBytesInto(aIter, 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(Message* aMsg, const paramType& aParam) {} + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + *aResult = {}; + return true; + } +}; + +template <> +struct ParamTraits<int8_t> { + typedef int8_t paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteBytes(&aParam, sizeof(aParam)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return aMsg->ReadBytesInto(aIter, aResult, sizeof(*aResult)); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + // Use 0xff to avoid sign extension. + aLog->append(StringPrintf(L"0x%02x", aParam & 0xff)); + } +}; + +template <> +struct ParamTraits<uint8_t> { + typedef uint8_t paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteBytes(&aParam, sizeof(aParam)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return aMsg->ReadBytesInto(aIter, aResult, sizeof(*aResult)); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"0x%02x", aParam)); + } +}; + +#if !defined(OS_POSIX) +// See above re: keeping definitions in sync +template <> +struct ParamTraits<base::FileDescriptor> { + typedef base::FileDescriptor paramType; + static void Write(Message* aMsg, const paramType& aParam) { + MOZ_CRASH("FileDescriptor isn't meaningful on this platform"); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + MOZ_CRASH("FileDescriptor isn't meaningful on this platform"); + return false; + } +}; +#endif // !defined(OS_POSIX) + +template <> +struct ParamTraits<mozilla::void_t> { + typedef mozilla::void_t paramType; + static void Write(Message* aMsg, const paramType& aParam) {} + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + *aResult = paramType(); + return true; + } +}; + +template <> +struct ParamTraits<mozilla::null_t> { + typedef mozilla::null_t paramType; + static void Write(Message* aMsg, const paramType& aParam) {} + static bool Read(const Message* aMsg, PickleIterator* aIter, + 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(const Message* aMsg, PickleIterator* aIter, + ParamType* aResult, + void (ParamType::*aSetter)(bool)) { + bool value; + if (ReadParam(aMsg, aIter, &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(Message* aMsg, const Ts&... aArgs) { + (WriteParam(aMsg, aArgs), ...); +} + +template <typename... Ts> +static bool ReadParams(const Message* aMsg, PickleIterator* aIter, + Ts&... aArgs) { + return (ReadParam(aMsg, aIter, &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(Message* aMsg, const paramType& aParam) { \ + WriteParams(aMsg, MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \ + (__VA_ARGS__))); \ + } \ + \ + static bool Read(const Message* aMsg, PickleIterator* aIter, \ + paramType* aResult) { \ + paramType& aParam = *aResult; \ + return ReadParams(aMsg, aIter, \ + 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(Message* aMsg, const paramType& aParam) { \ + WriteParam(aMsg, static_cast<const Super&>(aParam)); \ + WriteParams(aMsg, MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \ + (__VA_ARGS__))); \ + } \ + \ + static bool Read(const Message* aMsg, PickleIterator* aIter, \ + paramType* aResult) { \ + paramType& aParam = *aResult; \ + return ReadParam(aMsg, aIter, static_cast<Super*>(aResult)) && \ + ReadParams(aMsg, aIter, \ + MOZ_FOR_EACH_SEPARATED(ACCESS_PARAM_FIELD, (, ), (), \ + (__VA_ARGS__))); \ + } \ + }; + +#endif /* __IPC_GLUE_IPCMESSAGEUTILS_H__ */ diff --git a/ipc/glue/IPCMessageUtilsSpecializations.h b/ipc/glue/IPCMessageUtilsSpecializations.h new file mode 100644 index 0000000000..4fcaac046c --- /dev/null +++ b/ipc/glue/IPCMessageUtilsSpecializations.h @@ -0,0 +1,938 @@ +/* -*- 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/Unused.h" +#include "mozilla/Vector.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "nsCSSPropertyID.h" +#include "nsDebug.h" +#include "nsHashKeys.h" +#include "nsIContentPolicy.h" +#include "nsID.h" +#include "nsILoadInfo.h" +#include "nsIThread.h" +#include "nsLiteralString.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsTHashtable.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; +} + +namespace IPC { + +template <> +struct ParamTraits<nsACString> { + typedef nsACString paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + bool isVoid = aParam.IsVoid(); + aMsg->WriteBool(isVoid); + + if (isVoid) + // represents a nullptr pointer + return; + + uint32_t length = aParam.Length(); + WriteParam(aMsg, length); + aMsg->WriteBytes(aParam.BeginReading(), length); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + bool isVoid; + if (!aMsg->ReadBool(aIter, &isVoid)) return false; + + if (isVoid) { + aResult->SetIsVoid(true); + return true; + } + + uint32_t length; + if (!ReadParam(aMsg, aIter, &length)) { + return false; + } + if (!aMsg->HasBytesAvailable(aIter, length)) { + return false; + } + aResult->SetLength(length); + + return aMsg->ReadBytesInto(aIter, aResult->BeginWriting(), length); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + if (aParam.IsVoid()) + aLog->append(L"(NULL)"); + else + aLog->append(UTF8ToWide(aParam.BeginReading())); + } +}; + +template <> +struct ParamTraits<nsAString> { + typedef nsAString paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + bool isVoid = aParam.IsVoid(); + aMsg->WriteBool(isVoid); + + if (isVoid) + // represents a nullptr pointer + return; + + uint32_t length = aParam.Length(); + WriteParam(aMsg, length); + aMsg->WriteBytes(aParam.BeginReading(), length * sizeof(char16_t)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + bool isVoid; + if (!aMsg->ReadBool(aIter, &isVoid)) return false; + + if (isVoid) { + aResult->SetIsVoid(true); + return true; + } + + uint32_t length; + if (!ReadParam(aMsg, aIter, &length)) { + return false; + } + + mozilla::CheckedInt<uint32_t> byteLength = + mozilla::CheckedInt<uint32_t>(length) * sizeof(char16_t); + if (!byteLength.isValid() || + !aMsg->HasBytesAvailable(aIter, byteLength.value())) { + return false; + } + + aResult->SetLength(length); + + return aMsg->ReadBytesInto(aIter, aResult->BeginWriting(), + byteLength.value()); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + if (aParam.IsVoid()) + aLog->append(L"(NULL)"); + else { +#ifdef WCHAR_T_IS_UTF16 + aLog->append(reinterpret_cast<const wchar_t*>(aParam.BeginReading())); +#else + uint32_t length = aParam.Length(); + for (uint32_t index = 0; index < length; index++) { + aLog->push_back(std::wstring::value_type(aParam[index])); + } +#endif + } + } +}; + +template <> +struct ParamTraits<nsCString> : ParamTraits<nsACString> { + typedef nsCString paramType; +}; + +template <> +struct ParamTraits<nsLiteralCString> : ParamTraits<nsACString> { + typedef nsLiteralCString paramType; +}; + +#ifdef MOZILLA_INTERNAL_API + +template <> +struct ParamTraits<nsAutoCString> : ParamTraits<nsCString> { + typedef nsAutoCString paramType; +}; + +#endif // MOZILLA_INTERNAL_API + +template <> +struct ParamTraits<nsString> : ParamTraits<nsAString> { + typedef nsString paramType; +}; + +template <> +struct ParamTraits<nsLiteralString> : ParamTraits<nsAString> { + typedef nsLiteralString paramType; +}; + +template <> +struct ParamTraits<nsDependentSubstring> : ParamTraits<nsAString> { + typedef nsDependentSubstring paramType; +}; + +template <> +struct ParamTraits<nsDependentCSubstring> : ParamTraits<nsACString> { + typedef nsDependentCSubstring paramType; +}; + +#ifdef MOZILLA_INTERNAL_API + +template <> +struct ParamTraits<nsAutoString> : ParamTraits<nsString> { + typedef nsAutoString paramType; +}; + +#endif // MOZILLA_INTERNAL_API + +template <> +struct ParamTraits<nsTHashtable<nsUint64HashKey>> { + typedef nsTHashtable<nsUint64HashKey> paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + uint32_t count = aParam.Count(); + WriteParam(aMsg, count); + for (auto iter = aParam.ConstIter(); !iter.Done(); iter.Next()) { + WriteParam(aMsg, iter.Get()->GetKey()); + } + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint32_t count; + if (!ReadParam(aMsg, aIter, &count)) { + return false; + } + paramType table(count); + for (uint32_t i = 0; i < count; ++i) { + uint64_t key; + if (!ReadParam(aMsg, aIter, &key)) { + return false; + } + table.PutEntry(key); + } + *aResult = std::move(table); + return true; + } +}; + +// Pickle::ReadBytes and ::WriteBytes take the length in ints, so we must +// ensure there is no overflow. This returns |false| if it would overflow. +// Otherwise, it returns |true| and places the byte length in |aByteLength|. +bool ByteLengthIsValid(uint32_t aNumElements, size_t aElementSize, + int* aByteLength); + +// Note: IPDL will sometimes codegen specialized implementations of +// nsTArray serialization and deserialization code in +// implementSpecialArrayPickling(). This is needed when ParamTraits<E> +// is not defined. +template <typename E> +struct ParamTraits<nsTArray<E>> { + typedef nsTArray<E> paramType; + + // We write arrays of integer or floating-point data using a single pickling + // call, rather than writing each element individually. We deliberately do + // not use mozilla::IsPod here because it is perfectly reasonable to have + // a data structure T for which IsPod<T>::value is true, yet also have a + // ParamTraits<T> specialization. + static const bool sUseWriteBytes = + (std::is_integral_v<E> || std::is_floating_point_v<E>); + + static void Write(Message* aMsg, const paramType& aParam) { + uint32_t length = aParam.Length(); + WriteParam(aMsg, length); + + if (sUseWriteBytes) { + int pickledLength = 0; + MOZ_RELEASE_ASSERT(ByteLengthIsValid(length, sizeof(E), &pickledLength)); + aMsg->WriteBytes(aParam.Elements(), pickledLength); + } else { + const E* elems = aParam.Elements(); + for (uint32_t index = 0; index < length; index++) { + WriteParam(aMsg, elems[index]); + } + } + } + + // This method uses infallible allocation so that an OOM failure will + // show up as an OOM crash rather than an IPC FatalError. + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint32_t length; + if (!ReadParam(aMsg, aIter, &length)) { + return false; + } + + if (sUseWriteBytes) { + int pickledLength = 0; + if (!ByteLengthIsValid(length, sizeof(E), &pickledLength)) { + return false; + } + + E* elements = aResult->AppendElements(length); + return aMsg->ReadBytesInto(aIter, elements, pickledLength); + } else { + // Each ReadParam<E> may read more than 1 byte each; this is an attempt + // to minimally validate that the length isn't much larger than what's + // actually available in aMsg. + if (!aMsg->HasBytesAvailable(aIter, length)) { + return false; + } + + aResult->SetCapacity(length); + + for (uint32_t index = 0; index < length; index++) { + E* element = aResult->AppendElement(); + if (!ReadParam(aMsg, aIter, element)) { + return false; + } + } + return true; + } + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + for (uint32_t index = 0; index < aParam.Length(); index++) { + if (index) { + aLog->append(L" "); + } + LogParam(aParam[index], aLog); + } + } +}; + +template <typename E> +struct ParamTraits<CopyableTArray<E>> : ParamTraits<nsTArray<E>> {}; + +template <typename E> +struct ParamTraits<FallibleTArray<E>> { + typedef FallibleTArray<E> paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, static_cast<const nsTArray<E>&>(aParam)); + } + + // Deserialize the array infallibly, but return a FallibleTArray. + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + nsTArray<E> temp; + if (!ReadParam(aMsg, aIter, &temp)) return false; + + *aResult = std::move(temp); + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + LogParam(static_cast<const nsTArray<E>&>(aParam), aLog); + } +}; + +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 E, size_t N, typename AP> +struct ParamTraits<mozilla::Vector<E, N, AP>> { + typedef mozilla::Vector<E, N, AP> paramType; + + // We write arrays of integer or floating-point data using a single pickling + // call, rather than writing each element individually. We deliberately do + // not use mozilla::IsPod here because it is perfectly reasonable to have + // a data structure T for which IsPod<T>::value is true, yet also have a + // ParamTraits<T> specialization. + static const bool sUseWriteBytes = + (std::is_integral_v<E> || std::is_floating_point_v<E>); + + static void Write(Message* aMsg, const paramType& aParam) { + uint32_t length = aParam.length(); + WriteParam(aMsg, length); + + if (sUseWriteBytes) { + int pickledLength = 0; + MOZ_RELEASE_ASSERT(ByteLengthIsValid(length, sizeof(E), &pickledLength)); + aMsg->WriteBytes(aParam.begin(), pickledLength); + return; + } + + for (const E& elem : aParam) { + WriteParam(aMsg, elem); + } + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint32_t length; + if (!ReadParam(aMsg, aIter, &length)) { + return false; + } + + if (sUseWriteBytes) { + int pickledLength = 0; + if (!ByteLengthIsValid(length, sizeof(E), &pickledLength)) { + return false; + } + + if (!aResult->resizeUninitialized(length)) { + // So that OOM failure shows up as OOM crash instead of IPC FatalError. + NS_ABORT_OOM(length * sizeof(E)); + } + + E* elements = aResult->begin(); + return aMsg->ReadBytesInto(aIter, elements, pickledLength); + } + + // Each ReadParam<E> may read more than 1 byte each; this is an attempt + // to minimally validate that the length isn't much larger than what's + // actually available in aMsg. + if (!aMsg->HasBytesAvailable(aIter, length)) { + return false; + } + + if (!aResult->resize(length)) { + // So that OOM failure shows up as OOM crash instead of IPC FatalError. + NS_ABORT_OOM(length * sizeof(E)); + } + + for (uint32_t index = 0; index < length; ++index) { + if (!ReadParam(aMsg, aIter, &((*aResult)[index]))) { + return false; + } + } + + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + for (uint32_t index = 0, len = aParam.length(); index < len; ++index) { + if (index) { + aLog->append(L" "); + } + LogParam(aParam[index], aLog); + } + } +}; + +template <typename E> +struct ParamTraits<std::vector<E>> { + typedef std::vector<E> paramType; + + // We write arrays of integer or floating-point data using a single pickling + // call, rather than writing each element individually. We deliberately do + // not use mozilla::IsPod here because it is perfectly reasonable to have + // a data structure T for which IsPod<T>::value is true, yet also have a + // ParamTraits<T> specialization. + static const bool sUseWriteBytes = + (std::is_integral_v<E> || std::is_floating_point_v<E>); + + static void Write(Message* aMsg, const paramType& aParam) { + uint32_t length = aParam.size(); + WriteParam(aMsg, length); + + if (sUseWriteBytes) { + int pickledLength = 0; + MOZ_RELEASE_ASSERT(ByteLengthIsValid(length, sizeof(E), &pickledLength)); + aMsg->WriteBytes(aParam.data(), pickledLength); + return; + } + + for (const E& elem : aParam) { + WriteParam(aMsg, elem); + } + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint32_t length; + if (!ReadParam(aMsg, aIter, &length)) { + return false; + } + + if (sUseWriteBytes) { + int pickledLength = 0; + if (!ByteLengthIsValid(length, sizeof(E), &pickledLength)) { + return false; + } + + aResult->resize(length); + + E* elements = aResult->data(); + return aMsg->ReadBytesInto(aIter, elements, pickledLength); + } + + // Each ReadParam<E> may read more than 1 byte each; this is an attempt + // to minimally validate that the length isn't much larger than what's + // actually available in aMsg. + if (!aMsg->HasBytesAvailable(aIter, length)) { + return false; + } + + aResult->resize(length); + + for (uint32_t index = 0; index < length; ++index) { + if (!ReadParam(aMsg, aIter, &((*aResult)[index]))) { + return false; + } + } + + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + for (uint32_t index = 0, len = aParam.size(); index < len; ++index) { + if (index) { + aLog->append(L" "); + } + LogParam(aParam[index], aLog); + } + } +}; + +template <typename K, typename V> +struct ParamTraits<std::unordered_map<K, V>> final { + using T = std::unordered_map<K, V>; + + static void Write(Message* const msg, const T& in) { + WriteParam(msg, in.size()); + for (const auto& pair : in) { + WriteParam(msg, pair.first); + WriteParam(msg, pair.second); + } + } + + static bool Read(const Message* const msg, PickleIterator* const itr, + T* const out) { + size_t size = 0; + if (!ReadParam(msg, itr, &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(msg, itr, &(pair.first)) || + !ReadParam(msg, itr, &(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(Message* aMsg, const paramType& aParam) { + aMsg->WriteBytes(&aParam, sizeof(paramType)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return aMsg->ReadBytesInto(aIter, aResult, sizeof(*aResult)); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"%g", aParam)); + } +}; + +template <> +struct ParamTraits<nsCSSPropertyID> + : public ContiguousEnumSerializer<nsCSSPropertyID, eCSSProperty_UNKNOWN, + eCSSProperty_COUNT> {}; + +template <> +struct ParamTraits<nsID> { + typedef nsID paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.m0); + WriteParam(aMsg, aParam.m1); + WriteParam(aMsg, aParam.m2); + for (unsigned int i = 0; i < mozilla::ArrayLength(aParam.m3); i++) { + WriteParam(aMsg, aParam.m3[i]); + } + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (!ReadParam(aMsg, aIter, &(aResult->m0)) || + !ReadParam(aMsg, aIter, &(aResult->m1)) || + !ReadParam(aMsg, aIter, &(aResult->m2))) + return false; + + for (unsigned int i = 0; i < mozilla::ArrayLength(aResult->m3); i++) + if (!ReadParam(aMsg, aIter, &(aResult->m3[i]))) return false; + + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(L"{"); + aLog->append( + StringPrintf(L"%8.8X-%4.4X-%4.4X-", aParam.m0, aParam.m1, aParam.m2)); + for (unsigned int i = 0; i < mozilla::ArrayLength(aParam.m3); i++) + aLog->append(StringPrintf(L"%2.2X", aParam.m3[i])); + aLog->append(L"}"); + } +}; + +template <> +struct ParamTraits<nsContentPolicyType> + : public ContiguousEnumSerializerInclusive< + nsContentPolicyType, nsIContentPolicy::TYPE_INVALID, + nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD> {}; + +template <> +struct ParamTraits<mozilla::TimeDuration> { + typedef mozilla::TimeDuration paramType; + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mValue); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mValue); + }; +}; + +template <> +struct ParamTraits<mozilla::TimeStamp> { + typedef mozilla::TimeStamp paramType; + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mValue); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mValue); + }; +}; + +#ifdef XP_WIN + +template <> +struct ParamTraits<mozilla::TimeStampValue> { + typedef mozilla::TimeStampValue paramType; + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mGTC); + WriteParam(aMsg, aParam.mQPC); + WriteParam(aMsg, aParam.mUsedCanonicalNow); + WriteParam(aMsg, aParam.mIsNull); + WriteParam(aMsg, aParam.mHasQPC); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mGTC) && + ReadParam(aMsg, aIter, &aResult->mQPC) && + ReadParam(aMsg, aIter, &aResult->mUsedCanonicalNow) && + ReadParam(aMsg, aIter, &aResult->mIsNull) && + ReadParam(aMsg, aIter, &aResult->mHasQPC)); + } +}; + +#else + +template <> +struct ParamTraits<mozilla::TimeStamp63Bit> { + typedef mozilla::TimeStamp63Bit paramType; + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mUsedCanonicalNow); + WriteParam(aMsg, aParam.mTimeStamp); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + bool success = true; + uint64_t result; + + success &= ReadParam(aMsg, aIter, &result); + aResult->mUsedCanonicalNow = result & 0x01; + + success &= ReadParam(aMsg, aIter, &result); + aResult->mTimeStamp = result & 0x7FFFFFFFFFFFFFFF; + + return success; + } +}; + +#endif + +template <> +struct ParamTraits<mozilla::dom::ipc::StructuredCloneData> { + typedef mozilla::dom::ipc::StructuredCloneData paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aParam.WriteIPCParams(aMsg); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return aResult->ReadIPCParams(aMsg, aIter); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + LogParam(aParam.DataLength(), aLog); + } +}; + +template <class T> +struct ParamTraits<mozilla::Maybe<T>> { + typedef mozilla::Maybe<T> paramType; + + static void Write(Message* msg, const paramType& param) { + if (param.isSome()) { + WriteParam(msg, true); + WriteParam(msg, param.value()); + } else { + WriteParam(msg, false); + } + } + + static bool Read(const Message* msg, PickleIterator* iter, + paramType* result) { + bool isSome; + if (!ReadParam(msg, iter, &isSome)) { + return false; + } + if (isSome) { + T tmp; + if (!ReadParam(msg, iter, &tmp)) { + return false; + } + *result = mozilla::Some(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(Message* msg, const paramType& param) { + MOZ_RELEASE_ASSERT(IsLegalValue(param.serialize())); + WriteParam(msg, param.serialize()); + } + + static bool Read(const Message* msg, PickleIterator* iter, + paramType* result) { + serializedType tmp; + + if (ReadParam(msg, iter, &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(Message* msg, const paramType& param) { + WriteParam(msg, param.tag); + param.match([msg](const auto& t) { WriteParam(msg, 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(const Message* msg, PickleIterator* iter, 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(msg, iter, &result->template emplace<N - 1>()); + } else { + return Next::Read(msg, iter, 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(const Message* msg, PickleIterator* iter, Tag tag, + paramType* result) { + return false; + } + }; + + static bool Read(const Message* msg, PickleIterator* iter, + paramType* result) { + Tag tag; + if (ReadParam(msg, iter, &tag)) { + return VariantReader<sizeof...(Ts)>::Read(msg, iter, tag, result); + } + return false; + } +}; + +template <typename T> +struct ParamTraits<mozilla::dom::Optional<T>> { + typedef mozilla::dom::Optional<T> paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + if (aParam.WasPassed()) { + WriteParam(aMsg, true); + WriteParam(aMsg, aParam.Value()); + return; + } + + WriteParam(aMsg, false); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + bool wasPassed = false; + + if (!ReadParam(aMsg, aIter, &wasPassed)) { + return false; + } + + aResult->Reset(); + + if (wasPassed) { + if (!ReadParam(aMsg, aIter, &aResult->Construct())) { + return false; + } + } + + return true; + } +}; + +struct CrossOriginOpenerPolicyValidator { + static bool IsLegalValue(nsILoadInfo::CrossOriginOpenerPolicy e) { + return e == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE || + e == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN || + e == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS || + e == nsILoadInfo:: + OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP; + } +}; + +template <> +struct ParamTraits<nsILoadInfo::CrossOriginOpenerPolicy> + : EnumSerializer<nsILoadInfo::CrossOriginOpenerPolicy, + CrossOriginOpenerPolicyValidator> {}; + +struct CrossOriginEmbedderPolicyValidator { + static bool IsLegalValue(nsILoadInfo::CrossOriginEmbedderPolicy e) { + return e == nsILoadInfo::EMBEDDER_POLICY_NULL || + e == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP; + } +}; + +template <> +struct ParamTraits<nsILoadInfo::CrossOriginEmbedderPolicy> + : EnumSerializer<nsILoadInfo::CrossOriginEmbedderPolicy, + CrossOriginEmbedderPolicyValidator> {}; + +template <size_t N, typename Word> +struct ParamTraits<mozilla::BitSet<N, Word>> { + typedef mozilla::BitSet<N, Word> paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + for (Word word : aParam.Storage()) { + WriteParam(aMsg, word); + } + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + for (Word& word : aResult->Storage()) { + if (!ReadParam(aMsg, aIter, &word)) { + return false; + } + } + return true; + } +}; + +} /* 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..978ee80a34 --- /dev/null +++ b/ipc/glue/IPCStream.ipdlh @@ -0,0 +1,24 @@ +/* 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 PChildToParentStream; +include protocol PParentToChildStream; + +include BlobTypes; +include InputStreamParams; +include ProtocolTypes; + +namespace mozilla { +namespace ipc { + +// Use IPCStream in your ipdl to represent serialized nsIInputStreams. Then use +// AutoIPCStream from IPCStreamUtils.h to perform the serialization. +struct IPCStream +{ + InputStreamParams stream; + OptionalFileDescriptorSet optionalFds; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/IPCStreamAlloc.h b/ipc/glue/IPCStreamAlloc.h new file mode 100644 index 0000000000..c22b35e355 --- /dev/null +++ b/ipc/glue/IPCStreamAlloc.h @@ -0,0 +1,23 @@ +/* -*- 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_IPCStreamAlloc_h +#define mozilla_ipc_IPCStreamAlloc_h + +namespace mozilla { +namespace ipc { + +class PChildToParentStreamParent; +class PParentToChildStreamChild; + +PChildToParentStreamParent* AllocPChildToParentStreamParent(); + +PParentToChildStreamChild* AllocPParentToChildStreamChild(); + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_IPCStreamAlloc_h diff --git a/ipc/glue/IPCStreamChild.cpp b/ipc/glue/IPCStreamChild.cpp new file mode 100644 index 0000000000..d414a1eb95 --- /dev/null +++ b/ipc/glue/IPCStreamChild.cpp @@ -0,0 +1,157 @@ +/* -*- 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 "IPCStreamDestination.h" +#include "IPCStreamSource.h" + +#include "mozilla/Unused.h" +#include "mozilla/ipc/PChildToParentStreamChild.h" +#include "mozilla/ipc/PParentToChildStreamChild.h" + +namespace mozilla { +namespace ipc { + +// Child to Parent implementation +// ---------------------------------------------------------------------------- + +namespace { + +class IPCStreamSourceChild final : public PChildToParentStreamChild, + public IPCStreamSource { + public: + static IPCStreamSourceChild* Create(nsIAsyncInputStream* aInputStream) { + MOZ_ASSERT(aInputStream); + + IPCStreamSourceChild* source = new IPCStreamSourceChild(aInputStream); + if (!source->Initialize()) { + delete source; + return nullptr; + } + + return source; + } + + // PChildToParentStreamChild methods + + void ActorDestroy(ActorDestroyReason aReason) override { ActorDestroyed(); } + + IPCResult RecvStartReading() override { + Start(); + return IPC_OK(); + } + + IPCResult RecvRequestClose(const nsresult& aRv) override { + OnEnd(aRv); + return IPC_OK(); + } + + void Close(nsresult aRv) override { + MOZ_ASSERT(IPCStreamSource::mState == IPCStreamSource::eClosed); + Unused << SendClose(aRv); + } + + void SendData(const wr::ByteBuffer& aBuffer) override { + Unused << SendBuffer(aBuffer); + } + + private: + explicit IPCStreamSourceChild(nsIAsyncInputStream* aInputStream) + : IPCStreamSource(aInputStream) {} +}; + +} // anonymous namespace + +/* static */ +PChildToParentStreamChild* IPCStreamSource::Create( + nsIAsyncInputStream* aInputStream, + ChildToParentStreamActorManager* aManager) { + MOZ_ASSERT(aInputStream); + MOZ_ASSERT(aManager); + + IPCStreamSourceChild* source = IPCStreamSourceChild::Create(aInputStream); + if (!source) { + return nullptr; + } + + if (!aManager->SendPChildToParentStreamConstructor(source)) { + return nullptr; + } + + source->ActorConstructed(); + return source; +} + +/* static */ +IPCStreamSource* IPCStreamSource::Cast(PChildToParentStreamChild* aActor) { + MOZ_ASSERT(aActor); + return static_cast<IPCStreamSourceChild*>(aActor); +} + +// Parent to Child implementation +// ---------------------------------------------------------------------------- + +namespace { + +class IPCStreamDestinationChild final : public PParentToChildStreamChild, + public IPCStreamDestination { + public: + nsresult Initialize() { return IPCStreamDestination::Initialize(); } + + ~IPCStreamDestinationChild() = default; + + private: + // PParentToChildStreamChild methods + + void ActorDestroy(ActorDestroyReason aReason) override { ActorDestroyed(); } + + IPCResult RecvBuffer(const wr::ByteBuffer& aBuffer) override { + BufferReceived(aBuffer); + return IPC_OK(); + } + + IPCResult RecvClose(const nsresult& aRv) override { + CloseReceived(aRv); + return IPC_OK(); + } + + // IPCStreamDestination methods + + void StartReading() override { + MOZ_ASSERT(HasDelayedStart()); + Unused << SendStartReading(); + } + + void RequestClose(nsresult aRv) override { Unused << SendRequestClose(aRv); } + + void TerminateDestination() override { Unused << Send__delete__(this); } +}; + +} // anonymous namespace + +PParentToChildStreamChild* AllocPParentToChildStreamChild() { + IPCStreamDestinationChild* actor = new IPCStreamDestinationChild(); + + if (NS_WARN_IF(NS_FAILED(actor->Initialize()))) { + delete actor; + actor = nullptr; + } + + return actor; +} + +void DeallocPParentToChildStreamChild(PParentToChildStreamChild* aActor) { + delete aActor; +} + +/* static */ +IPCStreamDestination* IPCStreamDestination::Cast( + PParentToChildStreamChild* aActor) { + MOZ_ASSERT(aActor); + return static_cast<IPCStreamDestinationChild*>(aActor); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/IPCStreamDestination.cpp b/ipc/glue/IPCStreamDestination.cpp new file mode 100644 index 0000000000..d7071391e6 --- /dev/null +++ b/ipc/glue/IPCStreamDestination.cpp @@ -0,0 +1,400 @@ +/* -*- 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 "IPCStreamDestination.h" +#include "mozilla/InputStreamLengthWrapper.h" +#include "mozilla/Mutex.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBufferedStreams.h" +#include "nsICloneableInputStream.h" +#include "nsIPipe.h" +#include "nsThreadUtils.h" +#include "mozilla/webrender/WebRenderTypes.h" + +namespace mozilla { +namespace ipc { + +// ---------------------------------------------------------------------------- +// IPCStreamDestination::DelayedStartInputStream +// +// When AutoIPCStream is used with delayedStart, we need to ask for data at the +// first real use of the nsIInputStream. In order to do so, we wrap the +// nsIInputStream, created by the nsIPipe, with this wrapper. + +class IPCStreamDestination::DelayedStartInputStream final + : public nsIAsyncInputStream, + public nsIInputStreamCallback, + public nsISearchableInputStream, + public nsICloneableInputStream, + public nsIBufferedInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + DelayedStartInputStream(IPCStreamDestination* aDestination, + nsCOMPtr<nsIAsyncInputStream>&& aStream) + : mDestination(aDestination), + mStream(std::move(aStream)), + mMutex("IPCStreamDestination::DelayedStartInputStream::mMutex") { + MOZ_ASSERT(mDestination); + MOZ_ASSERT(mStream); + } + + void DestinationShutdown() { + MutexAutoLock lock(mMutex); + mDestination = nullptr; + } + + // nsIInputStream interface + + NS_IMETHOD + Close() override { + MaybeCloseDestination(); + return mStream->Close(); + } + + NS_IMETHOD + Available(uint64_t* aLength) override { + MaybeStartReading(); + return mStream->Available(aLength); + } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + MaybeStartReading(); + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + MaybeStartReading(); + return mStream->ReadSegments(aWriter, aClosure, aCount, aResult); + } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + MaybeStartReading(); + return mStream->IsNonBlocking(aNonBlocking); + } + + // nsIAsyncInputStream interface + + NS_IMETHOD + CloseWithStatus(nsresult aReason) override { + MaybeCloseDestination(); + return mStream->CloseWithStatus(aReason); + } + + NS_IMETHOD + AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, nsIEventTarget* aTarget) override { + { + MutexAutoLock lock(mMutex); + if (mAsyncWaitCallback && aCallback) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitCallback = aCallback; + + MaybeStartReading(lock); + } + + nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr; + return mStream->AsyncWait(callback, aFlags, aRequestedCount, aTarget); + } + + NS_IMETHOD + Search(const char* aForString, bool aIgnoreCase, bool* aFound, + uint32_t* aOffsetSearchedTo) override { + MaybeStartReading(); + nsCOMPtr<nsISearchableInputStream> searchable = do_QueryInterface(mStream); + MOZ_ASSERT(searchable); + return searchable->Search(aForString, aIgnoreCase, aFound, + aOffsetSearchedTo); + } + + // nsICloneableInputStream interface + + NS_IMETHOD + GetCloneable(bool* aCloneable) override { + MaybeStartReading(); + nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(mStream); + MOZ_ASSERT(cloneable); + return cloneable->GetCloneable(aCloneable); + } + + NS_IMETHOD + Clone(nsIInputStream** aResult) override { + MaybeStartReading(); + nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(mStream); + MOZ_ASSERT(cloneable); + return cloneable->Clone(aResult); + } + + // nsIBufferedInputStream + + NS_IMETHOD + Init(nsIInputStream* aStream, uint32_t aBufferSize) override { + MaybeStartReading(); + nsCOMPtr<nsIBufferedInputStream> stream = do_QueryInterface(mStream); + MOZ_ASSERT(stream); + return stream->Init(aStream, aBufferSize); + } + + NS_IMETHODIMP + GetData(nsIInputStream** aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // nsIInputStreamCallback + + NS_IMETHOD + OnInputStreamReady(nsIAsyncInputStream* aStream) override { + nsCOMPtr<nsIInputStreamCallback> callback; + + { + MutexAutoLock lock(mMutex); + + // We have been canceled in the meanwhile. + if (!mAsyncWaitCallback) { + return NS_OK; + } + + callback.swap(mAsyncWaitCallback); + } + + callback->OnInputStreamReady(this); + return NS_OK; + } + + void MaybeStartReading(); + void MaybeStartReading(const MutexAutoLock& aProofOfLook); + + void MaybeCloseDestination(); + + private: + ~DelayedStartInputStream() = default; + + IPCStreamDestination* mDestination; + nsCOMPtr<nsIAsyncInputStream> mStream; + + nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback; + + // This protects mDestination: any method can be called by any thread. + Mutex mMutex; + + class HelperRunnable; +}; + +class IPCStreamDestination::DelayedStartInputStream::HelperRunnable final + : public Runnable { + public: + enum Op { + eStartReading, + eCloseDestination, + }; + + HelperRunnable( + IPCStreamDestination::DelayedStartInputStream* aDelayedStartInputStream, + Op aOp) + : Runnable( + "ipc::IPCStreamDestination::DelayedStartInputStream::" + "HelperRunnable"), + mDelayedStartInputStream(aDelayedStartInputStream), + mOp(aOp) { + MOZ_ASSERT(aDelayedStartInputStream); + } + + NS_IMETHOD + Run() override { + switch (mOp) { + case eStartReading: + mDelayedStartInputStream->MaybeStartReading(); + break; + case eCloseDestination: + mDelayedStartInputStream->MaybeCloseDestination(); + break; + } + + return NS_OK; + } + + private: + RefPtr<IPCStreamDestination::DelayedStartInputStream> + mDelayedStartInputStream; + Op mOp; +}; + +void IPCStreamDestination::DelayedStartInputStream::MaybeStartReading() { + MutexAutoLock lock(mMutex); + MaybeStartReading(lock); +} + +void IPCStreamDestination::DelayedStartInputStream::MaybeStartReading( + const MutexAutoLock& aProofOfLook) { + if (!mDestination) { + return; + } + + if (mDestination->IsOnOwningThread()) { + mDestination->StartReading(); + mDestination = nullptr; + return; + } + + RefPtr<Runnable> runnable = + new HelperRunnable(this, HelperRunnable::eStartReading); + mDestination->DispatchRunnable(runnable.forget()); +} + +void IPCStreamDestination::DelayedStartInputStream::MaybeCloseDestination() { + MutexAutoLock lock(mMutex); + if (!mDestination) { + return; + } + + if (mDestination->IsOnOwningThread()) { + mDestination->RequestClose(NS_ERROR_ABORT); + mDestination = nullptr; + return; + } + + RefPtr<Runnable> runnable = + new HelperRunnable(this, HelperRunnable::eCloseDestination); + mDestination->DispatchRunnable(runnable.forget()); +} + +NS_IMPL_ADDREF(IPCStreamDestination::DelayedStartInputStream); +NS_IMPL_RELEASE(IPCStreamDestination::DelayedStartInputStream); + +NS_INTERFACE_MAP_BEGIN(IPCStreamDestination::DelayedStartInputStream) + NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback) + NS_INTERFACE_MAP_ENTRY(nsISearchableInputStream) + NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream) + NS_INTERFACE_MAP_ENTRY(nsIBufferedInputStream) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIInputStream, nsIAsyncInputStream) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAsyncInputStream) +NS_INTERFACE_MAP_END + +// ---------------------------------------------------------------------------- +// IPCStreamDestination + +IPCStreamDestination::IPCStreamDestination() + : mOwningThread(NS_GetCurrentThread()), + mDelayedStart(false) +#ifdef MOZ_DEBUG + , + mLengthSet(false) +#endif +{ +} + +IPCStreamDestination::~IPCStreamDestination() = default; + +nsresult IPCStreamDestination::Initialize() { + MOZ_ASSERT(!mReader); + MOZ_ASSERT(!mWriter); + + // use async versions for both reader and writer even though we are + // opening the writer as an infinite stream. We want to be able to + // use CloseWithStatus() to communicate errors through the pipe. + + // Use an "infinite" pipe because we cannot apply back-pressure through + // the async IPC layer at the moment. Blocking the IPC worker thread + // is not desirable, either. + nsresult rv = NS_NewPipe2(getter_AddRefs(mReader), getter_AddRefs(mWriter), + true, true, // non-blocking + 0, // segment size + UINT32_MAX); // "infinite" pipe + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +void IPCStreamDestination::SetDelayedStart(bool aDelayedStart) { + mDelayedStart = aDelayedStart; +} + +void IPCStreamDestination::SetLength(int64_t aLength) { + MOZ_ASSERT(mReader); + MOZ_ASSERT(!mLengthSet); + +#ifdef DEBUG + mLengthSet = true; +#endif + + if (aLength != -1) { + nsCOMPtr<nsIInputStream> finalStream; + finalStream = new InputStreamLengthWrapper(mReader.forget(), aLength); + mReader = do_QueryInterface(finalStream); + MOZ_ASSERT(mReader); + } +} + +already_AddRefed<nsIInputStream> IPCStreamDestination::TakeReader() { + MOZ_ASSERT(mReader); + MOZ_ASSERT(!mDelayedStartInputStream); + + if (mDelayedStart) { + mDelayedStartInputStream = + new DelayedStartInputStream(this, std::move(mReader)); + RefPtr<nsIAsyncInputStream> inputStream = mDelayedStartInputStream; + return inputStream.forget(); + } + + return mReader.forget(); +} + +bool IPCStreamDestination::IsOnOwningThread() const { + return mOwningThread == NS_GetCurrentThread(); +} + +void IPCStreamDestination::DispatchRunnable( + already_AddRefed<nsIRunnable>&& aRunnable) { + nsCOMPtr<nsIRunnable> runnable = aRunnable; + mOwningThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); +} + +void IPCStreamDestination::ActorDestroyed() { + MOZ_ASSERT(mWriter); + + // If we were gracefully closed we should have gotten RecvClose(). In + // that case, the writer will already be closed and this will have no + // effect. This just aborts the writer in the case where the child process + // crashes. + mWriter->CloseWithStatus(NS_ERROR_ABORT); + + if (mDelayedStartInputStream) { + mDelayedStartInputStream->DestinationShutdown(); + mDelayedStartInputStream = nullptr; + } +} + +void IPCStreamDestination::BufferReceived(const wr::ByteBuffer& aBuffer) { + MOZ_ASSERT(mWriter); + + uint32_t numWritten = 0; + + // This should only fail if we hit an OOM condition. + nsresult rv = mWriter->Write(reinterpret_cast<char*>(aBuffer.mData), + aBuffer.mLength, &numWritten); + if (NS_WARN_IF(NS_FAILED(rv))) { + RequestClose(rv); + } +} + +void IPCStreamDestination::CloseReceived(nsresult aRv) { + MOZ_ASSERT(mWriter); + mWriter->CloseWithStatus(aRv); + TerminateDestination(); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/IPCStreamDestination.h b/ipc/glue/IPCStreamDestination.h new file mode 100644 index 0000000000..8bd0cb3f87 --- /dev/null +++ b/ipc/glue/IPCStreamDestination.h @@ -0,0 +1,95 @@ +/* -*- 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_IPCStreamDestination_h +#define mozilla_ipc_IPCStreamDestination_h + +#include "mozilla/AlreadyAddRefed.h" +#include "nsIRunnable.h" +#include "nsIThread.h" + +class nsIInputStream; +class nsIAsyncInputStream; +class nsIAsyncOutputStream; + +namespace mozilla { + +namespace wr { +struct ByteBuffer; +} // namespace wr + +namespace ipc { + +class PChildToParentStreamParent; +class PParentToChildStreamChild; + +// On the destination side, you must simply call TakeReader() upon receiving a +// reference to the IPCStream{Child,Parent} actor. You do not need to maintain +// a reference to the actor itself. +class IPCStreamDestination { + public: + static IPCStreamDestination* Cast(PChildToParentStreamParent* aActor); + + static IPCStreamDestination* Cast(PParentToChildStreamChild* aActor); + + void SetDelayedStart(bool aDelayedStart); + + void SetLength(int64_t aLength); + + already_AddRefed<nsIInputStream> TakeReader(); + + bool IsOnOwningThread() const; + + void DispatchRunnable(already_AddRefed<nsIRunnable>&& aRunnable); + + protected: + IPCStreamDestination(); + virtual ~IPCStreamDestination(); + + nsresult Initialize(); + + // The implementation of the actor should call these methods. + + void ActorDestroyed(); + + void BufferReceived(const wr::ByteBuffer& aBuffer); + + void CloseReceived(nsresult aRv); + +#ifdef DEBUG + bool HasDelayedStart() const { return mDelayedStart; } +#endif + + // These methods will be implemented by the actor. + + virtual void StartReading() = 0; + + virtual void RequestClose(nsresult aRv) = 0; + + virtual void TerminateDestination() = 0; + + private: + nsCOMPtr<nsIAsyncInputStream> mReader; + nsCOMPtr<nsIAsyncOutputStream> mWriter; + + // This is created by TakeReader() if we need to delay the reading of data. + // We keep a reference to the stream in order to inform it when the actor goes + // away. If that happens, the reading of data will not be possible anymore. + class DelayedStartInputStream; + RefPtr<DelayedStartInputStream> mDelayedStartInputStream; + + nsCOMPtr<nsIThread> mOwningThread; + bool mDelayedStart; + +#ifdef MOZ_DEBUG + bool mLengthSet; +#endif +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_IPCStreamDestination_h diff --git a/ipc/glue/IPCStreamParent.cpp b/ipc/glue/IPCStreamParent.cpp new file mode 100644 index 0000000000..a0c95db657 --- /dev/null +++ b/ipc/glue/IPCStreamParent.cpp @@ -0,0 +1,158 @@ +/* -*- 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 "IPCStreamDestination.h" +#include "mozilla/ipc/IPCStreamSource.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/PChildToParentStreamParent.h" +#include "mozilla/ipc/PParentToChildStreamParent.h" +#include "mozilla/Unused.h" + +namespace mozilla { +namespace ipc { + +// Child to Parent implementation +// ---------------------------------------------------------------------------- + +namespace { + +class IPCStreamSourceParent final : public PParentToChildStreamParent, + public IPCStreamSource { + public: + static IPCStreamSourceParent* Create(nsIAsyncInputStream* aInputStream) { + MOZ_ASSERT(aInputStream); + + IPCStreamSourceParent* source = new IPCStreamSourceParent(aInputStream); + if (!source->Initialize()) { + delete source; + return nullptr; + } + + return source; + } + + // PParentToChildStreamParent methods + + void ActorDestroy(ActorDestroyReason aReason) override { ActorDestroyed(); } + + IPCResult RecvStartReading() override { + Start(); + return IPC_OK(); + } + + IPCResult RecvRequestClose(const nsresult& aRv) override { + OnEnd(aRv); + return IPC_OK(); + } + + void Close(nsresult aRv) override { + MOZ_ASSERT(IPCStreamSource::mState == IPCStreamSource::eClosed); + Unused << SendClose(aRv); + } + + void SendData(const wr::ByteBuffer& aBuffer) override { + Unused << SendBuffer(aBuffer); + } + + private: + explicit IPCStreamSourceParent(nsIAsyncInputStream* aInputStream) + : IPCStreamSource(aInputStream) {} +}; + +} // anonymous namespace + +/* static */ +PParentToChildStreamParent* IPCStreamSource::Create( + nsIAsyncInputStream* aInputStream, + ParentToChildStreamActorManager* aManager) { + MOZ_ASSERT(aInputStream); + MOZ_ASSERT(aManager); + + IPCStreamSourceParent* source = IPCStreamSourceParent::Create(aInputStream); + if (!source) { + return nullptr; + } + + if (!aManager->SendPParentToChildStreamConstructor(source)) { + // no delete here, the manager will delete the actor for us. + return nullptr; + } + + source->ActorConstructed(); + return source; +} + +/* static */ +IPCStreamSource* IPCStreamSource::Cast(PParentToChildStreamParent* aActor) { + MOZ_ASSERT(aActor); + return static_cast<IPCStreamSourceParent*>(aActor); +} + +// Child to Parent implementation +// ---------------------------------------------------------------------------- + +namespace { + +class IPCStreamDestinationParent final : public PChildToParentStreamParent, + public IPCStreamDestination { + public: + nsresult Initialize() { return IPCStreamDestination::Initialize(); } + + ~IPCStreamDestinationParent() = default; + + private: + // PChildToParentStreamParent methods + + void ActorDestroy(ActorDestroyReason aReason) override { ActorDestroyed(); } + + IPCResult RecvBuffer(const wr::ByteBuffer& aBuffer) override { + BufferReceived(aBuffer); + return IPC_OK(); + } + + IPCResult RecvClose(const nsresult& aRv) override { + CloseReceived(aRv); + return IPC_OK(); + } + + // IPCStreamDestination methods + + void StartReading() override { + MOZ_ASSERT(HasDelayedStart()); + Unused << SendStartReading(); + } + + void RequestClose(nsresult aRv) override { Unused << SendRequestClose(aRv); } + + void TerminateDestination() override { Unused << Send__delete__(this); } +}; + +} // anonymous namespace + +PChildToParentStreamParent* AllocPChildToParentStreamParent() { + IPCStreamDestinationParent* actor = new IPCStreamDestinationParent(); + + if (NS_WARN_IF(NS_FAILED(actor->Initialize()))) { + delete actor; + actor = nullptr; + } + + return actor; +} + +void DeallocPChildToParentStreamParent(PChildToParentStreamParent* aActor) { + delete aActor; +} + +/* static */ +IPCStreamDestination* IPCStreamDestination::Cast( + PChildToParentStreamParent* aActor) { + MOZ_ASSERT(aActor); + return static_cast<IPCStreamDestinationParent*>(aActor); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/IPCStreamSource.cpp b/ipc/glue/IPCStreamSource.cpp new file mode 100644 index 0000000000..3ddf0c9e52 --- /dev/null +++ b/ipc/glue/IPCStreamSource.cpp @@ -0,0 +1,277 @@ +/* -*- 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 "IPCStreamSource.h" + +#include "BackgroundParent.h" // for AssertIsOnBackgroundThread +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/RemoteWorkerService.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsIAsyncInputStream.h" +#include "nsICancelableRunnable.h" +#include "nsIRunnable.h" +#include "nsISerialEventTarget.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" + +using mozilla::wr::ByteBuffer; + +namespace mozilla { +namespace ipc { + +class IPCStreamSource::Callback final : public DiscardableRunnable, + public nsIInputStreamCallback { + public: + explicit Callback(IPCStreamSource* aSource) + : DiscardableRunnable("IPCStreamSource::Callback"), + mSource(aSource), + mOwningEventTarget(GetCurrentSerialEventTarget()) { + MOZ_ASSERT(mSource); + } + + NS_IMETHOD + OnInputStreamReady(nsIAsyncInputStream* aStream) override { + // any thread + if (mOwningEventTarget->IsOnCurrentThread()) { + return Run(); + } + + // If this fails, then it means the owning thread is a Worker that has + // been shutdown. Its ok to lose the event in this case because the + // IPCStreamChild listens for this event through the WorkerRef. + nsresult rv = + mOwningEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch stream readable event to owning thread"); + } + + return NS_OK; + } + + NS_IMETHOD + Run() override { + MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); + if (mSource) { + mSource->OnStreamReady(this); + } + return NS_OK; + } + + // OnDiscard() gets called when the Worker thread is being shutdown. We have + // nothing to do here because IPCStreamChild handles this case via + // the WorkerRef. + + void ClearSource() { + MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); + MOZ_ASSERT(mSource); + mSource = nullptr; + } + + private: + ~Callback() { + // called on any thread + + // ClearSource() should be called before the Callback is destroyed + MOZ_ASSERT(!mSource); + } + + // This is a raw pointer because the source keeps alive the callback and, + // before beeing destroyed, it nullifies this pointer (this happens when + // ActorDestroyed() is called). + IPCStreamSource* mSource; + + nsCOMPtr<nsISerialEventTarget> mOwningEventTarget; + + NS_DECL_ISUPPORTS_INHERITED +}; + +NS_IMPL_ISUPPORTS_INHERITED(IPCStreamSource::Callback, DiscardableRunnable, + nsIInputStreamCallback); + +IPCStreamSource::IPCStreamSource(nsIAsyncInputStream* aInputStream) + : mStream(aInputStream), mState(ePending) { + MOZ_ASSERT(aInputStream); +} + +IPCStreamSource::~IPCStreamSource() { + NS_ASSERT_OWNINGTHREAD(IPCStreamSource); + MOZ_ASSERT(mState == eClosed); + MOZ_ASSERT(!mCallback); + MOZ_ASSERT(!mWorkerRef); +} + +bool IPCStreamSource::Initialize() { + bool nonBlocking = false; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mStream->IsNonBlocking(&nonBlocking))); + // IPCStreamChild reads in the current thread, so it is only supported on + // non-blocking, async channels + if (!nonBlocking) { + return false; + } + + // A source can be used on any thread, but we only support IPCStream on + // main thread, Workers, Worker Launcher, and PBackground thread right now. + // This is due to the requirement that the thread be guaranteed to live long + // enough to receive messages. We can enforce this guarantee with a + // StrongWorkerRef on worker threads, but not other threads. Main-thread, + // PBackground, and Worker Launcher threads do not need anything special in + // order to be kept alive. + if (!NS_IsMainThread()) { + if (const auto workerPrivate = dom::GetCurrentThreadWorkerPrivate()) { + RefPtr<dom::StrongWorkerRef> workerRef = + dom::StrongWorkerRef::CreateForcibly(workerPrivate, + "IPCStreamSource"); + if (NS_WARN_IF(!workerRef)) { + return false; + } + + mWorkerRef = std::move(workerRef); + } else { + MOZ_DIAGNOSTIC_ASSERT( + IsOnBackgroundThread() || + dom::RemoteWorkerService::Thread()->IsOnCurrentThread()); + } + } + + return true; +} + +void IPCStreamSource::ActorConstructed() { + MOZ_ASSERT(mState == ePending); + mState = eActorConstructed; +} + +void IPCStreamSource::ActorDestroyed() { + NS_ASSERT_OWNINGTHREAD(IPCStreamSource); + + mState = eClosed; + + if (mCallback) { + mCallback->ClearSource(); + mCallback = nullptr; + } + + mWorkerRef = nullptr; +} + +void IPCStreamSource::Start() { + NS_ASSERT_OWNINGTHREAD(IPCStreamSource); + DoRead(); +} + +void IPCStreamSource::StartDestroy() { + NS_ASSERT_OWNINGTHREAD(IPCStreamSource); + OnEnd(NS_ERROR_ABORT); +} + +void IPCStreamSource::DoRead() { + NS_ASSERT_OWNINGTHREAD(IPCStreamSource); + MOZ_ASSERT(mState == eActorConstructed); + MOZ_ASSERT(!mCallback); + + // The input stream (likely a pipe) probably uses a segment size of + // 4kb. If there is data already buffered it would be nice to aggregate + // multiple segments into a single IPC call. Conversely, don't send too + // too large of a buffer in a single call to avoid spiking memory. + static const uint64_t kMaxBytesPerMessage = 32 * 1024; + static_assert(kMaxBytesPerMessage <= static_cast<uint64_t>(UINT32_MAX), + "kMaxBytesPerMessage must cleanly cast to uint32_t"); + + UniquePtr<char[]> buffer(new char[kMaxBytesPerMessage]); + + while (true) { + // It should not be possible to transition to closed state without + // this loop terminating via a return. + MOZ_ASSERT(mState == eActorConstructed); + + // See if the stream is closed by checking the return of Available. + uint64_t dummy; + nsresult rv = mStream->Available(&dummy); + if (NS_FAILED(rv)) { + OnEnd(rv); + return; + } + + uint32_t bytesRead = 0; + rv = mStream->Read(buffer.get(), kMaxBytesPerMessage, &bytesRead); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + MOZ_ASSERT(bytesRead == 0); + Wait(); + return; + } + + if (NS_FAILED(rv)) { + MOZ_ASSERT(bytesRead == 0); + OnEnd(rv); + return; + } + + // Zero-byte read indicates end-of-stream. + if (bytesRead == 0) { + OnEnd(NS_BASE_STREAM_CLOSED); + return; + } + + // We read some data from the stream, send it across. + SendData(ByteBuffer(bytesRead, reinterpret_cast<uint8_t*>(buffer.get()))); + } +} + +void IPCStreamSource::Wait() { + NS_ASSERT_OWNINGTHREAD(IPCStreamSource); + MOZ_ASSERT(mState == eActorConstructed); + MOZ_ASSERT(!mCallback); + + // Set mCallback immediately instead of waiting for success. Its possible + // AsyncWait() will callback synchronously. + mCallback = new Callback(this); + nsresult rv = mStream->AsyncWait(mCallback, 0, 0, nullptr); + if (NS_FAILED(rv)) { + OnEnd(rv); + return; + } +} + +void IPCStreamSource::OnStreamReady(Callback* aCallback) { + NS_ASSERT_OWNINGTHREAD(IPCStreamSource); + MOZ_ASSERT(mCallback); + MOZ_ASSERT(aCallback == mCallback); + mCallback->ClearSource(); + mCallback = nullptr; + + // Possibly closed if this callback is (indirectly) called by + // IPCStreamSourceParent::RecvRequestClose(). + if (mState == eClosed) { + return; + } + + DoRead(); +} + +void IPCStreamSource::OnEnd(nsresult aRv) { + NS_ASSERT_OWNINGTHREAD(IPCStreamSource); + MOZ_ASSERT(aRv != NS_BASE_STREAM_WOULD_BLOCK); + + if (mState == eClosed) { + return; + } + + mState = eClosed; + + mStream->CloseWithStatus(aRv); + + if (aRv == NS_BASE_STREAM_CLOSED) { + aRv = NS_OK; + } + + // This will trigger an ActorDestroy() from the other side + Close(aRv); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/IPCStreamSource.h b/ipc/glue/IPCStreamSource.h new file mode 100644 index 0000000000..b4284a8a12 --- /dev/null +++ b/ipc/glue/IPCStreamSource.h @@ -0,0 +1,124 @@ +/* -*- 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_IPCStreamSource_h +#define mozilla_ipc_IPCStreamSource_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/dom/WorkerRef.h" + +class nsIAsyncInputStream; + +namespace mozilla { + +namespace wr { +struct ByteBuffer; +} // namespace wr + +namespace ipc { + +class ParentToChildStreamActorManager; +class ChildToParentStreamActorManager; +class PChildToParentStreamChild; +class PParentToChildStreamParent; + +// The IPCStream IPC actor is designed to push an nsIInputStream from child to +// parent or parent to child incrementally. This is mainly needed for streams +// such as nsPipe that may not yet have all their data available when the +// stream must be sent across an IPC boundary. While many streams are handled +// by SerializeInputStream(), these streams cannot be serialized and must be +// sent using this actor. +// +// The IPCStream actor only support async, non-blocking streams because they +// must be read inline on the main thread and Worker threads. +// +// In general, the creation and handling of the IPCStream actor cannot be +// abstracted away behind SerializeInputStream() because the actor must be +// carefully managed. Specifically: +// +// 1) The data flow must be explicitly initiated by calling +// IPCStreamSource{Child,Parent}::Start() after the actor has been sent to +// the other-side actor. +// 2) If the actor is never sent to the other-side, then this code must +// call IPCStreamSource{Child,Parent}::StartDestroy() to avoid memory leaks. +// 3) The IPCStreamSource actor can only be used on threads that can be +// guaranteed to stay alive as long as the actor is alive. Right now +// this limits IPCStream to the main thread and Worker threads. +// +// In general you should probably use the AutoIPCStreamSource RAII class +// defined in InputStreamUtils.h instead of using IPCStreamSource directly. +class IPCStreamSource { + public: + // Create a IPCStreamSource using a ChildToParentStreamActorManager manager. + // This can return nullptr if the provided stream is blocking. + static PChildToParentStreamChild* Create( + nsIAsyncInputStream* aInputStream, + ChildToParentStreamActorManager* aManager); + + // Create a IPCStreamSource using a ParentToChildStreamActorManager manager. + // This can return nullptr if the provided stream is blocking. + static PParentToChildStreamParent* Create( + nsIAsyncInputStream* aInputStream, + ParentToChildStreamActorManager* aManager); + + static IPCStreamSource* Cast(PChildToParentStreamChild* aActor); + + static IPCStreamSource* Cast(PParentToChildStreamParent* aActor); + + // Start reading data from the nsIAsyncInputStream used to create the actor. + // This must be called after the actor is passed to the parent. If you + // use AutoIPCStream this is handled automatically. + void Start(); + + // Start cleaning up the actor. This must be called if the actor is never + // sent to the other side. If you use AutoIPCStream this is handled + // automatically. + void StartDestroy(); + + protected: + IPCStreamSource(nsIAsyncInputStream* aInputStream); + virtual ~IPCStreamSource(); + + bool Initialize(); + + void ActorDestroyed(); + + void OnEnd(nsresult aRv); + + virtual void Close(nsresult aRv) = 0; + + virtual void SendData(const wr::ByteBuffer& aBuffer) = 0; + + void ActorConstructed(); + + private: + class Callback; + + void DoRead(); + + void Wait(); + + void OnStreamReady(Callback* aCallback); + + nsCOMPtr<nsIAsyncInputStream> mStream; + RefPtr<Callback> mCallback; + + RefPtr<dom::StrongWorkerRef> mWorkerRef; + +#ifdef DEBUG + protected: +#endif + + enum { ePending, eActorConstructed, eClosed } mState; + + private: + NS_DECL_OWNINGTHREAD +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_IPCStreamSource_h diff --git a/ipc/glue/IPCStreamUtils.cpp b/ipc/glue/IPCStreamUtils.cpp new file mode 100644 index 0000000000..fba98891fe --- /dev/null +++ b/ipc/glue/IPCStreamUtils.cpp @@ -0,0 +1,560 @@ +/* -*- 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 "nsIIPCSerializableInputStream.h" + +#include "mozilla/Assertions.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/File.h" +#include "mozilla/ipc/FileDescriptorSetChild.h" +#include "mozilla/ipc/FileDescriptorSetParent.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/net/SocketProcessChild.h" +#include "mozilla/net/SocketProcessParent.h" +#include "mozilla/Unused.h" +#include "nsNetCID.h" +#include "BackgroundParentImpl.h" +#include "BackgroundChildImpl.h" + +using namespace mozilla::dom; + +namespace mozilla { +namespace ipc { + +namespace { + +// These serialization and cleanup functions could be externally exposed. For +// now, though, keep them private to encourage use of the safer RAII +// AutoIPCStream class. + +template <typename M> +bool SerializeInputStreamWithFdsChild(nsIIPCSerializableInputStream* aStream, + IPCStream& aValue, bool aDelayedStart, + M* aManager) { + MOZ_RELEASE_ASSERT(aStream); + MOZ_ASSERT(aManager); + + const uint64_t kTooLargeStream = 1024 * 1024; + + uint32_t sizeUsed = 0; + AutoTArray<FileDescriptor, 4> fds; + aStream->Serialize(aValue.stream(), fds, aDelayedStart, kTooLargeStream, + &sizeUsed, aManager); + + MOZ_ASSERT(sizeUsed <= kTooLargeStream); + + if (aValue.stream().type() == InputStreamParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } + + if (fds.IsEmpty()) { + aValue.optionalFds() = void_t(); + } else { + PFileDescriptorSetChild* fdSet = + aManager->SendPFileDescriptorSetConstructor(fds[0]); + for (uint32_t i = 1; i < fds.Length(); ++i) { + Unused << fdSet->SendAddFileDescriptor(fds[i]); + } + + aValue.optionalFds() = fdSet; + } + + return true; +} + +template <typename M> +bool SerializeInputStreamWithFdsParent(nsIIPCSerializableInputStream* aStream, + IPCStream& aValue, bool aDelayedStart, + M* aManager) { + MOZ_RELEASE_ASSERT(aStream); + MOZ_ASSERT(aManager); + + const uint64_t kTooLargeStream = 1024 * 1024; + + uint32_t sizeUsed = 0; + AutoTArray<FileDescriptor, 4> fds; + aStream->Serialize(aValue.stream(), fds, aDelayedStart, kTooLargeStream, + &sizeUsed, aManager); + + MOZ_ASSERT(sizeUsed <= kTooLargeStream); + + if (aValue.stream().type() == InputStreamParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } + + aValue.optionalFds() = void_t(); + if (!fds.IsEmpty()) { + PFileDescriptorSetParent* fdSet = + aManager->SendPFileDescriptorSetConstructor(fds[0]); + for (uint32_t i = 1; i < fds.Length(); ++i) { + if (NS_WARN_IF(!fdSet->SendAddFileDescriptor(fds[i]))) { + Unused << PFileDescriptorSetParent::Send__delete__(fdSet); + fdSet = nullptr; + break; + } + } + + if (fdSet) { + aValue.optionalFds() = fdSet; + } + } + + return true; +} + +template <typename M> +bool SerializeInputStream(nsIInputStream* aStream, IPCStream& aValue, + M* aManager, bool aDelayedStart) { + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + + InputStreamParams params; + InputStreamHelper::SerializeInputStreamAsPipe(aStream, params, aDelayedStart, + aManager); + + if (params.type() == InputStreamParams::T__None) { + return false; + } + + aValue.stream() = params; + aValue.optionalFds() = void_t(); + + return true; +} + +template <typename M> +bool SerializeInputStreamChild(nsIInputStream* aStream, M* aManager, + IPCStream* aValue, + Maybe<IPCStream>* aOptionalValue, + bool aDelayedStart) { + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(aValue || aOptionalValue); + + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(aStream); + + if (serializable) { + if (aValue) { + return SerializeInputStreamWithFdsChild(serializable, *aValue, + aDelayedStart, aManager); + } + + return SerializeInputStreamWithFdsChild(serializable, aOptionalValue->ref(), + aDelayedStart, aManager); + } + + if (aValue) { + return SerializeInputStream(aStream, *aValue, aManager, aDelayedStart); + } + + return SerializeInputStream(aStream, aOptionalValue->ref(), aManager, + aDelayedStart); +} + +template <typename M> +bool SerializeInputStreamParent(nsIInputStream* aStream, M* aManager, + IPCStream* aValue, + Maybe<IPCStream>* aOptionalValue, + bool aDelayedStart) { + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(aValue || aOptionalValue); + + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(aStream); + + if (serializable) { + if (aValue) { + return SerializeInputStreamWithFdsParent(serializable, *aValue, + aDelayedStart, aManager); + } + + return SerializeInputStreamWithFdsParent( + serializable, aOptionalValue->ref(), aDelayedStart, aManager); + } + + if (aValue) { + return SerializeInputStream(aStream, *aValue, aManager, aDelayedStart); + } + + return SerializeInputStream(aStream, aOptionalValue->ref(), aManager, + aDelayedStart); +} + +void ActivateAndCleanupIPCStream(IPCStream& aValue, bool aConsumedByIPC, + bool aDelayedStart) { + // Cleanup file descriptors if necessary + if (aValue.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetChild) { + AutoTArray<FileDescriptor, 4> fds; + + auto fdSetActor = static_cast<FileDescriptorSetChild*>( + aValue.optionalFds().get_PFileDescriptorSetChild()); + MOZ_ASSERT(fdSetActor); + + // FileDescriptorSet doesn't clear its fds in its ActorDestroy, so we + // unconditionally forget them here. The fds themselves are auto-closed + // in ~FileDescriptor since they originated in this process. + fdSetActor->ForgetFileDescriptors(fds); + + if (!aConsumedByIPC) { + Unused << FileDescriptorSetChild::Send__delete__(fdSetActor); + } + + } else if (aValue.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetParent) { + AutoTArray<FileDescriptor, 4> fds; + + auto fdSetActor = static_cast<FileDescriptorSetParent*>( + aValue.optionalFds().get_PFileDescriptorSetParent()); + MOZ_ASSERT(fdSetActor); + + // FileDescriptorSet doesn't clear its fds in its ActorDestroy, so we + // unconditionally forget them here. The fds themselves are auto-closed + // in ~FileDescriptor since they originated in this process. + fdSetActor->ForgetFileDescriptors(fds); + + if (!aConsumedByIPC) { + Unused << FileDescriptorSetParent::Send__delete__(fdSetActor); + } + } + + // Activate IPCRemoteStreamParams. + InputStreamHelper::PostSerializationActivation(aValue.stream(), + aConsumedByIPC, aDelayedStart); +} + +void ActivateAndCleanupIPCStream(Maybe<IPCStream>& aValue, bool aConsumedByIPC, + bool aDelayedStart) { + if (aValue.isNothing()) { + return; + } + + ActivateAndCleanupIPCStream(aValue.ref(), aConsumedByIPC, aDelayedStart); +} + +// Returns false if the serialization should not proceed. This means that the +// inputStream is null. +bool NormalizeOptionalValue(nsIInputStream* aStream, IPCStream* aValue, + Maybe<IPCStream>* aOptionalValue) { + if (aValue) { + // if aStream is null, we will crash when serializing. + return true; + } + + if (!aStream) { + aOptionalValue->reset(); + return false; + } + + aOptionalValue->emplace(); + return true; +} + +} // anonymous namespace + +already_AddRefed<nsIInputStream> DeserializeIPCStream(const IPCStream& aValue) { + // Note, we explicitly do not support deserializing the PChildToParentStream + // actor on the child side nor the PParentToChildStream actor on the parent + // side. + + AutoTArray<FileDescriptor, 4> fds; + if (aValue.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetParent) { + auto fdSetActor = static_cast<FileDescriptorSetParent*>( + aValue.optionalFds().get_PFileDescriptorSetParent()); + MOZ_ASSERT(fdSetActor); + + fdSetActor->ForgetFileDescriptors(fds); + MOZ_ASSERT(!fds.IsEmpty()); + + if (!FileDescriptorSetParent::Send__delete__(fdSetActor)) { + // child process is gone, warn and allow actor to clean up normally + NS_WARNING("Failed to delete fd set actor."); + } + } else if (aValue.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetChild) { + auto fdSetActor = static_cast<FileDescriptorSetChild*>( + aValue.optionalFds().get_PFileDescriptorSetChild()); + MOZ_ASSERT(fdSetActor); + + fdSetActor->ForgetFileDescriptors(fds); + MOZ_ASSERT(!fds.IsEmpty()); + + Unused << FileDescriptorSetChild::Send__delete__(fdSetActor); + } + + return InputStreamHelper::DeserializeInputStream(aValue.stream(), fds); +} + +already_AddRefed<nsIInputStream> DeserializeIPCStream( + const Maybe<IPCStream>& aValue) { + if (aValue.isNothing()) { + return nullptr; + } + + return DeserializeIPCStream(aValue.ref()); +} + +AutoIPCStream::AutoIPCStream(bool aDelayedStart) + : mOptionalValue(&mInlineValue), mDelayedStart(aDelayedStart) {} + +AutoIPCStream::AutoIPCStream(IPCStream& aTarget, bool aDelayedStart) + : mValue(&aTarget), mDelayedStart(aDelayedStart) {} + +AutoIPCStream::AutoIPCStream(Maybe<IPCStream>& aTarget, bool aDelayedStart) + : mOptionalValue(&aTarget), mDelayedStart(aDelayedStart) { + mOptionalValue->reset(); +} + +AutoIPCStream::~AutoIPCStream() { + MOZ_ASSERT(mValue || mOptionalValue); + if (mValue && IsSet()) { + ActivateAndCleanupIPCStream(*mValue, mTaken, mDelayedStart); + } else { + ActivateAndCleanupIPCStream(*mOptionalValue, mTaken, mDelayedStart); + } +} + +bool AutoIPCStream::Serialize(nsIInputStream* aStream, + dom::ContentChild* aManager) { + MOZ_ASSERT(aStream || !mValue); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + // If NormalizeOptionalValue returns false, we don't have to proceed. + if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) { + return true; + } + + if (!SerializeInputStreamChild(aStream, aManager, mValue, mOptionalValue, + mDelayedStart)) { + MOZ_CRASH("IPCStream creation failed!"); + } + + return true; +} + +bool AutoIPCStream::Serialize(nsIInputStream* aStream, + PBackgroundChild* aManager) { + MOZ_ASSERT(aStream || !mValue); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + // If NormalizeOptionalValue returns false, we don't have to proceed. + if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) { + return true; + } + + BackgroundChildImpl* impl = static_cast<BackgroundChildImpl*>(aManager); + if (!SerializeInputStreamChild(aStream, impl, mValue, mOptionalValue, + mDelayedStart)) { + MOZ_CRASH("IPCStream creation failed!"); + } + + return true; +} + +bool AutoIPCStream::Serialize(nsIInputStream* aStream, + net::SocketProcessChild* aManager) { + MOZ_ASSERT(aStream || !mValue); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + // If NormalizeOptionalValue returns false, we don't have to proceed. + if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) { + return true; + } + + if (!SerializeInputStreamChild(aStream, aManager, mValue, mOptionalValue, + mDelayedStart)) { + MOZ_CRASH("IPCStream creation failed!"); + } + + return true; +} + +bool AutoIPCStream::Serialize(nsIInputStream* aStream, + dom::ContentParent* aManager) { + MOZ_ASSERT(aStream || !mValue); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + // If NormalizeOptionalValue returns false, we don't have to proceed. + if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) { + return true; + } + + if (!SerializeInputStreamParent(aStream, aManager, mValue, mOptionalValue, + mDelayedStart)) { + return false; + } + + return true; +} + +bool AutoIPCStream::Serialize(nsIInputStream* aStream, + PBackgroundParent* aManager) { + MOZ_ASSERT(aStream || !mValue); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + // If NormalizeOptionalValue returns false, we don't have to proceed. + if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) { + return true; + } + + BackgroundParentImpl* impl = static_cast<BackgroundParentImpl*>(aManager); + if (!SerializeInputStreamParent(aStream, impl, mValue, mOptionalValue, + mDelayedStart)) { + return false; + } + + return true; +} + +bool AutoIPCStream::Serialize(nsIInputStream* aStream, + net::SocketProcessParent* aManager) { + MOZ_ASSERT(aStream || !mValue); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + // If NormalizeOptionalValue returns false, we don't have to proceed. + if (!NormalizeOptionalValue(aStream, mValue, mOptionalValue)) { + return true; + } + + if (!SerializeInputStreamParent(aStream, aManager, mValue, mOptionalValue, + mDelayedStart)) { + return false; + } + + return true; +} + +bool AutoIPCStream::IsSet() const { + MOZ_ASSERT(mValue || mOptionalValue); + if (mValue) { + return mValue->stream().type() != InputStreamParams::T__None; + } else { + return mOptionalValue->isSome() && + mOptionalValue->ref().stream().type() != InputStreamParams::T__None; + } +} + +IPCStream& AutoIPCStream::TakeValue() { + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(IsSet()); + + mTaken = true; + + if (mValue) { + return *mValue; + } + + IPCStream& value = mOptionalValue->ref(); + return value; +} + +Maybe<IPCStream>& AutoIPCStream::TakeOptionalValue() { + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!mValue); + MOZ_ASSERT(mOptionalValue); + mTaken = true; + return *mOptionalValue; +} + +void IPDLParamTraits<nsIInputStream*>::Write(IPC::Message* aMsg, + IProtocol* aActor, + nsIInputStream* aParam) { + auto autoStream = MakeRefPtr<HoldIPCStream>(); + + bool ok = false; + bool found = false; + + // We can only serialize our nsIInputStream if it's going to be sent over one + // of the protocols we support, or a protocol which is managed by one of the + // protocols we support. + IProtocol* actor = aActor; + while (!found && actor) { + switch (actor->GetProtocolId()) { + case PContentMsgStart: + if (actor->GetSide() == mozilla::ipc::ParentSide) { + ok = autoStream->Serialize( + aParam, static_cast<mozilla::dom::ContentParent*>(actor)); + } else { + MOZ_RELEASE_ASSERT(actor->GetSide() == mozilla::ipc::ChildSide); + ok = autoStream->Serialize( + aParam, static_cast<mozilla::dom::ContentChild*>(actor)); + } + found = true; + break; + case PBackgroundMsgStart: + if (actor->GetSide() == mozilla::ipc::ParentSide) { + ok = autoStream->Serialize( + aParam, static_cast<mozilla::ipc::PBackgroundParent*>(actor)); + } else { + MOZ_RELEASE_ASSERT(actor->GetSide() == mozilla::ipc::ChildSide); + ok = autoStream->Serialize( + aParam, static_cast<mozilla::ipc::PBackgroundChild*>(actor)); + } + found = true; + break; + default: + break; + } + + // Try the actor's manager. + actor = actor->Manager(); + } + + if (!found) { + aActor->FatalError( + "Attempt to send nsIInputStream over an unsupported ipdl protocol"); + } + MOZ_RELEASE_ASSERT(ok, "Failed to serialize nsIInputStream"); + + WriteIPDLParam(aMsg, aActor, autoStream->TakeOptionalValue()); + + // Dispatch the autoStream to an async runnable, so that we guarantee it + // outlives this callstack, and doesn't shut down any actors we created + // until after we've finished sending the current message. + NS_ProxyRelease("IPDLParamTraits<nsIInputStream*>::Write::autoStream", + NS_GetCurrentThread(), autoStream.forget(), true); +} + +bool IPDLParamTraits<nsIInputStream*>::Read(const IPC::Message* aMsg, + PickleIterator* aIter, + IProtocol* aActor, + RefPtr<nsIInputStream>* aResult) { + mozilla::Maybe<mozilla::ipc::IPCStream> ipcStream; + if (!ReadIPDLParam(aMsg, aIter, aActor, &ipcStream)) { + return false; + } + + *aResult = mozilla::ipc::DeserializeIPCStream(ipcStream); + return true; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/IPCStreamUtils.h b/ipc/glue/IPCStreamUtils.h new file mode 100644 index 0000000000..da85472247 --- /dev/null +++ b/ipc/glue/IPCStreamUtils.h @@ -0,0 +1,218 @@ +/* -*- 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 { + +namespace dom { +class ContentChild; +class ContentParent; +} // namespace dom + +namespace net { +class SocketProcessParent; +class SocketProcessChild; +} // namespace net + +namespace ipc { + +class PBackgroundChild; +class PBackgroundParent; + +// 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); + +// RAII helper class that serializes an nsIInputStream into an IPCStream struct. +// Any file descriptor or PChildToParentStream actors are automatically managed +// correctly. +// +// Here is a simple example: +// +// // in ipdl file +// Protocol PMyStuff +// { +// parent: +// async DoStuff(IPCStream aStream); +// child: +// async StuffDone(IPCStream aStream); +// }; +// +// // in child c++ code +// void CallDoStuff(PMyStuffChild* aActor, nsIInputStream* aStream) +// { +// AutoIPCStream autoStream; +// autoStream.Serialize(aStream, aActor->Manager()); +// aActor->SendDoStuff(autoStream.TakeValue()); +// } +// +// // in parent c++ code +// bool +// MyStuffParent::RecvDoStuff(const IPCStream& aIPCStream) { +// nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aIPCStream); +// // Do something with stream... +// +// // You can also serialize streams from parent-to-child as long as +// // they don't require PChildToParentStream actor support. +// AutoIPCStream anotherStream; +// anotherStream.Serialize(mFileStream, Manager()); +// SendStuffDone(anotherStream.TakeValue()); +// } +// +// The AutoIPCStream RAII class may also be used if your stream is embedded +// in a more complex IPDL structure. In this case you attach the AutoIPCStream +// to the embedded IPCStream and call TakeValue() after you pass the structure. +// For example: +// +// // in ipdl file +// struct Stuff +// { +// IPCStream stream; +// nsCString name; +// }; +// +// Protocol PMyStuff +// { +// parent: +// async DoStuff(Stuff aStream); +// }; +// +// // in child c++ code +// void CallDoStuff(PMyStuffChild* aActor, nsIInputStream* aStream) +// { +// Stuff stuff; +// AutoIPCStream autoStream(stuff.stream()); // attach to IPCStream here +// autoStream.Serialize(aStream, aActor->Manager()); +// aActor->SendDoStuff(stuff); +// autoStream.TakeValue(); // call take value after send +// } +// +// // in parent c++ code +// bool +// MyStuffParent::RecvDoStuff(const Stuff& aStuff) { +// nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStuff.stream()); +// /* do something with the nsIInputStream */ +// } +// +// Note: This example is about child-to-parent inputStream, but AutoIPCStream +// works also parent-to-child. +// +// The AutoIPCStream class also supports Maybe<IPCStream> values. As long as +// you did not initialize the object with a non-optional IPCStream, you can call +// TakeOptionalValue() instead. +// +// The AutoIPCStream class can also be used to serialize nsIInputStream objects +// on the parent side to send to the child. Currently, however, this only +// works for directly serializable stream types. The PChildToParentStream actor +// mechanism is not supported in this direction yet. +// +// Like SerializeInputStream(), the AutoIPCStream will crash if +// serialization cannot be completed. +// +// NOTE: This is not a MOZ_STACK_CLASS so that it can be more easily integrated +// with complex ipdl structures. For example, you may want to create an +// array of RAII AutoIPCStream objects or build your own wrapping +// RAII object to handle other actors that need to be cleaned up. +class AutoIPCStream { + public: + // Implicitly create an Maybe<IPCStream> value. Either + // TakeValue() or TakeOptionalValue() can be used. + explicit AutoIPCStream(bool aDelayedStart = false); + + // Wrap an existing IPCStream. Only TakeValue() may be + // used. If a nullptr nsIInputStream is passed to SerializeOrSend() then + // a crash will be forced. + explicit AutoIPCStream(IPCStream& aTarget, bool aDelayedStart = false); + + // Wrap an existing Maybe<IPCStream>. Either TakeValue() + // or TakeOptionalValue can be used. + explicit AutoIPCStream(Maybe<IPCStream>& aTarget, bool aDelayedStart = false); + + AutoIPCStream(const AutoIPCStream&) = delete; + AutoIPCStream(AutoIPCStream&&) = delete; + + AutoIPCStream& operator=(const AutoIPCStream&) = delete; + AutoIPCStream& operator=(AutoIPCStream&&) = delete; + + ~AutoIPCStream(); + + // Serialize the input stream or create a SendStream actor using the PContent + // manager. If neither of these succeed, then crash. This should only be + // used on the main thread. + bool Serialize(nsIInputStream* aStream, dom::ContentChild* aManager); + + // Serialize the input stream or create a SendStream actor using the + // PBackground manager. If neither of these succeed, then crash. This can + // be called on the main thread or Worker threads. + bool Serialize(nsIInputStream* aStream, PBackgroundChild* aManager); + + // Serialize the input stream or create a SendStream actor using the + // SocketProcess manager. If neither of these succeed, then crash. This + // should only be used on the main thread. + bool Serialize(nsIInputStream* aStream, net::SocketProcessChild* aManager); + + // Serialize the input stream. + [[nodiscard]] bool Serialize(nsIInputStream* aStream, + dom::ContentParent* aManager); + + // Serialize the input stream. + [[nodiscard]] bool Serialize(nsIInputStream* aStream, + PBackgroundParent* aManager); + + // Serialize the input stream. + [[nodiscard]] bool Serialize(nsIInputStream* aStream, + net::SocketProcessParent* aManager); + + // Get the IPCStream as a non-optional value. This will + // assert if a stream has not been serialized or if it has already been taken. + // This should only be called if the value is being, or has already been, sent + // to the other side. + IPCStream& TakeValue(); + + // Get the Maybe<IPCStream> value. This will assert if the value has already + // been taken. This should only be called if the value is being, or has + // already been, sent to the other side. + Maybe<IPCStream>& TakeOptionalValue(); + + private: + bool IsSet() const; + + Maybe<IPCStream> mInlineValue; + IPCStream* const mValue = nullptr; + Maybe<IPCStream>* const mOptionalValue = nullptr; + bool mTaken = false; + const bool mDelayedStart; +}; + +class HoldIPCStream final : public AutoIPCStream { + public: + NS_INLINE_DECL_REFCOUNTING(HoldIPCStream) + + private: + ~HoldIPCStream() = default; +}; + +template <> +struct IPDLParamTraits<nsIInputStream*> { + static void Write(IPC::Message* aMsg, IProtocol* aActor, + nsIInputStream* aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, RefPtr<nsIInputStream>* aResult); +}; + +} // namespace ipc +} // namespace mozilla + +#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..f258bdcaf6 --- /dev/null +++ b/ipc/glue/IPDLParamTraits.h @@ -0,0 +1,444 @@ +/* -*- 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 "mozilla/UniquePtr.h" +#include "mozilla/Variant.h" +#include "mozilla/Tuple.h" +#include "nsTArray.h" + +#include <type_traits> + +namespace mozilla { +namespace ipc { + +class IProtocol; + +// +// IPDLParamTraits are an extended version of ParamTraits. Unlike ParamTraits, +// IPDLParamTraits supports passing an additional IProtocol* argument to the +// write and read methods. +// +// This is important for serializing and deserializing types which require +// knowledge of which protocol they're being sent over, such as actors and +// nsIInputStreams. +// +// All types which already implement ParamTraits also support IPDLParamTraits. +// +template <typename P> +struct IPDLParamTraits { + // This is the default impl which discards the actor parameter and calls into + // ParamTraits. Types which want to use the actor parameter must specialize + // IPDLParamTraits. + template <typename R> + static inline void Write(IPC::Message* aMsg, IProtocol*, R&& aParam) { + IPC::ParamTraits<P>::Write(aMsg, std::forward<R>(aParam)); + } + + template <typename R> + static inline bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol*, R* aResult) { + return IPC::ParamTraits<P>::Read(aMsg, aIter, aResult); + } +}; + +// +// WriteIPDLParam and ReadIPDLParam are like IPC::WriteParam and IPC::ReadParam, +// however, they also accept an extra actor argument, and use IPDLParamTraits +// rather than ParamTraits. +// +// NOTE: WriteIPDLParam takes a universal reference, so that it can support +// whatever reference type is supported by the underlying IPDLParamTraits::Write +// implementation. See the comment on IPDLParamTraits<nsTArray<T>>::Write for +// more information. +// +template <typename P> +static MOZ_NEVER_INLINE void WriteIPDLParam(IPC::Message* aMsg, + IProtocol* aActor, P&& aParam) { + IPDLParamTraits<std::decay_t<P>>::Write(aMsg, aActor, + std::forward<P>(aParam)); +} + +template <typename P> +static MOZ_NEVER_INLINE bool ReadIPDLParam(const IPC::Message* aMsg, + PickleIterator* aIter, + IProtocol* aActor, P* aResult) { + return IPDLParamTraits<P>::Read(aMsg, aIter, aActor, aResult); +} + +template <typename P> +static MOZ_NEVER_INLINE bool ReadIPDLParamInfallible( + const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, + P* aResult, const char* aCrashMessage) { + bool ok = ReadIPDLParam(aMsg, aIter, aActor, aResult); + if (!ok) { + MOZ_CRASH_UNSAFE(aCrashMessage); + } + return ok; +} + +constexpr void WriteIPDLParamList(IPC::Message*, IProtocol*) {} + +template <typename P, typename... Ps> +static void WriteIPDLParamList(IPC::Message* aMsg, IProtocol* aActor, + P&& aParam, Ps&&... aParams) { + WriteIPDLParam(aMsg, aActor, std::forward<P>(aParam)); + WriteIPDLParamList(aMsg, aActor, std::forward<Ps>(aParams)...); +} + +constexpr bool ReadIPDLParamList(const IPC::Message*, PickleIterator*, + IProtocol*) { + return true; +} + +template <typename P, typename... Ps> +static bool ReadIPDLParamList(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, P* aResult, Ps*... aResults) { + return ReadIPDLParam(aMsg, aIter, aActor, aResult) && + ReadIPDLParamList(aMsg, aIter, aActor, aResults...); +} + +// When being passed `RefPtr<T>` or `nsCOMPtr<T>`, forward to a specialization +// for the underlying target type. The parameter type will be passed as `T*`, +// and result as `RefPtr<T>*`. +// +// This is done explicitly to ensure that the deleted `&&` overload for +// `operator T*` is not selected in generic contexts, and to support +// deserializing into `nsCOMPtr<T>`. +template <typename T> +struct IPDLParamTraits<RefPtr<T>> { + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const RefPtr<T>& aParam) { + IPDLParamTraits<T*>::Write(aMsg, aActor, aParam.get()); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, RefPtr<T>* aResult) { + return IPDLParamTraits<T*>::Read(aMsg, aIter, aActor, aResult); + } +}; + +template <typename T> +struct IPDLParamTraits<nsCOMPtr<T>> { + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const nsCOMPtr<T>& aParam) { + IPDLParamTraits<T*>::Write(aMsg, aActor, aParam.get()); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, nsCOMPtr<T>* aResult) { + RefPtr<T> refptr; + if (!IPDLParamTraits<T*>::Read(aMsg, aIter, aActor, &refptr)) { + return false; + } + *aResult = refptr.forget(); + return true; + } +}; + +// nsTArray support for IPDLParamTraits +template <typename T> +struct IPDLParamTraits<nsTArray<T>> { + // Some serializers need to take a mutable reference to their backing object, + // such as Shmem segments and Byte Buffers. These serializers take the + // backing data and move it into the IPC layer for efficiency. `Write` uses a + // forwarding reference as occasionally these types appear inside of IPDL + // arrays. + template <typename U> + static void Write(IPC::Message* aMsg, IProtocol* aActor, U&& aParam) { + uint32_t length = aParam.Length(); + WriteIPDLParam(aMsg, aActor, length); + + if (sUseWriteBytes) { + auto pickledLength = CheckedInt<int>(length) * sizeof(T); + MOZ_RELEASE_ASSERT(pickledLength.isValid()); + aMsg->WriteBytes(aParam.Elements(), pickledLength.value()); + } else { + WriteValues(aMsg, aActor, std::forward<U>(aParam)); + } + } + + // This method uses infallible allocation so that an OOM failure will + // show up as an OOM crash rather than an IPC FatalError. + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, nsTArray<T>* aResult) { + uint32_t length; + if (!ReadIPDLParam(aMsg, aIter, aActor, &length)) { + return false; + } + + if (sUseWriteBytes) { + auto pickledLength = CheckedInt<int>(length) * sizeof(T); + if (!pickledLength.isValid() || + !aMsg->HasBytesAvailable(aIter, pickledLength.value())) { + return false; + } + + // XXX(nika): This currently default-constructs the backing data before + // passing it into ReadBytesInto, which is technically unnecessary here. + // Perhaps we should consider using an API which doesn't initialize the + // elements? + T* elements = aResult->AppendElements(length); + return aMsg->ReadBytesInto(aIter, elements, pickledLength.value()); + } + + // Each ReadIPDLParam<E> may read more than 1 byte each; this is an attempt + // to minimally validate that the length isn't much larger than what's + // actually available in aMsg. We cannot use |pickledLength|, like in the + // codepath above, because ReadIPDLParam can read variable amounts of data + // from aMsg. + if (!aMsg->HasBytesAvailable(aIter, length)) { + return false; + } + + aResult->SetCapacity(length); + + for (uint32_t index = 0; index < length; index++) { + T* element = aResult->AppendElement(); + if (!ReadIPDLParam(aMsg, aIter, aActor, element)) { + return false; + } + } + return true; + } + + private: + // Length has already been written. Const overload. + static void WriteValues(IPC::Message* aMsg, IProtocol* aActor, + const nsTArray<T>& aParam) { + for (auto& elt : aParam) { + WriteIPDLParam(aMsg, aActor, elt); + } + } + + // Length has already been written. Rvalue overload. + static void WriteValues(IPC::Message* aMsg, IProtocol* aActor, + nsTArray<T>&& aParam) { + for (auto& elt : aParam) { + WriteIPDLParam(aMsg, aActor, std::move(elt)); + } + + // As we just moved all of our values out, let's clean up after ourselves & + // clear the input array. This means our move write method will act more + // like a traditional move constructor. + aParam.Clear(); + } + + // We write arrays of integer or floating-point data using a single pickling + // call, rather than writing each element individually. We deliberately do + // not use mozilla::IsPod here because it is perfectly reasonable to have + // a data structure T for which IsPod<T>::value is true, yet also have a + // {IPDL,}ParamTraits<T> specialization. + static const bool sUseWriteBytes = + (std::is_integral_v<T> || std::is_floating_point_v<T>); +}; + +template <typename T> +struct IPDLParamTraits<CopyableTArray<T>> : IPDLParamTraits<nsTArray<T>> {}; + +// Maybe support for IPDLParamTraits +template <typename T> +struct IPDLParamTraits<Maybe<T>> { + typedef Maybe<T> paramType; + + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const Maybe<T>& aParam) { + bool isSome = aParam.isSome(); + WriteIPDLParam(aMsg, aActor, isSome); + + if (isSome) { + WriteIPDLParam(aMsg, aActor, aParam.ref()); + } + } + + static void Write(IPC::Message* aMsg, IProtocol* aActor, Maybe<T>&& aParam) { + bool isSome = aParam.isSome(); + WriteIPDLParam(aMsg, aActor, isSome); + + if (isSome) { + WriteIPDLParam(aMsg, aActor, std::move(aParam.ref())); + } + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, Maybe<T>* aResult) { + bool isSome; + if (!ReadIPDLParam(aMsg, aIter, aActor, &isSome)) { + return false; + } + + if (isSome) { + aResult->emplace(); + if (!ReadIPDLParam(aMsg, aIter, aActor, aResult->ptr())) { + return false; + } + } else { + aResult->reset(); + } + return true; + } +}; + +template <typename T> +struct IPDLParamTraits<UniquePtr<T>> { + typedef UniquePtr<T> paramType; + + template <typename U> + static void Write(IPC::Message* aMsg, IProtocol* aActor, U&& aParam) { + bool isNull = aParam == nullptr; + WriteIPDLParam(aMsg, aActor, isNull); + + if (!isNull) { + WriteValue(aMsg, aActor, std::forward<U>(aParam)); + } + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, UniquePtr<T>* aResult) { + bool isNull = true; + if (!ReadParam(aMsg, aIter, &isNull)) { + return false; + } + + if (isNull) { + aResult->reset(); + } else { + *aResult = MakeUnique<T>(); + if (!ReadIPDLParam(aMsg, aIter, aActor, aResult->get())) { + return false; + } + } + return true; + } + + private: + // If we have an rvalue, clear out our passed-in parameter. + static void WriteValue(IPC::Message* aMsg, IProtocol* aActor, + UniquePtr<T>&& aParam) { + WriteIPDLParam(aMsg, aActor, std::move(*aParam.get())); + aParam = nullptr; + } + + static void WriteValue(IPC::Message* aMsg, IProtocol* aActor, + const UniquePtr<T>& aParam) { + WriteIPDLParam(aMsg, aActor, *aParam.get()); + } +}; + +template <typename... Ts> +struct IPDLParamTraits<Tuple<Ts...>> { + typedef Tuple<Ts...> paramType; + + template <typename U> + static void Write(IPC::Message* aMsg, IProtocol* aActor, U&& aParam) { + WriteInternal(aMsg, aActor, std::forward<U>(aParam), + std::index_sequence_for<Ts...>{}); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, Tuple<Ts...>* aResult) { + return ReadInternal(aMsg, aIter, aActor, *aResult, + std::index_sequence_for<Ts...>{}); + } + + private: + template <size_t... Is> + static void WriteInternal(IPC::Message* aMsg, IProtocol* aActor, + const Tuple<Ts...>& aParam, + std::index_sequence<Is...>) { + WriteIPDLParamList(aMsg, aActor, Get<Is>(aParam)...); + } + + template <size_t... Is> + static void WriteInternal(IPC::Message* aMsg, IProtocol* aActor, + Tuple<Ts...>&& aParam, std::index_sequence<Is...>) { + WriteIPDLParamList(aMsg, aActor, std::move(Get<Is>(aParam))...); + } + + template <size_t... Is> + static bool ReadInternal(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, Tuple<Ts...>& aResult, + std::index_sequence<Is...>) { + return ReadIPDLParamList(aMsg, aIter, aActor, &Get<Is>(aResult)...); + } +}; + +template <class... Ts> +struct IPDLParamTraits<mozilla::Variant<Ts...>> { + typedef mozilla::Variant<Ts...> paramType; + using Tag = typename mozilla::detail::VariantTag<Ts...>::Type; + + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const paramType& aParam) { + WriteIPDLParam(aMsg, aActor, aParam.tag); + aParam.match( + [aMsg, aActor](const auto& t) { WriteIPDLParam(aMsg, aActor, t); }); + } + + static void Write(IPC::Message* aMsg, IProtocol* aActor, paramType&& aParam) { + WriteIPDLParam(aMsg, aActor, aParam.tag); + aParam.match([aMsg, aActor](auto& t) { + WriteIPDLParam(aMsg, aActor, std::move(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(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, Tag aTag, paramType* aResult) { + // 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 (aTag == 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 ReadIPDLParam(aMsg, aIter, aActor, + &aResult->template emplace<N - 1>()); + } + return Next::Read(aMsg, aIter, aActor, aTag, aResult); + } + + }; // 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(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, Tag aTag, paramType* aResult) { + return false; + } + }; + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult) { + Tag tag; + if (!ReadIPDLParam(aMsg, aIter, aActor, &tag)) { + return false; + } + + return VariantReader<sizeof...(Ts)>::Read(aMsg, aIter, aActor, tag, + aResult); + } +}; + +} // namespace ipc +} // namespace mozilla + +#endif // defined(mozilla_ipc_IPDLParamTraits_h) diff --git a/ipc/glue/IdleSchedulerChild.cpp b/ipc/glue/IdleSchedulerChild.cpp new file mode 100644 index 0000000000..330d327bac --- /dev/null +++ b/ipc/glue/IdleSchedulerChild.cpp @@ -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/. */ + +#include "mozilla/ipc/IdleSchedulerChild.h" +#include "mozilla/ipc/IdleSchedulerParent.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/Atomics.h" +#include "mozilla/IdlePeriodState.h" +#include "BackgroundChild.h" + +namespace mozilla { +namespace ipc { + +static IdleSchedulerChild* sMainThreadIdleScheduler = nullptr; + +IdleSchedulerChild::~IdleSchedulerChild() { + if (sMainThreadIdleScheduler == this) { + sMainThreadIdleScheduler = nullptr; + } + MOZ_ASSERT(!mIdlePeriodState); +} + +void IdleSchedulerChild::Init(IdlePeriodState* aIdlePeriodState) { + mIdlePeriodState = aIdlePeriodState; + + RefPtr<IdleSchedulerChild> scheduler = this; + auto resolve = + [&](Tuple<mozilla::Maybe<SharedMemoryHandle>, uint32_t>&& aResult) { + if (Get<0>(aResult)) { + mActiveCounter.SetHandle(*Get<0>(aResult), false); + mActiveCounter.Map(sizeof(int32_t)); + mChildId = 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; +} + +IdleSchedulerChild* IdleSchedulerChild::GetMainThreadIdleScheduler() { + MOZ_ASSERT(NS_IsMainThread()); + + if (sMainThreadIdleScheduler) { + return sMainThreadIdleScheduler; + } + + ipc::PBackgroundChild* background = + ipc::BackgroundChild::GetOrCreateForCurrentThread(); + if (background) { + sMainThreadIdleScheduler = new ipc::IdleSchedulerChild(); + background->SendPIdleSchedulerConstructor(sMainThreadIdleScheduler); + } + return sMainThreadIdleScheduler; +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/IdleSchedulerChild.h b/ipc/glue/IdleSchedulerChild.h new file mode 100644 index 0000000000..5b8aa11b7c --- /dev/null +++ b/ipc/glue/IdleSchedulerChild.h @@ -0,0 +1,58 @@ +/* -*- 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(); + + static IdleSchedulerChild* GetMainThreadIdleScheduler(); + + private: + ~IdleSchedulerChild(); + + friend class BackgroundChildImpl; + + // See IdleScheduleParent::sActiveChildCounter + base::SharedMemory mActiveCounter; + + IdlePeriodState* mIdlePeriodState = nullptr; + + uint32_t mChildId = 0; +}; + +} // 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..292f75eb01 --- /dev/null +++ b/ipc/glue/IdleSchedulerParent.cpp @@ -0,0 +1,301 @@ +/* -*- 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/Unused.h" +#include "mozilla/ipc/IdleSchedulerParent.h" +#include "nsSystemInfo.h" +#include "nsThreadUtils.h" +#include "nsITimer.h" + +namespace mozilla { +namespace ipc { + +base::SharedMemory* IdleSchedulerParent::sActiveChildCounter = nullptr; +std::bitset<NS_IDLE_SCHEDULER_COUNTER_ARRAY_LENGHT> + IdleSchedulerParent::sInUseChildCounters; +LinkedList<IdleSchedulerParent> IdleSchedulerParent::sWaitingForIdle; +Atomic<int32_t> IdleSchedulerParent::sMaxConcurrentIdleTasksInChildProcesses( + -1); +uint32_t IdleSchedulerParent::sChildProcessesRunningPrioritizedOperation = 0; +uint32_t IdleSchedulerParent::sChildProcessesAlive = 0; +nsITimer* IdleSchedulerParent::sStarvationPreventer = nullptr; + +IdleSchedulerParent::IdleSchedulerParent() { + sChildProcessesAlive++; + + if (sMaxConcurrentIdleTasksInChildProcesses == -1) { + // nsISystemInfo can be initialized only on the main thread. + // While waiting for the real logical core count behave as if there was just + // one core. + sMaxConcurrentIdleTasksInChildProcesses = 1; + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); + nsCOMPtr<nsIRunnable> runnable = + NS_NewRunnableFunction("cpucount getter", [thread]() { + // Always pretend that there is at least one core for child processes. + // If there are multiple logical cores, reserve one for the parent + // process and for the non-main threads. + ProcessInfo processInfo = {}; + if (NS_SUCCEEDED(CollectProcessInfo(processInfo)) && + processInfo.cpuCount > 1) { + // On one and two processor (or hardware thread) systems this will + // allow one concurrent idle task. + sMaxConcurrentIdleTasksInChildProcesses = + std::max(processInfo.cpuCount - 1, 1); + // We have a new cpu count, reschedule idle scheduler. + nsCOMPtr<nsIRunnable> runnable = + NS_NewRunnableFunction("IdleSchedulerParent::Schedule", []() { + 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); + }); + thread->Dispatch(runnable, NS_DISPATCH_NORMAL); + } + }); + NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_EVENT_MAY_BLOCK); + } +} + +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 (isInList()) { + remove(); + } + + MOZ_ASSERT(sChildProcessesAlive > 0); + sChildProcessesAlive--; + if (sChildProcessesAlive == 0) { + MOZ_ASSERT(sWaitingForIdle.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; + SharedMemoryHandle handle; + if (sActiveChildCounter && + sActiveChildCounter->ShareToProcess(OtherPid(), &handle)) { + activeCounter.emplace(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(Tuple<const mozilla::Maybe<SharedMemoryHandle>&, const uint32_t&>( + activeCounter, mChildId)); + return IPC_OK(); +} + +IPCResult IdleSchedulerParent::RecvRequestIdleTime(uint64_t aId, + TimeDuration aBudget) { + MOZ_ASSERT(aBudget); + MOZ_ASSERT(IsNotDoingIdleTask()); + + mCurrentRequestId = aId; + mRequestedIdleBudget = aBudget; + + sWaitingForIdle.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()) { + 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(); +} + +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; +} + +void IdleSchedulerParent::SendIdleTime() { + // We would assert that IsWaiting() except after removing the task from it's + // list this will return false. Instead check IsDoingIdleTask() + MOZ_ASSERT(IsDoingIdleTask()); + Unused << SendIdleTime(mCurrentRequestId, mRequestedIdleBudget); +} + +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) { + // If the requester is prioritized, just let it run itself. + if (aRequester->isInList()) { + aRequester->remove(); + } + aRequester->SendIdleTime(); + activeCount++; + } + + while (!sWaitingForIdle.isEmpty() && HasSpareCycles(activeCount)) { + // We can run an idle task. + RefPtr<IdleSchedulerParent> idleRequester = sWaitingForIdle.popFirst(); + idleRequester->SendIdleTime(); + activeCount++; + } + + if (!sWaitingForIdle.isEmpty()) { + 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) { + if (!sWaitingForIdle.isEmpty()) { + RefPtr<IdleSchedulerParent> first = sWaitingForIdle.getFirst(); + // Treat the first process waiting for idle time as running prioritized + // operation so that it gets run. + ++first->mRunningPrioritizedOperation; + ++sChildProcessesRunningPrioritizedOperation; + Schedule(first); + --first->mRunningPrioritizedOperation; + --sChildProcessesRunningPrioritizedOperation; + } + NS_RELEASE(sStarvationPreventer); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/IdleSchedulerParent.h b/ipc/glue/IdleSchedulerParent.h new file mode 100644 index 0000000000..01696e6525 --- /dev/null +++ b/ipc/glue/IdleSchedulerParent.h @@ -0,0 +1,114 @@ +/* -*- 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(); + + private: + friend class BackgroundParentImpl; + IdleSchedulerParent(); + ~IdleSchedulerParent(); + + static int32_t ActiveCount(); + static void Schedule(IdleSchedulerParent* aRequester); + static bool HasSpareCycles(int32_t aActiveCount); + using PIdleSchedulerParent::SendIdleTime; + void SendIdleTime(); + + 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; + + uint32_t mChildId = 0; + + // Current state, only one of these may be true at a time. + bool IsWaitingForIdle() const { + MOZ_ASSERT_IF(isInList(), mRequestedIdleBudget); + return isInList(); + } + bool IsDoingIdleTask() const { return !isInList() && mRequestedIdleBudget; } + bool IsNotDoingIdleTask() const { + return !isInList() && !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 idle time, but haven't got it yet. + // Their mRequestedIdleBudget field is non-zero. + // Child processes not on this list have either been granted their request for + // idle time (mRequestedIdleBudget is non-zero) or have not requested idle + // time ever or since they last finished an idle task or (mRequestedIdleBudget + // is zero), + static LinkedList<IdleSchedulerParent> sWaitingForIdle; + + static Atomic<int32_t> sMaxConcurrentIdleTasksInChildProcesses; + + // 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; +}; + +} // 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..c7208eaf7f --- /dev/null +++ b/ipc/glue/InputStreamParams.ipdlh @@ -0,0 +1,125 @@ +/* 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; + +include protocol PChildToParentStream; +include protocol PParentToChildStream; +include protocol PRemoteLazyInputStream; + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; + +namespace mozilla { +namespace ipc { + +struct HeaderEntry +{ + nsCString name; + nsCString value; +}; + +struct StringInputStreamParams +{ + nsCString data; +}; + +struct FileInputStreamParams +{ + uint32_t fileDescriptorIndex; + 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 RemoteLazyInputStreamRef +{ + nsID id; + uint64_t start; + uint64_t length; +}; + +union RemoteLazyInputStreamParams +{ + RemoteLazyInputStreamRef; + PRemoteLazyInputStream; +}; + +union IPCRemoteStreamType +{ + PChildToParentStream; + PParentToChildStream; +}; + +struct IPCRemoteStreamParams +{ + // If this is true, the stream will send data only when the first operation + // is done on the destination side. The benefit of this is that we do not + // send data if not needed. On the other hand, it could have a performance + // issue. + bool delayedStart; + + IPCRemoteStreamType stream; + + int64_t length; +}; + +union InputStreamParams +{ + StringInputStreamParams; + FileInputStreamParams; + BufferedInputStreamParams; + MIMEInputStreamParams; + MultiplexInputStreamParams; + SlicedInputStreamParams; + RemoteLazyInputStreamParams; + InputStreamLengthWrapperParams; + IPCRemoteStreamParams; + EncryptedFileInputStreamParams; +}; + +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..fed7bde795 --- /dev/null +++ b/ipc/glue/InputStreamUtils.cpp @@ -0,0 +1,400 @@ +/* -*- 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/IPCStreamDestination.h" +#include "mozilla/ipc/IPCStreamSource.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 { + +NS_DEFINE_CID(kStringInputStreamCID, NS_STRINGINPUTSTREAM_CID); +NS_DEFINE_CID(kFileInputStreamCID, NS_LOCALFILEINPUTSTREAM_CID); +NS_DEFINE_CID(kBufferedInputStreamCID, NS_BUFFEREDINPUTSTREAM_CID); +NS_DEFINE_CID(kMIMEInputStreamCID, NS_MIMEINPUTSTREAM_CID); +NS_DEFINE_CID(kMultiplexInputStreamCID, NS_MULTIPLEXINPUTSTREAM_CID); + +} // namespace + +namespace mozilla { +namespace ipc { + +namespace { + +template <typename M> +void SerializeInputStreamInternal(nsIInputStream* aInputStream, + InputStreamParams& aParams, + nsTArray<FileDescriptor>& aFileDescriptors, + bool aDelayedStart, uint32_t aMaxSize, + uint32_t* aSizeUsed, M* aManager) { + MOZ_ASSERT(aInputStream); + MOZ_ASSERT(aManager); + + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(aInputStream); + if (!serializable) { + MOZ_CRASH("Input stream is not serializable!"); + } + + serializable->Serialize(aParams, aFileDescriptors, aDelayedStart, aMaxSize, + aSizeUsed, aManager); + + if (aParams.type() == InputStreamParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } +} + +template <typename M> +void SerializeInputStreamAsPipeInternal(nsIInputStream* aInputStream, + InputStreamParams& aParams, + bool aDelayedStart, M* aManager) { + MOZ_ASSERT(aInputStream); + MOZ_ASSERT(aManager); + + // 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; + } + + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aInputStream); + + // As a fallback, attempt to stream the data across using a IPCStream + // actor. For blocking streams, create a nonblocking pipe instead, + bool nonBlocking = false; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aInputStream->IsNonBlocking(&nonBlocking))); + if (!nonBlocking || !asyncStream) { + const uint32_t kBufferSize = 32768; // matches IPCStream buffer size. + nsCOMPtr<nsIAsyncOutputStream> sink; + nsresult rv = NS_NewPipe2(getter_AddRefs(asyncStream), getter_AddRefs(sink), + true, false, kBufferSize, UINT32_MAX); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + + // Since the source stream could be used by others, let's not close it when + // the copy is done. + rv = NS_AsyncCopy(aInputStream, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS, + kBufferSize, nullptr, nullptr, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + } + MOZ_DIAGNOSTIC_ASSERT(asyncStream); + + auto* streamSource = IPCStreamSource::Create(asyncStream, aManager); + if (NS_WARN_IF(!streamSource)) { + // Failed to create IPCStreamSource, which would cause a failure should we + // attempt to serialize it later. So abort now. + return; + } + + aParams = IPCRemoteStreamParams(aDelayedStart, streamSource, length); +} + +} // namespace + +void InputStreamHelper::SerializeInputStream( + nsIInputStream* aInputStream, InputStreamParams& aParams, + nsTArray<FileDescriptor>& aFileDescriptors, bool aDelayedStart, + uint32_t aMaxSize, uint32_t* aSizeUsed, + ParentToChildStreamActorManager* aManager) { + SerializeInputStreamInternal(aInputStream, aParams, aFileDescriptors, + aDelayedStart, aMaxSize, aSizeUsed, aManager); +} + +void InputStreamHelper::SerializeInputStream( + nsIInputStream* aInputStream, InputStreamParams& aParams, + nsTArray<FileDescriptor>& aFileDescriptors, bool aDelayedStart, + uint32_t aMaxSize, uint32_t* aSizeUsed, + ChildToParentStreamActorManager* aManager) { + SerializeInputStreamInternal(aInputStream, aParams, aFileDescriptors, + aDelayedStart, aMaxSize, aSizeUsed, aManager); +} + +void InputStreamHelper::SerializeInputStreamAsPipe( + nsIInputStream* aInputStream, InputStreamParams& aParams, + bool aDelayedStart, ParentToChildStreamActorManager* aManager) { + SerializeInputStreamAsPipeInternal(aInputStream, aParams, aDelayedStart, + aManager); +} + +void InputStreamHelper::SerializeInputStreamAsPipe( + nsIInputStream* aInputStream, InputStreamParams& aParams, + bool aDelayedStart, ChildToParentStreamActorManager* aManager) { + SerializeInputStreamAsPipeInternal(aInputStream, aParams, aDelayedStart, + aManager); +} + +void InputStreamHelper::PostSerializationActivation(InputStreamParams& aParams, + bool aConsumedByIPC, + bool aDelayedStart) { + switch (aParams.type()) { + case InputStreamParams::TBufferedInputStreamParams: { + BufferedInputStreamParams& params = + aParams.get_BufferedInputStreamParams(); + InputStreamHelper::PostSerializationActivation( + params.optionalStream(), aConsumedByIPC, aDelayedStart); + return; + } + + case InputStreamParams::TMIMEInputStreamParams: { + MIMEInputStreamParams& params = aParams.get_MIMEInputStreamParams(); + InputStreamHelper::PostSerializationActivation( + params.optionalStream(), aConsumedByIPC, aDelayedStart); + return; + } + + case InputStreamParams::TMultiplexInputStreamParams: { + MultiplexInputStreamParams& params = + aParams.get_MultiplexInputStreamParams(); + for (InputStreamParams& subParams : params.streams()) { + InputStreamHelper::PostSerializationActivation( + subParams, aConsumedByIPC, aDelayedStart); + } + return; + } + + case InputStreamParams::TSlicedInputStreamParams: { + SlicedInputStreamParams& params = aParams.get_SlicedInputStreamParams(); + InputStreamHelper::PostSerializationActivation( + params.stream(), aConsumedByIPC, aDelayedStart); + return; + } + + case InputStreamParams::TInputStreamLengthWrapperParams: { + InputStreamLengthWrapperParams& params = + aParams.get_InputStreamLengthWrapperParams(); + InputStreamHelper::PostSerializationActivation( + params.stream(), aConsumedByIPC, aDelayedStart); + return; + } + + case InputStreamParams::TIPCRemoteStreamParams: { + IPCRemoteStreamType& remoteInputStream = + aParams.get_IPCRemoteStreamParams().stream(); + + IPCStreamSource* source = nullptr; + if (remoteInputStream.type() == + IPCRemoteStreamType::TPChildToParentStreamChild) { + source = IPCStreamSource::Cast( + remoteInputStream.get_PChildToParentStreamChild()); + } else { + MOZ_ASSERT(remoteInputStream.type() == + IPCRemoteStreamType::TPParentToChildStreamParent); + source = IPCStreamSource::Cast( + remoteInputStream.get_PParentToChildStreamParent()); + } + + MOZ_ASSERT(source); + + // If the source stream has not been taken to be sent to the other side, + // we can destroy it. + if (!aConsumedByIPC) { + source->StartDestroy(); + return; + } + + if (!aDelayedStart) { + // If we don't need to do a delayedStart, we start it now. Otherwise, + // the Start() will be called at the first use by the + // IPCStreamDestination::DelayedStartInputStream. + source->Start(); + } + + return; + } + + case InputStreamParams::TStringInputStreamParams: + break; + + case InputStreamParams::TFileInputStreamParams: + break; + + case InputStreamParams::TRemoteLazyInputStreamParams: + break; + + case InputStreamParams::TEncryptedFileInputStreamParams: + break; + + default: + MOZ_CRASH( + "A new stream? Should decide if it must be processed recursively or " + "not."); + } +} + +void InputStreamHelper::PostSerializationActivation( + Maybe<InputStreamParams>& aParams, bool aConsumedByIPC, + bool aDelayedStart) { + if (aParams.isSome()) { + InputStreamHelper::PostSerializationActivation( + aParams.ref(), aConsumedByIPC, aDelayedStart); + } +} + +already_AddRefed<nsIInputStream> InputStreamHelper::DeserializeInputStream( + const InputStreamParams& aParams, + const nsTArray<FileDescriptor>& aFileDescriptors) { + if (aParams.type() == InputStreamParams::TRemoteLazyInputStreamParams) { + const RemoteLazyInputStreamParams& params = + aParams.get_RemoteLazyInputStreamParams(); + + // RemoteLazyInputStreamRefs are not deserializable on the parent side, + // because the parent is the only one that has a copy of the original stream + // in the RemoteLazyInputStreamStorage. + if (params.type() == + RemoteLazyInputStreamParams::TRemoteLazyInputStreamRef) { + MOZ_ASSERT(XRE_IsParentProcess()); + const RemoteLazyInputStreamRef& ref = + params.get_RemoteLazyInputStreamRef(); + + auto storage = RemoteLazyInputStreamStorage::Get().unwrapOr(nullptr); + MOZ_ASSERT(storage); + nsCOMPtr<nsIInputStream> stream; + storage->GetStream(ref.id(), ref.start(), ref.length(), + getter_AddRefs(stream)); + return stream.forget(); + } + + // parent -> child serializations receive an RemoteLazyInputStream actor. + MOZ_ASSERT(params.type() == + RemoteLazyInputStreamParams::TPRemoteLazyInputStreamChild); + RemoteLazyInputStreamChild* actor = + static_cast<RemoteLazyInputStreamChild*>( + params.get_PRemoteLazyInputStreamChild()); + nsCOMPtr<nsIInputStream> stream = actor->CreateStream(); + return stream.forget(); + } + + if (aParams.type() == InputStreamParams::TIPCRemoteStreamParams) { + const IPCRemoteStreamParams& remoteStream = + aParams.get_IPCRemoteStreamParams(); + const IPCRemoteStreamType& remoteStreamType = remoteStream.stream(); + IPCStreamDestination* destinationStream; + + if (remoteStreamType.type() == + IPCRemoteStreamType::TPChildToParentStreamParent) { + destinationStream = IPCStreamDestination::Cast( + remoteStreamType.get_PChildToParentStreamParent()); + } else { + MOZ_ASSERT(remoteStreamType.type() == + IPCRemoteStreamType::TPParentToChildStreamChild); + destinationStream = IPCStreamDestination::Cast( + remoteStreamType.get_PParentToChildStreamChild()); + } + + destinationStream->SetDelayedStart(remoteStream.delayedStart()); + destinationStream->SetLength(remoteStream.length()); + return destinationStream->TakeReader(); + } + + 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(nullptr, NS_GET_IID(nsIFileInputStream), + getter_AddRefs(stream)); + serializable = do_QueryInterface(stream); + } break; + + case InputStreamParams::TBufferedInputStreamParams: { + nsCOMPtr<nsIBufferedInputStream> stream; + nsBufferedInputStream::Create(nullptr, NS_GET_IID(nsIBufferedInputStream), + getter_AddRefs(stream)); + serializable = do_QueryInterface(stream); + } break; + + case InputStreamParams::TMIMEInputStreamParams: { + nsCOMPtr<nsIMIMEInputStream> stream; + nsMIMEInputStreamConstructor(nullptr, NS_GET_IID(nsIMIMEInputStream), + getter_AddRefs(stream)); + serializable = do_QueryInterface(stream); + } break; + + case InputStreamParams::TMultiplexInputStreamParams: { + nsCOMPtr<nsIMultiplexInputStream> stream; + nsMultiplexInputStreamConstructor( + nullptr, 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, aFileDescriptors)) { + 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..4fafa5bb1a --- /dev/null +++ b/ipc/glue/InputStreamUtils.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_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; +class PFileDescriptorSetChild; +class PFileDescriptorSetParent; +class PChildToParentStreamChild; +class PParentToChildStreamParent; + +// Provide two interfaces for sending PParentToChildStream and +// PFileDescriptorSet constructor messages. +class ParentToChildStreamActorManager { + public: + virtual PParentToChildStreamParent* SendPParentToChildStreamConstructor( + PParentToChildStreamParent* aActor) = 0; + virtual PFileDescriptorSetParent* SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) = 0; +}; + +class ChildToParentStreamActorManager { + public: + virtual PChildToParentStreamChild* SendPChildToParentStreamConstructor( + PChildToParentStreamChild* aActor) = 0; + virtual PFileDescriptorSetChild* SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) = 0; +}; + +// If you want to serialize an inputStream, please use AutoIPCStream. +class InputStreamHelper { + public: + // 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 + // IPCRemoteStream, and, its content will be sent to the other side of the IPC + // pipe in chunks. This sending can start immediatelly or at the first read + // based on the value of |aDelayedStart|. The IPC message size is returned + // into |aSizeUsed|. + static void SerializeInputStream(nsIInputStream* aInputStream, + InputStreamParams& aParams, + nsTArray<FileDescriptor>& aFileDescriptors, + bool aDelayedStart, uint32_t aMaxSize, + uint32_t* aSizeUsed, + ParentToChildStreamActorManager* aManager); + + static void SerializeInputStream(nsIInputStream* aInputStream, + InputStreamParams& aParams, + nsTArray<FileDescriptor>& aFileDescriptors, + bool aDelayedStart, uint32_t aMaxSize, + uint32_t* aSizeUsed, + ChildToParentStreamActorManager* aManager); + + // When a stream wants to serialize itself as IPCRemoteStream, it uses one of + // these methods. + static void SerializeInputStreamAsPipe( + nsIInputStream* aInputStream, InputStreamParams& aParams, + bool aDelayedStart, ParentToChildStreamActorManager* aManager); + + static void SerializeInputStreamAsPipe( + nsIInputStream* aInputStream, InputStreamParams& aParams, + bool aDelayedStart, ChildToParentStreamActorManager* aManager); + + // After the sending of the inputStream into the IPC pipe, some of the + // InputStreamParams data struct needs to be activated (IPCRemoteStream). + // These 2 methods do that. + static void PostSerializationActivation(InputStreamParams& aParams, + bool aConsumedByIPC, + bool aDelayedStart); + + static void PostSerializationActivation(Maybe<InputStreamParams>& aParams, + bool aConsumedByIPC, + bool aDelayedStart); + + static already_AddRefed<nsIInputStream> DeserializeInputStream( + const InputStreamParams& aParams, + const nsTArray<FileDescriptor>& aFileDescriptors); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_InputStreamUtils_h diff --git a/ipc/glue/LibrarySandboxPreload.cpp b/ipc/glue/LibrarySandboxPreload.cpp new file mode 100644 index 0000000000..a49b8458b3 --- /dev/null +++ b/ipc/glue/LibrarySandboxPreload.cpp @@ -0,0 +1,82 @@ +/* -*- 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 "LibrarySandboxPreload.h" + +#include "BinaryPath.h" +#include "prlink.h" + +namespace mozilla { +namespace ipc { + +static nsAutoCString GetSandboxedPath(const nsACString& libName) { + nsCOMPtr<nsIFile> binaryPath; + nsresult rv = mozilla::BinaryPath::GetFile(getter_AddRefs(binaryPath)); + if (NS_FAILED(rv)) { + MOZ_CRASH("Library preload failure: Failed to get binary file\n"); + } + + nsCOMPtr<nsIFile> libFile; + rv = binaryPath->GetParent(getter_AddRefs(libFile)); + if (NS_FAILED(rv)) { + MOZ_CRASH("Library preload failure: Failed to get binary folder\n"); + } + + rv = libFile->AppendNative(libName); + + if (NS_FAILED(rv)) { + MOZ_CRASH("Library preload failure: Failed to get library file\n"); + } + + nsAutoString fullPath; + rv = libFile->GetPath(fullPath); + if (NS_FAILED(rv)) { + MOZ_CRASH("Library preload failure: Failed to get library path\n"); + } + + nsAutoCString converted_path = NS_ConvertUTF16toUTF8(fullPath); + return converted_path; +} + +nsAutoCString GetSandboxedGraphitePath() { + return GetSandboxedPath( + nsLiteralCString(MOZ_DLL_PREFIX "graphitewasm" MOZ_DLL_SUFFIX)); +} + +nsAutoCString GetSandboxedOggPath() { + return GetSandboxedPath( + nsLiteralCString(MOZ_DLL_PREFIX "oggwasm" MOZ_DLL_SUFFIX)); +} + +PRLibrary* PreloadLibrary(const nsAutoCString& path) { + PRLibSpec libSpec; + libSpec.type = PR_LibSpec_Pathname; + libSpec.value.pathname = path.get(); + PRLibrary* ret = PR_LoadLibraryWithFlags(libSpec, PR_LD_LAZY); + return ret; +} + +void PreloadSandboxedDynamicLibraries() { + // The process level sandbox does not allow loading of dynamic libraries. + // This preloads wasm sandboxed libraries before the process level sandbox is + // enabled. Currently, this is only needed for Linux as Mac allows loading + // libraries from the package file. +#if defined(XP_LINUX) +# if defined(MOZ_WASM_SANDBOXING_GRAPHITE) + if (!PreloadLibrary(GetSandboxedGraphitePath())) { + MOZ_CRASH("Library preload failure: Failed to load libgraphite\n"); + } +# endif +# if defined(MOZ_WASM_SANDBOXING_OGG) + if (!PreloadLibrary(GetSandboxedOggPath())) { + MOZ_CRASH("Library preload failure: Failed to load libogg\n"); + } +# endif +#endif +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/LibrarySandboxPreload.h b/ipc/glue/LibrarySandboxPreload.h new file mode 100644 index 0000000000..0193f2f8a6 --- /dev/null +++ b/ipc/glue/LibrarySandboxPreload.h @@ -0,0 +1,18 @@ +/* -*- 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_LibrarySandboxPreload_h +#define ipc_glue_LibrarySandboxPreload_h + +namespace mozilla { +namespace ipc { +nsAutoCString GetSandboxedGraphitePath(); +nsAutoCString GetSandboxedOggPath(); +void PreloadSandboxedDynamicLibraries(); +} // namespace ipc +} // namespace mozilla +#endif diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp new file mode 100644 index 0000000000..7f90742b44 --- /dev/null +++ b/ipc/glue/MessageChannel.cpp @@ -0,0 +1,2951 @@ +/* -*- 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/Logging.h" +#include "mozilla/Mutex.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/ProcessChild.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "nsAppRunner.h" +#include "nsContentUtils.h" +#include "nsDataHashtable.h" +#include "nsDebug.h" +#include "nsExceptionHandler.h" +#include "nsIMemoryReporter.h" +#include "nsISupportsImpl.h" +#include "nsPrintfCString.h" + +#ifdef OS_WIN +# include "mozilla/gfx/Logging.h" +#endif + +#ifdef MOZ_TASK_TRACER +# include "GeckoTaskTracer.h" +using namespace mozilla::tasktracer; +#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 three kinds of messages: async, sync, and intr. Sync and intr + * 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). Intr messages cannot nest. 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 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. + * + * Intr messages are blocking and can nest, but they don't participate in the + * nesting levels. While waiting for an intr response, all incoming messages are + * dispatched until a response is received. When two intr messages race with + * each other, a similar scheme is used to ensure that one side wins. The + * winning side is chosen based on the message type. + * + * Intr messages differ from sync messages in that, while sending an intr + * message, we may dispatch an async message. This causes some additional + * complexity. One issue is that replies can be received out of order. It's also + * more difficult to determine whether one message is nested inside + * another. Consequently, intr handling uses mOutOfTurnReplies and + * mRemoteStackDepthGuess, which are not needed for sync messages. + */ + +using namespace mozilla; +using namespace mozilla::ipc; + +using mozilla::MonitorAutoLock; +using mozilla::MonitorAutoUnlock; +using mozilla::dom::AutoNoJSAPI; + +#define IPC_ASSERT(_cond, ...) \ + do { \ + if (!(_cond)) DebugAbort(__FILE__, __LINE__, #_cond, ##__VA_ARGS__); \ + } while (0) + +static MessageChannel* gParentProcessBlocker; + +namespace mozilla { +namespace ipc { + +static const uint32_t kMinTelemetryMessageSize = 4096; + +// Note: we round the time we spend to the nearest millisecond. So a min value +// of 1 ms actually captures from 500us and above. +static const uint32_t kMinTelemetryIPCWriteLatencyMs = 1; + +// 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; + +const int32_t MessageChannel::kNoTimeout = INT32_MIN; + +// static +bool MessageChannel::sIsPumpingMessages = false; + +enum Direction { IN_MESSAGE, OUT_MESSAGE }; + +class MessageChannel::InterruptFrame { + private: + enum Semantics { INTR_SEMS, SYNC_SEMS, ASYNC_SEMS }; + + public: + InterruptFrame(Direction direction, const Message* msg) + : mMessageName(msg->name()), + mMessageRoutingId(msg->routing_id()), + mMesageSemantics(msg->is_interrupt() ? INTR_SEMS + : msg->is_sync() ? SYNC_SEMS + : ASYNC_SEMS), + mDirection(direction), + mMoved(false) { + MOZ_RELEASE_ASSERT(mMessageName); + } + + InterruptFrame(InterruptFrame&& aOther) { + MOZ_RELEASE_ASSERT(aOther.mMessageName); + mMessageName = aOther.mMessageName; + aOther.mMessageName = nullptr; + mMoved = aOther.mMoved; + aOther.mMoved = true; + + mMessageRoutingId = aOther.mMessageRoutingId; + mMesageSemantics = aOther.mMesageSemantics; + mDirection = aOther.mDirection; + } + + ~InterruptFrame() { MOZ_RELEASE_ASSERT(mMessageName || mMoved); } + + InterruptFrame& operator=(InterruptFrame&& aOther) { + MOZ_RELEASE_ASSERT(&aOther != this); + this->~InterruptFrame(); + new (this) InterruptFrame(std::move(aOther)); + return *this; + } + + bool IsInterruptIncall() const { + return INTR_SEMS == mMesageSemantics && IN_MESSAGE == mDirection; + } + + bool IsInterruptOutcall() const { + return INTR_SEMS == mMesageSemantics && OUT_MESSAGE == mDirection; + } + + bool IsOutgoingSync() const { + return (mMesageSemantics == INTR_SEMS || mMesageSemantics == SYNC_SEMS) && + mDirection == OUT_MESSAGE; + } + + void Describe(int32_t* id, const char** dir, const char** sems, + const char** name) const { + *id = mMessageRoutingId; + *dir = (IN_MESSAGE == mDirection) ? "in" : "out"; + *sems = (INTR_SEMS == mMesageSemantics) ? "intr" + : (SYNC_SEMS == mMesageSemantics) ? "sync" + : "async"; + *name = mMessageName; + } + + int32_t GetRoutingId() const { return mMessageRoutingId; } + + private: + const char* mMessageName; + int32_t mMessageRoutingId; + Semantics mMesageSemantics; + Direction mDirection; + bool mMoved; + + // Disable harmful methods. + InterruptFrame(const InterruptFrame& aOther) = delete; + InterruptFrame& operator=(const InterruptFrame&) = delete; +}; + +class MOZ_STACK_CLASS MessageChannel::CxxStackFrame { + public: + CxxStackFrame(MessageChannel& that, Direction direction, const Message* msg) + : mThat(that) { + mThat.AssertWorkerThread(); + + if (mThat.mCxxStackFrames.empty()) mThat.EnteredCxxStack(); + + if (!mThat.mCxxStackFrames.append(InterruptFrame(direction, msg))) + MOZ_CRASH(); + + const InterruptFrame& frame = mThat.mCxxStackFrames.back(); + + if (frame.IsInterruptIncall()) mThat.EnteredCall(); + + if (frame.IsOutgoingSync()) mThat.EnteredSyncSend(); + + mThat.mSawInterruptOutMsg |= frame.IsInterruptOutcall(); + } + + ~CxxStackFrame() { + mThat.AssertWorkerThread(); + + MOZ_RELEASE_ASSERT(!mThat.mCxxStackFrames.empty()); + + const InterruptFrame& frame = mThat.mCxxStackFrames.back(); + bool exitingSync = frame.IsOutgoingSync(); + bool exitingCall = frame.IsInterruptIncall(); + mThat.mCxxStackFrames.shrinkBy(1); + + bool exitingStack = mThat.mCxxStackFrames.empty(); + + // According how lifetime is declared, mListener on MessageChannel + // lives longer than MessageChannel itself. Hence is expected to + // be alive. There is nothing to even assert here, there is no place + // we would be nullifying mListener on MessageChannel. + + if (exitingCall) mThat.ExitedCall(); + + if (exitingSync) mThat.ExitedSyncSend(); + + if (exitingStack) mThat.ExitedCxxStack(); + } + + private: + MessageChannel& mThat; + + // Disable harmful methods. + CxxStackFrame() = delete; + CxxStackFrame(const CxxStackFrame&) = delete; + CxxStackFrame& operator=(const CxxStackFrame&) = delete; +}; + +class AutoEnterTransaction { + public: + explicit AutoEnterTransaction(MessageChannel* aChan, int32_t aMsgSeqno, + int32_t aTransactionID, int aNestedLevel) + : 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) + : 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() { + 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(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 = MakeUnique<IPC::Message>(std::move(aMessage)); + MOZ_RELEASE_ASSERT(IsComplete()); + } + + void HandleReply(IPC::Message&& aMessage) { + 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 = nsDataHashtable<nsDepCharHashKey, ChannelCounts>; + + static StaticMutex sChannelCountMutex; + static CountTable* sChannelCounts; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + StaticMutexAutoLock countLock(sChannelCountMutex); + if (!sChannelCounts) { + return NS_OK; + } + for (auto iter = sChannelCounts->Iter(); !iter.Done(); iter.Next()) { + nsPrintfCString pathNow("ipc-channels/%s", iter.Key()); + nsPrintfCString pathMax("ipc-channels-peak/%s", iter.Key()); + nsPrintfCString descNow( + "Number of IPC channels for" + " top-level actor type %s", + iter.Key()); + nsPrintfCString descMax( + "Peak number of IPC channels for" + " top-level actor type %s", + iter.Key()); + + aHandleReport->Callback(""_ns, pathNow, KIND_OTHER, UNITS_COUNT, + iter.Data().mNow, descNow, aData); + aHandleReport->Callback(""_ns, pathMax, KIND_OTHER, UNITS_COUNT, + iter.Data().mMax, descMax, aData); + } + return NS_OK; + } + + static void Increment(const char* aName) { + StaticMutexAutoLock countLock(sChannelCountMutex); + if (!sChannelCounts) { + sChannelCounts = new CountTable; + } + sChannelCounts->GetOrInsert(aName).Inc(); + } + + static void Decrement(const char* aName) { + StaticMutexAutoLock countLock(sChannelCountMutex); + MOZ_ASSERT(sChannelCounts); + sChannelCounts->GetOrInsert(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), + mChannelState(ChannelClosed), + mSide(UnknownSide), + mIsCrossProcess(false), + mChannelErrorTask(nullptr), + mTimeoutMs(kNoTimeout), + mInTimeoutSecondHalf(false), + mNextSeqno(0), + mLastSendError(SyncSendError::SendSuccess), + mDispatchingAsyncMessage(false), + mDispatchingAsyncMessageNestedLevel(0), + mTransactionStack(nullptr), + mTimedOutMessageSeqno(0), + mTimedOutMessageNestedLevel(0), + mMaybeDeferredPendingCount(0), + mRemoteStackDepthGuess(0), + mSawInterruptOutMsg(false), + mIsWaitingForIncoming(false), + mAbortOnError(false), + mNotifiedChannelDone(false), + mFlags(REQUIRE_DEFAULT), + mPeerPidSet(false), + mPeerPid(-1), + mIsPostponingSends(false), + mBuildIDsConfirmedMatch(false), + mIsSameThreadChannel(false) { + MOZ_COUNT_CTOR(ipc::MessageChannel); + +#ifdef OS_WIN + mTopFrame = nullptr; + mIsSyncWaitingOnNonMainThread = false; +#endif + + mOnChannelConnectedTask = NewNonOwningCancelableRunnableMethod( + "ipc::MessageChannel::DispatchOnChannelConnected", this, + &MessageChannel::DispatchOnChannelConnected); + +#ifdef OS_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); + IPC_ASSERT(mCxxStackFrames.empty(), "mismatched CxxStackFrame ctor/dtors"); +#ifdef OS_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 + Clear(); +} + +#ifdef DEBUG +void MessageChannel::AssertMaybeDeferredCountCorrect() { + size_t count = 0; + for (MessageTask* task : mPending) { + 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) { + const char* from = (side == ChildSide) + ? "Child" + : ((side == ParentSide) ? "Parent" : "Unknown"); + printf_stderr("\n###!!! [%s][%s] Error: %s\n\n", from, channelName, msg); +} + +bool MessageChannel::Connected() const { + mMonitor->AssertCurrentThreadOwns(); + + // The transport layer allows us to send messages before + // receiving the "connected" ack from the remote side. + return (ChannelOpening == mChannelState || ChannelConnected == mChannelState); +} + +bool MessageChannel::CanSend() const { + if (!mMonitor) { + return false; + } + MonitorAutoLock lock(*mMonitor); + return Connected(); +} + +void MessageChannel::Clear() { + // Don't clear mWorkerThread; we use it in AssertLinkThread() and + // 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. + // + // In practice, mListener owns the channel, so the channel gets deleted + // before mListener. But just to be safe, mListener is a weak pointer. + +#if !defined(ANDROID) + if (!Unsound_IsClosed()) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCFatalErrorProtocol, + nsDependentCString(mName)); + switch (mChannelState) { + case ChannelOpening: + MOZ_CRASH( + "MessageChannel destroyed without being closed " + "(mChannelState == ChannelOpening)."); + break; + case ChannelConnected: + MOZ_CRASH( + "MessageChannel destroyed without being closed " + "(mChannelState == ChannelConnected)."); + break; + case ChannelTimeout: + MOZ_CRASH( + "MessageChannel destroyed without being closed " + "(mChannelState == ChannelTimeout)."); + 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."); + } + } +#endif + + if (gParentProcessBlocker == this) { + gParentProcessBlocker = nullptr; + } + + gUnresolvedResponses -= mPendingResponses.size(); + for (auto& pair : mPendingResponses) { + pair.second.get()->Reject(ResponseRejectReason::ChannelClosed); + } + mPendingResponses.clear(); + + if (mLink != nullptr && mIsCrossProcess) { + ChannelCountReporter::Decrement(mName); + } + + if (mLink) { + mLink->PrepareToDestroy(); + mLink = nullptr; + } + + mOnChannelConnectedTask->Cancel(); + + if (mChannelErrorTask) { + mChannelErrorTask->Cancel(); + mChannelErrorTask = nullptr; + } + + // Free up any memory used by pending messages. + for (MessageTask* task : mPending) { + task->Clear(); + } + mPending.clear(); + + mMaybeDeferredPendingCount = 0; + + mOutOfTurnReplies.clear(); + while (!mDeferred.empty()) { + mDeferred.pop(); + } +} + +bool MessageChannel::Open(mozilla::UniquePtr<Transport> aTransport, + MessageLoop* aIOLoop, Side aSide) { + MOZ_ASSERT(!mLink, "Open() called > once"); + + mMonitor = new RefCountedMonitor(); + mWorkerThread = GetCurrentSerialEventTarget(); + MOZ_ASSERT(mWorkerThread, "We should always be on a nsISerialEventTarget"); + mListener->OnIPCChannelOpened(); + + auto link = MakeUnique<ProcessLink>(this); + link->Open(std::move(aTransport), aIOLoop, + aSide); // :TODO: n.b.: sets mChild + mLink = std::move(link); + mIsCrossProcess = true; + ChannelCountReporter::Increment(mName); + return true; +} + +bool MessageChannel::Open(MessageChannel* aTargetChan, + nsISerialEventTarget* aEventTarget, Side aSide) { + // Opens a connection to another thread in the same process. + + // This handshake proceeds as follows: + // - Let A be the thread initiating the process (either child or parent) + // and B be the other thread. + // - A spawns thread for B, obtaining B's message loop + // - A creates ProtocolChild and ProtocolParent instances. + // Let PA be the one appropriate to A and PB the side for B. + // - A invokes PA->Open(PB, ...): + // - set state to mChannelOpening + // - this will place a work item in B's worker loop (see next bullet) + // and then spins until PB->mChannelState becomes mChannelConnected + // - meanwhile, on PB's worker loop, the work item is removed and: + // - invokes PB->OpenAsOtherThread(PA, ...): + // - sets its state and that of PA to Connected + MOZ_ASSERT(aTargetChan, "Need a target channel"); + MOZ_ASSERT(ChannelClosed == mChannelState, "Not currently closed"); + + CommonThreadOpenInit(aTargetChan, GetCurrentSerialEventTarget(), aSide); + + Side oppSide = UnknownSide; + switch (aSide) { + case ChildSide: + oppSide = ParentSide; + break; + case ParentSide: + oppSide = ChildSide; + break; + case UnknownSide: + break; + } + + mMonitor = new RefCountedMonitor(); + + MonitorAutoLock lock(*mMonitor); + mChannelState = ChannelOpening; + MOZ_ALWAYS_SUCCEEDS(aEventTarget->Dispatch( + NewNonOwningRunnableMethod<MessageChannel*, nsISerialEventTarget*, Side>( + "ipc::MessageChannel::OpenAsOtherThread", aTargetChan, + &MessageChannel::OpenAsOtherThread, this, aEventTarget, oppSide))); + + while (ChannelOpening == mChannelState) mMonitor->Wait(); + MOZ_RELEASE_ASSERT(ChannelConnected == mChannelState, + "not connected when awoken"); + return (ChannelConnected == mChannelState); +} + +void MessageChannel::OpenAsOtherThread(MessageChannel* aTargetChan, + nsISerialEventTarget* aThread, + Side aSide) { + // Invoked when the other side has begun the open. + MOZ_ASSERT(ChannelClosed == mChannelState, "Not currently closed"); + MOZ_ASSERT(ChannelOpening == aTargetChan->mChannelState, + "Target channel not in the process of opening"); + + CommonThreadOpenInit(aTargetChan, aThread, aSide); + mMonitor = aTargetChan->mMonitor; + + MonitorAutoLock lock(*mMonitor); + MOZ_RELEASE_ASSERT(ChannelOpening == aTargetChan->mChannelState, + "Target channel not in the process of opening"); + mChannelState = ChannelConnected; + aTargetChan->mChannelState = ChannelConnected; + aTargetChan->mMonitor->Notify(); +} + +void MessageChannel::CommonThreadOpenInit(MessageChannel* aTargetChan, + nsISerialEventTarget* aThread, + Side aSide) { + MOZ_ASSERT(aThread); + mWorkerThread = aThread; + mListener->OnIPCChannelOpened(); + + mLink = MakeUnique<ThreadLink>(this, aTargetChan); + mSide = aSide; +} + +bool MessageChannel::OpenOnSameThread(MessageChannel* aTargetChan, + mozilla::ipc::Side aSide) { + nsCOMPtr<nsISerialEventTarget> currentThread = GetCurrentSerialEventTarget(); + CommonThreadOpenInit(aTargetChan, currentThread, aSide); + + Side oppSide = UnknownSide; + switch (aSide) { + case ChildSide: + oppSide = ParentSide; + break; + case ParentSide: + oppSide = ChildSide; + break; + case UnknownSide: + break; + } + mIsSameThreadChannel = true; + + // XXX(nika): Avoid setting up a monitor for same thread channels? We + // shouldn't need it. + mMonitor = new RefCountedMonitor(); + + mChannelState = ChannelOpening; + aTargetChan->CommonThreadOpenInit(this, currentThread, oppSide); + + aTargetChan->mIsSameThreadChannel = true; + aTargetChan->mMonitor = mMonitor; + + mChannelState = ChannelConnected; + aTargetChan->mChannelState = ChannelConnected; + return true; +} + +bool MessageChannel::Send(UniquePtr<Message> aMsg) { + if (aMsg->size() >= kMinTelemetryMessageSize) { + Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE2, aMsg->size()); + } + + // If the message was created by the IPC bindings, the create time will be + // recorded. Use this information to report the + // IPC_WRITE_MAIN_THREAD_LATENCY_MS (time from message creation to it being + // sent). + if (NS_IsMainThread() && aMsg->create_time()) { + uint32_t latencyMs = round( + (mozilla::TimeStamp::Now() - aMsg->create_time()).ToMilliseconds()); + if (latencyMs >= kMinTelemetryIPCWriteLatencyMs) { + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::IPC_WRITE_MAIN_THREAD_LATENCY_MS, + nsDependentCString(aMsg->name()), latencyMs); + } + } + + MOZ_RELEASE_ASSERT(!aMsg->is_sync()); + MOZ_RELEASE_ASSERT(aMsg->nested_level() != IPC::Message::NESTED_INSIDE_SYNC); + + CxxStackFrame frame(*this, OUT_MESSAGE, aMsg.get()); + + 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("MessageChannel", aMsg.get()); + return false; + } + + AddProfilerMarker(*aMsg, MessageDirection::eSending); + SendMessageToLink(std::move(aMsg)); + return true; +} + +void MessageChannel::SendMessageToLink(UniquePtr<Message> aMsg) { + if (mIsPostponingSends) { + mPostponedSends.push_back(std::move(aMsg)); + return; + } + mLink->SendMessage(std::move(aMsg)); +} + +void MessageChannel::BeginPostponingSends() { + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + + MonitorAutoLock lock(*mMonitor); + { + MOZ_ASSERT(!mIsPostponingSends); + mIsPostponingSends = true; + } +} + +void MessageChannel::StopPostponingSends() { + // Note: this can be called from any thread. + MonitorAutoLock lock(*mMonitor); + + MOZ_ASSERT(mIsPostponingSends); + + for (UniquePtr<Message>& iter : mPostponedSends) { + mLink->SendMessage(std::move(iter)); + } + + // We unset this after SendMessage so we can make correct thread + // assertions in MessageLink. + mIsPostponingSends = false; + mPostponedSends.clear(); +} + +UniquePtr<MessageChannel::UntypedCallbackHolder> MessageChannel::PopCallback( + const Message& aMsg) { + auto iter = mPendingResponses.find(aMsg.seqno()); + if (iter != mPendingResponses.end()) { + UniquePtr<MessageChannel::UntypedCallbackHolder> ret = + std::move(iter->second); + mPendingResponses.erase(iter); + gUnresolvedResponses--; + return ret; + } + return nullptr; +} + +void MessageChannel::RejectPendingResponsesForActor(ActorIdType 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("MessageChannel", msg.get()); + return false; + } + mLink->SendMessage(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) { + AssertLinkThread(); + mMonitor->AssertCurrentThreadOwns(); + + if (MSG_ROUTING_NONE == aMsg.routing_id()) { + if (GOODBYE_MESSAGE_TYPE == aMsg.type()) { + // :TODO: Sort out Close() on this side racing with Close() on the + // other side + mChannelState = ChannelClosing; + if (LoggingEnabled()) { + printf("NOTE: %s process received `Goodbye', closing down\n", + (mSide == ChildSide) ? "child" : "parent"); + } + 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::NotifyImpendingShutdown(); + 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(Message&& aMsg) { + AssertLinkThread(); + mMonitor->AssertCurrentThreadOwns(); + + if (MaybeInterceptSpecialIOMessage(aMsg)) return; + + mListener->OnChannelReceivedMessage(aMsg); + + // Regardless of the Interrupt stack, 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); + + bool reuseTask = false; + if (aMsg.compress_type() == IPC::Message::COMPRESSION_ENABLED) { + bool compress = + (!mPending.isEmpty() && + mPending.getLast()->Msg().type() == aMsg.type() && + mPending.getLast()->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(mPending.getLast()->Msg().compress_type() == + IPC::Message::COMPRESSION_ENABLED); + mPending.getLast()->Msg() = std::move(aMsg); + + reuseTask = true; + } + } else if (aMsg.compress_type() == IPC::Message::COMPRESSION_ALL && + !mPending.isEmpty()) { + for (MessageTask* p = mPending.getLast(); p; p = p->getPrevious()) { + 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 wakeUpSyncSend = AwaitingSyncReply() && !ShouldDeferMessage(aMsg); + + bool shouldWakeUp = + AwaitingInterruptReply() || wakeUpSyncSend || AwaitingIncomingMessage(); + + // 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. + bool shouldPostTask = !shouldWakeUp || wakeUpSyncSend; + + IPC_LOG("Receive on link thread; seqno=%d, xid=%d, shouldWakeUp=%d", + aMsg.seqno(), aMsg.transaction_id(), shouldWakeUp); + + if (reuseTask) { + return; + } + + // There are three cases we're concerned about, relating to the state of the + // main thread: + // + // (1) We are waiting on a sync reply - main thread is blocked on the + // IPC monitor. + // - If the message is NESTED_INSIDE_SYNC, we wake up the main thread to + // deliver the message depending on ShouldDeferMessage. Otherwise, we + // leave it in the mPending queue, posting a task to the main event + // loop, where it will be processed once the synchronous reply has been + // received. + // + // (2) We are waiting on an Interrupt reply - main thread is blocked on the + // IPC monitor. + // - Always notify and wake up the main thread. + // + // (3) We are not waiting on a reply. + // - We post a task to the main event loop. + // + // Note that, we may notify the main thread even though the monitor is not + // blocked. This is okay, since we always check for pending events before + // blocking again. + +#ifdef MOZ_TASK_TRACER + aMsg.TaskTracerDispatch(); +#endif + RefPtr<MessageTask> task = new MessageTask(this, std::move(aMsg)); + mPending.insertBack(task); + + if (!alwaysDeferred) { + mMaybeDeferredPendingCount++; + } + + if (shouldWakeUp) { + NotifyWorkerThread(); + } + + if (shouldPostTask) { + 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) { + const Message& msg = it->Msg(); + if (!aInvoke(msg)) { + break; + } + } +} + +void MessageChannel::ProcessPendingRequests( + 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; + } + + mozilla::Vector<Message> toProcess; + + for (MessageTask* p = mPending.getFirst(); p;) { + 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 it = toProcess.begin(); it != toProcess.end(); it++) { + ProcessPendingRequest(std::move(*it)); + } + } + + AssertMaybeDeferredCountCorrect(); +} + +bool MessageChannel::Send(UniquePtr<Message> aMsg, 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!"); + +#ifdef OS_WIN + SyncStackFrame frame(this, false); + NeuteredWindowRegion neuteredRgn(mFlags & + REQUIRE_DEFERRED_MESSAGE_PROTECTION); +#endif +#ifdef MOZ_TASK_TRACER + AutoScopedLabel autolabel("sync message %s", aMsg->name()); +#endif + + CxxStackFrame f(*this, OUT_MESSAGE, aMsg.get()); + + 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()); + MOZ_RELEASE_ASSERT(!mIsPostponingSends); + IPC_LOG("Cancel from Send"); + auto cancel = + MakeUnique<CancelMessage>(CurrentNestedInsideSyncTransaction()); + CancelTransaction(CurrentNestedInsideSyncTransaction()); + mLink->SendMessage(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("MessageChannel::SendAndWait", aMsg.get()); + 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); + + bool handleWindowsMessages = mListener->HandleWindowsMessages(*aMsg.get()); + AutoEnterTransaction transact(this, seqno, transaction, nestedLevel); + + IPC_LOG("Send seqno=%d, xid=%d", seqno, transaction); + + // aMsg will be destroyed soon, but name() is not owned by aMsg. + const char* msgName = aMsg->name(); + + AddProfilerMarker(*aMsg, MessageDirection::eSending); + SendMessageToLink(std::move(aMsg)); + + while (true) { + MOZ_RELEASE_ASSERT(!transact.IsCanceled()); + ProcessPendingRequests(transact); + if (transact.IsComplete()) { + break; + } + if (!Connected()) { + ReportConnectionError("MessageChannel::Send"); + mLastSendError = SyncSendError::DisconnectedDuringSend; + return false; + } + + MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno); + MOZ_RELEASE_ASSERT(!transact.IsComplete()); + MOZ_RELEASE_ASSERT(mTransactionStack == &transact); + + bool maybeTimedOut = !WaitForSyncNotify(handleWindowsMessages); + + if (mListener->NeedArtificialSleep()) { + MonitorAutoUnlock unlock(*mMonitor); + mListener->ArtificialSleep(); + } + + if (!Connected()) { + ReportConnectionError("MessageChannel::SendAndWait"); + 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); + + *aReply = std::move(*reply); + if (aReply->size() >= kMinTelemetryMessageSize) { + Telemetry::Accumulate(Telemetry::IPC_REPLY_SIZE, + nsDependentCString(msgName), aReply->size()); + } + + // 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::Call(UniquePtr<Message> aMsg, Message* aReply) { + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + MOZ_RELEASE_ASSERT(!mIsSameThreadChannel, + "intr call send over same-thread channel will deadlock!"); + +#ifdef OS_WIN + SyncStackFrame frame(this, true); +#endif +#ifdef MOZ_TASK_TRACER + AutoScopedLabel autolabel("sync message %s", aMsg->name()); +#endif + + // This must come before MonitorAutoLock, as its destructor acquires the + // monitor lock. + CxxStackFrame cxxframe(*this, OUT_MESSAGE, aMsg.get()); + + MonitorAutoLock lock(*mMonitor); + if (!Connected()) { + ReportConnectionError("MessageChannel::Call", aMsg.get()); + return false; + } + + // Sanity checks. + IPC_ASSERT(!AwaitingSyncReply(), + "cannot issue Interrupt call while blocked on sync request"); + IPC_ASSERT(!DispatchingSyncMessage(), "violation of sync handler invariant"); + IPC_ASSERT(aMsg->is_interrupt(), "can only Call() Interrupt messages here"); + IPC_ASSERT(!mIsPostponingSends, "not postponing sends"); + + aMsg->set_seqno(NextSeqno()); + aMsg->set_interrupt_remote_stack_depth_guess(mRemoteStackDepthGuess); + aMsg->set_interrupt_local_stack_depth(1 + InterruptStackDepth()); + mInterruptStack.push(MessageInfo(*aMsg)); + + AddProfilerMarker(*aMsg, MessageDirection::eSending); + + mLink->SendMessage(std::move(aMsg)); + + while (true) { + // if a handler invoked by *Dispatch*() spun a nested event + // loop, and the connection was broken during that loop, we + // might have already processed the OnError event. if so, + // trying another loop iteration will be futile because + // channel state will have been cleared + if (!Connected()) { + ReportConnectionError("MessageChannel::Call"); + return false; + } + +#ifdef OS_WIN + // We need to limit the scoped of neuteredRgn to this spot in the code. + // Window neutering can't be enabled during some plugin calls because + // we then risk the neutered window procedure being subclassed by a + // plugin. + { + NeuteredWindowRegion neuteredRgn(mFlags & + REQUIRE_DEFERRED_MESSAGE_PROTECTION); + /* We should pump messages at this point to ensure that the IPC + peer does not become deadlocked on a pending inter-thread + SendMessage() */ + neuteredRgn.PumpOnce(); + } +#endif + + // Now might be the time to process a message deferred because of race + // resolution. + MaybeUndeferIncall(); + + // Wait for an event to occur. + while (!InterruptEventOccurred()) { + bool maybeTimedOut = !WaitForInterruptNotify(); + + // We might have received a "subtly deferred" message in a nested + // loop that it's now time to process. + if (InterruptEventOccurred() || + (!maybeTimedOut && + (!mDeferred.empty() || !mOutOfTurnReplies.empty()))) { + break; + } + + if (maybeTimedOut && !ShouldContinueFromTimeout()) return false; + } + + Message recvd; + MessageMap::iterator it; + + if ((it = mOutOfTurnReplies.find(mInterruptStack.top().seqno())) != + mOutOfTurnReplies.end()) { + recvd = std::move(it->second); + mOutOfTurnReplies.erase(it); + } else if (!mPending.isEmpty()) { + RefPtr<MessageTask> task = mPending.popFirst(); + recvd = std::move(task->Msg()); + if (!IsAlwaysDeferred(recvd)) { + mMaybeDeferredPendingCount--; + } + } else { + // because of subtleties with nested event loops, it's possible + // that we got here and nothing happened. or, we might have a + // deferred in-call that needs to be processed. either way, we + // won't break the inner while loop again until something new + // happens. + continue; + } + + // If the message is not Interrupt, we can dispatch it as normal. + if (!recvd.is_interrupt()) { + DispatchMessage(std::move(recvd)); + if (!Connected()) { + ReportConnectionError("MessageChannel::DispatchMessage"); + return false; + } + continue; + } + + // If the message is an Interrupt reply, either process it as a reply to our + // call, or add it to the list of out-of-turn replies we've received. + if (recvd.is_reply()) { + IPC_ASSERT(!mInterruptStack.empty(), "invalid Interrupt stack"); + + // If this is not a reply the call we've initiated, add it to our + // out-of-turn replies and keep polling for events. + { + const MessageInfo& outcall = mInterruptStack.top(); + + // Note, In the parent, sequence numbers increase from 0, and + // in the child, they decrease from 0. + if ((mSide == ChildSide && recvd.seqno() > outcall.seqno()) || + (mSide != ChildSide && recvd.seqno() < outcall.seqno())) { + mOutOfTurnReplies[recvd.seqno()] = std::move(recvd); + continue; + } + + IPC_ASSERT( + recvd.is_reply_error() || (recvd.type() == (outcall.type() + 1) && + recvd.seqno() == outcall.seqno()), + "somebody's misbehavin'", true); + } + + // We received a reply to our most recent outstanding call. Pop + // this frame and return the reply. + mInterruptStack.pop(); + + AddProfilerMarker(recvd, MessageDirection::eReceiving); + + bool is_reply_error = recvd.is_reply_error(); + if (!is_reply_error) { + *aReply = std::move(recvd); + } + + // If we have no more pending out calls waiting on replies, then + // the reply queue should be empty. + IPC_ASSERT(!mInterruptStack.empty() || mOutOfTurnReplies.empty(), + "still have pending replies with no pending out-calls", true); + + return !is_reply_error; + } + + // Dispatch an Interrupt in-call. Snapshot the current stack depth while we + // own the monitor. + size_t stackDepth = InterruptStackDepth(); + { +#ifdef MOZ_TASK_TRACER + Message::AutoTaskTracerRun tasktracerRun(recvd); +#endif + MonitorAutoUnlock unlock(*mMonitor); + + CxxStackFrame frame(*this, IN_MESSAGE, &recvd); + RefPtr<ActorLifecycleProxy> listenerProxy = + mListener->GetLifecycleProxy(); + DispatchInterruptMessage(listenerProxy, std::move(recvd), stackDepth); + } + if (!Connected()) { + ReportConnectionError("MessageChannel::DispatchInterruptMessage"); + return false; + } + } + + return true; +} + +bool MessageChannel::WaitForIncomingMessage() { +#ifdef OS_WIN + SyncStackFrame frame(this, true); + NeuteredWindowRegion neuteredRgn(mFlags & + REQUIRE_DEFERRED_MESSAGE_PROTECTION); +#endif + + MonitorAutoLock lock(*mMonitor); + AutoEnterWaitForIncoming waitingForIncoming(*this); + if (mChannelState != ChannelConnected) { + return false; + } + if (!HasPendingEvents()) { + return WaitForInterruptNotify(); + } + + MOZ_RELEASE_ASSERT(!mPending.isEmpty()); + RefPtr<MessageTask> task = mPending.getFirst(); + RunMessage(*task); + return true; +} + +bool MessageChannel::HasPendingEvents() { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + return Connected() && !mPending.isEmpty(); +} + +bool MessageChannel::InterruptEventOccurred() { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + IPC_ASSERT(InterruptStackDepth() > 0, "not in wait loop"); + + return (!Connected() || !mPending.isEmpty() || + (!mOutOfTurnReplies.empty() && + mOutOfTurnReplies.find(mInterruptStack.top().seqno()) != + mOutOfTurnReplies.end())); +} + +bool MessageChannel::ProcessPendingRequest(Message&& aUrgent) { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + IPC_LOG("Process pending: seqno=%d, xid=%d", aUrgent.seqno(), + aUrgent.transaction_id()); + + DispatchMessage(std::move(aUrgent)); + if (!Connected()) { + ReportConnectionError("MessageChannel::ProcessPendingRequest"); + 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(MessageTask& aTask) { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + Message& msg = aTask.Msg(); + + if (!Connected()) { + ReportConnectionError("RunMessage"); + return; + } + + // Check that we're going to run the first message that's valid to run. +#if 0 +# ifdef DEBUG + nsCOMPtr<nsIEventTarget> messageTarget = + mListener->GetMessageEventTarget(msg); + + for (MessageTask* task : mPending) { + if (task == &aTask) { + break; + } + + nsCOMPtr<nsIEventTarget> taskTarget = + mListener->GetMessageEventTarget(task->Msg()); + + MOZ_ASSERT(!ShouldRunMessage(task->Msg()) || + taskTarget != messageTarget || + aTask.Msg().priority() != task->Msg().priority()); + + } +# endif +#endif + + if (!mDeferred.empty()) { + MaybeUndeferIncall(); + } + + if (!ShouldRunMessage(msg)) { + return; + } + + MOZ_RELEASE_ASSERT(aTask.isInList()); + aTask.remove(); + + if (!IsAlwaysDeferred(msg)) { + mMaybeDeferredPendingCount--; + } + + if (IsOnCxxStack() && msg.is_interrupt() && msg.is_reply()) { + // We probably just received a reply in a nested loop for an + // Interrupt call sent before entering that loop. + mOutOfTurnReplies[msg.seqno()] = std::move(msg); + return; + } + + DispatchMessage(std::move(msg)); +} + +NS_IMPL_ISUPPORTS_INHERITED(MessageChannel::MessageTask, CancelableRunnable, + nsIRunnablePriority, nsIRunnableIPCMessageType) + +MessageChannel::MessageTask::MessageTask(MessageChannel* aChannel, + Message&& aMessage) + : CancelableRunnable(aMessage.name()), + mChannel(aChannel), + mMessage(std::move(aMessage)), + mScheduled(false) {} + +nsresult MessageChannel::MessageTask::Run() { + if (!mChannel) { + return NS_OK; + } + + mChannel->AssertWorkerThread(); + mChannel->mMonitor->AssertNotCurrentThreadOwns(); + + MonitorAutoLock lock(*mChannel->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; + } + + mChannel->RunMessage(*this); + return NS_OK; +} + +// Warning: This method removes the receiver from whatever list it might be in. +nsresult MessageChannel::MessageTask::Cancel() { + if (!mChannel) { + return NS_OK; + } + + mChannel->AssertWorkerThread(); + mChannel->mMonitor->AssertNotCurrentThreadOwns(); + + MonitorAutoLock lock(*mChannel->mMonitor); + + if (!isInList()) { + return NS_OK; + } + remove(); + + if (!IsAlwaysDeferred(Msg())) { + mChannel->mMaybeDeferredPendingCount--; + } + + return NS_OK; +} + +void MessageChannel::MessageTask::Post() { + MOZ_RELEASE_ASSERT(!mScheduled); + MOZ_RELEASE_ASSERT(isInList()); + + mScheduled = true; + + RefPtr<MessageTask> self = this; + nsCOMPtr<nsISerialEventTarget> eventTarget = + mChannel->mListener->GetMessageEventTarget(mMessage); + + if (eventTarget) { + eventTarget->Dispatch(self.forget(), NS_DISPATCH_NORMAL); + } else { + mChannel->mWorkerThread->Dispatch(self.forget()); + } +} + +void MessageChannel::MessageTask::Clear() { + mChannel->AssertWorkerThread(); + + mChannel = nullptr; +} + +NS_IMETHODIMP +MessageChannel::MessageTask::GetPriority(uint32_t* aPriority) { + switch (mMessage.priority()) { + case Message::NORMAL_PRIORITY: + *aPriority = PRIORITY_NORMAL; + break; + case Message::INPUT_PRIORITY: + *aPriority = PRIORITY_INPUT_HIGH; + break; + case Message::HIGH_PRIORITY: + *aPriority = PRIORITY_HIGH; + break; + case Message::MEDIUMHIGH_PRIORITY: + *aPriority = PRIORITY_MEDIUMHIGH; + break; + default: + MOZ_ASSERT(false); + break; + } + return NS_OK; +} + +NS_IMETHODIMP +MessageChannel::MessageTask::GetType(uint32_t* aType) { + if (!Msg().is_valid()) { + // If mMessage has been moved already elsewhere, we can't know what the type + // has been. + return NS_ERROR_FAILURE; + } + + *aType = Msg().type(); + return NS_OK; +} + +void MessageChannel::DispatchMessage(Message&& aMsg) { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + RefPtr<ActorLifecycleProxy> listenerProxy = mListener->GetLifecycleProxy(); + + Maybe<AutoNoJSAPI> nojsapi; + if (NS_IsMainThread() && CycleCollectedJSContext::Get()) { + nojsapi.emplace(); + } + + UniquePtr<Message> reply; + + 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()); + + { +#ifdef MOZ_TASK_TRACER + Message::AutoTaskTracerRun tasktracerRun(aMsg); +#endif + MonitorAutoUnlock unlock(*mMonitor); + CxxStackFrame frame(*this, IN_MESSAGE, &aMsg); + + mListener->ArtificialSleep(); + + if (aMsg.is_sync()) { + DispatchSyncMessage(listenerProxy, aMsg, *getter_Transfers(reply)); + } else if (aMsg.is_interrupt()) { + DispatchInterruptMessage(listenerProxy, std::move(aMsg), 0); + } else { + DispatchAsyncMessage(listenerProxy, 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; + } + } + + if (reply && ChannelConnected == mChannelState) { + IPC_LOG("Sending reply seqno=%d, xid=%d", aMsg.seqno(), + aMsg.transaction_id()); + AddProfilerMarker(*reply, MessageDirection::eSending); + + mLink->SendMessage(std::move(reply)); + } +} + +void MessageChannel::DispatchSyncMessage(ActorLifecycleProxy* aProxy, + const Message& aMsg, + Message*& aReply) { + AssertWorkerThread(); + + mozilla::TimeStamp start = TimeStamp::Now(); + + int nestedLevel = aMsg.nested_level(); + + MOZ_RELEASE_ASSERT(nestedLevel == IPC::Message::NOT_NESTED || + NS_IsMainThread()); +#ifdef MOZ_TASK_TRACER + AutoScopedLabel autolabel("sync message %s", aMsg.name()); +#endif + + 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_interrupt() && !aMsg.is_sync()); + + if (aMsg.routing_id() == MSG_ROUTING_NONE) { + MOZ_CRASH("unhandled special message!"); + } + + 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::DispatchInterruptMessage(ActorLifecycleProxy* aProxy, + Message&& aMsg, + size_t stackDepth) { + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + + IPC_ASSERT(aMsg.is_interrupt() && !aMsg.is_reply(), "wrong message type"); + + if (ShouldDeferInterruptMessage(aMsg, stackDepth)) { + // We now know the other side's stack has one more frame + // than we thought. + ++mRemoteStackDepthGuess; // decremented in MaybeProcessDeferred() + mDeferred.push(std::move(aMsg)); + return; + } + + // If we "lost" a race and need to process the other side's in-call, we + // don't need to fix up the mRemoteStackDepthGuess here, because we're just + // about to increment it, which will make it correct again. + +#ifdef OS_WIN + SyncStackFrame frame(this, true); +#endif + + UniquePtr<Message> reply; + + ++mRemoteStackDepthGuess; + Result rv = aProxy->Get()->OnCallReceived(aMsg, *getter_Transfers(reply)); + --mRemoteStackDepthGuess; + + if (!MaybeHandleError(rv, aMsg, "DispatchInterruptMessage")) { + reply = WrapUnique(Message::ForInterruptDispatchError()); + } + reply->set_seqno(aMsg.seqno()); + + MonitorAutoLock lock(*mMonitor); + if (ChannelConnected == mChannelState) { + AddProfilerMarker(*reply, MessageDirection::eSending); + mLink->SendMessage(std::move(reply)); + } +} + +bool MessageChannel::ShouldDeferInterruptMessage(const Message& aMsg, + size_t aStackDepth) { + AssertWorkerThread(); + + // We may or may not own the lock in this function, so don't access any + // channel state. + + IPC_ASSERT(aMsg.is_interrupt() && !aMsg.is_reply(), "wrong message type"); + + // Race detection: see the long comment near mRemoteStackDepthGuess in + // MessageChannel.h. "Remote" stack depth means our side, and "local" means + // the other side. + if (aMsg.interrupt_remote_stack_depth_guess() == + RemoteViewOfStackDepth(aStackDepth)) { + return false; + } + + // Interrupt in-calls have raced. The winner, if there is one, gets to defer + // processing of the other side's in-call. + bool defer; + const char* winner; + const MessageInfo parentMsgInfo = + (mSide == ChildSide) ? MessageInfo(aMsg) : mInterruptStack.top(); + const MessageInfo childMsgInfo = + (mSide == ChildSide) ? mInterruptStack.top() : MessageInfo(aMsg); + switch (mListener->MediateInterruptRace(parentMsgInfo, childMsgInfo)) { + case RIPChildWins: + winner = "child"; + defer = (mSide == ChildSide); + break; + case RIPParentWins: + winner = "parent"; + defer = (mSide != ChildSide); + break; + case RIPError: + MOZ_CRASH("NYI: 'Error' Interrupt race policy"); + default: + MOZ_CRASH("not reached"); + } + + IPC_LOG("race in %s: %s won", (mSide == ChildSide) ? "child" : "parent", + winner); + + return defer; +} + +void MessageChannel::MaybeUndeferIncall() { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + if (mDeferred.empty()) return; + + size_t stackDepth = InterruptStackDepth(); + + Message& deferred = mDeferred.top(); + + // the other side can only *under*-estimate our actual stack depth + IPC_ASSERT(deferred.interrupt_remote_stack_depth_guess() <= stackDepth, + "fatal logic error"); + + if (ShouldDeferInterruptMessage(deferred, stackDepth)) { + return; + } + + // maybe time to process this message + Message call(std::move(deferred)); + mDeferred.pop(); + + // fix up fudge factor we added to account for race + IPC_ASSERT(0 < mRemoteStackDepthGuess, "fatal logic error"); + --mRemoteStackDepthGuess; + + MOZ_RELEASE_ASSERT(call.nested_level() == IPC::Message::NOT_NESTED); + RefPtr<MessageTask> task = new MessageTask(this, std::move(call)); + mPending.insertBack(task); + MOZ_ASSERT(IsAlwaysDeferred(task->Msg())); + task->Post(); +} + +void MessageChannel::EnteredCxxStack() { mListener->EnteredCxxStack(); } + +void MessageChannel::ExitedCxxStack() { + mListener->ExitedCxxStack(); + if (mSawInterruptOutMsg) { + MonitorAutoLock lock(*mMonitor); + // see long comment in OnMaybeDequeueOne() + EnqueuePendingMessages(); + mSawInterruptOutMsg = false; + } +} + +void MessageChannel::EnteredCall() { mListener->EnteredCall(); } + +void MessageChannel::ExitedCall() { mListener->ExitedCall(); } + +void MessageChannel::EnteredSyncSend() { mListener->OnEnteredSyncSend(); } + +void MessageChannel::ExitedSyncSend() { mListener->OnExitedSyncSend(); } + +void MessageChannel::EnqueuePendingMessages() { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + MaybeUndeferIncall(); + + // XXX performance tuning knob: could process all or k pending + // messages here, rather than enqueuing for later processing + + RepostAllMessages(); +} + +bool MessageChannel::WaitResponse(bool aWaitTimedOut) { + 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 OS_WIN +bool MessageChannel::WaitForSyncNotify(bool /* aHandleWindowsMessages */) { +# ifdef DEBUG + // WARNING: We don't release the lock here. We can't because the link thread + // 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); +} + +bool MessageChannel::WaitForInterruptNotify() { + return WaitForSyncNotify(true); +} + +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::OnChannelConnected(int32_t peer_id) { + MOZ_RELEASE_ASSERT(!mPeerPidSet); + mPeerPidSet = true; + mPeerPid = peer_id; + RefPtr<CancelableRunnable> task = mOnChannelConnectedTask; + mWorkerThread->Dispatch(task.forget()); +} + +void MessageChannel::DispatchOnChannelConnected() { + AssertWorkerThread(); + MOZ_RELEASE_ASSERT(mPeerPidSet); + mListener->OnChannelConnected(mPeerPid); +} + +void MessageChannel::ReportMessageRouteError(const char* channelName) const { + PrintErrorMessage(mSide, channelName, "Need a route"); + mListener->ProcessingError(MsgRouteError, "MsgRouteError"); +} + +void MessageChannel::ReportConnectionError(const char* aChannelName, + Message* aMsg) const { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + + const char* errorMsg = nullptr; + switch (mChannelState) { + case ChannelClosed: + errorMsg = "Closed channel: cannot send/recv"; + break; + case ChannelOpening: + errorMsg = "Opening channel: not yet ready for send/recv"; + break; + case ChannelTimeout: + errorMsg = "Channel timeout: cannot send/recv"; + break; + case ChannelClosing: + errorMsg = + "Channel closing: too late to send/recv, messages will be lost"; + break; + case ChannelError: + errorMsg = "Channel error: cannot send/recv"; + break; + + default: + MOZ_CRASH("unreached"); + } + + if (aMsg) { + char reason[512]; + SprintfLiteral(reason, "(msgtype=0x%X,name=%s) %s", aMsg->type(), + aMsg->name(), errorMsg); + + PrintErrorMessage(mSide, aChannelName, reason); + } else { + PrintErrorMessage(mSide, aChannelName, errorMsg); + } + + MonitorAutoUnlock unlock(*mMonitor); + mListener->ProcessingError(MsgDropped, errorMsg); +} + +bool MessageChannel::MaybeHandleError(Result code, const Message& aMsg, + const char* channelName) { + if (MsgProcessed == code) return true; + + 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() { + AssertLinkThread(); + mMonitor->AssertCurrentThreadOwns(); + + IPC_LOG("OnChannelErrorFromLink"); + + if (InterruptStackDepth() > 0) NotifyWorkerThread(); + + if (AwaitingSyncReply() || AwaitingIncomingMessage()) NotifyWorkerThread(); + + if (ChannelClosing != mChannelState) { + 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() { + mMonitor->AssertNotCurrentThreadOwns(); + + // TODO sort out Close() on this side racing with Close() on the other side + if (ChannelClosing == mChannelState) { + // the channel closed, but we received a "Goodbye" message warning us + // about it. no worries + mChannelState = ChannelClosed; + NotifyChannelClosed(); + return; + } + + Clear(); + + // Oops, error! Let the listener know about it. + mChannelState = ChannelError; + + // IPDL assumes these notifications do not fire twice, so we do not let + // that happen. + if (mNotifiedChannelDone) { + return; + } + mNotifiedChannelDone = true; + + // After this, the channel may be deleted. Based on the premise that + // mListener owns this channel, any calls back to this class that may + // work with mListener should still work on living objects. + mListener->OnChannelError(); +} + +void MessageChannel::OnNotifyMaybeChannelError() { + AssertWorkerThread(); + mMonitor->AssertNotCurrentThreadOwns(); + + mChannelErrorTask = nullptr; + + // OnChannelError holds mMonitor when it posts this task and this + // task cannot be allowed to run until OnChannelError has + // exited. We enforce that order by grabbing the mutex here which + // should only continue once OnChannelError has completed. + { + MonitorAutoLock lock(*mMonitor); + // nothing to do here + } + + if (IsOnCxxStack()) { + mChannelErrorTask = NewNonOwningCancelableRunnableMethod( + "ipc::MessageChannel::OnNotifyMaybeChannelError", this, + &MessageChannel::OnNotifyMaybeChannelError); + RefPtr<Runnable> task = mChannelErrorTask; + // This used to post a 10ms delayed patch; however not all + // nsISerialEventTarget implementations support delayed dispatch. + // The delay being completely arbitrary, we may not as well have any. + mWorkerThread->Dispatch(task.forget()); + return; + } + + NotifyMaybeChannelError(); +} + +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); + RefPtr<Runnable> task = mChannelErrorTask; + mWorkerThread->Dispatch(task.forget()); +} + +// 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::SynchronouslyClose() { + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + mLink->SendClose(); + + MOZ_RELEASE_ASSERT(!mIsSameThreadChannel || ChannelClosed == mChannelState, + "same-thread channel failed to synchronously close?"); + + while (ChannelClosed != mChannelState) mMonitor->Wait(); +} + +void MessageChannel::CloseWithError() { + AssertWorkerThread(); + + MonitorAutoLock lock(*mMonitor); + if (ChannelConnected != mChannelState) { + return; + } + SynchronouslyClose(); + mChannelState = ChannelError; + PostErrorNotifyTask(); +} + +void MessageChannel::CloseWithTimeout() { + AssertWorkerThread(); + + MonitorAutoLock lock(*mMonitor); + if (ChannelConnected != mChannelState) { + return; + } + SynchronouslyClose(); + mChannelState = ChannelTimeout; +} + +void MessageChannel::NotifyImpendingShutdown() { + UniquePtr<Message> msg = + MakeUnique<Message>(MSG_ROUTING_NONE, IMPENDING_SHUTDOWN_MESSAGE_TYPE); + MonitorAutoLock lock(*mMonitor); + if (Connected()) { + MOZ_DIAGNOSTIC_ASSERT(mIsCrossProcess); + mLink->SendMessage(std::move(msg)); + } +} + +void MessageChannel::Close() { + AssertWorkerThread(); + + { + // We don't use MonitorAutoLock here as that causes some sort of + // deadlock in the error/timeout-with-a-listener state below when + // compiling an optimized msvc build. + mMonitor->Lock(); + + // Instead just use a ScopeExit to manage the unlock. + RefPtr<RefCountedMonitor> monitor(mMonitor); + auto exit = MakeScopeExit([m = std::move(monitor)]() { m->Unlock(); }); + + if (ChannelError == mChannelState || ChannelTimeout == mChannelState) { + // 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. + if (mListener) { + exit.release(); // Explicitly unlocking, clear scope exit. + mMonitor->Unlock(); + NotifyMaybeChannelError(); + } + return; + } + + if (ChannelOpening == mChannelState) { + // SynchronouslyClose() waits for an ack from the other side, so + // the opening sequence should complete before this returns. + SynchronouslyClose(); + mChannelState = ChannelError; + NotifyMaybeChannelError(); + return; + } + + if (ChannelClosed == mChannelState) { + // Slightly unexpected but harmless; ignore. See bug 1554244. + return; + } + + // 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) { + mLink->SendMessage(MakeUnique<GoodbyeMessage>()); + } + SynchronouslyClose(); + } + + NotifyChannelClosed(); +} + +void MessageChannel::NotifyChannelClosed() { + mMonitor->AssertNotCurrentThreadOwns(); + + 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; + + // OK, the IO thread just closed the channel normally. Let the + // listener know about it. After this point the channel may be + // deleted. + mListener->OnChannelClose(); +} + +void MessageChannel::DebugAbort(const char* file, int line, const char* cond, + const char* why, bool reply) { + printf_stderr( + "###!!! [MessageChannel][%s][%s:%d] " + "Assertion (%s) failed. %s %s\n", + mSide == ChildSide ? "Child" : "Parent", file, line, cond, why, + reply ? "(reply)" : ""); + // technically we need the mutex for this, but we're dying anyway + DumpInterruptStack(" "); + printf_stderr(" remote Interrupt stack guess: %zu\n", + mRemoteStackDepthGuess); + printf_stderr(" deferred stack size: %zu\n", mDeferred.size()); + printf_stderr(" out-of-turn Interrupt replies stack size: %zu\n", + mOutOfTurnReplies.size()); + + MessageQueue pending = std::move(mPending); + while (!pending.isEmpty()) { + printf_stderr( + " [ %s%s ]\n", + pending.getFirst()->Msg().is_interrupt() + ? "intr" + : (pending.getFirst()->Msg().is_sync() ? "sync" : "async"), + pending.getFirst()->Msg().is_reply() ? "reply" : ""); + pending.popFirst(); + } + + MOZ_CRASH_UNSAFE(why); +} + +void MessageChannel::DumpInterruptStack(const char* const pfx) const { + NS_WARNING_ASSERTION(!mWorkerThread->IsOnCurrentThread(), + "The worker thread had better be paused in a debugger!"); + + printf_stderr("%sMessageChannel 'backtrace':\n", pfx); + + // print a python-style backtrace, first frame to last + for (uint32_t i = 0; i < mCxxStackFrames.length(); ++i) { + int32_t id; + const char* dir; + const char* sems; + const char* name; + mCxxStackFrames[i].Describe(&id, &dir, &sems, &name); + + printf_stderr("%s[(%u) %s %s %s(actor=%d) ]\n", pfx, i, dir, sems, name, + id); + } +} + +void MessageChannel::AddProfilerMarker(const IPC::Message& aMessage, + MessageDirection aDirection) { + mMonitor->AssertCurrentThreadOwns(); +#ifdef MOZ_GECKO_PROFILER + if (profiler_feature_active(ProfilerFeature::IPCMessages)) { + int32_t 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 != kInvalidProcessId && !profiler_is_locked_on_current_thread()) { + // The current timestamp must be given to the `IPCMarker` payload. + const TimeStamp now = TimeStamp::NowUnfuzzed(); + PROFILER_MARKER("IPC", IPC, MarkerTiming::InstantAt(now), IPCMarker, now, + now, pid, aMessage.seqno(), aMessage.type(), mSide, + aDirection, MessagePhase::Endpoint, aMessage.is_sync()); + } + } +#endif +} + +int32_t MessageChannel::GetTopmostMessageRoutingId() const { + AssertWorkerThread(); + + if (mCxxStackFrames.empty()) { + return MSG_ROUTING_NONE; + } + const InterruptFrame& frame = mCxxStackFrames.back(); + return frame.GetRoutingId(); +} + +void MessageChannel::EndTimeout() { + mMonitor->AssertCurrentThreadOwns(); + + IPC_LOG("Ending timeout of seqno=%d", mTimedOutMessageSeqno); + mTimedOutMessageSeqno = 0; + mTimedOutMessageNestedLevel = 0; + + RepostAllMessages(); +} + +void MessageChannel::RepostAllMessages() { + bool needRepost = false; + for (MessageTask* task : mPending) { + 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()) { + RefPtr<MessageTask> newTask = new MessageTask(this, std::move(task->Msg())); + 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;) { + 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()); + mLink->SendMessage(std::move(cancel)); + } +} + +void CancelCPOWs() { + if (gParentProcessBlocker) { + mozilla::Telemetry::Accumulate(mozilla::Telemetry::IPC_TRANSACTION_CANCEL, + true); + gParentProcessBlocker->CancelCurrentTransaction(); + } +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/MessageChannel.h b/ipc/glue/MessageChannel.h new file mode 100644 index 0000000000..972615ea76 --- /dev/null +++ b/ipc/glue/MessageChannel.h @@ -0,0 +1,956 @@ +/* -*- 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 1 + +#include "ipc/EnumSerializer.h" +#include "mozilla/Atomics.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Monitor.h" +#include "mozilla/Vector.h" +#if defined(OS_WIN) +# include "mozilla/ipc/Neutering.h" +#endif // defined(OS_WIN) + +#include <functional> +#include <map> +#include <stack> +#include <vector> + +#include "MessageLink.h" // for HasResultCodes +#include "mozilla/ipc/Transport.h" + +#ifdef MOZ_GECKO_PROFILER +# include "mozilla/BaseProfilerMarkers.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") {} + + 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, + EndGuard_, +}; + +template <typename T> +using ResolveCallback = std::function<void(T&&)>; + +using RejectCallback = std::function<void(ResponseRejectReason)>; + +enum ChannelState { + ChannelClosed, + ChannelOpening, + ChannelConnected, + ChannelTimeout, + ChannelClosing, + ChannelError +}; + +class AutoEnterTransaction; + +class MessageChannel : HasResultCodes { + friend class ProcessLink; + friend class ThreadLink; +#ifdef FUZZING + friend class ProtocolFuzzerHelper; +#endif + + class CxxStackFrame; + class InterruptFrame; + + typedef mozilla::Monitor Monitor; + + // We could templatize the actor type but it would unnecessarily + // expand the code size. Using the actor address as the + // identifier is already good enough. + typedef void* ActorIdType; + + public: + struct UntypedCallbackHolder { + UntypedCallbackHolder(ActorIdType aActorId, RejectCallback&& aReject) + : mActorId(aActorId), mReject(std::move(aReject)) {} + + virtual ~UntypedCallbackHolder() = default; + + void Reject(ResponseRejectReason&& aReason) { mReject(std::move(aReason)); } + + ActorIdType mActorId; + RejectCallback mReject; + }; + + template <typename Value> + struct CallbackHolder : public UntypedCallbackHolder { + CallbackHolder(ActorIdType aActorId, ResolveCallback<Value>&& aResolve, + RejectCallback&& aReject) + : UntypedCallbackHolder(aActorId, 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 const int32_t kNoTimeout; + + typedef IPC::Message Message; + typedef IPC::MessageInfo MessageInfo; + typedef mozilla::ipc::Transport Transport; + + explicit MessageChannel(const char* aName, IToplevelProtocol* aListener); + ~MessageChannel(); + + IToplevelProtocol* Listener() const { return mListener; } + + // "Open" from the perspective of the transport layer; the underlying + // socketpair/pipe should already be created. + // + // Returns true if the transport layer was successfully connected, + // i.e., mChannelState == ChannelConnected. + bool Open(UniquePtr<Transport> aTransport, MessageLoop* aIOLoop = 0, + Side aSide = UnknownSide); + + // "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(); + + // Close the underlying transport channel. + void Close(); + + // Force the channel to behave as if a channel error occurred. Valid + // for process links only, not thread links. + void CloseWithError(); + + void CloseWithTimeout(); + + void SetAbortOnError(bool abort) { 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); + + // 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, + // Windows: When this flag is specified, any wait that occurs during + // synchronous IPC will be alertable, thus allowing a11y code in the + // chrome process to reenter content while content is waiting on a + // synchronous call. + REQUIRE_A11Y_REENTRY = 1 << 1, + }; + 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); + + // Asynchronously send a message to the other side of the channel + // and wait for asynchronous reply. + template <typename Value> + void Send(UniquePtr<Message> aMsg, ActorIdType aActorId, + ResolveCallback<Value>&& aResolve, RejectCallback&& aReject) { + int32_t seqno = NextSeqno(); + aMsg->set_seqno(seqno); + if (!Send(std::move(aMsg))) { + aReject(ResponseRejectReason::SendError); + return; + } + + UniquePtr<UntypedCallbackHolder> callback = + MakeUnique<CallbackHolder<Value>>(aActorId, std::move(aResolve), + std::move(aReject)); + mPendingResponses.insert(std::make_pair(seqno, std::move(callback))); + gUnresolvedResponses++; + } + + bool SendBuildIDsMatchMessage(const char* aParentBuildI); + bool DoBuildIDsMatch() { return mBuildIDsConfirmedMatch; } + + // Synchronously send |msg| (i.e., wait for |reply|) + bool Send(UniquePtr<Message> aMsg, Message* aReply); + + // Make an Interrupt call to the other side of the channel + bool Call(UniquePtr<Message> aMsg, Message* aReply); + + // Wait until a message is received + bool WaitForIncomingMessage(); + + bool CanSend() const; + + // Remove and return a callback that needs reply + UniquePtr<UntypedCallbackHolder> PopCallback(const Message& aMsg); + + // Used to reject and remove pending responses owned by the given + // actor when it's about to be destroyed. + void RejectPendingResponsesForActor(ActorIdType aActorId); + + // If sending a sync message returns an error, this function gives a more + // descriptive error message. + SyncSendError LastSendError() const { + AssertWorkerThread(); + return mLastSendError; + } + + // Currently only for debugging purposes, doesn't aquire mMonitor. + ChannelState GetChannelState__TotallyRacy() const { return mChannelState; } + + void SetReplyTimeoutMs(int32_t aTimeoutMs); + + bool IsOnCxxStack() const { return !mCxxStackFrames.empty(); } + + void CancelCurrentTransaction(); + + // Force all calls to Send to defer actually sending messages. This will + // cause sync messages to block until another thread calls + // StopPostponingSends. + // + // This must be called from the worker thread. + void BeginPostponingSends(); + + // Stop postponing sent messages, and immediately flush all postponed + // messages to the link. This may be called from any thread. + // + // Note that there are no ordering guarantees between two different + // MessageChannels. If channel B sends a message, then stops postponing + // channel A, messages from A may arrive before B. The easiest way to order + // this, if needed, is to make B send a sync message. + void StopPostponingSends(); + + /** + * This function is used by hang annotation code to determine which IPDL + * actor is highest in the call stack at the time of the hang. It should + * be called from the main thread when a sync or intr message is about to + * be sent. + */ + int32_t GetTopmostMessageRoutingId() const; + + // Unsound_IsClosed and Unsound_NumQueuedMessages are safe to call from any + // thread, but they make no guarantees about whether you'll get an + // up-to-date value; the values are written on one thread and read without + // locking, on potentially different threads. Thus you should only use + // them when you don't particularly care about getting a recent value (e.g. + // in a memory report). + bool Unsound_IsClosed() const { + return mLink ? mLink->Unsound_IsClosed() : true; + } + uint32_t Unsound_NumQueuedMessages() const { + return mLink ? mLink->Unsound_NumQueuedMessages() : 0; + } + + static bool IsPumpingMessages() { return sIsPumpingMessages; } + static void SetIsPumpingMessages(bool aIsPumping) { + sIsPumpingMessages = aIsPumping; + } + + /** + * Does this MessageChannel cross process boundaries? + */ + bool IsCrossProcess() const { return mIsCrossProcess; } + +#ifdef OS_WIN + struct MOZ_STACK_CLASS SyncStackFrame { + SyncStackFrame(MessageChannel* channel, bool interrupt); + ~SyncStackFrame(); + + bool mInterrupt; + 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; + + bool mIsSyncWaitingOnNonMainThread; + + // The deepest sync stack frame on any channel. + static SyncStackFrame* sStaticTopFrame; + + public: + void ProcessNativeEventsInInterruptCall(); + static void NotifyGeckoEventDispatch(); + + private: + void SpinInternalEventLoop(); +# if defined(ACCESSIBILITY) + bool WaitForSyncNotifyWithA11yReentry(); +# endif // defined(ACCESSIBILITY) +#endif // defined(OS_WIN) + + private: + void CommonThreadOpenInit(MessageChannel* aTargetChan, + nsISerialEventTarget* aThread, Side aSide); + void OpenAsOtherThread(MessageChannel* aTargetChan, + nsISerialEventTarget* aThread, Side aSide); + + void PostErrorNotifyTask(); + void OnNotifyMaybeChannelError(); + void ReportConnectionError(const char* aChannelName, + Message* aMsg = nullptr) const; + void ReportMessageRouteError(const char* channelName) const; + bool MaybeHandleError(Result code, const Message& aMsg, + const char* channelName); + + void Clear(); + + // Send OnChannelConnected notification to listeners. + void DispatchOnChannelConnected(); + + bool InterruptEventOccurred(); + bool HasPendingEvents(); + + void ProcessPendingRequests(AutoEnterTransaction& aTransaction); + bool ProcessPendingRequest(Message&& aUrgent); + + void MaybeUndeferIncall(); + void EnqueuePendingMessages(); + + // Dispatches an incoming message to its appropriate handler. + void DispatchMessage(Message&& aMsg); + + // DispatchMessage will route to one of these functions depending on the + // protocol type of the message. + void DispatchSyncMessage(ActorLifecycleProxy* aProxy, const Message& aMsg, + Message*& aReply); + void DispatchAsyncMessage(ActorLifecycleProxy* aProxy, const Message& aMsg); + void DispatchInterruptMessage(ActorLifecycleProxy* aProxy, Message&& aMsg, + size_t aStackDepth); + + // 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(bool aHandleWindowsMessages); + bool WaitForInterruptNotify(); + + bool WaitResponse(bool aWaitTimedOut); + + bool ShouldContinueFromTimeout(); + + void EndTimeout(); + void CancelTransaction(int transaction); + + void RepostAllMessages(); + + // The "remote view of stack depth" can be different than the + // actual stack depth when there are out-of-turn replies. When we + // receive one, our actual Interrupt stack depth doesn't decrease, but + // the other side (that sent the reply) thinks it has. So, the + // "view" returned here is |stackDepth| minus the number of + // out-of-turn replies. + // + // Only called from the worker thread. + size_t RemoteViewOfStackDepth(size_t stackDepth) const { + AssertWorkerThread(); + return stackDepth - mOutOfTurnReplies.size(); + } + + int32_t NextSeqno() { + AssertWorkerThread(); + return (mSide == ChildSide) ? --mNextSeqno : ++mNextSeqno; + } + + // This helper class manages mCxxStackDepth on behalf of MessageChannel. + // When the stack depth is incremented from zero to non-zero, it invokes + // a callback, and similarly for when the depth goes from non-zero to zero. + void EnteredCxxStack(); + void ExitedCxxStack(); + + void EnteredCall(); + void ExitedCall(); + + void EnteredSyncSend(); + void ExitedSyncSend(); + + void DebugAbort(const char* file, int line, const char* cond, const char* why, + bool reply = false); + + // This method is only safe to call on the worker thread, or in a + // debugger with all threads paused. + void DumpInterruptStack(const char* const pfx = "") const; + + void AddProfilerMarker(const IPC::Message& aMessage, + MessageDirection aDirection); + + private: + // Called from both threads + size_t InterruptStackDepth() const { + mMonitor->AssertCurrentThreadOwns(); + return mInterruptStack.size(); + } + + bool AwaitingInterruptReply() const { + mMonitor->AssertCurrentThreadOwns(); + return !mInterruptStack.empty(); + } + bool AwaitingIncomingMessage() const { + mMonitor->AssertCurrentThreadOwns(); + return mIsWaitingForIncoming; + } + + class MOZ_STACK_CLASS AutoEnterWaitForIncoming { + public: + explicit AutoEnterWaitForIncoming(MessageChannel& aChannel) + : mChannel(aChannel) { + aChannel.mMonitor->AssertCurrentThreadOwns(); + aChannel.mIsWaitingForIncoming = true; + } + + ~AutoEnterWaitForIncoming() { mChannel.mIsWaitingForIncoming = false; } + + private: + MessageChannel& mChannel; + }; + friend class AutoEnterWaitForIncoming; + + // Returns true if we're dispatching an async message's callback. + bool DispatchingAsyncMessage() const { + AssertWorkerThread(); + return mDispatchingAsyncMessage; + } + + int DispatchingAsyncMessageNestedLevel() const { + AssertWorkerThread(); + return mDispatchingAsyncMessageNestedLevel; + } + + bool Connected() const; + + private: + // Executed on the IO thread. + void NotifyWorkerThread(); + + // 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); + + void OnChannelConnected(int32_t peer_id); + + // Tell the IO thread to close the channel and wait for it to ACK. + void SynchronouslyClose(); + + // 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. This should only be used for + // non-special messages that might have to be postponed. + void SendMessageToLink(UniquePtr<Message> aMsg); + + bool WasTransactionCanceled(int transaction); + bool ShouldDeferMessage(const Message& aMsg); + bool ShouldDeferInterruptMessage(const Message& aMsg, size_t aStackDepth); + void OnMessageReceivedFromLink(Message&& aMsg); + void OnChannelErrorFromLink(); + + private: + // Run on the not current thread. + void NotifyChannelClosed(); + void NotifyMaybeChannelError(); + + private: + void AssertWorkerThread() const { + MOZ_ASSERT(mWorkerThread, "Channel hasn't been opened yet"); + MOZ_RELEASE_ASSERT(mWorkerThread && mWorkerThread->IsOnCurrentThread(), + "not on worker thread!"); + } + + // The "link" thread is either the I/O thread (ProcessLink), the other + // actor's work thread (ThreadLink), or the worker thread (same-thread + // channels). + void AssertLinkThread() const { + if (mIsSameThreadChannel) { + // If we're a same-thread channel, we have to be on our worker + // thread. + AssertWorkerThread(); + return; + } + + // If we aren't a same-thread channel, our "link" thread is _not_ our + // worker thread! + MOZ_ASSERT(mWorkerThread, "Channel hasn't been opened yet"); + MOZ_RELEASE_ASSERT(mWorkerThread && !mWorkerThread->IsOnCurrentThread(), + "on worker thread but should not be!"); + } + + private: + class MessageTask : public CancelableRunnable, + public LinkedListElement<RefPtr<MessageTask>>, + public nsIRunnablePriority, + public nsIRunnableIPCMessageType { + public: + explicit MessageTask(MessageChannel* aChannel, Message&& aMessage); + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD Run() override; + nsresult Cancel() override; + NS_IMETHOD GetPriority(uint32_t* aPriority) override; + NS_DECL_NSIRUNNABLEIPCMESSAGETYPE + void Post(); + void Clear(); + + bool IsScheduled() const { return mScheduled; } + + Message& Msg() { return mMessage; } + const Message& Msg() const { return mMessage; } + + private: + MessageTask() = delete; + MessageTask(const MessageTask&) = delete; + ~MessageTask() = default; + + MessageChannel* mChannel; + Message mMessage; + bool mScheduled : 1; + }; + + bool ShouldRunMessage(const Message& aMsg); + void RunMessage(MessageTask& aTask); + + typedef LinkedList<RefPtr<MessageTask>> MessageQueue; + typedef std::map<size_t, Message> MessageMap; + 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* mName; + + // Based on presumption the listener owns and overlives the channel, + // this is never nullified. + IToplevelProtocol* mListener; + ChannelState mChannelState; + RefPtr<RefCountedMonitor> mMonitor; + Side mSide; + bool mIsCrossProcess; + UniquePtr<MessageLink> mLink; + RefPtr<CancelableRunnable> + mChannelErrorTask; // NotifyMaybeChannelError runnable + + // Thread we are allowed to send and receive on. + nsCOMPtr<nsISerialEventTarget> mWorkerThread; + + // 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. + int32_t mTimeoutMs; + bool mInTimeoutSecondHalf; + + // Worker-thread only; sequence numbers for messages that require + // replies. + int32_t mNextSeqno; + + static bool sIsPumpingMessages; + + // If ::Send returns false, this gives a more descriptive error. + SyncSendError mLastSendError; + + 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; + int mDispatchingAsyncMessageNestedLevel; + + // 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; + + int32_t CurrentNestedInsideSyncTransaction() const; + + bool AwaitingSyncReply() const; + int AwaitingSyncReplyNestedLevel() const; + + bool DispatchingSyncMessage() const; + int DispatchingSyncMessageNestedLevel() const; + +#ifdef DEBUG + void AssertMaybeDeferredCountCorrect(); +#else + void AssertMaybeDeferredCountCorrect() {} +#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; + int mTimedOutMessageNestedLevel; + + // Queue of all incoming messages. + // + // If both this side and the other side are functioning correctly, the queue + // can only be in certain configurations. Let + // + // |A<| be an async in-message, + // |S<| be a sync in-message, + // |C<| be an Interrupt in-call, + // |R<| be an Interrupt reply. + // + // The queue can only match this configuration + // + // A<* (S< | C< | R< (?{mInterruptStack.size() == 1} A<* (S< | C<))) + // + // The other side can send as many async messages |A<*| as it wants before + // sending us a blocking message. + // + // The first case is |S<|, a sync in-msg. The other side must be blocked, + // and thus can't send us any more messages until we process the sync + // in-msg. + // + // The second case is |C<|, an Interrupt in-call; the other side must be + // blocked. (There's a subtlety here: this in-call might have raced with an + // out-call, but we detect that with the mechanism below, + // |mRemoteStackDepth|, and races don't matter to the queue.) + // + // Final case, the other side replied to our most recent out-call |R<|. + // If that was the *only* out-call on our stack, + // |?{mInterruptStack.size() == 1}|, then other side "finished with us," + // and went back to its own business. That business might have included + // sending any number of async message |A<*| until sending a blocking + // message |(S< | C<)|. If we had more than one Interrupt call on our + // stack, the other side *better* not have sent us another blocking + // message, because it's blocked on a reply from us. + // + MessageQueue mPending; + + // 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; + + // Stack of all the out-calls on which this channel is awaiting responses. + // Each stack refers to a different protocol and the stacks are mutually + // exclusive: multiple outcalls of the same kind cannot be initiated while + // another is active. + std::stack<MessageInfo> mInterruptStack; + + // This is what we think the Interrupt stack depth is on the "other side" of + // this Interrupt channel. We maintain this variable so that we can detect + // racy Interrupt calls. With each Interrupt out-call sent, we send along + // what *we* think the stack depth of the remote side is *before* it will + // receive the Interrupt call. + // + // After sending the out-call, our stack depth is "incremented" by pushing + // that pending message onto mPending. + // + // Then when processing an in-call |c|, it must be true that + // + // mInterruptStack.size() == c.remoteDepth + // + // I.e., my depth is actually the same as what the other side thought it + // was when it sent in-call |c|. If this fails to hold, we have detected + // racy Interrupt calls. + // + // We then increment mRemoteStackDepth *just before* processing the + // in-call, since we know the other side is waiting on it, and decrement + // it *just after* finishing processing that in-call, since our response + // will pop the top of the other side's |mPending|. + // + // One nice aspect of this race detection is that it is symmetric; if one + // side detects a race, then the other side must also detect the same race. + size_t mRemoteStackDepthGuess; + + // Approximation of code frames on the C++ stack. It can only be + // interpreted as the implication: + // + // !mCxxStackFrames.empty() => MessageChannel code on C++ stack + // + // This member is only accessed on the worker thread, and so is not + // protected by mMonitor. It is managed exclusively by the helper + // |class CxxStackFrame|. + mozilla::Vector<InterruptFrame> mCxxStackFrames; + + // Did we process an Interrupt out-call during this stack? Only meaningful in + // ExitedCxxStack(), from which this variable is reset. + bool mSawInterruptOutMsg; + + // Are we waiting on this channel for an incoming message? This is used + // to implement WaitForIncomingMessage(). Must only be accessed while owning + // mMonitor. + bool mIsWaitingForIncoming; + + // Map of replies received "out of turn", because of Interrupt + // in-calls racing with replies to outstanding in-calls. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=521929. + MessageMap mOutOfTurnReplies; + + // Map of async Callbacks that are still waiting replies. + CallbackMap mPendingResponses; + + // Stack of Interrupt in-calls that were deferred because of race + // conditions. + std::stack<Message> mDeferred; + +#ifdef OS_WIN + HANDLE mEvent; +#endif + + // Should the channel abort the process from the I/O thread when + // a channel error occurs? + bool mAbortOnError; + + // True if the listener has already been notified of a channel close or + // error. + bool mNotifiedChannelDone; + + // See SetChannelFlags + ChannelFlags mFlags; + + // Task and state used to asynchronously notify channel has been connected + // safely. This is necessary to be able to cancel notification if we are + // closed at the same time. + RefPtr<CancelableRunnable> mOnChannelConnectedTask; + bool mPeerPidSet; + int32_t mPeerPid; + + // Channels can enter messages are not sent immediately; instead, they are + // held in a queue until another thread deems it is safe to send them. + bool mIsPostponingSends; + std::vector<UniquePtr<Message>> mPostponedSends; + + bool mBuildIDsConfirmedMatch; + + // If this is true, both ends of this message channel have event targets + // on the same thread. + bool mIsSameThreadChannel; +}; + +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 + +#ifdef MOZ_GECKO_PROFILER +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) { + 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); + } + 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 + +#endif // ifndef ipc_glue_MessageChannel_h diff --git a/ipc/glue/MessageLink.cpp b/ipc/glue/MessageLink.cpp new file mode 100644 index 0000000000..3c6b8e51c8 --- /dev/null +++ b/ipc/glue/MessageLink.cpp @@ -0,0 +1,386 @@ +/* -*- 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 "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/ipc/ProtocolUtils.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; + +// We rely on invariants about the lifetime of the transport: +// +// - outlives this MessageChannel +// - deleted on the IO thread +// +// These invariants allow us to send messages directly through the +// transport without having to worry about orphaned Send() tasks on +// the IO thread touching MessageChannel memory after it's been deleted +// on the worker thread. We also don't need to refcount the +// Transport, because whatever task triggers its deletion only runs on +// the IO thread, and only runs after this MessageChannel is done with +// the Transport. + +namespace mozilla { +namespace ipc { + +MessageLink::MessageLink(MessageChannel* aChan) : mChan(aChan) {} + +MessageLink::~MessageLink() { +#ifdef DEBUG + mChan = nullptr; +#endif +} + +ProcessLink::ProcessLink(MessageChannel* aChan) + : MessageLink(aChan), mIOLoop(nullptr), mExistingListener(nullptr) {} + +ProcessLink::~ProcessLink() { + // Dispatch the delete of the transport to the IO thread. + RefPtr<DeleteTask<IPC::Channel>> task = + new DeleteTask<IPC::Channel>(mTransport.release()); + XRE_GetIOMessageLoop()->PostTask(task.forget()); + +#ifdef DEBUG + mIOLoop = nullptr; + mExistingListener = nullptr; +#endif +} + +void ProcessLink::Open(UniquePtr<Transport> aTransport, MessageLoop* aIOLoop, + Side aSide) { + mChan->AssertWorkerThread(); + + MOZ_ASSERT(aTransport, "need transport layer"); + + // FIXME need to check for valid channel + + mTransport = std::move(aTransport); + + // FIXME figure out whether we're in parent or child, grab IO loop + // appropriately + bool needOpen = true; + if (aIOLoop) { + // We're a child or using the new arguments. Either way, we + // need an open. + needOpen = true; + mChan->mSide = (aSide == UnknownSide) ? ChildSide : aSide; + } else { + MOZ_ASSERT(aSide == UnknownSide, "expected default side arg"); + + // parent + mChan->mSide = ParentSide; + needOpen = false; + aIOLoop = XRE_GetIOMessageLoop(); + } + + mIOLoop = aIOLoop; + + NS_ASSERTION(mIOLoop, "need an IO loop"); + NS_ASSERTION(mChan->mWorkerThread, "need a worker thread"); + + // If we were never able to open the transport, immediately post an error + // message. + if (mTransport->Unsound_IsClosed()) { + mIOLoop->PostTask( + NewNonOwningRunnableMethod("ipc::ProcessLink::OnChannelConnectError", + this, &ProcessLink::OnChannelConnectError)); + return; + } + + { + MonitorAutoLock lock(*mChan->mMonitor); + + if (needOpen) { + // Transport::Connect() has not been called. Call it so + // we start polling our pipe and processing outgoing + // messages. + mIOLoop->PostTask( + NewNonOwningRunnableMethod("ipc::ProcessLink::OnChannelOpened", this, + &ProcessLink::OnChannelOpened)); + } else { + // Transport::Connect() has already been called. Take + // over the channel from the previous listener and process + // any queued messages. + mIOLoop->PostTask(NewNonOwningRunnableMethod( + "ipc::ProcessLink::OnTakeConnectedChannel", this, + &ProcessLink::OnTakeConnectedChannel)); + } + + // Wait until one of the runnables above changes the state of the + // channel. Note that the state could be changed again after that (to + // ChannelClosing, for example, by the IO thread). We can rely on it not + // changing back to Closed: only the worker thread changes it to closed, + // and we're on the worker thread, blocked. + while (mChan->mChannelState == ChannelClosed) { + mChan->mMonitor->Wait(); + } + } +} + +void ProcessLink::SendMessage(UniquePtr<Message> msg) { + if (msg->size() > IPC::Channel::kMaximumMessageSize) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCMessageName, + nsDependentCString(msg->name())); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCMessageSize, + static_cast<unsigned int>(msg->size())); + MOZ_CRASH("IPC message size is too large"); + } + + if (!mChan->mIsPostponingSends) { + mChan->AssertWorkerThread(); + } + mChan->mMonitor->AssertCurrentThreadOwns(); + + msg->AssertAsLargeAsHeader(); + + mIOLoop->PostTask(NewNonOwningRunnableMethod<UniquePtr<Message>&&>( + "IPC::Channel::Send", mTransport.get(), &Transport::Send, + std::move(msg))); +} + +void ProcessLink::SendClose() { + mChan->AssertWorkerThread(); + mChan->mMonitor->AssertCurrentThreadOwns(); + + mIOLoop->PostTask(NewNonOwningRunnableMethod( + "ipc::ProcessLink::OnCloseChannel", this, &ProcessLink::OnCloseChannel)); +} + +ThreadLink::ThreadLink(MessageChannel* aChan, MessageChannel* aTargetChan) + : MessageLink(aChan), mTargetChan(aTargetChan) {} + +void ThreadLink::PrepareToDestroy() { + MOZ_ASSERT(mChan); + MOZ_ASSERT(mChan->mMonitor); + MonitorAutoLock lock(*mChan->mMonitor); + + // Bug 848949: We need to prevent the other side + // from sending us any more messages to avoid Use-After-Free. + // The setup here is as shown: + // + // (Us) (Them) + // MessageChannel MessageChannel + // | ^ \ / ^ | + // | | X | | + // v | / \ | v + // ThreadLink ThreadLink + // + // We want to null out the diagonal link from their ThreadLink + // to our MessageChannel. Note that we must hold the monitor so + // that we do this atomically with respect to them trying to send + // us a message. Since the channels share the same monitor this + // also protects against the two PrepareToDestroy() calls racing. + // + // + // Why splitting is done in a method separate from ~ThreadLink: + // + // ThreadLinks are destroyed in MessageChannel::Clear(), when + // nullptr is assigned to the UniquePtr<> MessageChannel::mLink. + // This single line of code gets executed in three separate steps: + // 1. Load the value of mLink into a temporary. + // 2. Store nullptr in the mLink field. + // 3. Call the destructor on the temporary from step 1. + // This is all done without holding the monitor. + // The splitting operation, among other things, loads the mLink field + // of the other thread's MessageChannel while holding the monitor. + // If splitting was done in the destructor, and the two sides were + // both running MessageChannel::Clear(), then there would be a race + // between the store to mLink in Clear() and the load of mLink + // during splitting. + // Instead, we call PrepareToDestroy() prior to step 1. One thread or + // the other will run the entire method before the other thread, + // because this method acquires the monitor. Once that is done, the + // mTargetChan of both ThreadLink will be null, so they will no + // longer be able to access the other and so there won't be any races. + // + // An alternate approach would be to hold the monitor in Clear() or + // make mLink atomic, but MessageLink does not have to worry about + // Clear() racing with Clear(), so it would be inefficient. + if (mTargetChan) { + MOZ_ASSERT(mTargetChan->mLink); + static_cast<ThreadLink*>(mTargetChan->mLink.get())->mTargetChan = nullptr; + } + mTargetChan = nullptr; +} + +void ThreadLink::SendMessage(UniquePtr<Message> msg) { + if (!mChan->mIsPostponingSends) { + mChan->AssertWorkerThread(); + } + mChan->mMonitor->AssertCurrentThreadOwns(); + + if (mTargetChan) mTargetChan->OnMessageReceivedFromLink(std::move(*msg)); +} + +void ThreadLink::SendClose() { + mChan->AssertWorkerThread(); + mChan->mMonitor->AssertCurrentThreadOwns(); + + mChan->mChannelState = ChannelClosed; + + // In a ProcessLink, we would close our half the channel. This + // would show up on the other side as an error on the I/O thread. + // The I/O thread would then invoke OnChannelErrorFromLink(). + // As usual, we skip that process and just invoke the + // OnChannelErrorFromLink() method directly. + if (mTargetChan) mTargetChan->OnChannelErrorFromLink(); +} + +bool ThreadLink::Unsound_IsClosed() const { + MonitorAutoLock lock(*mChan->mMonitor); + return mChan->mChannelState == ChannelClosed; +} + +uint32_t ThreadLink::Unsound_NumQueuedMessages() const { + // ThreadLinks don't have a message queue. + return 0; +} + +// +// The methods below run in the context of the IO thread +// + +void ProcessLink::OnMessageReceived(Message&& msg) { + AssertIOThread(); + NS_ASSERTION(mChan->mChannelState != ChannelError, "Shouldn't get here!"); + MonitorAutoLock lock(*mChan->mMonitor); + mChan->OnMessageReceivedFromLink(std::move(msg)); +} + +void ProcessLink::OnChannelOpened() { + AssertIOThread(); + + { + MonitorAutoLock lock(*mChan->mMonitor); + + mExistingListener = mTransport->set_listener(this); +#ifdef DEBUG + if (mExistingListener) { + std::queue<Message> pending; + mExistingListener->GetQueuedMessages(pending); + MOZ_ASSERT(pending.empty()); + } +#endif // DEBUG + + mChan->mChannelState = ChannelOpening; + lock.Notify(); + } + + if (!mTransport->Connect()) { + mTransport->Close(); + OnChannelError(); + } +} + +void ProcessLink::OnTakeConnectedChannel() { + AssertIOThread(); + + std::queue<Message> pending; + { + MonitorAutoLock lock(*mChan->mMonitor); + + mChan->mChannelState = ChannelConnected; + + mExistingListener = mTransport->set_listener(this); + if (mExistingListener) { + mExistingListener->GetQueuedMessages(pending); + } + lock.Notify(); + } + + // Dispatch whatever messages the previous listener had queued up. + while (!pending.empty()) { + OnMessageReceived(std::move(pending.front())); + pending.pop(); + } +} + +void ProcessLink::OnChannelConnected(int32_t peer_pid) { + AssertIOThread(); + + bool notifyChannel = false; + + { + MonitorAutoLock lock(*mChan->mMonitor); + // Do not force it into connected if it has errored out, started + // closing, etc. Note that we can be in the Connected state already + // since the parent starts out Connected. + if (mChan->mChannelState == ChannelOpening || + mChan->mChannelState == ChannelConnected) { + mChan->mChannelState = ChannelConnected; + mChan->mMonitor->Notify(); + notifyChannel = true; + } + } + + if (mExistingListener) { + mExistingListener->OnChannelConnected(peer_pid); + } + + if (notifyChannel) { + mChan->OnChannelConnected(peer_pid); + } +} + +void ProcessLink::OnChannelConnectError() { + AssertIOThread(); + + MonitorAutoLock lock(*mChan->mMonitor); + + mChan->OnChannelErrorFromLink(); +} + +void ProcessLink::OnChannelError() { + AssertIOThread(); + + MonitorAutoLock lock(*mChan->mMonitor); + + MOZ_ALWAYS_TRUE(this == mTransport->set_listener(mExistingListener)); + + mChan->OnChannelErrorFromLink(); +} + +void ProcessLink::OnCloseChannel() { + AssertIOThread(); + + mTransport->Close(); + + MonitorAutoLock lock(*mChan->mMonitor); + + DebugOnly<IPC::Channel::Listener*> previousListener = + mTransport->set_listener(mExistingListener); + + // OnChannelError may have reset the listener already. + MOZ_ASSERT(previousListener == this || previousListener == mExistingListener); + + mChan->mChannelState = ChannelClosed; + mChan->mMonitor->Notify(); +} + +bool ProcessLink::Unsound_IsClosed() const { + return mTransport->Unsound_IsClosed(); +} + +uint32_t ProcessLink::Unsound_NumQueuedMessages() const { + return mTransport->Unsound_NumQueuedMessages(); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/MessageLink.h b/ipc/glue/MessageLink.h new file mode 100644 index 0000000000..bad3930799 --- /dev/null +++ b/ipc/glue/MessageLink.h @@ -0,0 +1,129 @@ +/* -*- 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 1 + +#include <cstdint> +#include "base/message_loop.h" +#include "mozilla/Assertions.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/ipc/Transport.h" + +namespace IPC { +class Message; +} + +namespace mozilla { +namespace ipc { + +class MessageChannel; + +struct HasResultCodes { + enum Result { + MsgProcessed, + MsgDropped, + MsgNotKnown, + MsgNotAllowed, + MsgPayloadError, + MsgProcessingError, + MsgRouteError, + MsgValueError + }; +}; + +enum Side : uint8_t { ParentSide, ChildSide, UnknownSide }; + +class MessageLink { + public: + typedef IPC::Message Message; + + explicit MessageLink(MessageChannel* aChan); + virtual ~MessageLink(); + + // This is called immediately before the MessageChannel destroys its + // MessageLink. See the implementation in ThreadLink for details. + virtual void PrepareToDestroy(){}; + + // n.b.: These methods all require that the channel monitor is + // held when they are invoked. + virtual void SendMessage(mozilla::UniquePtr<Message> msg) = 0; + virtual void SendClose() = 0; + + virtual bool Unsound_IsClosed() const = 0; + virtual uint32_t Unsound_NumQueuedMessages() const = 0; + + protected: + MessageChannel* mChan; +}; + +class ProcessLink : public MessageLink, public Transport::Listener { + void OnCloseChannel(); + void OnChannelOpened(); + void OnTakeConnectedChannel(); + + void AssertIOThread() const { + MOZ_ASSERT(mIOLoop == MessageLoop::current(), "not on I/O thread!"); + } + + public: + explicit ProcessLink(MessageChannel* chan); + virtual ~ProcessLink(); + + // The ProcessLink will register itself as the IPC::Channel::Listener on the + // transport passed here. If the transport already has a listener registered + // then a listener chain will be established (the ProcessLink listener + // methods will be called first and may call some methods on the original + // listener as well). Once the channel is closed (either via normal shutdown + // or a pipe error) the chain will be destroyed and the original listener + // will again be registered. + void Open(UniquePtr<Transport> aTransport, MessageLoop* aIOLoop, Side aSide); + + // Run on the I/O thread, only when using inter-process link. + // These methods acquire the monitor and forward to the + // similarly named methods in AsyncChannel below + // (OnMessageReceivedFromLink(), etc) + virtual void OnMessageReceived(Message&& msg) override; + virtual void OnChannelConnected(int32_t peer_pid) override; + virtual void OnChannelError() override; + + virtual void SendMessage(mozilla::UniquePtr<Message> msg) override; + virtual void SendClose() override; + + virtual bool Unsound_IsClosed() const override; + virtual uint32_t Unsound_NumQueuedMessages() const override; + + protected: + void OnChannelConnectError(); + + protected: + UniquePtr<Transport> mTransport; + MessageLoop* mIOLoop; // thread where IO happens + Transport::Listener* mExistingListener; // channel's previous listener +}; + +class ThreadLink : public MessageLink { + public: + ThreadLink(MessageChannel* aChan, MessageChannel* aTargetChan); + virtual ~ThreadLink() = default; + + virtual void PrepareToDestroy() override; + + virtual void SendMessage(mozilla::UniquePtr<Message> msg) override; + virtual void SendClose() override; + + virtual bool Unsound_IsClosed() const override; + virtual uint32_t Unsound_NumQueuedMessages() const override; + + protected: + MessageChannel* mTargetChan; +}; + +} // 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..d274dbf12f --- /dev/null +++ b/ipc/glue/MessagePump.cpp @@ -0,0 +1,449 @@ +/* -*- 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; + +NS_DEFINE_NAMED_CID(NS_TIMER_CID); + +#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(nsIEventTarget* 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); +} + +nsIEventTarget* MessagePump::GetXPCOMThread() { + if (mEventTarget) { + return mEventTarget; + } + + // Main thread + return GetMainThreadEventTarget(); +} + +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; +} + +#if defined(XP_WIN) + +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); + + base::ScopedNSAutoreleasePool autoReleasePool; + for (;;) { + autoReleasePool.Recycle(); + + 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; +} + +#endif // XP_WIN + +#if defined(MOZ_WIDGET_ANDROID) +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 TimeTicks& delayed_work_time) { + MOZ_CRASH("MessagePumpForAndroidUI should never ScheduleDelayedWork"); +} +#endif // defined(MOZ_WIDGET_ANDROID) diff --git a/ipc/glue/MessagePump.h b/ipc/glue/MessagePump.h new file mode 100644 index 0000000000..0b7ff11902 --- /dev/null +++ b/ipc/glue/MessagePump.h @@ -0,0 +1,174 @@ +/* -*- 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" +#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(nsIEventTarget* 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 nsIEventTarget* GetXPCOMThread() override; + + protected: + virtual ~MessagePump(); + + private: + // Only called by DoWorkRunnable. + void DoDelayedWork(base::MessagePump::Delegate* aDelegate); + + protected: + nsIEventTarget* 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(nsIEventTarget* 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. Currently only +// implemented for Win. +class MessagePumpForNonMainUIThreads final : public base::MessagePumpForUI, + public nsIThreadObserver { + public: + // We don't want xpcom refing, chromium controls our lifetime via + // RefCountedThreadSafe. + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override { return 2; } + NS_IMETHOD_(MozExternalRefCountType) Release(void) override { return 1; } + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + + NS_DECL_NSITHREADOBSERVER + + public: + explicit MessagePumpForNonMainUIThreads(nsIEventTarget* aEventTarget) + : mInWait(false), mWaitLock("mInWait") {} + + // The main run loop for this thread. + virtual void DoRunLoop() override; + + virtual nsIEventTarget* 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; + mozilla::Mutex mWaitLock; +}; +#endif // defined(XP_WIN) + +#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(nsIEventTarget* aEventTarget) + : mEventTarget(aEventTarget) {} + + virtual void Run(Delegate* delegate); + virtual void Quit(); + virtual void ScheduleWork(); + virtual void ScheduleDelayedWork(const base::TimeTicks& delayed_work_time); + virtual nsIEventTarget* GetXPCOMThread() { return mEventTarget; } + + private: + ~MessagePumpForAndroidUI() {} + MessagePumpForAndroidUI() {} + + nsIEventTarget* mEventTarget; +}; +#endif // defined(MOZ_WIDGET_ANDROID) + +} /* namespace ipc */ +} /* namespace mozilla */ + +#endif /* __IPC_GLUE_MESSAGEPUMP_H__ */ diff --git a/ipc/glue/MiniTransceiver.cpp b/ipc/glue/MiniTransceiver.cpp new file mode 100644 index 0000000000..11b376dcd4 --- /dev/null +++ b/ipc/glue/MiniTransceiver.cpp @@ -0,0 +1,241 @@ +/* -*- 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 "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> + +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, int 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. + auto cbuf = new char[CMSG_SPACE(sizeof(int) * aMaxNumFds)]; + aHdr->msg_control = cbuf; + aHdr->msg_controllen = CMSG_SPACE(sizeof(int) * aMaxNumFds); +} + +/** + * 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. + int num_fds = aMsg.file_descriptor_set()->size(); + + cmsghdr* cmsg = CMSG_FIRSTHDR(aHdr); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds); + aMsg.file_descriptor_set()->GetDescriptors( + reinterpret_cast<int*>(CMSG_DATA(cmsg))); + + // This number will be sent in the header of the message. So, we + // can check it at the other side. + aMsg.header()->num_fds = 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.file_descriptor_set()->CommitAll(); }); + + int num_fds = aMsg.file_descriptor_set()->size(); + 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(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); + msg->file_descriptor_set()->SetDescriptors(all_fds, num_all_fds); + + if (mDataBufClear == DataBufferClear::AfterReceiving) { + // Avoid content processes from reading the content of + // messages. + memset(databuf.get(), 0, msgsz); + } + + MOZ_ASSERT(msg->header()->num_fds == msg->file_descriptor_set()->size(), + "The number of file descriptors in the header is different from" + " the number actually received"); + + aMsg = std::move(*msg); + return true; +} diff --git a/ipc/glue/MiniTransceiver.h b/ipc/glue/MiniTransceiver.h new file mode 100644 index 0000000000..f7b482e3ca --- /dev/null +++ b/ipc/glue/MiniTransceiver.h @@ -0,0 +1,117 @@ +/* -*- 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(IPC::Message& aMsg); + inline bool RecvInfallible(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/PBackground.ipdl b/ipc/glue/PBackground.ipdl new file mode 100644 index 0000000000..c0de216610 --- /dev/null +++ b/ipc/glue/PBackground.ipdl @@ -0,0 +1,289 @@ +/* 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 PBackgroundStorage; +include protocol PBackgroundTest; +include protocol PBroadcastChannel; +include protocol PCache; +include protocol PCacheStorage; +include protocol PCacheStreamControl; +include protocol PClientManager; +include protocol PEndpointForReport; +include protocol PFileDescriptorSet; +include protocol PFileSystemRequest; +include protocol PGamepadEventChannel; +include protocol PGamepadTestChannel; +include protocol PHttpBackgroundChannel; +include protocol PIdleScheduler; +include protocol PRemoteLazyInputStream; +include protocol PMediaTransport; +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 PMIDIManager; +include protocol PMIDIPort; +include protocol PQuota; +include protocol PChildToParentStream; +include protocol PParentToChildStream; +include protocol PServiceWorker; +include protocol PServiceWorkerContainer; +include protocol PServiceWorkerManager; +include protocol PServiceWorkerRegistration; +include protocol PWebAuthnTransaction; +include protocol PUDPSocket; +include protocol PVerifySSLServerCert; +include protocol PVsync; +include protocol PRemoteDecoderManager; + +include DOMTypes; +include IPCBlob; +include IPCServiceWorkerDescriptor; +include IPCServiceWorkerRegistrationDescriptor; +include PBackgroundLSSharedTypes; +include PBackgroundSharedTypes; +include PBackgroundIDBSharedTypes; +include PFileSystemParams; +include ProtocolTypes; +include PSMIPCTypes; +include RemoteWorkerTypes; +include MIDITypes; + +include "mozilla/dom/cache/IPCUtils.h"; +include "mozilla/dom/quota/SerializationHelpers.h"; +include "mozilla/layers/LayersMessageUtils.h"; + +using mozilla::dom::cache::Namespace + from "mozilla/dom/cache/Types.h"; + +namespace mozilla { +namespace ipc { + +sync refcounted protocol PBackground +{ + manages PBackgroundDataBridge; + manages PBackgroundIDBFactory; + manages PBackgroundIndexedDBUtils; + manages PBackgroundSDBConnection; + manages PBackgroundLSDatabase; + manages PBackgroundLSObserver; + manages PBackgroundLSRequest; + manages PBackgroundLSSimpleRequest; + manages PBackgroundLocalStorageCache; + manages PBackgroundSessionStorageManager; + manages PBackgroundStorage; + manages PBackgroundTest; + manages PBroadcastChannel; + manages PCache; + manages PCacheStorage; + manages PCacheStreamControl; + manages PClientManager; + manages PEndpointForReport; + manages PFileDescriptorSet; + manages PFileSystemRequest; + manages PGamepadEventChannel; + manages PGamepadTestChannel; + manages PHttpBackgroundChannel; + manages PIdleScheduler; + manages PRemoteLazyInputStream; + manages PMediaTransport; + manages PRemoteWorker; + manages PRemoteWorkerController; + manages PRemoteWorkerService; + manages PSharedWorker; + manages PTemporaryIPCBlob; + manages PFileCreator; + manages PMessagePort; + manages PCameras; + manages PMIDIManager; + manages PMIDIPort; + manages PQuota; + manages PChildToParentStream; + manages PParentToChildStream; + manages PServiceWorker; + manages PServiceWorkerContainer; + manages PServiceWorkerManager; + manages PServiceWorkerRegistration; + manages PWebAuthnTransaction; + manages PUDPSocket; + manages PVerifySSLServerCert; + manages PVsync; + +parent: + // Only called at startup during mochitests to check the basic infrastructure. + async PBackgroundTest(nsCString testArg); + + async PBackgroundDataBridge(uint64_t channelID); + + 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); + + /** + * Asynchronously propagates the "last-pb-context-exited" observer + * notification to LocalStorage NextGen implementation so it can clear + * retained private-browsing in-memory Datastores. Using a (same-process) + * IPC message avoids the need for the main-thread nsIObserver to have a + * reference to the PBackground thread and directly dispatch a runnable to it. + */ + async LSClearPrivateBrowsing(); + + async PBackgroundLocalStorageCache(PrincipalInfo principalInfo, + nsCString originKey, + uint32_t privateBrowsingId); + + async PBackgroundSessionStorageManager(uint64_t aTopContextId); + + async PBackgroundStorage(nsString profilePath, uint32_t privateBrowsingId); + + 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 PChildToParentStream(); + + 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 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 PMIDIManager(); + async PMIDIPort(MIDIPortInfo portInfo, bool sysexEnabled); + + // 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 PMediaTransport(); + + async PVerifySSLServerCert(ByteArray aServerCert, + ByteArray[] aPeerCertChain, + nsCString aHostName, + int32_t aPort, + OriginAttributes aOriginAttributes, + ByteArray? aStapledOCSPResponse, + ByteArray? aSctsFromTLSExtension, + DelegatedCredentialInfoArg? aDcInfo, + uint32_t aProviderFlags, + uint32_t aCertVerifierFlags); + + async EnsureRDDProcessAndCreateBridge() + returns (nsresult rv, Endpoint<PRemoteDecoderManagerChild> aEndpoint); + +child: + async PCache(); + async PCacheStreamControl(); + + async PParentToChildStream(); + + async PRemoteWorker(RemoteWorkerData data); + +both: + // PRemoteLazyInputStream is created on the parent side only if the child + // starts a migration. + async PRemoteLazyInputStream(nsID aID, uint64_t aSize); + + async PFileDescriptorSet(FileDescriptor fd); +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/PBackgroundSharedTypes.ipdlh b/ipc/glue/PBackgroundSharedTypes.ipdlh new file mode 100644 index 0000000000..00cea24a2b --- /dev/null +++ b/ipc/glue/PBackgroundSharedTypes.ipdlh @@ -0,0 +1,73 @@ +/* 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; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/PBackgroundTest.ipdl b/ipc/glue/PBackgroundTest.ipdl new file mode 100644 index 0000000000..527cd4ce7d --- /dev/null +++ b/ipc/glue/PBackgroundTest.ipdl @@ -0,0 +1,20 @@ +/* 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. +protocol PBackgroundTest +{ + manager PBackground; + +child: + async __delete__(nsCString testArg); +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/PChildToParentStream.ipdl b/ipc/glue/PChildToParentStream.ipdl new file mode 100644 index 0000000000..ac0e2ce142 --- /dev/null +++ b/ipc/glue/PChildToParentStream.ipdl @@ -0,0 +1,47 @@ +/* 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; +include protocol PContent; +include protocol PSocketProcess; + +include "mozilla/layers/WebRenderMessageUtils.h"; + +using mozilla::wr::ByteBuffer from "mozilla/webrender/WebRenderTypes.h"; + +namespace mozilla { +namespace ipc { + +// This is protocol is the opposite of PParentToChildStream. Please keep these +// protocols in sync. +protocol PChildToParentStream +{ + manager PBackground or PContent or PSocketProcess; + +parent: + async Buffer(ByteBuffer aBuffer); + async Close(nsresult aRv); + +child: + // The remote stream can be used in 2 ways: it can start receiving data + // immediately after the creation of the child actor, or it can wait until + // the child stream is actually used. This second configuration is enabled by + // passing 'true' to delayedStart in AutoIPCStream CTOR. + // If we are delaying the reading, at the first use of the remote stream, we + // must activate the sending of data. This happens by calling this method. + async StartReading(); + + // The parent side has hit an error condition and has requested the child + // actor issue a Close() message. The close must be initiated by the child + // to avoid racing with an in-flight Buffer() message. + async RequestClose(nsresult aRv); + + // Stream is always destroyed from the parent side. This occurs if the + // parent encounters an error while writing to its pipe or if the child + // signals the stream should close by SendClose(). + async __delete__(); +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/PFileDescriptorSet.ipdl b/ipc/glue/PFileDescriptorSet.ipdl new file mode 100644 index 0000000000..e56fa48855 --- /dev/null +++ b/ipc/glue/PFileDescriptorSet.ipdl @@ -0,0 +1,23 @@ +/* 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; +include protocol PContent; +include protocol PSocketProcess; + +namespace mozilla { +namespace ipc { + +protocol PFileDescriptorSet +{ + manager PBackground or PContent or PSocketProcess; + +both: + async AddFileDescriptor(FileDescriptor fd); + + async __delete__(); +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/PIdleScheduler.ipdl b/ipc/glue/PIdleScheduler.ipdl new file mode 100644 index 0000000000..90e0350727 --- /dev/null +++ b/ipc/glue/PIdleScheduler.ipdl @@ -0,0 +1,61 @@ +/* -*- 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"; +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 refcounted 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(); + + // 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/PParentToChildStream.ipdl b/ipc/glue/PParentToChildStream.ipdl new file mode 100644 index 0000000000..6040dc8a98 --- /dev/null +++ b/ipc/glue/PParentToChildStream.ipdl @@ -0,0 +1,47 @@ +/* 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; +include protocol PContent; +include protocol PSocketProcess; + +include "mozilla/layers/WebRenderMessageUtils.h"; + +using mozilla::wr::ByteBuffer from "mozilla/webrender/WebRenderTypes.h"; + +namespace mozilla { +namespace ipc { + +// This is protocol is the opposite of PChildToParentStream. Please keep these +// protocols in sync. +protocol PParentToChildStream +{ + manager PBackground or PContent or PSocketProcess; + +child: + async Buffer(ByteBuffer aBuffer); + async Close(nsresult aRv); + +parent: + // The remote stream can be used in 2 ways: it can start receiving data + // immediately after the creation of the child actor, or it can wait until + // the child stream is actually used. This second configuration is enabled by + // passing 'true' to delayedStart in AutoIPCStream CTOR. + // If we are delaying the reading, at the first use of the remote stream, we + // must activate the sending of data. This happens by calling this method. + async StartReading(); + + // The parent side has hit an error condition and has requested the child + // actor issue a Close() message. The close must be initiated by the child + // to avoid racing with an in-flight Buffer() message. + async RequestClose(nsresult aRv); + + // Stream is always destroyed from the parent side. This occurs if the + // parent encounters an error while writing to its pipe or if the child + // signals the stream should close by SendClose(). + async __delete__(); +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProcessChild.cpp b/ipc/glue/ProcessChild.cpp new file mode 100644 index 0000000000..94ea41c66a --- /dev/null +++ b/ipc/glue/ProcessChild.cpp @@ -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/. */ + +#include "mozilla/ipc/ProcessChild.h" + +#include "nsDebug.h" + +#ifdef XP_WIN +# include <stdlib.h> // for _exit() +#else +# include <unistd.h> // for _exit() +#endif + +#include "mozilla/AppShutdown.h" +#include "mozilla/ipc/IOThreadChild.h" + +namespace mozilla { +namespace ipc { + +ProcessChild* ProcessChild::gProcessChild; + +static Atomic<bool> sExpectingShutdown(false); + +ProcessChild::ProcessChild(ProcessId aParentPid) + : ChildProcess(new IOThreadChild()), + mUILoop(MessageLoop::current()), + mParentPid(aParentPid) { + MOZ_ASSERT(mUILoop, "UILoop should be created by now"); + MOZ_ASSERT(!gProcessChild, "should only be one ProcessChild"); + gProcessChild = this; +} + +ProcessChild::~ProcessChild() { gProcessChild = nullptr; } + +/* static */ +void ProcessChild::NotifyImpendingShutdown() { sExpectingShutdown = true; } + +/* static */ +bool ProcessChild::ExpectingShutdown() { return sExpectingShutdown; } + +/* static */ +void ProcessChild::QuickExit() { AppShutdown::DoImmediateExit(); } + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProcessChild.h b/ipc/glue/ProcessChild.h new file mode 100644 index 0000000000..c2c49b565b --- /dev/null +++ b/ipc/glue/ProcessChild.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: */ +/* 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 "base/message_loop.h" +#include "base/process.h" + +#include "chrome/common/child_process.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); + virtual ~ProcessChild(); + + virtual bool Init(int aArgc, char* aArgv[]) = 0; + virtual void CleanUp() {} + + static MessageLoop* message_loop() { return gProcessChild->mUILoop; } + + static void NotifyImpendingShutdown(); + + 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; } + + private: + static ProcessChild* gProcessChild; + + MessageLoop* mUILoop; + ProcessId mParentPid; + + DISALLOW_EVIL_CONSTRUCTORS(ProcessChild); +}; + +} // 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..6805249e11 --- /dev/null +++ b/ipc/glue/ProcessUtils.h @@ -0,0 +1,80 @@ +/* -*- 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 "FileDescriptor.h" +#include "base/shared_memory.h" +#include "mozilla/Maybe.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: + SharedPreferenceSerializer(); + SharedPreferenceSerializer(SharedPreferenceSerializer&& aOther); + ~SharedPreferenceSerializer(); + + bool SerializeToSharedMemory(); + + 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(char* aPrefsHandleStr, + char* aPrefMapHandleStr, char* aPrefsLenStr, + char* aPrefMapSizeStr); + + const base::SharedMemoryHandle& GetPrefsHandle() const; + const FileDescriptor& GetPrefMapHandle() const; + + private: + DISALLOW_COPY_AND_ASSIGN(SharedPreferenceDeserializer); + Maybe<base::SharedMemoryHandle> mPrefsHandle; + 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 + +} // 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..fa113f2e58 --- /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(OS_NETBSD) +# include <pthread_np.h> +#endif + +namespace mozilla { +namespace ipc { + +void SetThisProcessName(const char* aName) { +#if defined(OS_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..d07faba6f4 --- /dev/null +++ b/ipc/glue/ProcessUtils_common.cpp @@ -0,0 +1,210 @@ +/* -*- 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/ipc/GeckoChildProcessHost.h" +#include "mozilla/UniquePtrExtensions.h" + +namespace mozilla { +namespace ipc { + +SharedPreferenceSerializer::SharedPreferenceSerializer() : mPrefMapSize(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() { + mPrefMapHandle = + Preferences::EnsureSnapshot(&mPrefMapSize).TakePlatformHandle(); + + // Serialize the early prefs. + nsAutoCStringN<1024> prefs; + Preferences::SerializePreferences(prefs); + 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 { + // Formats a pointer or pointer-sized-integer as a string suitable for passing + // in an arguments list. + auto formatPtrArg = [](auto arg) { + return nsPrintfCString("%zu", uintptr_t(arg)); + }; + +#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()); + aExtraOpts.push_back("-prefsHandle"); + aExtraOpts.push_back(formatPtrArg(GetPrefsHandle().get()).get()); + aExtraOpts.push_back("-prefMapHandle"); + aExtraOpts.push_back(formatPtrArg(GetPrefMapHandle().get()).get()); +#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. + aExtraOpts.push_back("-prefsLen"); + aExtraOpts.push_back(formatPtrArg(GetPrefsLength()).get()); + aExtraOpts.push_back("-prefMapSize"); + aExtraOpts.push_back(formatPtrArg(GetPrefMapSize()).get()); +} + +#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( + char* aPrefsHandleStr, char* aPrefMapHandleStr, char* aPrefsLenStr, + char* aPrefMapSizeStr) { +#ifdef XP_WIN + MOZ_ASSERT(aPrefsHandleStr && aPrefMapHandleStr, "Can't be null"); +#endif + MOZ_ASSERT(aPrefsLenStr && aPrefMapSizeStr, "Can't be null"); + + // Parses an arg containing a pointer-sized-integer. + auto parseUIntPtrArg = [](char*& aArg) { + // ContentParent uses %zu to print a word-sized unsigned integer. So + // even though strtoull() returns a long long int, it will fit in a + // uintptr_t. + return uintptr_t(strtoull(aArg, &aArg, 10)); + }; + +#ifdef XP_WIN + auto parseHandleArg = [&](char*& aArg) { + return HANDLE(parseUIntPtrArg(aArg)); + }; + + mPrefsHandle = Some(parseHandleArg(aPrefsHandleStr)); + if (!aPrefsHandleStr || aPrefsHandleStr[0] != '\0') { + return false; + } + + FileDescriptor::UniquePlatformHandle handle( + parseHandleArg(aPrefMapHandleStr)); + if (!aPrefMapHandleStr || aPrefMapHandleStr[0] != '\0') { + return false; + } + + mPrefMapHandle.emplace(std::move(handle)); +#endif + + mPrefsLen = Some(parseUIntPtrArg(aPrefsLenStr)); + if (!aPrefsLenStr || aPrefsLenStr[0] != '\0') { + return false; + } + + mPrefMapSize = Some(parseUIntPtrArg(aPrefMapSizeStr)); + if (!aPrefMapSizeStr || aPrefMapSizeStr[0] != '\0') { + return false; + } + +#ifdef ANDROID + // Android is different; get the FD via gPrefsFd instead of a fixed fd. + MOZ_RELEASE_ASSERT(gPrefsFd != -1); + mPrefsHandle = Some(base::FileDescriptor(gPrefsFd, /* auto_close */ true)); + + mPrefMapHandle.emplace(UniqueFileHandle(gPrefMapFd)); +#elif XP_UNIX + mPrefsHandle = Some(base::FileDescriptor(kPrefsFileDescriptor, + /* auto_close */ true)); + + mPrefMapHandle.emplace(UniqueFileHandle(kPrefMapFileDescriptor)); +#endif + + if (mPrefsHandle.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(*mPrefsHandle, /* 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 base::SharedMemoryHandle& SharedPreferenceDeserializer::GetPrefsHandle() + const { + MOZ_ASSERT(mPrefsHandle.isSome()); + + return mPrefsHandle.ref(); +} + +const FileDescriptor& SharedPreferenceDeserializer::GetPrefMapHandle() const { + MOZ_ASSERT(mPrefMapHandle.isSome()); + + return mPrefMapHandle.ref(); +} + +} // 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..0991738f9a --- /dev/null +++ b/ipc/glue/ProcessUtils_mac.mm @@ -0,0 +1,19 @@ +/* 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 "mozilla/plugins/PluginUtilsOSX.h" + +namespace mozilla { +namespace ipc { + +void SetThisProcessName(const char* aName) { + mozilla::plugins::PluginUtilsOSX::SetProcessName(aName); +} + +} // 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..6b004dd4e5 --- /dev/null +++ b/ipc/glue/ProtocolMessageUtils.h @@ -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 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 "mozilla/Assertions.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/ipc/Transport.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; + +template <> +struct ParamTraits<mozilla::ipc::ActorHandle> { + typedef mozilla::ipc::ActorHandle paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + IPC::WriteParam(aMsg, aParam.mId); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + int id; + if (IPC::ReadParam(aMsg, aIter, &id)) { + aResult->mId = id; + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"(%d)", aParam.mId)); + } +}; + +template <class PFooSide> +struct ParamTraits<mozilla::ipc::Endpoint<PFooSide>> { + typedef mozilla::ipc::Endpoint<PFooSide> paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + IPC::WriteParam(aMsg, aParam.mValid); + if (!aParam.mValid) { + return; + } + + IPC::WriteParam(aMsg, static_cast<uint32_t>(aParam.mMode)); + + // We duplicate the descriptor so that our own file descriptor remains + // valid after the write. An alternative would be to set + // aParam.mTransport.mValid to false, but that won't work because aParam + // is const. + mozilla::ipc::TransportDescriptor desc = + mozilla::ipc::DuplicateDescriptor(aParam.mTransport); + IPC::WriteParam(aMsg, desc); + + IPC::WriteParam(aMsg, aParam.mMyPid); + IPC::WriteParam(aMsg, aParam.mOtherPid); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + MOZ_RELEASE_ASSERT(!aResult->mValid); + + if (!IPC::ReadParam(aMsg, aIter, &aResult->mValid)) { + return false; + } + if (!aResult->mValid) { + // Object is empty, but read succeeded. + return true; + } + + uint32_t mode; + if (!IPC::ReadParam(aMsg, aIter, &mode) || + !IPC::ReadParam(aMsg, aIter, &aResult->mTransport) || + !IPC::ReadParam(aMsg, aIter, &aResult->mMyPid) || + !IPC::ReadParam(aMsg, aIter, &aResult->mOtherPid)) { + return false; + } + aResult->mMode = Channel::Mode(mode); + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"Endpoint")); + } +}; + +template <class PFooSide> +struct ParamTraits<mozilla::ipc::ManagedEndpoint<PFooSide>> { + typedef mozilla::ipc::ManagedEndpoint<PFooSide> paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + IPC::WriteParam(aMsg, aParam.mId); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + MOZ_RELEASE_ASSERT(aResult->mId == 0); + + if (!IPC::ReadParam(aMsg, aIter, &aResult->mId)) { + return false; + } + return true; + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(StringPrintf(L"ManagedEndpoint")); + } +}; + +} // namespace IPC + +namespace mozilla::ipc { +template <> +struct IPDLParamTraits<FileDescriptor> { + typedef FileDescriptor paramType; + + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const paramType& aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + 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..1c85da2f3d --- /dev/null +++ b/ipc/glue/ProtocolUtils.cpp @@ -0,0 +1,909 @@ +/* -*- 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 OS_POSIX +# 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/Transport.h" +#include "mozilla/StaticMutex.h" +#if defined(DEBUG) || defined(FUZZING) +# include "mozilla/Tokenizer.h" +#endif +#include "mozilla/Unused.h" +#include "nsPrintfCString.h" + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) +# include "mozilla/sandboxTarget.h" +#endif + +#if defined(XP_WIN) +# include "aclapi.h" +# include "sddl.h" +#endif + +using namespace IPC; + +using base::GetCurrentProcId; +using base::ProcessHandle; +using base::ProcessId; + +namespace mozilla { + +#if defined(XP_WIN) +// Generate RAII classes for LPTSTR and PSECURITY_DESCRIPTOR. +MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedLPTStr, + std::remove_pointer_t<LPTSTR>, + ::LocalFree) +MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE( + ScopedPSecurityDescriptor, std::remove_pointer_t<PSECURITY_DESCRIPTOR>, + ::LocalFree) +#endif + +namespace ipc { + +IPCResult IPCResult::Fail(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()); + return IPCResult(false); +} + +#if defined(XP_WIN) +bool DuplicateHandle(HANDLE aSourceHandle, DWORD aTargetProcessId, + HANDLE* aTargetHandle, DWORD aDesiredAccess, + DWORD aOptions) { + // If our process is the target just duplicate the handle. + if (aTargetProcessId == base::GetCurrentProcId()) { + return !!::DuplicateHandle(::GetCurrentProcess(), aSourceHandle, + ::GetCurrentProcess(), aTargetHandle, + aDesiredAccess, false, aOptions); + } + +# if defined(MOZ_SANDBOX) + // Try the broker next (will fail if not sandboxed). + if (SandboxTarget::Instance()->BrokerDuplicateHandle( + aSourceHandle, aTargetProcessId, aTargetHandle, aDesiredAccess, + aOptions)) { + return true; + } +# endif + + // Finally, see if we already have access to the process. + ScopedProcessHandle targetProcess( + OpenProcess(PROCESS_DUP_HANDLE, FALSE, aTargetProcessId)); + if (!targetProcess) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCTransportFailureReason, + "Failed to open target process."_ns); + return false; + } + + return !!::DuplicateHandle(::GetCurrentProcess(), aSourceHandle, + targetProcess, aTargetHandle, aDesiredAccess, + FALSE, aOptions); +} +#endif + +void AnnotateSystemError() { + int64_t error = 0; +#if defined(XP_WIN) + error = ::GetLastError(); +#elif defined(OS_POSIX) + 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) +// 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, 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 && + t.AsString() == 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 "][%d%s%d] [%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 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); +} + +void TableToArray(const nsTHashtable<nsPtrHashKey<void>>& aTable, + nsTArray<void*>& aArray) { + uint32_t i = 0; + void** elements = aArray.AppendElements(aTable.Count()); + for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) { + elements[i] = iter.Get()->GetKey(); + ++i; + } +} + +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(); +} + +ActorLifecycleProxy::~ActorLifecycleProxy() { + // 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; +} + +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) { + // FIXME: It would be nice to have this print out the name of the + // misbehaving actor, to help people notice it's their fault! + NS_WARNING( + "Actor destructor called before IPC lifecycle complete!\n" + "References to this actor may unexpectedly dangle!"); + + 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, SharedMemory::SharedMemoryType aType, bool aUnsafe, + int32_t* aId) { + return mToplevel->CreateSharedMemory(aSize, aType, 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(); +} + +void IProtocol::SetEventTargetForActor(IProtocol* aActor, + nsISerialEventTarget* aEventTarget) { + // Make sure we have a manager for the internal method to access. + aActor->SetManager(this); + mToplevel->SetEventTargetForActorInternal(aActor, aEventTarget); +} + +void IProtocol::ReplaceEventTargetForActor(IProtocol* aActor, + nsISerialEventTarget* aEventTarget) { + MOZ_ASSERT(aActor->Manager()); + mToplevel->ReplaceEventTargetForActor(aActor, aEventTarget); +} + +nsISerialEventTarget* IProtocol::GetActorEventTarget() { + // FIXME: It's a touch sketchy that we don't return a strong reference here. + RefPtr<nsISerialEventTarget> target = GetActorEventTarget(this); + return target; +} +already_AddRefed<nsISerialEventTarget> IProtocol::GetActorEventTarget( + IProtocol* aActor) { + return mToplevel->GetActorEventTarget(aActor); +} + +ProcessId IProtocol::OtherPid() const { return mToplevel->OtherPid(); } + +void IProtocol::SetId(int32_t aId) { + MOZ_ASSERT(mId == aId || mLinkStatus == LinkStatus::Inactive); + mId = aId; +} + +Maybe<IProtocol*> IProtocol::ReadActor(const IPC::Message* aMessage, + PickleIterator* aIter, bool aNullable, + const char* aActorDescription, + int32_t aProtocolTypeId) { + int32_t id; + if (!IPC::ReadParam(aMessage, aIter, &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) const { + HandleFatalError(aErrorMsg); +} + +void IProtocol::HandleFatalError(const char* aErrorMsg) const { + if (IProtocol* manager = Manager()) { + manager->HandleFatalError(aErrorMsg); + return; + } + + mozilla::ipc::FatalError(aErrorMsg, mSide == ParentSide); +} + +bool IProtocol::AllocShmem(size_t aSize, + Shmem::SharedMemory::SharedMemoryType aType, + 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, aType, false, &id)); + if (!rawmem) { + return false; + } + + *aOutMem = Shmem(Shmem::PrivateIPDLCaller(), rawmem, id); + return true; +} + +bool IProtocol::AllocUnsafeShmem(size_t aSize, + Shmem::SharedMemory::SharedMemoryType aType, + 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, aType, true, &id)); + if (!rawmem) { + return false; + } + + *aOutMem = Shmem(Shmem::PrivateIPDLCaller(), rawmem, id); + 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(Shmem::PrivateIPDLCaller()); + 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(IPC::Message* aMsg) { + UniquePtr<IPC::Message> msg(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(msg)); + return true; + } + + NS_WARNING("IPC message discarded: actor cannot send"); + return false; +} + +bool IProtocol::ChannelSend(IPC::Message* aMsg, IPC::Message* aReply) { + UniquePtr<IPC::Message> msg(aMsg); + if (CanSend()) { + return GetIPCChannel()->Send(std::move(msg), aReply); + } + + NS_WARNING("IPC message discarded: actor cannot send"); + return false; +} + +bool IProtocol::ChannelCall(IPC::Message* aMsg, IPC::Message* aReply) { + UniquePtr<IPC::Message> msg(aMsg); + if (CanSend()) { + return GetIPCChannel()->Call(std::move(msg), aReply); + } + + NS_WARNING("IPC message discarded: actor cannot send"); + return false; +} + +void IProtocol::ActorConnected() { + if (mLinkStatus != LinkStatus::Inactive) { + return; + } + + 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"); + + // 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(this); + ActorDestroy(aWhy); + mLinkStatus = LinkStatus::Destroyed; +} + +IToplevelProtocol::IToplevelProtocol(const char* aName, ProtocolId aProtoId, + Side aSide) + : IProtocol(aProtoId, aSide), + mOtherPid(mozilla::ipc::kInvalidProcessId), + mLastLocalId(0), + mEventTargetMutex("ProtocolEventTargetMutex"), + mChannel(aName, this) { + mToplevel = this; +} + +base::ProcessId IToplevelProtocol::OtherPid() const { + base::ProcessId pid = OtherPidMaybeInvalid(); + MOZ_RELEASE_ASSERT(pid != kInvalidProcessId); + return pid; +} + +void IToplevelProtocol::SetOtherProcessId(base::ProcessId aOtherPid) { + mOtherPid = aOtherPid; +} + +bool IToplevelProtocol::Open(UniquePtr<Transport> aTransport, + base::ProcessId aOtherPid, MessageLoop* aThread, + mozilla::ipc::Side aSide) { + SetOtherProcessId(aOtherPid); + return GetIPCChannel()->Open(std::move(aTransport), aThread, aSide); +} + +bool IToplevelProtocol::Open(MessageChannel* aChannel, + nsISerialEventTarget* aEventTarget, + mozilla::ipc::Side aSide) { + SetOtherProcessId(base::GetCurrentProcId()); + return GetIPCChannel()->Open(aChannel, aEventTarget, aSide); +} + +bool IToplevelProtocol::OpenOnSameThread(MessageChannel* aChannel, Side aSide) { + SetOtherProcessId(base::GetCurrentProcId()); + return GetIPCChannel()->OpenOnSameThread(aChannel, 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(); + } + int32_t id = RegisterID(aRouted, NextId()); + + // Inherit our event target from our manager. + if (IProtocol* manager = aRouted->Manager()) { + MutexAutoLock lock(mEventTargetMutex); + if (nsCOMPtr<nsISerialEventTarget> target = + mEventTargetMap.Get(manager->Id())) { + MOZ_ASSERT(!mEventTargetMap.Contains(id), + "Don't insert with an existing ID"); + mEventTargetMap.Put(id, target); + } + } + + return id; +} + +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.Put(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); + + MutexAutoLock lock(mEventTargetMutex); + mEventTargetMap.Remove(aId); +} + +Shmem::SharedMemory* IToplevelProtocol::CreateSharedMemory( + size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, bool aUnsafe, + Shmem::id_t* aId) { + RefPtr<Shmem::SharedMemory> segment( + Shmem::Alloc(Shmem::PrivateIPDLCaller(), aSize, aType, aUnsafe)); + if (!segment) { + return nullptr; + } + int32_t id = NextId(); + Shmem shmem(Shmem::PrivateIPDLCaller(), segment.get(), id); + + base::ProcessId pid = +#ifdef ANDROID + // We use OtherPidMaybeInvalid() because on Android this method is + // actually called on an unconnected protocol, but Android's shared memory + // implementation doesn't actually use the PID. + OtherPidMaybeInvalid(); +#else + OtherPid(); +#endif + + UniquePtr<Message> descriptor = + shmem.ShareTo(Shmem::PrivateIPDLCaller(), pid, MSG_ROUTING_CONTROL); + if (!descriptor) { + return nullptr; + } + Unused << GetIPCChannel()->Send(std::move(descriptor)); + + *aId = shmem.Id(Shmem::PrivateIPDLCaller()); + Shmem::SharedMemory* rawSegment = segment.get(); + MOZ_ASSERT(!mShmemMap.Contains(*aId), "Don't insert with an existing ID"); + mShmemMap.Put(*aId, segment.forget().take()); + return rawSegment; +} + +Shmem::SharedMemory* IToplevelProtocol::LookupSharedMemory(Shmem::id_t aId) { + return mShmemMap.Get(aId); +} + +bool IToplevelProtocol::IsTrackingSharedMemory(Shmem::SharedMemory* segment) { + for (const auto& iter : mShmemMap) { + if (segment == iter.GetData()) { + return true; + } + } + return false; +} + +bool IToplevelProtocol::DestroySharedMemory(Shmem& shmem) { + Shmem::id_t aId = shmem.Id(Shmem::PrivateIPDLCaller()); + Shmem::SharedMemory* segment = LookupSharedMemory(aId); + if (!segment) { + return false; + } + + UniquePtr<Message> descriptor = + shmem.UnshareFrom(Shmem::PrivateIPDLCaller(), MSG_ROUTING_CONTROL); + + MOZ_ASSERT(mShmemMap.Contains(aId), + "Attempting to remove an ID not in the shmem map"); + mShmemMap.Remove(aId); + Shmem::Dealloc(Shmem::PrivateIPDLCaller(), segment); + + MessageChannel* channel = GetIPCChannel(); + if (!channel->CanSend()) { + return true; + } + + return descriptor && channel->Send(std::move(descriptor)); +} + +void IToplevelProtocol::DeallocShmems() { + for (const auto& cit : mShmemMap) { + Shmem::Dealloc(Shmem::PrivateIPDLCaller(), cit.GetData()); + } + mShmemMap.Clear(); +} + +bool IToplevelProtocol::ShmemCreated(const Message& aMsg) { + Shmem::id_t id; + RefPtr<Shmem::SharedMemory> rawmem( + Shmem::OpenExisting(Shmem::PrivateIPDLCaller(), aMsg, &id, true)); + if (!rawmem) { + return false; + } + MOZ_ASSERT(!mShmemMap.Contains(id), "Don't insert with an existing ID"); + mShmemMap.Put(id, rawmem.forget().take()); + return true; +} + +bool IToplevelProtocol::ShmemDestroyed(const Message& aMsg) { + Shmem::id_t id; + PickleIterator iter = PickleIterator(aMsg); + if (!IPC::ReadParam(&aMsg, &iter, &id)) { + return false; + } + aMsg.EndRead(iter); + + Shmem::SharedMemory* rawmem = LookupSharedMemory(id); + if (rawmem) { + MOZ_ASSERT(mShmemMap.Contains(id), + "Attempting to remove an ID not in the shmem map"); + mShmemMap.Remove(id); + Shmem::Dealloc(Shmem::PrivateIPDLCaller(), rawmem); + } + return true; +} + +already_AddRefed<nsISerialEventTarget> IToplevelProtocol::GetMessageEventTarget( + const Message& aMsg) { + int32_t route = aMsg.routing_id(); + + Maybe<MutexAutoLock> lock; + lock.emplace(mEventTargetMutex); + + nsCOMPtr<nsISerialEventTarget> target = mEventTargetMap.Get(route); + + if (aMsg.is_constructor()) { + ActorHandle handle; + PickleIterator iter = PickleIterator(aMsg); + if (!IPC::ReadParam(&aMsg, &iter, &handle)) { + return nullptr; + } + +#ifdef DEBUG + // If this function is called more than once for the same message, the actor + // handle ID will already be in the map, but it should have the same target. + nsCOMPtr<nsISerialEventTarget> existingTgt = + mEventTargetMap.Get(handle.mId); + MOZ_ASSERT(existingTgt == target || existingTgt == nullptr); +#endif /* DEBUG */ + + mEventTargetMap.Put(handle.mId, target); + } + + return target.forget(); +} + +already_AddRefed<nsISerialEventTarget> IToplevelProtocol::GetActorEventTarget( + IProtocol* aActor) { + MOZ_RELEASE_ASSERT(aActor->Id() != kNullActorId && + aActor->Id() != kFreedActorId); + + MutexAutoLock lock(mEventTargetMutex); + nsCOMPtr<nsISerialEventTarget> target = mEventTargetMap.Get(aActor->Id()); + return target.forget(); +} + +nsISerialEventTarget* IToplevelProtocol::GetActorEventTarget() { + // The EventTarget of a ToplevelProtocol shall never be set. + return nullptr; +} + +void IToplevelProtocol::SetEventTargetForActorInternal( + IProtocol* aActor, nsISerialEventTarget* aEventTarget) { + // The EventTarget of a ToplevelProtocol shall never be set. + MOZ_RELEASE_ASSERT(aActor != this); + + // We should only call this function on actors that haven't been used for IPC + // code yet. Otherwise we'll be posting stuff to the wrong event target before + // we're called. + MOZ_RELEASE_ASSERT(aActor->Id() == kNullActorId || + aActor->Id() == kFreedActorId); + + MOZ_ASSERT(aActor->Manager() && aActor->ToplevelProtocol() == this); + + // Register the actor early. When it's registered again, it will keep the same + // ID. + int32_t id = Register(aActor); + aActor->SetId(id); + + MutexAutoLock lock(mEventTargetMutex); + // FIXME bug 1445121 - sometimes the id is already mapped. + mEventTargetMap.Put(id, aEventTarget); +} + +void IToplevelProtocol::ReplaceEventTargetForActor( + IProtocol* aActor, nsISerialEventTarget* aEventTarget) { + // The EventTarget of a ToplevelProtocol shall never be set. + MOZ_RELEASE_ASSERT(aActor != this); + + int32_t id = aActor->Id(); + // The ID of the actor should have existed. + MOZ_RELEASE_ASSERT(id != kNullActorId && id != kFreedActorId); + + MutexAutoLock lock(mEventTargetMutex); + MOZ_ASSERT(mEventTargetMap.Contains(id), "Only replace an existing ID"); + mEventTargetMap.Put(id, aEventTarget); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/ProtocolUtils.h b/ipc/glue/ProtocolUtils.h new file mode 100644 index 0000000000..4dc14b0a70 --- /dev/null +++ b/ipc/glue/ProtocolUtils.h @@ -0,0 +1,728 @@ +/* -*- 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 1 + +#include <cstddef> +#include <cstdint> +#include <utility> +#include "IPCMessageStart.h" +#include "base/basictypes.h" +#include "base/process.h" +#include "chrome/common/ipc_message.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Scoped.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 "nsDataHashtable.h" +#include "nsDebug.h" +#include "nsISupports.h" +#include "nsTArrayForwardDeclare.h" +#include "nsTHashtable.h" + +// XXX Things that could be replaced by a forward header +#include "mozilla/ipc/Transport.h" // for Transport + +// 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 { + 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; +class nsUint32HashKey; + +namespace mozilla { +class SchedulerGroup; + +namespace dom { +class ContentParent; +} // namespace dom + +namespace net { +class NeckoParent; +} // namespace net + +namespace ipc { + +#ifdef FUZZING +class ProtocolFuzzerHelper; +#endif + +#ifdef XP_WIN +const base::ProcessHandle kInvalidProcessHandle = INVALID_HANDLE_VALUE; + +// In theory, on Windows, this is a valid process ID, but in practice they are +// currently divisible by four. Process IDs share the kernel handle allocation +// code and they are guaranteed to be divisible by four. +// As this could change for process IDs we shouldn't generally rely on this +// property, however even if that were to change, it seems safe to rely on this +// particular value never being used. +const base::ProcessId kInvalidProcessId = kuint32max; +#else +const base::ProcessHandle kInvalidProcessHandle = -1; +const base::ProcessId kInvalidProcessId = -1; +#endif + +// Scoped base::ProcessHandle to ensure base::CloseProcessHandle is called. +struct ScopedProcessHandleTraits { + typedef base::ProcessHandle type; + + static type empty() { return kInvalidProcessHandle; } + + static void release(type aProcessHandle) { + if (aProcessHandle && aProcessHandle != kInvalidProcessHandle) { + base::CloseProcessHandle(aProcessHandle); + } + } +}; +typedef mozilla::Scoped<ScopedProcessHandleTraits> ScopedProcessHandle; + +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 IToplevelProtocol; +class ActorLifecycleProxy; + +class IProtocol : public HasResultCodes { + public: + enum ActorDestroyReason { + FailedConstructor, + Deletion, + AncestorDeletion, + NormalShutdown, + AbnormalShutdown + }; + + typedef base::ProcessId ProcessId; + typedef IPC::Message Message; + typedef IPC::MessageInfo MessageInfo; + + IProtocol(ProtocolId aProtoId, Side aSide) + : mId(0), + mProtocolId(aProtoId), + mSide(aSide), + mLinkStatus(LinkStatus::Inactive), + mLifecycleProxy(nullptr), + mManager(nullptr), + mToplevel(nullptr) {} + + IToplevelProtocol* ToplevelProtocol() { 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, + SharedMemory::SharedMemoryType aType, + 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; + + // Sets an event target to which all messages for aActor will be + // dispatched. This method must be called before right before the SendPFoo + // message for aActor is sent. And SendPFoo *must* be called if + // SetEventTargetForActor is called. The receiver when calling + // SetEventTargetForActor must be the actor that will be the manager for + // aActor. + void SetEventTargetForActor(IProtocol* aActor, + nsISerialEventTarget* aEventTarget); + + // Replace the event target for the messages of aActor. There must not be + // any messages of aActor in the task queue, or we might run into some + // unexpected behavior. + void ReplaceEventTargetForActor(IProtocol* aActor, + nsISerialEventTarget* aEventTarget); + + nsISerialEventTarget* GetActorEventTarget(); + already_AddRefed<nsISerialEventTarget> GetActorEventTarget(IProtocol* aActor); + + ProcessId OtherPid() const; + + // 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; } + + 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(const IPC::Message* aMessage, + PickleIterator* aIter, bool aNullable, + const char* aActorDescription, + int32_t aProtocolTypeId); + + virtual Result OnMessageReceived(const Message& aMessage) = 0; + virtual Result OnMessageReceived(const Message& aMessage, + Message*& aReply) = 0; + virtual Result OnCallReceived(const Message& aMessage, Message*& aReply) = 0; + bool AllocShmem(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, + Shmem* aOutMem); + bool AllocUnsafeShmem(size_t aSize, + Shmem::SharedMemory::SharedMemoryType aType, + Shmem* aOutMem); + bool DeallocShmem(Shmem& aMem); + + void FatalError(const char* const aErrorMsg) const; + virtual void HandleFatalError(const char* aErrorMsg) const; + + protected: + virtual ~IProtocol(); + + friend class IToplevelProtocol; + friend class ActorLifecycleProxy; + + 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(IPC::Message* aMsg); + bool ChannelSend(IPC::Message* aMsg, IPC::Message* aReply); + bool ChannelCall(IPC::Message* aMsg, IPC::Message* aReply); + template <typename Value> + void ChannelSend(IPC::Message* aMsg, ResolveCallback<Value>&& aResolve, + RejectCallback&& aReject) { + UniquePtr<IPC::Message> msg(aMsg); + if (CanSend()) { + GetIPCChannel()->Send(std::move(msg), this, std::move(aResolve), + std::move(aReject)); + } else { + NS_WARNING("IPC message discarded: actor cannot send"); + 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; + + // 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() {} + + // 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() { + if (Manager()) { + Manager()->DeallocManagee(mProtocolId, this); + } + } + + static const int32_t kNullActorId = 0; + static const int32_t kFreedActorId = 1; + + private: + 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__) + +/** + * All message deserializer and message handler should return this + * type via 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. + */ +class IPCResult { + public: + static IPCResult Ok() { return IPCResult(true); } + static IPCResult Fail(NotNull<IProtocol*> aActor, const char* aWhere, + const char* aWhy = ""); + MOZ_IMPLICIT operator bool() const { return mSuccess; } + + private: + explicit IPCResult(bool aResult) : mSuccess(aResult) {} + bool mSuccess; +}; + +template <class PFooSide> +class Endpoint; + +template <class PFooSide> +class ManagedEndpoint; + +/** + * All top-level protocols should inherit this class. + * + * IToplevelProtocol tracks all top-level protocol actors created from + * this protocol actor. + */ +class IToplevelProtocol : public IProtocol { +#ifdef FUZZING + friend class mozilla::ipc::ProtocolFuzzerHelper; +#endif + + 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, + SharedMemory::SharedMemoryType aType, + 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; } + + // NOTE: The target actor's Manager must already be set. + void SetEventTargetForActorInternal(IProtocol* aActor, + nsISerialEventTarget* aEventTarget); + void ReplaceEventTargetForActor(IProtocol* aActor, + nsISerialEventTarget* aEventTarget); + nsISerialEventTarget* GetActorEventTarget(); + already_AddRefed<nsISerialEventTarget> GetActorEventTarget(IProtocol* aActor); + + ProcessId OtherPid() const; + void SetOtherProcessId(base::ProcessId aOtherPid); + + virtual void OnChannelClose() = 0; + virtual void OnChannelError() = 0; + virtual void ProcessingError(Result aError, const char* aMsgName) {} + virtual void OnChannelConnected(int32_t peer_pid) {} + + bool Open(UniquePtr<Transport> aTransport, base::ProcessId aOtherPid, + MessageLoop* aThread = nullptr, + mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide); + + bool Open(MessageChannel* aChannel, 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(MessageChannel* aChannel, + 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 + + virtual void EnteredCxxStack() {} + virtual void ExitedCxxStack() {} + virtual void EnteredCall() {} + virtual void ExitedCall() {} + + bool IsOnCxxStack() const; + + virtual RacyInterruptPolicy MediateInterruptRace(const MessageInfo& parent, + const MessageInfo& child) { + return RIPChildWins; + } + + /** + * Return true if windows messages can be handled while waiting for a reply + * to a sync IPDL message. + */ + virtual bool HandleWindowsMessages(const Message& aMsg) const { return true; } + + virtual void OnEnteredSyncSend() {} + virtual void OnExitedSyncSend() {} + + virtual void ProcessRemoteNativeEventsInInterruptCall() {} + + virtual void OnChannelReceivedMessage(const Message& aMsg) {} + + void OnIPCChannelOpened() { ActorConnected(); } + + already_AddRefed<nsISerialEventTarget> GetMessageEventTarget( + const Message& aMsg); + + base::ProcessId OtherPidMaybeInvalid() const { return mOtherPid; } + + private: + int32_t NextId(); + + template <class T> + using IDMap = nsDataHashtable<nsUint32HashKey, T>; + + base::ProcessId mOtherPid; + + // NOTE NOTE NOTE + // Used to be on mState + int32_t mLastLocalId; + IDMap<IProtocol*> mActorMap; + IDMap<Shmem::SharedMemory*> mShmemMap; + + // XXX: We no longer need mEventTargetMap for Quantum DOM, so it may be + // worthwhile to remove it before people start depending on it for other weird + // things. + Mutex mEventTargetMutex; + IDMap<nsCOMPtr<nsISerialEventTarget>> mEventTargetMap; + + MessageChannel mChannel; +}; + +class IShmemAllocator { + public: + virtual bool AllocShmem(size_t aSize, + mozilla::ipc::SharedMemory::SharedMemoryType aShmType, + mozilla::ipc::Shmem* aShmem) = 0; + virtual bool AllocUnsafeShmem( + size_t aSize, mozilla::ipc::SharedMemory::SharedMemoryType aShmType, + 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::SharedMemory::SharedMemoryType aShmType, \ + mozilla::ipc::Shmem* aShmem) override { \ + return aImplClass::AllocShmem(aSize, aShmType, aShmem); \ + } \ + virtual bool AllocUnsafeShmem( \ + size_t aSize, mozilla::ipc::SharedMemory::SharedMemoryType aShmType, \ + mozilla::ipc::Shmem* aShmem) override { \ + return aImplClass::AllocUnsafeShmem(aSize, aShmType, 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, const char* aFilter); +#endif + +inline bool LoggingEnabledFor(const char* aTopLevelProtocol) { +#if defined(DEBUG) || defined(FUZZING) + return LoggingEnabledFor(aTopLevelProtocol, 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); + +// 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); + +#if defined(XP_WIN) +// This is a restricted version of Windows' DuplicateHandle() function +// that works inside the sandbox and can send handles but not retrieve +// them. Unlike DuplicateHandle(), it takes a process ID rather than +// a process handle. It returns true on success, false otherwise. +bool DuplicateHandle(HANDLE aSourceHandle, DWORD aTargetProcessId, + HANDLE* aTargetHandle, DWORD aDesiredAccess, + DWORD aOptions); +#endif + +/** + * 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; } + + 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; +}; + +void TableToArray(const nsTHashtable<nsPtrHashKey<void>>& aTable, + nsTArray<void*>& aArray); + +} // namespace ipc + +template <typename Protocol> +class ManagedContainer : public nsTHashtable<nsPtrHashKey<Protocol>> { + typedef nsTHashtable<nsPtrHashKey<Protocol>> BaseClass; + + public: + // Having the core logic work on void pointers, rather than typed pointers, + // means that we can have one instance of this code out-of-line, rather + // than several hundred instances of this code out-of-lined. (Those + // repeated instances don't necessarily get folded together by the linker + // because they contain member offsets and such that differ between the + // functions.) We do have to pay for it with some eye-bleedingly bad casts, + // though. + void ToArray(nsTArray<Protocol*>& aArray) const { + ::mozilla::ipc::TableToArray( + *reinterpret_cast<const nsTHashtable<nsPtrHashKey<void>>*>( + static_cast<const BaseClass*>(this)), + reinterpret_cast<nsTArray<void*>&>(aArray)); + } +}; + +template <typename Protocol> +Protocol* LoneManagedOrNullAsserts( + const ManagedContainer<Protocol>& aManagees) { + if (aManagees.IsEmpty()) { + return nullptr; + } + MOZ_ASSERT(aManagees.Count() == 1); + return aManagees.ConstIter().Get()->GetKey(); +} + +template <typename Protocol> +Protocol* SingleManagedOrNull(const ManagedContainer<Protocol>& aManagees) { + if (aManagees.Count() != 1) { + return nullptr; + } + return aManagees.ConstIter().Get()->GetKey(); +} + +} // namespace mozilla + +#endif // mozilla_ipc_ProtocolUtils_h diff --git a/ipc/glue/ProtocolUtilsFwd.h b/ipc/glue/ProtocolUtilsFwd.h new file mode 100644 index 0000000000..b8143e273d --- /dev/null +++ b/ipc/glue/ProtocolUtilsFwd.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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ipc_ProtocolUtilsFwd_h +#define mozilla_ipc_ProtocolUtilsFwd_h 1 + +namespace mozilla::ipc { + +class IProtocol; + +} // namespace mozilla::ipc + +#endif // mozilla_ipc_ProtocolUtilsFwd_h diff --git a/ipc/glue/ScopedXREEmbed.cpp b/ipc/glue/ScopedXREEmbed.cpp new file mode 100644 index 0000000000..c83bf7e175 --- /dev/null +++ b/ipc/glue/ScopedXREEmbed.cpp @@ -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/. */ + +#include "ScopedXREEmbed.h" + +#include "base/command_line.h" +#include "base/string_util.h" + +#include "nsIFile.h" + +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsXULAppAPI.h" + +using mozilla::ipc::ScopedXREEmbed; + +ScopedXREEmbed::ScopedXREEmbed() : mShouldKillEmbedding(false) { NS_LogInit(); } + +ScopedXREEmbed::~ScopedXREEmbed() { + Stop(); + NS_LogTerm(); +} + +void ScopedXREEmbed::SetAppDir(const nsACString& aPath) { + bool flag; + nsresult rv = + XRE_GetFileFromPath(aPath.BeginReading(), getter_AddRefs(mAppDir)); + if (NS_FAILED(rv) || NS_FAILED(mAppDir->Exists(&flag)) || !flag) { + NS_WARNING("Invalid application directory passed to content process."); + mAppDir = nullptr; + } +} + +void ScopedXREEmbed::Start() { + nsCOMPtr<nsIFile> localFile; + nsresult rv = XRE_GetBinaryPath(getter_AddRefs(localFile)); + if (NS_FAILED(rv)) return; + + nsCOMPtr<nsIFile> parent; + rv = localFile->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) return; + + localFile = parent; + NS_ENSURE_TRUE_VOID(localFile); + +#ifdef OS_MACOSX + if (XRE_IsContentProcess()) { + // We're an XPCOM-using subprocess. Walk out of + // [subprocess].app/Contents/MacOS to the real GRE dir. + rv = localFile->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) return; + + localFile = parent; + NS_ENSURE_TRUE_VOID(localFile); + + rv = localFile->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) return; + + localFile = parent; + NS_ENSURE_TRUE_VOID(localFile); + + rv = localFile->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) return; + + localFile = parent; + NS_ENSURE_TRUE_VOID(localFile); + + rv = localFile->SetNativeLeafName("Resources"_ns); + if (NS_FAILED(rv)) { + return; + } + } +#endif + + if (mAppDir) + rv = XRE_InitEmbedding2(localFile, mAppDir, nullptr); + else + rv = XRE_InitEmbedding2(localFile, localFile, nullptr); + if (NS_FAILED(rv)) return; + + mShouldKillEmbedding = true; +} + +void ScopedXREEmbed::Stop() { + if (mShouldKillEmbedding) { + XRE_TermEmbedding(); + mShouldKillEmbedding = false; + } +} diff --git a/ipc/glue/ScopedXREEmbed.h b/ipc/glue/ScopedXREEmbed.h new file mode 100644 index 0000000000..3e495d8850 --- /dev/null +++ b/ipc/glue/ScopedXREEmbed.h @@ -0,0 +1,34 @@ +/* -*- 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_SCOPEDXREEMBED_H__ +#define __IPC_GLUE_SCOPEDXREEMBED_H__ + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIFile.h" + +namespace mozilla { +namespace ipc { + +class ScopedXREEmbed { + public: + ScopedXREEmbed(); + ~ScopedXREEmbed(); + + void Start(); + void Stop(); + void SetAppDir(const nsACString& aPath); + + private: + bool mShouldKillEmbedding; + nsCOMPtr<nsIFile> mAppDir; +}; + +} /* namespace ipc */ +} /* namespace mozilla */ + +#endif /* __IPC_GLUE_SCOPEDXREEMBED_H__ */ diff --git a/ipc/glue/SerializedStructuredCloneBuffer.h b/ipc/glue/SerializedStructuredCloneBuffer.h new file mode 100644 index 0000000000..c81a1525b3 --- /dev/null +++ b/ipc/glue/SerializedStructuredCloneBuffer.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 __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(Message* aMsg, const paramType& aParam) { + MOZ_ASSERT(!(aParam.Size() % sizeof(uint64_t))); + WriteParam(aMsg, aParam.Size()); + aParam.ForEachDataChunk([&](const char* aData, size_t aSize) { + return aMsg->WriteBytes(aData, aSize, sizeof(uint64_t)); + }); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + size_t length = 0; + if (!ReadParam(aMsg, aIter, &length)) { + return false; + } + MOZ_ASSERT(!(length % sizeof(uint64_t))); + + mozilla::BufferList<InfallibleAllocPolicy> buffers(0, 0, 4096); + + // 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. One + // case is PContentChild::SendGetXPCOMProcessAttributes. We can't + // return a borrowed buffer because the out param outlives the + // IPDL callback. + if (length && + !aMsg->ExtractBuffers(aIter, length, &buffers, sizeof(uint64_t))) { + return false; + } + + bool success; + mozilla::BufferList<js::SystemAllocPolicy> out = + buffers.MoveFallible<js::SystemAllocPolicy>(&success); + if (!success) { + return false; + } + + *aResult = JSStructuredCloneData( + std::move(out), JS::StructuredCloneScope::DifferentProcess); + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::SerializedStructuredCloneBuffer> { + typedef mozilla::SerializedStructuredCloneBuffer paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.data); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->data); + } + + static void Log(const paramType& aParam, std::wstring* aLog) { + LogParam(aParam.data.Size(), aLog); + } +}; + +} // namespace IPC + +#endif /* __IPC_GLUE_SERIALIZEDSTRUCTUREDCLONEBUFFER_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..c7c230389e --- /dev/null +++ b/ipc/glue/SharedMemory.h @@ -0,0 +1,146 @@ +/* -*- 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 SharedMemoryType { TYPE_BASIC, TYPE_UNKNOWN }; + + 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 CloseHandle() = 0; + + virtual SharedMemoryType Type() const = 0; + + virtual bool ShareHandle(base::ProcessId aProcessId, + IPC::Message* aMessage) = 0; + virtual bool ReadHandle(const IPC::Message* aMessage, + PickleIterator* aIter) = 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 bool ShareToProcess(base::ProcessId aProcessId, Handle* aHandle) = 0; + virtual bool IsHandleValid(const Handle& aHandle) const = 0; + virtual bool SetHandle(const Handle& aHandle, OpenRights aRights) = 0; + + virtual bool ShareHandle(base::ProcessId aProcessId, + IPC::Message* aMessage) override { + Handle handle; + if (!ShareToProcess(aProcessId, &handle)) { + return false; + } + IPC::WriteParam(aMessage, handle); + return true; + } + + virtual bool ReadHandle(const IPC::Message* aMessage, + PickleIterator* aIter) override { + Handle handle; + return IPC::ReadParam(aMessage, aIter, &handle) && IsHandleValid(handle) && + SetHandle(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..13026be8b2 --- /dev/null +++ b/ipc/glue/SharedMemoryBasic.h @@ -0,0 +1,18 @@ +/* -*- 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 ANDROID +# include "mozilla/ipc/SharedMemoryBasic_android.h" +#elif defined(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_android.cpp b/ipc/glue/SharedMemoryBasic_android.cpp new file mode 100644 index 0000000000..30eb169e99 --- /dev/null +++ b/ipc/glue/SharedMemoryBasic_android.cpp @@ -0,0 +1,135 @@ +/* -*- 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 <android/log.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/process_util.h" + +#include "SharedMemoryBasic.h" + +#include "mozilla/Ashmem.h" + +namespace mozilla { +namespace ipc { + +static void LogError(const char* what) { + __android_log_print(ANDROID_LOG_ERROR, "Gecko", "%s: %s (%d)", what, + strerror(errno), errno); +} + +SharedMemoryBasic::SharedMemoryBasic() + : mShmFd(-1), mMemory(nullptr), mOpenRights(RightsReadWrite) {} + +SharedMemoryBasic::~SharedMemoryBasic() { + Unmap(); + CloseHandle(); +} + +bool SharedMemoryBasic::SetHandle(const Handle& aHandle, OpenRights aRights) { + MOZ_ASSERT(-1 == mShmFd, "Already Create()d"); + mShmFd = aHandle.fd; + mOpenRights = aRights; + return true; +} + +bool SharedMemoryBasic::Create(size_t aNbytes) { + MOZ_ASSERT(-1 == mShmFd, "Already Create()d"); + + // Carve a new instance off of /dev/ashmem + int shmfd = mozilla::android::ashmem_create(nullptr, aNbytes); + if (-1 == shmfd) { + LogError("ShmemAndroid::Create():open"); + return false; + } + + mShmFd = shmfd; + Created(aNbytes); + return true; +} + +bool SharedMemoryBasic::Map(size_t nBytes, void* fixed_address) { + MOZ_ASSERT(nullptr == mMemory, "Already Map()d"); + + int prot = PROT_READ; + if (mOpenRights == RightsReadWrite) { + prot |= PROT_WRITE; + } + + // Don't use MAP_FIXED when a fixed_address was specified, since that can + // replace pages that are alread mapped at that address. + mMemory = mmap(fixed_address, nBytes, prot, MAP_SHARED, mShmFd, 0); + + if (MAP_FAILED == mMemory) { + if (!fixed_address) { + LogError("ShmemAndroid::Map()"); + } + mMemory = nullptr; + return false; + } + + if (fixed_address && mMemory != fixed_address) { + if (munmap(mMemory, nBytes)) { + LogError("ShmemAndroid::Map():unmap"); + mMemory = nullptr; + return false; + } + } + + Mapped(nBytes); + return true; +} + +void* SharedMemoryBasic::FindFreeAddressSpace(size_t size) { + void* memory = + mmap(NULL, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + munmap(memory, size); + return memory != (void*)-1 ? memory : NULL; +} + +bool SharedMemoryBasic::ShareToProcess(base::ProcessId /*unused*/, + Handle* aNewHandle) { + MOZ_ASSERT(mShmFd >= 0, "Should have been Create()d by now"); + + int shmfdDup = dup(mShmFd); + if (-1 == shmfdDup) { + LogError("ShmemAndroid::ShareToProcess()"); + return false; + } + + aNewHandle->fd = shmfdDup; + aNewHandle->auto_close = true; + return true; +} + +void SharedMemoryBasic::Unmap() { + if (!mMemory) { + return; + } + + if (munmap(mMemory, Size())) { + LogError("ShmemAndroid::Unmap()"); + } + mMemory = nullptr; +} + +void SharedMemoryBasic::CloseHandle() { + if (mShmFd != -1) { + close(mShmFd); + mShmFd = -1; + mOpenRights = RightsReadWrite; + } +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/SharedMemoryBasic_android.h b/ipc/glue/SharedMemoryBasic_android.h new file mode 100644 index 0000000000..6e6eddeac1 --- /dev/null +++ b/ipc/glue/SharedMemoryBasic_android.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_SharedMemoryBasic_android_h +#define mozilla_ipc_SharedMemoryBasic_android_h + +#include "base/file_descriptor_posix.h" + +#include "SharedMemory.h" + +#ifdef FUZZING +# include "SharedMemoryFuzzer.h" +#endif + +// +// 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::FileDescriptor> { + public: + SharedMemoryBasic(); + + virtual bool SetHandle(const 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 CloseHandle() override; + + virtual void* memory() const override { +#ifdef FUZZING + return SharedMemoryFuzzer::MutateSharedMemory(mMemory, mAllocSize); +#else + return mMemory; +#endif + } + + virtual SharedMemoryType Type() const override { return TYPE_BASIC; } + + static Handle NULLHandle() { return Handle(); } + + static void* FindFreeAddressSpace(size_t aSize); + + virtual bool IsHandleValid(const Handle& aHandle) const override { + return aHandle.fd >= 0; + } + + virtual bool ShareToProcess(base::ProcessId aProcessId, + Handle* aNewHandle) override; + + private: + ~SharedMemoryBasic(); + + void Unmap(); + + // The /dev/ashmem fd we allocate. + int mShmFd; + // 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_android_h diff --git a/ipc/glue/SharedMemoryBasic_chromium.h b/ipc/glue/SharedMemoryBasic_chromium.h new file mode 100644 index 0000000000..09ec1b60e0 --- /dev/null +++ b/ipc/glue/SharedMemoryBasic_chromium.h @@ -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/. */ + +#ifndef mozilla_ipc_SharedMemoryBasic_chromium_h +#define mozilla_ipc_SharedMemoryBasic_chromium_h + +#include "base/shared_memory.h" +#include "SharedMemory.h" + +#ifdef FUZZING +# include "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(const Handle& aHandle, OpenRights aRights) override { + return mSharedMemory.SetHandle(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 CloseHandle() override { mSharedMemory.Close(false); } + + virtual void* memory() const override { +#ifdef FUZZING + return SharedMemoryFuzzer::MutateSharedMemory(mSharedMemory.memory(), + mAllocSize); +#else + return mSharedMemory.memory(); +#endif + } + + virtual SharedMemoryType Type() const override { return TYPE_BASIC; } + + static Handle NULLHandle() { return base::SharedMemory::NULLHandle(); } + + virtual bool IsHandleValid(const Handle& aHandle) const override { + return base::SharedMemory::IsHandleValid(aHandle); + } + + virtual bool ShareToProcess(base::ProcessId aProcessId, + Handle* new_handle) override { + base::SharedMemoryHandle handle; + bool ret = mSharedMemory.ShareToProcess(aProcessId, &handle); + if (ret) *new_handle = handle; + return ret; + } + + 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..fd2885b8d2 --- /dev/null +++ b/ipc/glue/SharedMemoryBasic_mach.h @@ -0,0 +1,111 @@ +/* -*- 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/file_descriptor_posix.h" +#include "base/process.h" + +#include "SharedMemory.h" +#include <mach/port.h> +#include "chrome/common/mach_ipc_mac.h" + +#ifdef FUZZING +# include "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 { + +enum { + kGetPortsMsg = 1, + kSharePortsMsg, + kWaitForTexturesMsg, + kUpdateTextureLocksMsg, + kReturnIdMsg, + kReturnWaitForTexturesMsg, + kReturnPortsMsg, + kShutdownMsg, + kCleanupMsg, +}; + +struct MemoryPorts { + MachPortSender* mSender; + ReceivePort* mReceiver; + + MemoryPorts() = default; + MemoryPorts(MachPortSender* sender, ReceivePort* receiver) + : mSender(sender), mReceiver(receiver) {} +}; + +class SharedMemoryBasic final : public SharedMemoryCommon<mach_port_t> { + public: + static void SetupMachMemory(pid_t pid, ReceivePort* listen_port, + MachPortSender* listen_port_ack, + MachPortSender* send_port, + ReceivePort* send_port_ack, bool pidIsParent); + + static void CleanupForPid(pid_t pid); + static void CleanupForPidWithLock(pid_t pid); + + static void Shutdown(); + + static bool SendMachMessage(pid_t pid, MachSendMessage& message, + MachReceiveMessage* response); + + SharedMemoryBasic(); + + virtual bool SetHandle(const 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 CloseHandle() override; + + virtual void* memory() const override { +#ifdef FUZZING + return SharedMemoryFuzzer::MutateSharedMemory(mMemory, mAllocSize); +#else + return mMemory; +#endif + } + + virtual SharedMemoryType Type() const override { return TYPE_BASIC; } + + static Handle NULLHandle() { return Handle(); } + + static void* FindFreeAddressSpace(size_t aSize); + + virtual bool IsHandleValid(const Handle& aHandle) const override; + + virtual bool ShareToProcess(base::ProcessId aProcessId, + Handle* aNewHandle) override; + + private: + ~SharedMemoryBasic(); + + void Unmap(); + mach_port_t 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..306f2504e0 --- /dev/null +++ b/ipc/glue/SharedMemoryBasic_mach.mm @@ -0,0 +1,676 @@ +/* -*- 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" +#include "mozilla/layers/TextureSync.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 + +#define CHECK_MACH_ERROR(kr, msg) \ + PR_BEGIN_MACRO \ + if (kr != KERN_SUCCESS) { \ + LOG_ERROR("%s %s (%x)\n", msg, mach_error_string(kr), kr); \ + return false; \ + } \ + PR_END_MACRO + +/* + * This code is responsible for sharing memory between processes. Memory can be + * shared between parent and child or between two children. Each memory region is + * referenced via a Mach port. Mach ports are also used for messaging when + * sharing a memory region. + * + * When the parent starts a child, it starts a thread whose only purpose is to + * communicate with the child about shared memory. Once the child has started, + * it starts a similar thread for communicating with the parent. Each side can + * communicate with the thread on the other side via Mach ports. When either + * side wants to share memory with the other, it sends a Mach message to the + * other side. Attached to the message is the port that references the shared + * memory region. When the other side receives the message, it automatically + * gets access to the region. It sends a reply (also via a Mach port) so that + * the originating side can continue. + * + * The two sides communicate using four ports. Two ports are used when the + * parent shares memory with the child. The other two are used when the child + * shares memory with the parent. One of these two ports is used for sending the + * "share" message and the other is used for the reply. + * + * If a child wants to share memory with another child, it sends a "GetPorts" + * message to the parent. The parent forwards this GetPorts message to the + * target child. The message includes some ports so that the children can talk + * directly. Both children start up a thread to communicate with the other child, + * similar to the way parent and child communicate. In the future, when these + * two children want to communicate, they re-use the channels that were created. + * + * When a child shuts down, the parent notifies all other children. Those + * children then have the opportunity to shut down any threads they might have + * been using to communicate directly with that child. + */ + +namespace mozilla { +namespace ipc { + +// Protects gMemoryCommPorts and gThreads. +static StaticMutex gMutex; +static std::map<pid_t, MemoryPorts> gMemoryCommPorts; + +const int kTimeout = 1000; +const int kLongTimeout = 60 * kTimeout; + +pid_t gParentPid = 0; + +struct PIDPair { + pid_t mRequester; + pid_t mRequested; + + PIDPair(pid_t requester, pid_t requested) : mRequester(requester), mRequested(requested) {} +}; + +struct ListeningThread { + pthread_t mThread; + MemoryPorts* mPorts; + + ListeningThread() = default; + ListeningThread(pthread_t thread, MemoryPorts* ports) : mThread(thread), mPorts(ports) {} +}; + +struct SharePortsReply { + uint64_t serial; + mach_port_t port; +}; + +std::map<pid_t, ListeningThread> gThreads; + +static void* PortServerThread(void* argument); + +static void SetupMachMemory(pid_t pid, ReceivePort* listen_port, MachPortSender* listen_port_ack, + MachPortSender* send_port, ReceivePort* send_port_ack, + bool pidIsParent) { + if (pidIsParent) { + gParentPid = pid; + } + auto* listen_ports = new MemoryPorts(listen_port_ack, listen_port); + pthread_t thread; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + int err = pthread_create(&thread, &attr, PortServerThread, listen_ports); + if (err) { + LOG_ERROR("pthread_create failed with %x\n", err); + return; + } + + gMutex.AssertCurrentThreadOwns(); + gThreads[pid] = ListeningThread(thread, listen_ports); + gMemoryCommPorts[pid] = MemoryPorts(send_port, send_port_ack); +} + +// Send two communication ports to another process along with the pid of the process that is +// listening on them. +bool SendPortsMessage(MachPortSender* sender, mach_port_t ports_in_receiver, + mach_port_t ports_out_receiver, PIDPair pid_pair) { + MachSendMessage getPortsMsg(kGetPortsMsg); + if (!getPortsMsg.AddDescriptor(MachMsgPortDescriptor(ports_in_receiver))) { + LOG_ERROR("Adding descriptor to message failed"); + return false; + } + if (!getPortsMsg.AddDescriptor(MachMsgPortDescriptor(ports_out_receiver))) { + LOG_ERROR("Adding descriptor to message failed"); + return false; + } + + getPortsMsg.SetData(&pid_pair, sizeof(PIDPair)); + kern_return_t err = sender->SendMessage(getPortsMsg, kTimeout); + if (KERN_SUCCESS != err) { + LOG_ERROR("Error sending get ports message %s (%x)\n", mach_error_string(err), err); + return false; + } + return true; +} + +// Receive two communication ports from another process +bool RecvPortsMessage(ReceivePort* receiver, mach_port_t* ports_in_sender, + mach_port_t* ports_out_sender) { + MachReceiveMessage rcvPortsMsg; + kern_return_t err = receiver->WaitForMessage(&rcvPortsMsg, kTimeout); + if (KERN_SUCCESS != err) { + LOG_ERROR("Error receiving get ports message %s (%x)\n", mach_error_string(err), err); + } + if (rcvPortsMsg.GetTranslatedPort(0) == MACH_PORT_NULL) { + LOG_ERROR("GetTranslatedPort(0) failed"); + return false; + } + *ports_in_sender = rcvPortsMsg.GetTranslatedPort(0); + + if (rcvPortsMsg.GetTranslatedPort(1) == MACH_PORT_NULL) { + LOG_ERROR("GetTranslatedPort(1) failed"); + return false; + } + *ports_out_sender = rcvPortsMsg.GetTranslatedPort(1); + return true; +} + +// Send two communication ports to another process and receive two back +bool RequestPorts(const MemoryPorts& request_ports, mach_port_t ports_in_receiver, + mach_port_t* ports_in_sender, mach_port_t* ports_out_sender, + mach_port_t ports_out_receiver, PIDPair pid_pair) { + if (!SendPortsMessage(request_ports.mSender, ports_in_receiver, ports_out_receiver, pid_pair)) { + return false; + } + return RecvPortsMessage(request_ports.mReceiver, ports_in_sender, ports_out_sender); +} + +MemoryPorts* GetMemoryPortsForPid(pid_t pid) { + gMutex.AssertCurrentThreadOwns(); + + if (gMemoryCommPorts.find(pid) == gMemoryCommPorts.end()) { + // We don't have the ports open to communicate with that pid, so we're going to + // ask our parent process over IPC to set them up for us. + if (gParentPid == 0) { + // If we're the top level parent process, we have no parent to ask. + LOG_ERROR("request for ports for pid %d, but we're the chrome process\n", pid); + return nullptr; + } + const MemoryPorts& parent = gMemoryCommPorts[gParentPid]; + + // Create two receiving ports in this process to send to the parent. One will be used for + // for listening for incoming memory to be shared, the other for getting the Handle of + // memory we share to the other process. + auto* ports_in_receiver = new ReceivePort(); + auto* ports_out_receiver = new ReceivePort(); + mach_port_t raw_ports_in_sender, raw_ports_out_sender; + if (!RequestPorts(parent, ports_in_receiver->GetPort(), &raw_ports_in_sender, + &raw_ports_out_sender, ports_out_receiver->GetPort(), + PIDPair(getpid(), pid))) { + LOG_ERROR("failed to request ports\n"); + return nullptr; + } + // Our parent process sent us two ports, one is for sending new memory to, the other + // is for replying with the Handle when we receive new memory. + auto* ports_in_sender = new MachPortSender(raw_ports_in_sender); + auto* ports_out_sender = new MachPortSender(raw_ports_out_sender); + SetupMachMemory(pid, ports_in_receiver, ports_in_sender, ports_out_sender, ports_out_receiver, + false); + MOZ_ASSERT(gMemoryCommPorts.find(pid) != gMemoryCommPorts.end()); + } + return &gMemoryCommPorts.at(pid); +} + +// We just received a port representing a region of shared memory, reply to +// the process that set it with the mach_port_t that represents it in this +// process. That will be the Handle to be shared over normal IPC. +// +// WARNING: this function is called while gMutex is not held and must not +// reference structures protected by gMutex. See the deadlock warning in +// ShareToProcess(). +void HandleSharePortsMessage(MachReceiveMessage* rmsg, MemoryPorts* ports) { + mach_port_t port = rmsg->GetTranslatedPort(0); + uint64_t* serial = reinterpret_cast<uint64_t*>(rmsg->GetData()); + MachSendMessage msg(kReturnIdMsg); + // Construct the reply message, echoing the serial, and adding the port + SharePortsReply replydata; + replydata.port = port; + replydata.serial = *serial; + msg.SetData(&replydata, sizeof(SharePortsReply)); + kern_return_t err = ports->mSender->SendMessage(msg, kTimeout); + if (KERN_SUCCESS != err) { + LOG_ERROR("SendMessage failed 0x%x %s\n", err, mach_error_string(err)); + } +} + +// We were asked by another process to get communications ports to some process. Return +// those ports via an IPC message. +bool SendReturnPortsMsg(MachPortSender* sender, mach_port_t raw_ports_in_sender, + mach_port_t raw_ports_out_sender) { + MachSendMessage getPortsMsg(kReturnPortsMsg); + if (!getPortsMsg.AddDescriptor(MachMsgPortDescriptor(raw_ports_in_sender))) { + LOG_ERROR("Adding descriptor to message failed"); + return false; + } + + if (!getPortsMsg.AddDescriptor(MachMsgPortDescriptor(raw_ports_out_sender))) { + LOG_ERROR("Adding descriptor to message failed"); + return false; + } + kern_return_t err = sender->SendMessage(getPortsMsg, kTimeout); + if (KERN_SUCCESS != err) { + LOG_ERROR("Error sending get ports message %s (%x)\n", mach_error_string(err), err); + return false; + } + return true; +} + +// We were asked for communcations ports to a process that isn't us. Assuming that process +// is one of our children, forward that request on. +void ForwardGetPortsMessage(MachReceiveMessage* rmsg, MemoryPorts* ports, PIDPair* pid_pair) { + if (rmsg->GetTranslatedPort(0) == MACH_PORT_NULL) { + LOG_ERROR("GetTranslatedPort(0) failed"); + return; + } + if (rmsg->GetTranslatedPort(1) == MACH_PORT_NULL) { + LOG_ERROR("GetTranslatedPort(1) failed"); + return; + } + mach_port_t raw_ports_in_sender, raw_ports_out_sender; + MemoryPorts* requestedPorts = GetMemoryPortsForPid(pid_pair->mRequested); + if (!requestedPorts) { + LOG_ERROR("failed to find port for process\n"); + return; + } + if (!RequestPorts(*requestedPorts, rmsg->GetTranslatedPort(0), &raw_ports_in_sender, + &raw_ports_out_sender, rmsg->GetTranslatedPort(1), *pid_pair)) { + LOG_ERROR("failed to request ports\n"); + return; + } + SendReturnPortsMsg(ports->mSender, raw_ports_in_sender, raw_ports_out_sender); +} + +// We receieved a message asking us to get communications ports for another process +void HandleGetPortsMessage(MachReceiveMessage* rmsg, MemoryPorts* ports) { + PIDPair* pid_pair; + if (rmsg->GetDataLength() != sizeof(PIDPair)) { + LOG_ERROR("Improperly formatted message\n"); + return; + } + pid_pair = reinterpret_cast<PIDPair*>(rmsg->GetData()); + if (pid_pair->mRequested != getpid()) { + // This request is for ports to a process that isn't us, forward it to that process + ForwardGetPortsMessage(rmsg, ports, pid_pair); + } else { + if (rmsg->GetTranslatedPort(0) == MACH_PORT_NULL) { + LOG_ERROR("GetTranslatedPort(0) failed"); + return; + } + + if (rmsg->GetTranslatedPort(1) == MACH_PORT_NULL) { + LOG_ERROR("GetTranslatedPort(1) failed"); + return; + } + + auto* ports_in_sender = new MachPortSender(rmsg->GetTranslatedPort(0)); + auto* ports_out_sender = new MachPortSender(rmsg->GetTranslatedPort(1)); + + auto* ports_in_receiver = new ReceivePort(); + auto* ports_out_receiver = new ReceivePort(); + if (SendReturnPortsMsg(ports->mSender, ports_in_receiver->GetPort(), + ports_out_receiver->GetPort())) { + SetupMachMemory(pid_pair->mRequester, ports_out_receiver, ports_out_sender, ports_in_sender, + ports_in_receiver, false); + } + } +} + +static void* PortServerThread(void* argument) { + pthread_setname_np("PortServerThread"); + MemoryPorts* ports = static_cast<MemoryPorts*>(argument); + MachReceiveMessage child_message; + while (true) { + MachReceiveMessage rmsg; + kern_return_t err = ports->mReceiver->WaitForMessage(&rmsg, MACH_MSG_TIMEOUT_NONE); + if (err != KERN_SUCCESS) { + LOG_ERROR("Wait for message failed 0x%x %s\n", err, mach_error_string(err)); + continue; + } + if (rmsg.GetMessageID() == kShutdownMsg) { + delete ports->mSender; + delete ports->mReceiver; + delete ports; + return nullptr; + } + if (rmsg.GetMessageID() == kWaitForTexturesMsg) { + layers::TextureSync::HandleWaitForTexturesMessage(&rmsg, ports); + } else if (rmsg.GetMessageID() == kUpdateTextureLocksMsg) { + layers::TextureSync::DispatchCheckTexturesForUnlock(); + } else { + switch (rmsg.GetMessageID()) { + case kSharePortsMsg: { + // Don't acquire gMutex here while calling HandleSharePortsMessage() + // to avoid deadlock. If gMutex is held by ShareToProcess(), we will + // block and create the following deadlock chain. + // + // 1) local:PortServerThread() blocked on local:gMutex held by + // 2) local:ShareToProcess() waiting for reply from + // 3) peer:PortServerThread() blocked on peer:gMutex held by + // 4) peer:ShareToProcess() waiting for reply from 1. + // + // It's safe to call HandleSharePortsMessage() without gMutex + // because HandleSharePortsMessage() only sends an outgoing message + // without referencing data structures protected by gMutex. The + // |ports| struct is deallocated on this thread in the kShutdownMsg + // message handling before this thread exits. + HandleSharePortsMessage(&rmsg, ports); + break; + } + case kGetPortsMsg: { + StaticMutexAutoLock smal(gMutex); + HandleGetPortsMessage(&rmsg, ports); + break; + } + case kCleanupMsg: { + StaticMutexAutoLock smal(gMutex); + if (gParentPid == 0) { + LOG_ERROR("Cleanup message not valid for parent process"); + continue; + } + + pid_t* pid; + if (rmsg.GetDataLength() != sizeof(pid_t)) { + LOG_ERROR("Improperly formatted message\n"); + continue; + } + pid = reinterpret_cast<pid_t*>(rmsg.GetData()); + SharedMemoryBasic::CleanupForPid(*pid); + break; + } + default: { + // gMutex not required + LOG_ERROR("Unknown message\n"); + } + } + } + } +} + +void SharedMemoryBasic::SetupMachMemory(pid_t pid, ReceivePort* listen_port, + MachPortSender* listen_port_ack, MachPortSender* send_port, + ReceivePort* send_port_ack, bool pidIsParent) { + StaticMutexAutoLock smal(gMutex); + mozilla::ipc::SetupMachMemory(pid, listen_port, listen_port_ack, send_port, send_port_ack, + pidIsParent); +} + +void SharedMemoryBasic::Shutdown() { + StaticMutexAutoLock smal(gMutex); + + layers::TextureSync::Shutdown(); + + for (auto& thread : gThreads) { + MachSendMessage shutdownMsg(kShutdownMsg); + thread.second.mPorts->mReceiver->SendMessageToSelf(shutdownMsg, kTimeout); + } + gThreads.clear(); + + for (auto& memoryCommPort : gMemoryCommPorts) { + delete memoryCommPort.second.mSender; + delete memoryCommPort.second.mReceiver; + } + gMemoryCommPorts.clear(); +} + +void SharedMemoryBasic::CleanupForPidWithLock(pid_t pid) { + StaticMutexAutoLock smal(gMutex); + CleanupForPid(pid); +} + +void SharedMemoryBasic::CleanupForPid(pid_t pid) { + gMutex.AssertCurrentThreadOwns(); + + if (gThreads.find(pid) == gThreads.end()) { + return; + } + + layers::TextureSync::CleanupForPid(pid); + + const ListeningThread& listeningThread = gThreads[pid]; + MachSendMessage shutdownMsg(kShutdownMsg); + kern_return_t ret = listeningThread.mPorts->mReceiver->SendMessageToSelf(shutdownMsg, kTimeout); + if (ret != KERN_SUCCESS) { + LOG_ERROR("sending shutdown msg failed %s %x\n", mach_error_string(ret), ret); + } + gThreads.erase(pid); + + if (gParentPid == 0) { + // We're the parent. Broadcast the cleanup message to everyone else. + for (auto& memoryCommPort : gMemoryCommPorts) { + MachSendMessage msg(kCleanupMsg); + msg.SetData(&pid, sizeof(pid)); + // We don't really care if this fails, we could be trying to send to an already shut down proc + memoryCommPort.second.mSender->SendMessage(msg, kTimeout); + } + } + + MemoryPorts& ports = gMemoryCommPorts[pid]; + delete ports.mSender; + delete ports.mReceiver; + gMemoryCommPorts.erase(pid); +} + +bool SharedMemoryBasic::SendMachMessage(pid_t pid, MachSendMessage& message, + MachReceiveMessage* response) { + StaticMutexAutoLock smal(gMutex); + ipc::MemoryPorts* ports = GetMemoryPortsForPid(pid); + if (!ports) { + LOG_ERROR("Unable to get ports for process.\n"); + return false; + } + + kern_return_t err = ports->mSender->SendMessage(message, kTimeout); + if (err != KERN_SUCCESS) { + LOG_ERROR("Failed updating texture locks.\n"); + return false; + } + + if (response) { + err = ports->mReceiver->WaitForMessage(response, kTimeout); + if (err != KERN_SUCCESS) { + LOG_ERROR("short timeout didn't get an id %s %x\n", mach_error_string(err), err); + err = ports->mReceiver->WaitForMessage(response, kLongTimeout); + + if (err != KERN_SUCCESS) { + LOG_ERROR("long timeout didn't get an id %s %x\n", mach_error_string(err), err); + return false; + } + } + } + + return true; +} + +SharedMemoryBasic::SharedMemoryBasic() + : mPort(MACH_PORT_NULL), mMemory(nullptr), mOpenRights(RightsReadWrite) {} + +SharedMemoryBasic::~SharedMemoryBasic() { + Unmap(); + CloseHandle(); +} + +bool SharedMemoryBasic::SetHandle(const Handle& aHandle, OpenRights aRights) { + MOZ_ASSERT(mPort == MACH_PORT_NULL, "already initialized"); + + mPort = 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, &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; + } + + Mapped(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, 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(), mPort, 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(), mPort, 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); +} + +bool SharedMemoryBasic::ShareToProcess(base::ProcessId pid, Handle* aNewHandle) { + if (pid == getpid()) { + *aNewHandle = mPort; + return mach_port_mod_refs(mach_task_self(), *aNewHandle, MACH_PORT_RIGHT_SEND, 1) == + KERN_SUCCESS; + } + StaticMutexAutoLock smal(gMutex); + + // Serially number the messages, to check whether + // the reply we get was meant for us. + static uint64_t serial = 0; + uint64_t my_serial = serial; + serial++; + + MemoryPorts* ports = GetMemoryPortsForPid(pid); + if (!ports) { + LOG_ERROR("Unable to get ports for process.\n"); + return false; + } + MachSendMessage smsg(kSharePortsMsg); + smsg.AddDescriptor(MachMsgPortDescriptor(mPort, MACH_MSG_TYPE_COPY_SEND)); + smsg.SetData(&my_serial, sizeof(uint64_t)); + kern_return_t err = ports->mSender->SendMessage(smsg, kTimeout); + if (err != KERN_SUCCESS) { + LOG_ERROR("sending port failed %s %x\n", mach_error_string(err), err); + return false; + } + MachReceiveMessage msg; + err = ports->mReceiver->WaitForMessage(&msg, kTimeout); + if (err != KERN_SUCCESS) { + LOG_ERROR("short timeout didn't get an id %s %x\n", mach_error_string(err), err); + err = ports->mReceiver->WaitForMessage(&msg, kLongTimeout); + + if (err != KERN_SUCCESS) { + LOG_ERROR("long timeout didn't get an id %s %x\n", mach_error_string(err), err); + return false; + } + } + if (msg.GetDataLength() != sizeof(SharePortsReply)) { + LOG_ERROR("Improperly formatted reply\n"); + return false; + } + SharePortsReply* msg_data = reinterpret_cast<SharePortsReply*>(msg.GetData()); + mach_port_t id = msg_data->port; + uint64_t serial_check = msg_data->serial; + if (serial_check != my_serial) { + LOG_ERROR("Serials do not match up: %" PRIu64 " vs %" PRIu64 "", serial_check, my_serial); + return false; + } + *aNewHandle = id; + return true; +} + +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; +} + +void SharedMemoryBasic::CloseHandle() { + if (mPort != MACH_PORT_NULL) { + mach_port_deallocate(mach_task_self(), mPort); + mPort = MACH_PORT_NULL; + mOpenRights = RightsReadWrite; + } +} + +bool SharedMemoryBasic::IsHandleValid(const Handle& aHandle) const { return aHandle > 0; } + +} // 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..00bec892fa --- /dev/null +++ b/ipc/glue/Shmem.cpp @@ -0,0 +1,465 @@ +/* -*- 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 "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, + SharedMemory::SharedMemoryType aType) + : IPC::Message(routingId, SHMEM_CREATED_MESSAGE_TYPE, 0, + HeaderFlags(NESTED_INSIDE_CPOW)) { + MOZ_RELEASE_ASSERT(aSize < std::numeric_limits<uint32_t>::max(), + "Tried to create Shmem with size larger than 4GB"); + IPC::WriteParam(this, aIPDLId); + IPC::WriteParam(this, uint32_t(aSize)); + IPC::WriteParam(this, int32_t(aType)); + } + + static bool ReadInfo(const Message* msg, PickleIterator* iter, id_t* aIPDLId, + size_t* aSize, SharedMemory::SharedMemoryType* aType) { + uint32_t size = 0; + if (!IPC::ReadParam(msg, iter, aIPDLId) || + !IPC::ReadParam(msg, iter, &size) || + !IPC::ReadParam(msg, iter, reinterpret_cast<int32_t*>(aType))) { + 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) { + IPC::WriteParam(this, aIPDLId); + } +}; + +static SharedMemory* NewSegment(SharedMemory::SharedMemoryType aType) { + if (SharedMemory::TYPE_BASIC == aType) { + return new SharedMemoryBasic; + } else { + NS_ERROR("unknown Shmem type"); + return nullptr; + } +} + +static already_AddRefed<SharedMemory> CreateSegment( + SharedMemory::SharedMemoryType aType, size_t aNBytes, size_t aExtraSize) { + RefPtr<SharedMemory> segment = NewSegment(aType); + if (!segment) { + return nullptr; + } + size_t size = SharedMemory::PageAlignedSize(aNBytes + aExtraSize); + 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, + size_t aExtraSize) { + if (SHMEM_CREATED_MESSAGE_TYPE != aDescriptor.type()) { + NS_ERROR("expected 'shmem created' message"); + return nullptr; + } + SharedMemory::SharedMemoryType type; + PickleIterator iter(aDescriptor); + if (!ShmemCreated::ReadInfo(&aDescriptor, &iter, aId, aNBytes, &type)) { + return nullptr; + } + RefPtr<SharedMemory> segment = NewSegment(type); + if (!segment) { + return nullptr; + } + if (!segment->ReadHandle(&aDescriptor, &iter)) { + NS_ERROR("trying to open invalid handle"); + return nullptr; + } + aDescriptor.EndRead(iter); + size_t size = SharedMemory::PageAlignedSize(*aNBytes + aExtraSize); + if (!segment->Map(size)) { + return nullptr; + } + // close the handle to the segment after it is mapped + segment->CloseHandle(); + return segment.forget(); +} + +static void DestroySegment(SharedMemory* aSegment) { + // the SharedMemory dtor closes and unmaps the actual OS shmem segment + if (aSegment) { + aSegment->Release(); + } +} + +#if defined(DEBUG) + +static const char sMagic[] = + "This little piggy went to market.\n" + "This little piggy stayed at home.\n" + "This little piggy has roast beef,\n" + "This little piggy had none.\n" + "And this little piggy cried \"Wee! Wee! Wee!\" all the way home"; + +struct Header { + // Don't use size_t or bool here because their size depends on the + // architecture. + uint32_t mSize; + uint32_t mUnsafe; + char mMagic[sizeof(sMagic)]; +}; + +static void GetSections(Shmem::SharedMemory* aSegment, Header** aHeader, + char** aFrontSentinel, char** aData, + char** aBackSentinel) { + MOZ_ASSERT(aSegment && aFrontSentinel && aData && aBackSentinel, + "null param(s)"); + + *aFrontSentinel = reinterpret_cast<char*>(aSegment->memory()); + MOZ_ASSERT(*aFrontSentinel, "null memory()"); + + *aHeader = reinterpret_cast<Header*>(*aFrontSentinel); + + size_t pageSize = Shmem::SharedMemory::SystemPageSize(); + *aData = *aFrontSentinel + pageSize; + + *aBackSentinel = *aFrontSentinel + aSegment->Size() - pageSize; +} + +static Header* GetHeader(Shmem::SharedMemory* aSegment) { + Header* header; + char* dontcare; + GetSections(aSegment, &header, &dontcare, &dontcare, &dontcare); + return header; +} + +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); +} + +// +// In debug builds, we specially allocate shmem segments. The layout +// is as follows +// +// Page 0: "front sentinel" +// size of mapping +// magic bytes +// Page 1 through n-1: +// user data +// Page n: "back sentinel" +// [nothing] +// +// The mapping can be in one of the following states, wrt to the +// current process. +// +// State "unmapped": all pages are mapped with no access rights. +// +// State "mapping": all pages are mapped with read/write access. +// +// State "mapped": the front and back sentinels are mapped with no +// access rights, and all the other pages are mapped with +// read/write access. +// +// When a SharedMemory segment is first allocated, it starts out in +// the "mapping" state for the process that allocates the segment, and +// in the "unmapped" state for the other process. The allocating +// process will then create a Shmem, which takes the segment into the +// "mapped" state, where it can be accessed by clients. +// +// When a Shmem is sent to another process in an IPDL message, the +// segment transitions into the "unmapped" state for the sending +// process, and into the "mapping" state for the receiving process. +// The receiving process will then create a Shmem from the underlying +// segment, and take the segment into the "mapped" state. +// +// In the "mapping" state, we use the front sentinel to verify the +// integrity of the shmem segment. If valid, it has a size_t +// containing the number of bytes the user allocated followed by the +// magic bytes above. +// +// In the "mapped" state, the front and back sentinels have no access +// rights. They act as guards against buffer overflows and underflows +// in client code; if clients touch a sentinel, they die with SIGSEGV. +// +// The "unmapped" state is used to enforce single-owner semantics of +// the shmem segment. If a process other than the current owner tries +// to touch the segment, it dies with SIGSEGV. +// + +Shmem::Shmem(PrivateIPDLCaller, SharedMemory* aSegment, id_t aId) + : mSegment(aSegment), mData(nullptr), mSize(0) { + MOZ_ASSERT(mSegment, "null segment"); + MOZ_ASSERT(aId != 0, "invalid ID"); + + Unprotect(mSegment); + + Header* header; + char* frontSentinel; + char* data; + char* backSentinel; + GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel); + + // do a quick validity check to avoid weird-looking crashes in libc + char check = *frontSentinel; + (void)check; + + MOZ_ASSERT(!strncmp(header->mMagic, sMagic, sizeof(sMagic)), + "invalid segment"); + mSize = static_cast<size_t>(header->mSize); + + size_t pageSize = SharedMemory::SystemPageSize(); + // transition into the "mapped" state by protecting the front and + // back sentinels (which guard against buffer under/overflows) + mSegment->Protect(frontSentinel, pageSize, RightsNone); + mSegment->Protect(backSentinel, pageSize, RightsNone); + + // don't set these until we know they're valid + mData = data; + mId = aId; +} + +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(PrivateIPDLCaller) { + AssertInvariants(); + + size_t pageSize = SharedMemory::SystemPageSize(); + Header* header = GetHeader(mSegment); + + // Open this up for reading temporarily + mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsRead); + + if (!header->mUnsafe) { + Protect(mSegment); + } else { + mSegment->Protect(reinterpret_cast<char*>(header), pageSize, RightsNone); + } +} + +// static +already_AddRefed<Shmem::SharedMemory> Shmem::Alloc(PrivateIPDLCaller, + size_t aNBytes, + SharedMemoryType aType, + bool aUnsafe, + bool aProtect) { + NS_ASSERTION(aNBytes <= UINT32_MAX, "Will truncate shmem segment size!"); + MOZ_ASSERT(!aProtect || !aUnsafe, "protect => !unsafe"); + + size_t pageSize = SharedMemory::SystemPageSize(); + // |2*pageSize| is for the front and back sentinel + RefPtr<SharedMemory> segment = CreateSegment(aType, aNBytes, 2 * pageSize); + if (!segment) { + return nullptr; + } + + Header* header; + char* frontSentinel; + char* data; + char* backSentinel; + GetSections(segment, &header, &frontSentinel, &data, &backSentinel); + + // initialize the segment with Shmem-internal information + + // NB: this can't be a static assert because technically pageSize + // isn't known at compile time, event though in practice it's always + // going to be 4KiB + MOZ_ASSERT(sizeof(Header) <= pageSize, "Shmem::Header has gotten too big"); + memcpy(header->mMagic, sMagic, sizeof(sMagic)); + header->mSize = static_cast<uint32_t>(aNBytes); + header->mUnsafe = aUnsafe; + + if (aProtect) Protect(segment); + + return segment.forget(); +} + +// static +already_AddRefed<Shmem::SharedMemory> Shmem::OpenExisting( + PrivateIPDLCaller, const IPC::Message& aDescriptor, id_t* aId, + bool aProtect) { + size_t size; + size_t pageSize = SharedMemory::SystemPageSize(); + // |2*pageSize| is for the front and back sentinels + RefPtr<SharedMemory> segment = + ReadSegment(aDescriptor, aId, &size, 2 * pageSize); + if (!segment) { + return nullptr; + } + + Header* header = GetHeader(segment); + + if (size != header->mSize) { + // Deallocation should zero out the header, so check for that. + if (header->mSize || header->mUnsafe || header->mMagic[0] || + memcmp(header->mMagic, &header->mMagic[1], + sizeof(header->mMagic) - 1)) { + NS_ERROR("Wrong size for this Shmem!"); + } else { + NS_WARNING("Shmem was deallocated"); + } + return nullptr; + } + + // The caller of this function may not know whether the segment is + // unsafe or not + if (!header->mUnsafe && aProtect) Protect(segment); + + return segment.forget(); +} + +// static +void Shmem::Dealloc(PrivateIPDLCaller, SharedMemory* aSegment) { + if (!aSegment) return; + + size_t pageSize = SharedMemory::SystemPageSize(); + Header* header; + char* frontSentinel; + char* data; + char* backSentinel; + GetSections(aSegment, &header, &frontSentinel, &data, &backSentinel); + + aSegment->Protect(frontSentinel, pageSize, RightsWrite | RightsRead); + memset(header->mMagic, 0, sizeof(sMagic)); + header->mSize = 0; + header->mUnsafe = false; // make it "safe" so as to catch errors + + DestroySegment(aSegment); +} + +#else // !defined(DEBUG) + +// static +already_AddRefed<Shmem::SharedMemory> Shmem::Alloc(PrivateIPDLCaller, + size_t aNBytes, + SharedMemoryType aType, + bool /*unused*/, + bool /*unused*/) { + RefPtr<SharedMemory> segment = + CreateSegment(aType, aNBytes, sizeof(uint32_t)); + if (!segment) { + return nullptr; + } + + *PtrToSize(segment) = static_cast<uint32_t>(aNBytes); + + return segment.forget(); +} + +// static +already_AddRefed<Shmem::SharedMemory> Shmem::OpenExisting( + PrivateIPDLCaller, const IPC::Message& aDescriptor, id_t* aId, + bool /*unused*/) { + size_t size; + RefPtr<SharedMemory> segment = + ReadSegment(aDescriptor, aId, &size, sizeof(uint32_t)); + if (!segment) { + return nullptr; + } + + // this is the only validity check done in non-DEBUG builds + if (size != static_cast<size_t>(*PtrToSize(segment))) { + return nullptr; + } + + return segment.forget(); +} + +// static +void Shmem::Dealloc(PrivateIPDLCaller, SharedMemory* aSegment) { + DestroySegment(aSegment); +} + +#endif // if defined(DEBUG) + +UniquePtr<IPC::Message> Shmem::ShareTo(PrivateIPDLCaller, + base::ProcessId aTargetPid, + int32_t routingId) { + AssertInvariants(); + + auto msg = MakeUnique<ShmemCreated>(routingId, mId, mSize, mSegment->Type()); + if (!mSegment->ShareHandle(aTargetPid, msg.get())) { + return nullptr; + } + // close the handle to the segment after it is shared + mSegment->CloseHandle(); + return msg; +} + +UniquePtr<IPC::Message> Shmem::UnshareFrom(PrivateIPDLCaller, + int32_t routingId) { + AssertInvariants(); + return MakeUnique<ShmemDestroyed>(routingId, mId); +} + +void IPDLParamTraits<Shmem>::Write(IPC::Message* aMsg, IProtocol* aActor, + Shmem&& aParam) { + WriteIPDLParam(aMsg, aActor, aParam.mId); + + aParam.RevokeRights(Shmem::PrivateIPDLCaller()); + aParam.forget(Shmem::PrivateIPDLCaller()); +} + +bool IPDLParamTraits<Shmem>::Read(const IPC::Message* aMsg, + PickleIterator* aIter, IProtocol* aActor, + paramType* aResult) { + paramType::id_t id; + if (!ReadIPDLParam(aMsg, aIter, aActor, &id)) { + return false; + } + + Shmem::SharedMemory* rawmem = aActor->LookupSharedMemory(id); + if (rawmem) { + *aResult = Shmem(Shmem::PrivateIPDLCaller(), rawmem, id); + 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..48a3779d2c --- /dev/null +++ b/ipc/glue/Shmem.h @@ -0,0 +1,210 @@ +/* -*- 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/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 { +namespace layers { +class ShadowLayerForwarder; +} // namespace layers + +namespace ipc { + +template <typename P> +struct IPDLParamTraits; + +class Shmem final { + friend struct IPDLParamTraits<mozilla::ipc::Shmem>; +#ifdef DEBUG + // For ShadowLayerForwarder::CheckSurfaceDescriptor + friend class mozilla::layers::ShadowLayerForwarder; +#endif + + public: + typedef int32_t id_t; + // Low-level wrapper around platform shmem primitives. + typedef mozilla::ipc::SharedMemory SharedMemory; + typedef SharedMemory::SharedMemoryType SharedMemoryType; + // Shmem objects should only be constructed directly from SharedMemory + // objects by the Shmem implementation itself, or by a select few functions + // in ProtocolUtils.{h,cpp}. You should not need to add new instances of + // this token. + struct PrivateIPDLCaller {}; + + Shmem() : mSegment(nullptr), mData(nullptr), mSize(0), mId(0) {} + + Shmem(const Shmem& aOther) = default; + +#if !defined(DEBUG) + Shmem(PrivateIPDLCaller, SharedMemory* aSegment, id_t aId) + : mSegment(aSegment), mData(aSegment->memory()), mSize(0), mId(aId) { + mSize = static_cast<size_t>(*PtrToSize(mSegment)); + } +#else + Shmem(PrivateIPDLCaller, SharedMemory* aSegment, id_t aId); +#endif + + ~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(PrivateIPDLCaller()); + } + + 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); + } + + // These shouldn't be used directly, use the IPDL interface instead. + id_t Id(PrivateIPDLCaller) const { return mId; } + + SharedMemory* Segment(PrivateIPDLCaller) const { return mSegment; } + +#ifndef DEBUG + void RevokeRights(PrivateIPDLCaller) {} +#else + void RevokeRights(PrivateIPDLCaller); +#endif + + void forget(PrivateIPDLCaller) { + mSegment = nullptr; + mData = nullptr; + mSize = 0; + mId = 0; + } + + static already_AddRefed<Shmem::SharedMemory> Alloc(PrivateIPDLCaller, + size_t aNBytes, + SharedMemoryType aType, + bool aUnsafe, + bool aProtect = false); + + // Prepare this to be shared with |aProcess|. 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> ShareTo(PrivateIPDLCaller, base::ProcessId aTargetPid, + int32_t routingId); + + // Stop sharing this with |aTargetPid|. 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> UnshareFrom(PrivateIPDLCaller, 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( + PrivateIPDLCaller, const IPC::Message& aDescriptor, id_t* aId, + bool aProtect = false); + + static void Dealloc(PrivateIPDLCaller, SharedMemory* aSegment); + + private: + template <typename T> + void AssertAligned() const { + if (0 != (mSize % sizeof(T))) MOZ_CRASH("shmem is not T-aligned"); + } + +#if !defined(DEBUG) + void AssertInvariants() const {} + + static uint32_t* PtrToSize(SharedMemory* aSegment) { + char* endOfSegment = + reinterpret_cast<char*>(aSegment->memory()) + aSegment->Size(); + return reinterpret_cast<uint32_t*>(endOfSegment - sizeof(uint32_t)); + } + +#else + void AssertInvariants() const; +#endif + + RefPtr<SharedMemory> mSegment; + void* mData; + size_t mSize; + id_t mId; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef mozilla_ipc_Shmem_h diff --git a/ipc/glue/ShmemMessageUtils.h b/ipc/glue/ShmemMessageUtils.h new file mode 100644 index 0000000000..1b84b6558e --- /dev/null +++ b/ipc/glue/ShmemMessageUtils.h @@ -0,0 +1,33 @@ +/* -*- 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::Message* aMsg, IProtocol* aActor, paramType&& aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult); + + static void Log(const paramType& aParam, std::wstring* aLog) { + aLog->append(L"(shmem segment)"); + } +}; + +} // namespace ipc +} // namespace mozilla + +#endif // ifndef mozilla_ipc_ShmemMessageUtils_h diff --git a/ipc/glue/StringUtil.cpp b/ipc/glue/StringUtil.cpp new file mode 100644 index 0000000000..2eae9a1282 --- /dev/null +++ b/ipc/glue/StringUtil.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 "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" + +#include "build/build_config.h" + +// FIXME/cjones: these really only pertain to the linux sys string +// converters. +#ifdef WCHAR_T_IS_UTF16 +# 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 GhettoStringConvert(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(OS_MACOSX) && !defined(OS_WIN) +std::string SysWideToUTF8(const std::wstring& wide) { + // FIXME/cjones: do this with iconv + return GhettoStringConvert<std::wstring, std::string>(wide); +} +#endif + +#if !defined(OS_MACOSX) && !defined(OS_WIN) +std::wstring SysUTF8ToWide(const StringPiece& utf8) { + // FIXME/cjones: do this with iconv + return GhettoStringConvert<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..0c93c92f81 --- /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::Message* aMsg, IProtocol* aActor, + const mozilla::Tainted<T>& aParam) { + WriteIPDLParam(aMsg, aActor, aParam.mValue); + } + + static void Write(IPC::Message* aMsg, IProtocol* aActor, + mozilla::Tainted<T>&& aParam) { + WriteIPDLParam(aMsg, aActor, std::move(aParam.mValue)); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, mozilla::Tainted<T>* aResult) { + return ReadIPDLParam(aMsg, aIter, 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/Transport.h b/ipc/glue/Transport.h new file mode 100644 index 0000000000..9621d02354 --- /dev/null +++ b/ipc/glue/Transport.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 mozilla_ipc_Transport_h +#define mozilla_ipc_Transport_h 1 + +#include "base/process_util.h" +#include "chrome/common/ipc_channel.h" + +#ifdef OS_POSIX +# include "mozilla/ipc/Transport_posix.h" +#elif OS_WIN +# include "mozilla/ipc/Transport_win.h" +#endif +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace ipc { + +class FileDescriptor; + +typedef IPC::Channel Transport; + +nsresult CreateTransport(base::ProcessId aProcIdOne, TransportDescriptor* aOne, + TransportDescriptor* aTwo); + +UniquePtr<Transport> OpenDescriptor(const TransportDescriptor& aTd, + Transport::Mode aMode); + +UniquePtr<Transport> OpenDescriptor(const FileDescriptor& aFd, + Transport::Mode aMode); + +TransportDescriptor DuplicateDescriptor(const TransportDescriptor& aTd); + +void CloseDescriptor(const TransportDescriptor& aTd); + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_Transport_h diff --git a/ipc/glue/TransportSecurityInfoUtils.cpp b/ipc/glue/TransportSecurityInfoUtils.cpp new file mode 100644 index 0000000000..0fa88dc5c8 --- /dev/null +++ b/ipc/glue/TransportSecurityInfoUtils.cpp @@ -0,0 +1,79 @@ +/* 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" + +namespace IPC { + +void ParamTraits<nsITransportSecurityInfo*>::Write( + Message* aMsg, nsITransportSecurityInfo* aParam) { + bool nonNull = !!aParam; + WriteParam(aMsg, nonNull); + if (!nonNull) { + return; + } + + aParam->SerializeToIPC(aMsg); +} + +bool ParamTraits<nsITransportSecurityInfo*>::Read( + const Message* aMsg, PickleIterator* aIter, + RefPtr<nsITransportSecurityInfo>* aResult) { + *aResult = nullptr; + + bool nonNull = false; + if (!ReadParam(aMsg, aIter, &nonNull)) { + return false; + } + + if (!nonNull) { + return true; + } + + RefPtr<nsITransportSecurityInfo> info = + new mozilla::psm::TransportSecurityInfo(); + if (!info->DeserializeFromIPC(aMsg, aIter)) { + return false; + } + + *aResult = std::move(info); + return true; +} + +void ParamTraits<nsIX509Cert*>::Write(Message* aMsg, nsIX509Cert* aParam) { + bool nonNull = !!aParam; + WriteParam(aMsg, nonNull); + if (!nonNull) { + return; + } + + aParam->SerializeToIPC(aMsg); +} + +bool ParamTraits<nsIX509Cert*>::Read(const Message* aMsg, PickleIterator* aIter, + RefPtr<nsIX509Cert>* aResult) { + *aResult = nullptr; + + bool nonNull = false; + if (!ReadParam(aMsg, aIter, &nonNull)) { + return false; + } + + if (!nonNull) { + return true; + } + + RefPtr<nsIX509Cert> cert = new nsNSSCertificate(); + if (!cert->DeserializeFromIPC(aMsg, aIter)) { + 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..a782931d6b --- /dev/null +++ b/ipc/glue/TransportSecurityInfoUtils.h @@ -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/. */ + +#ifndef mozilla_ipc_TransportSecurityInfoUtils_h +#define mozilla_ipc_TransportSecurityInfoUtils_h + +#include "nsCOMPtr.h" +#include "nsITransportSecurityInfo.h" + +namespace IPC { + +template <typename> +struct ParamTraits; + +template <> +struct ParamTraits<nsITransportSecurityInfo*> { + static void Write(Message* aMsg, nsITransportSecurityInfo* aParam); + static bool Read(const Message* aMsg, PickleIterator* aIter, + RefPtr<nsITransportSecurityInfo>* aResult); +}; + +template <> +struct ParamTraits<nsIX509Cert*> { + static void Write(Message* aMsg, nsIX509Cert* aCert); + static bool Read(const Message* aMsg, PickleIterator* aIter, + RefPtr<nsIX509Cert>* aResult); +}; + +} // namespace IPC + +#endif // mozilla_ipc_TransportSecurityInfoUtils_h diff --git a/ipc/glue/Transport_posix.cpp b/ipc/glue/Transport_posix.cpp new file mode 100644 index 0000000000..c756c98b0e --- /dev/null +++ b/ipc/glue/Transport_posix.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 <unistd.h> + +#include <string> + +#include "base/eintr_wrapper.h" + +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/Transport.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "ProtocolUtils.h" + +using base::ProcessHandle; + +namespace mozilla { +namespace ipc { + +nsresult CreateTransport(base::ProcessId aProcIdOne, TransportDescriptor* aOne, + TransportDescriptor* aTwo) { + auto id = IPC::Channel::GenerateVerifiedChannelID(); + // Use MODE_SERVER to force creation of the socketpair + Transport t(id, Transport::MODE_SERVER, nullptr); + int fd1 = t.GetFileDescriptor(); + int fd2, dontcare; + t.GetClientFileDescriptorMapping(&fd2, &dontcare); + if (fd1 < 0 || fd2 < 0) { + return NS_ERROR_TRANSPORT_INIT; + } + + // The Transport closes these fds when it goes out of scope, so we + // dup them here + fd1 = dup(fd1); + if (fd1 < 0) { + AnnotateCrashReportWithErrno( + CrashReporter::Annotation::IpcCreateTransportDupErrno, errno); + } + fd2 = dup(fd2); + if (fd2 < 0) { + AnnotateCrashReportWithErrno( + CrashReporter::Annotation::IpcCreateTransportDupErrno, errno); + } + + if (fd1 < 0 || fd2 < 0) { + IGNORE_EINTR(close(fd1)); + IGNORE_EINTR(close(fd2)); + return NS_ERROR_DUPLICATE_HANDLE; + } + + aOne->mFd = base::FileDescriptor(fd1, true /*close after sending*/); + aTwo->mFd = base::FileDescriptor(fd2, true /*close after sending*/); + return NS_OK; +} + +UniquePtr<Transport> OpenDescriptor(const TransportDescriptor& aTd, + Transport::Mode aMode) { + return MakeUnique<Transport>(aTd.mFd.fd, aMode, nullptr); +} + +UniquePtr<Transport> OpenDescriptor(const FileDescriptor& aFd, + Transport::Mode aMode) { + auto rawFD = aFd.ClonePlatformHandle(); + return MakeUnique<Transport>(rawFD.release(), aMode, nullptr); +} + +TransportDescriptor DuplicateDescriptor(const TransportDescriptor& aTd) { + TransportDescriptor result = aTd; + result.mFd.fd = dup(aTd.mFd.fd); + if (result.mFd.fd == -1) { + AnnotateSystemError(); + } + MOZ_RELEASE_ASSERT(result.mFd.fd != -1, "DuplicateDescriptor failed"); + return result; +} + +void CloseDescriptor(const TransportDescriptor& aTd) { close(aTd.mFd.fd); } + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/Transport_posix.h b/ipc/glue/Transport_posix.h new file mode 100644 index 0000000000..f0dfeccb49 --- /dev/null +++ b/ipc/glue/Transport_posix.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_Transport_posix_h +#define mozilla_ipc_Transport_posix_h 1 + +#include "ipc/IPCMessageUtils.h" + +namespace mozilla { +namespace ipc { + +struct TransportDescriptor { + base::FileDescriptor mFd; +}; + +} // namespace ipc +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::ipc::TransportDescriptor> { + typedef mozilla::ipc::TransportDescriptor paramType; + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mFd); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mFd); + } +}; + +} // namespace IPC + +#endif // mozilla_ipc_Transport_posix_h diff --git a/ipc/glue/Transport_win.cpp b/ipc/glue/Transport_win.cpp new file mode 100644 index 0000000000..fddd781b41 --- /dev/null +++ b/ipc/glue/Transport_win.cpp @@ -0,0 +1,115 @@ +/* -*- 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/message_loop.h" + +#include "mozilla/ipc/Transport.h" +#include "mozilla/ipc/ProtocolUtils.h" + +using base::ProcessHandle; + +namespace mozilla { +namespace ipc { + +nsresult CreateTransport(base::ProcessId aProcIdOne, TransportDescriptor* aOne, + TransportDescriptor* aTwo) { + auto id = IPC::Channel::GenerateVerifiedChannelID(); + // Use MODE_SERVER to force creation of the pipe + Transport t(id, Transport::MODE_SERVER, nullptr); + HANDLE serverPipe = t.GetServerPipeHandle(); + if (!serverPipe) { + return NS_ERROR_TRANSPORT_INIT; + } + + // NB: we create the server pipe immediately, instead of just + // grabbing an ID, on purpose. In the current setup, the client + // needs to connect to an existing server pipe, so to prevent race + // conditions, we create the server side here. When we send the pipe + // to the server, we DuplicateHandle it to the server process to give it + // access. + HANDLE serverDup; + DWORD access = 0; + DWORD options = DUPLICATE_SAME_ACCESS; + if (!DuplicateHandle(serverPipe, base::GetCurrentProcId(), &serverDup, access, + options)) { + return NS_ERROR_DUPLICATE_HANDLE; + } + + aOne->mPipeName = aTwo->mPipeName = id; + aOne->mServerPipeHandle = serverDup; + aOne->mDestinationProcessId = aProcIdOne; + aTwo->mServerPipeHandle = INVALID_HANDLE_VALUE; + aTwo->mDestinationProcessId = 0; + return NS_OK; +} + +HANDLE +TransferHandleToProcess(HANDLE source, base::ProcessId pid) { + // At this point we're sending the handle to another process. + + if (source == INVALID_HANDLE_VALUE) { + return source; + } + HANDLE handleDup; + DWORD access = 0; + DWORD options = DUPLICATE_SAME_ACCESS; + bool ok = DuplicateHandle(source, pid, &handleDup, access, options); + if (!ok) { + return nullptr; + } + + // Now close our own copy of the handle (we're supposed to be transferring, + // not copying). + CloseHandle(source); + + return handleDup; +} + +UniquePtr<Transport> OpenDescriptor(const TransportDescriptor& aTd, + Transport::Mode aMode) { + if (aTd.mServerPipeHandle != INVALID_HANDLE_VALUE) { + MOZ_RELEASE_ASSERT(aTd.mDestinationProcessId == base::GetCurrentProcId()); + } + return MakeUnique<Transport>(aTd.mPipeName, aTd.mServerPipeHandle, aMode, + nullptr); +} + +UniquePtr<Transport> OpenDescriptor(const FileDescriptor& aFd, + Transport::Mode aMode) { + MOZ_ASSERT_UNREACHABLE("Not implemented!"); + return nullptr; +} + +TransportDescriptor DuplicateDescriptor(const TransportDescriptor& aTd) { + // We're duplicating this handle in our own process for bookkeeping purposes. + + if (aTd.mServerPipeHandle == INVALID_HANDLE_VALUE) { + return aTd; + } + + HANDLE serverDup; + DWORD access = 0; + DWORD options = DUPLICATE_SAME_ACCESS; + bool ok = DuplicateHandle(aTd.mServerPipeHandle, base::GetCurrentProcId(), + &serverDup, access, options); + if (!ok) { + AnnotateSystemError(); + } + MOZ_RELEASE_ASSERT(ok); + + TransportDescriptor desc = aTd; + desc.mServerPipeHandle = serverDup; + return desc; +} + +void CloseDescriptor(const TransportDescriptor& aTd) { + // We're closing our own local copy of the pipe. + + CloseHandle(aTd.mServerPipeHandle); +} + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/Transport_win.h b/ipc/glue/Transport_win.h new file mode 100644 index 0000000000..9cc45bcd19 --- /dev/null +++ b/ipc/glue/Transport_win.h @@ -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/. */ + +#ifndef mozilla_ipc_Transport_win_h +#define mozilla_ipc_Transport_win_h 1 + +#include <string> + +#include "base/process.h" +#include "ipc/IPCMessageUtils.h" +#include "nsWindowsHelpers.h" +#include "nsXULAppAPI.h" + +namespace mozilla { +namespace ipc { + +struct TransportDescriptor { + std::wstring mPipeName; + HANDLE mServerPipeHandle; + base::ProcessId mDestinationProcessId; +}; + +HANDLE +TransferHandleToProcess(HANDLE source, base::ProcessId pid); + +} // namespace ipc +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::ipc::TransportDescriptor> { + typedef mozilla::ipc::TransportDescriptor paramType; + static void Write(Message* aMsg, const paramType& aParam) { + HANDLE pipe = mozilla::ipc::TransferHandleToProcess( + aParam.mServerPipeHandle, aParam.mDestinationProcessId); + DWORD duplicateFromProcessId = 0; + if (!pipe) { + if (XRE_IsParentProcess()) { + // If we are the parent and failed to transfer then there is no hope, + // just close the handle. + ::CloseHandle(aParam.mServerPipeHandle); + } else { + // We are probably sending to parent so it should be able to duplicate. + pipe = aParam.mServerPipeHandle; + duplicateFromProcessId = ::GetCurrentProcessId(); + } + } + + WriteParam(aMsg, aParam.mPipeName); + WriteParam(aMsg, pipe); + WriteParam(aMsg, duplicateFromProcessId); + WriteParam(aMsg, aParam.mDestinationProcessId); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + DWORD duplicateFromProcessId; + bool r = (ReadParam(aMsg, aIter, &aResult->mPipeName) && + ReadParam(aMsg, aIter, &aResult->mServerPipeHandle) && + ReadParam(aMsg, aIter, &duplicateFromProcessId) && + ReadParam(aMsg, aIter, &aResult->mDestinationProcessId)); + if (!r) { + return r; + } + + MOZ_RELEASE_ASSERT( + aResult->mServerPipeHandle, + "Main process failed to duplicate pipe handle to child."); + + // If this is a not the "server" side descriptor, we have finished. + if (aResult->mServerPipeHandle == INVALID_HANDLE_VALUE) { + return true; + } + + MOZ_RELEASE_ASSERT(aResult->mDestinationProcessId == + base::GetCurrentProcId()); + + // If the pipe has already been duplicated to us, we have finished. + if (!duplicateFromProcessId) { + return true; + } + + // Otherwise duplicate the handle to us. + nsAutoHandle sourceProcess( + ::OpenProcess(PROCESS_DUP_HANDLE, FALSE, duplicateFromProcessId)); + if (!sourceProcess) { + return false; + } + + HANDLE ourHandle; + BOOL duped = ::DuplicateHandle( + sourceProcess, aResult->mServerPipeHandle, ::GetCurrentProcess(), + &ourHandle, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + if (!duped) { + aResult->mServerPipeHandle = INVALID_HANDLE_VALUE; + return false; + } + + aResult->mServerPipeHandle = ourHandle; + return true; + } +}; + +} // namespace IPC + +#endif // mozilla_ipc_Transport_win_h diff --git a/ipc/glue/URIParams.ipdlh b/ipc/glue/URIParams.ipdlh new file mode 100644 index 0000000000..16aaea257d --- /dev/null +++ b/ipc/glue/URIParams.ipdlh @@ -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/. */ + + +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 NullPrincipalURIParams +{ + // Purposefully empty. Null principal URIs do not round-trip. +}; + +struct HostObjectURIParams +{ + SimpleURIParams simpleParams; + bool revoked; +}; + +union URIParams +{ + SimpleURIParams; + StandardURLParams; + JARURIParams; + IconURIParams; + NullPrincipalURIParams; + JSURIParams; + SimpleNestedURIParams; + HostObjectURIParams; + DefaultURIParams; + NestedAboutURIParams; +}; + +struct JSURIParams +{ + SimpleURIParams simpleParams; + URIParams? baseURI; +}; + +struct SimpleNestedURIParams +{ + SimpleURIParams simpleParams; + URIParams innerURI; +}; + +struct NestedAboutURIParams +{ + SimpleNestedURIParams nestedParams; + URIParams? baseURI; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/URIUtils.cpp b/ipc/glue/URIUtils.cpp new file mode 100644 index 0000000000..45d88ec071 --- /dev/null +++ b/ipc/glue/URIUtils.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 "URIUtils.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/BlobURL.h" +#include "mozilla/net/DefaultURI.h" +#include "mozilla/net/SubstitutingURL.h" +#include "mozilla/NullPrincipalURI.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(NS_IsMainThread()); + MOZ_ASSERT(aURI); + + aURI->Serialize(aParams); + if (aParams.type() == URIParams::T__None) { + MOZ_CRASH("Serialize failed!"); + } +} + +void SerializeURI(nsIURI* aURI, Maybe<URIParams>& aParams) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aURI) { + URIParams params; + SerializeURI(aURI, params); + aParams = Some(std::move(params)); + } else { + aParams = Nothing(); + } +} + +already_AddRefed<nsIURI> DeserializeURI(const URIParams& aParams) { + MOZ_ASSERT(NS_IsMainThread()); + + 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::TNullPrincipalURIParams: + mutator = new NullPrincipalURI::Mutator(); + 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; + + 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) { + MOZ_ASSERT(NS_IsMainThread()); + + 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..fa595f3575 --- /dev/null +++ b/ipc/glue/URIUtils.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_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::Message* aMsg, IProtocol* aActor, nsIURI* aParam) { + Maybe<URIParams> params; + SerializeURI(aParam, params); + WriteIPDLParam(aMsg, aActor, params); + } + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, RefPtr<nsIURI>* aResult) { + Maybe<URIParams> params; + if (!ReadIPDLParam(aMsg, aIter, aActor, ¶ms)) { + return false; + } + *aResult = DeserializeURI(params); + return true; + } +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_URIUtils_h diff --git a/ipc/glue/WindowsMessageLoop.cpp b/ipc/glue/WindowsMessageLoop.cpp new file mode 100644 index 0000000000..2a18346811 --- /dev/null +++ b/ipc/glue/WindowsMessageLoop.cpp @@ -0,0 +1,1408 @@ +/* -*- 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/UniquePtr.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"; +const wchar_t k3rdPartyWindowProp[] = L"Mozilla3rdPartyWindow"; + +// This isn't defined before Windows XP. +enum { WM_XP_THEMECHANGED = 0x031A }; + +nsTArray<HWND>* gNeuteredWindows = nullptr; + +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) { + int idx = 0; + while (mozilla::widget::gAllEvents[idx].mId != uMsg && + mozilla::widget::gAllEvents[idx].mStr != nullptr) { + idx++; + } + if (mozilla::widget::gAllEvents[idx].mStr) { + log.AppendPrintf("ui message \"%s\"", + mozilla::widget::gAllEvents[idx].mStr); + } 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%X", 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: { + if (!::GetPropW(hwnd, k3rdPartyWindowProp)) { + DWORD objId = static_cast<DWORD>(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 + +// We need the pointer value of this in PluginInstanceChild. +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; + } + + // Plugin windows that can trigger ipc calls in child: + // 'ShockwaveFlashFullScreen' - flash fullscreen window + if (className.EqualsLiteral("ShockwaveFlashFullScreen")) { + SetPropW(hWnd, k3rdPartyWindowProp, (HANDLE)1); + 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); + RemovePropW(hWnd, k3rdPartyWindowProp); + 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); + RemovePropW(hWnd, k3rdPartyWindowProp); +} + +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, + bool interrupt) + : mInterrupt(interrupt), + 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>(); + NS_ASSERTION(gNeuteredWindows, "Out of memory!"); + } +} + +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!"); + delete gNeuteredWindows; + 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; + +#if defined(ACCESSIBILITY) +bool MessageChannel::WaitForSyncNotifyWithA11yReentry() { + mMonitor->AssertCurrentThreadOwns(); + MonitorAutoUnlock unlock(*mMonitor); + + const DWORD waitStart = ::GetTickCount(); + DWORD elapsed = 0; + DWORD timeout = + mTimeoutMs == kNoTimeout ? INFINITE : static_cast<DWORD>(mTimeoutMs); + bool timedOut = false; + + while (true) { + { // Scope for lock + MonitorAutoLock lock(*mMonitor); + if (!Connected()) { + break; + } + } + if (timeout != static_cast<DWORD>(kNoTimeout)) { + elapsed = ::GetTickCount() - waitStart; + } + if (elapsed >= timeout) { + timedOut = true; + break; + } + DWORD waitResult = 0; + ::SetLastError(ERROR_SUCCESS); + HRESULT hr = ::CoWaitForMultipleHandles(COWAIT_ALERTABLE, timeout - elapsed, + 1, &mEvent, &waitResult); + if (hr == RPC_S_CALLPENDING) { + timedOut = true; + break; + } + if (hr == S_OK) { + if (waitResult == 0) { + // mEvent is signaled + BOOL success = ::ResetEvent(mEvent); + if (!success) { + gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle) + << "WindowsMessageChannel::WaitForSyncNotifyWithA11yReentry " + "failed to reset event. GetLastError: " + << GetLastError(); + } + break; + } + if (waitResult == WAIT_IO_COMPLETION) { + // APC fired, keep waiting + continue; + } + } + NS_ERROR("CoWaitForMultipleHandles failed"); + break; + } + + return WaitResponse(timedOut); +} +#endif + +bool MessageChannel::WaitForSyncNotify(bool aHandleWindowsMessages) { + mMonitor->AssertCurrentThreadOwns(); + + if (!gUIThreadId) { + mozilla::ipc::windows::InitUIThread(); + } + +#if defined(ACCESSIBILITY) + if (mFlags & REQUIRE_A11Y_REENTRY) { + MOZ_ASSERT(!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)); + return WaitForSyncNotifyWithA11yReentry(); + } +#endif + + // Use a blocking wait if this channel does not require + // Windows message deferral behavior. + if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION) || + !aHandleWindowsMessages) { + 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 && !mTopFrame->mInterrupt, + "Top frame is not a sync 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); +} + +bool MessageChannel::WaitForInterruptNotify() { + mMonitor->AssertCurrentThreadOwns(); + + // Receiving the interrupt notification may require JS to execute on a + // worker. + dom::AutoYieldJSThreadExecution yield; + + if (!gUIThreadId) { + mozilla::ipc::windows::InitUIThread(); + } + + // Re-use sync notification wait code if this channel does not require + // Windows message deferral behavior. + if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) { + return WaitForSyncNotify(true); + } + + if (!InterruptStackDepth() && !AwaitingIncomingMessage()) { + // There is currently no way to recover from this condition. + MOZ_CRASH("StackDepth() is 0 in call to MessageChannel::WaitForNotify!"); + } + + NS_ASSERTION( + mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION, + "Shouldn't be here for channels that don't use message deferral!"); + NS_ASSERTION(mTopFrame && mTopFrame->mInterrupt, + "Top frame is not a sync frame!"); + + MonitorAutoUnlock unlock(*mMonitor); + + bool timedout = false; + + UINT_PTR timerId = 0; + TimeoutData timeoutData = {0}; + + // gWindowHook is used as a flag variable for the loop below: if it is set + // and we start to spin a nested event loop, we need to clear the hook and + // process deferred/pending messages. + while (1) { + NS_ASSERTION((!!gWindowHook) == MessageChannel::IsPumpingMessages(), + "gWindowHook out of sync with reality"); + + if (mTopFrame->mSpinNestedEvents) { + if (gWindowHook && timerId) { + KillTimer(nullptr, timerId); + timerId = 0; + } + DeneuteredWindowRegion deneuteredRgn; + SpinInternalEventLoop(); + BOOL success = ResetEvent(mEvent); + if (!success) { + gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle) + << "WindowsMessageChannel::WaitForInterruptNotify::" + "SpinNestedEvents failed to reset event. GetLastError: " + << GetLastError(); + } + return true; + } + + if (mTimeoutMs != kNoTimeout && !timerId) { + InitTimeoutData(&timeoutData, mTimeoutMs); + timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr); + NS_ASSERTION(timerId, "SetTimer failed!"); + } + + NeuteredWindowRegion neuteredRgn(true); + + MSG msg = {0}; + + // Don't get wrapped up in here if the child connection dies. + { + MonitorAutoLock lock(*mMonitor); + if (!Connected()) { + break; + } + } + + 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::WaitForInterruptNotify::" + "WaitForMultipleObjects 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; + } + + // See MessageChannel's WaitFor*Notify for details. + bool haveSentMessagesPending = + (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0; + + // Run all COM messages *after* looking at the queue status. + if (gCOMWindow) { + if (PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + ::DispatchMessageW(&msg); + } + } + + // PeekMessage markes the messages as "old" so that they don't wake up + // MsgWaitForMultipleObjects every time. + 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..9fd9327f16 --- /dev/null +++ b/ipc/glue/moz.build @@ -0,0 +1,267 @@ +# -*- 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 += [ + "BackgroundChild.h", + "BackgroundParent.h", + "BackgroundUtils.h", + "BrowserProcessSubThread.h", + "ByteBuf.h", + "ByteBufUtils.h", + "CrashReporterClient.h", + "CrashReporterHelper.h", + "CrashReporterHost.h", + "CrossProcessMutex.h", + "CrossProcessSemaphore.h", + "Endpoint.h", + "EnvironmentMap.h", + "FileDescriptor.h", + "FileDescriptorSetChild.h", + "FileDescriptorSetParent.h", + "FileDescriptorUtils.h", + "GeckoChildProcessHost.h", + "IdleSchedulerChild.h", + "IdleSchedulerParent.h", + "InputStreamUtils.h", + "IOThreadChild.h", + "IPCCore.h", + "IPCStreamAlloc.h", + "IPCStreamDestination.h", + "IPCStreamSource.h", + "IPCStreamUtils.h", + "IPCTypes.h", + "IPDLParamTraits.h", + "LibrarySandboxPreload.h", + "MessageChannel.h", + "MessageLink.h", + "Neutering.h", + "ProcessChild.h", + "ProtocolMessageUtils.h", + "ProtocolUtils.h", + "ProtocolUtilsFwd.h", + "ScopedXREEmbed.h", + "SerializedStructuredCloneBuffer.h", + "SharedMemory.h", + "SharedMemoryBasic.h", + "Shmem.h", + "ShmemMessageUtils.h", + "TaintingIPCUtils.h", + "TaskFactory.h", + "Transport.h", + "TransportSecurityInfoUtils.h", + "URIUtils.h", + "WindowsMessageLoop.h", +] + +if CONFIG["OS_ARCH"] == "WINNT": + EXPORTS.mozilla.ipc += [ + "Transport_win.h", + ] + SOURCES += [ + "SharedMemory_windows.cpp", + "Transport_win.cpp", + "WindowsMessageLoop.cpp", + ] +else: + EXPORTS.mozilla.ipc += [ + "Transport_posix.h", + ] + UNIFIED_SOURCES += [ + "SharedMemory_posix.cpp", + "Transport_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": + UNIFIED_SOURCES += [ + "CrossProcessSemaphore_posix.cpp", + ] +else: + UNIFIED_SOURCES += [ + "CrossProcessSemaphore_unimplemented.cpp", + ] + +# Android has its own, +# almost-but-not-quite-compatible-with-POSIX-or-/dev/shm shared memory +# impl. +if CONFIG["OS_TARGET"] == "Android": + EXPORTS.mozilla.ipc += ["SharedMemoryBasic_android.h"] + UNIFIED_SOURCES += [ + "SharedMemoryBasic_android.cpp", + ] +elif 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", + ] +elif CONFIG["OS_ARCH"] in ("DragonFly", "FreeBSD", "NetBSD", "OpenBSD"): + UNIFIED_SOURCES += ["ProcessUtils_bsd.cpp"] +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", + "BrowserProcessSubThread.cpp", + "CrashReporterClient.cpp", + "CrashReporterHost.cpp", + "FileDescriptor.cpp", + "FileDescriptorUtils.cpp", + "IdleSchedulerChild.cpp", + "IdleSchedulerParent.cpp", + "InputStreamUtils.cpp", + "IPCMessageUtils.cpp", + "IPCStreamChild.cpp", + "IPCStreamDestination.cpp", + "IPCStreamParent.cpp", + "IPCStreamSource.cpp", + "IPCStreamUtils.cpp", + "LibrarySandboxPreload.cpp", + "MessageChannel.cpp", + "MessageLink.cpp", + "MessagePump.cpp", + "ProcessChild.cpp", + "ProcessUtils_common.cpp", + "ProtocolUtils.cpp", + "ScopedXREEmbed.cpp", + "SharedMemory.cpp", + "Shmem.cpp", + "StringUtil.cpp", + "TransportSecurityInfoUtils.cpp", + "URIUtils.cpp", +] + +SOURCES += [ + "BackgroundChildImpl.cpp", + "BackgroundParentImpl.cpp", + "FileDescriptorSetChild.cpp", + "FileDescriptorSetParent.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", + ] + +LOCAL_INCLUDES += [ + "/caps", + "/dom/broadcastchannel", + "/dom/indexedDB", + "/dom/storage", + "/netwerk/base", + "/third_party/libwebrtc", + "/third_party/libwebrtc/webrtc", + "/xpcom/build", +] + +IPDL_SOURCES = [ + "InputStreamParams.ipdlh", + "IPCStream.ipdlh", + "PBackground.ipdl", + "PBackgroundSharedTypes.ipdlh", + "PBackgroundTest.ipdl", + "PChildToParentStream.ipdl", + "PFileDescriptorSet.ipdl", + "PIdleScheduler.ipdl", + "PParentToChildStream.ipdl", + "ProtocolTypes.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" + +for var in ("MOZ_CHILD_PROCESS_NAME", "MOZ_CHILD_PROCESS_BUNDLE"): + 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["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-shadow"] + +# 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..459ba65186 --- /dev/null +++ b/ipc/glue/nsIIPCSerializableInputStream.h @@ -0,0 +1,125 @@ +/* -*- 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; +class ChildToParentStreamActorManager; +class ParentToChildStreamActorManager; + +} // 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: + typedef nsTArray<mozilla::ipc::FileDescriptor> FileDescriptorArray; + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIPCSERIALIZABLEINPUTSTREAM_IID) + + virtual void Serialize( + mozilla::ipc::InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors, bool aDelayedStart, + uint32_t aMaxSize, uint32_t* aSizeUsed, + mozilla::ipc::ParentToChildStreamActorManager* aManager) = 0; + + virtual void Serialize( + mozilla::ipc::InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors, bool aDelayedStart, + uint32_t aMaxSize, uint32_t* aSizeUsed, + mozilla::ipc::ChildToParentStreamActorManager* aManager) = 0; + + virtual bool Deserialize(const mozilla::ipc::InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIIPCSerializableInputStream, + NS_IIPCSERIALIZABLEINPUTSTREAM_IID) + +#define NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM \ + virtual void Serialize( \ + mozilla::ipc::InputStreamParams&, FileDescriptorArray&, bool, uint32_t, \ + uint32_t*, mozilla::ipc::ParentToChildStreamActorManager*) override; \ + \ + virtual void Serialize( \ + mozilla::ipc::InputStreamParams&, FileDescriptorArray&, bool, uint32_t, \ + uint32_t*, mozilla::ipc::ChildToParentStreamActorManager*) override; \ + \ + virtual bool Deserialize(const mozilla::ipc::InputStreamParams&, \ + const FileDescriptorArray&) override; + +#define NS_FORWARD_NSIIPCSERIALIZABLEINPUTSTREAM(_to) \ + virtual void Serialize( \ + mozilla::ipc::InputStreamParams& aParams, \ + FileDescriptorArray& aFileDescriptors, bool aDelayedStart, \ + uint32_t aMaxSize, uint32_t* aSizeUsed, \ + mozilla::ipc::ParentToChildStreamActorManager* aManager) override { \ + _to Serialize(aParams, aFileDescriptors, aDelayedStart, aMaxSize, \ + aSizeUsed, aManager); \ + } \ + \ + virtual void Serialize( \ + mozilla::ipc::InputStreamParams& aParams, \ + FileDescriptorArray& aFileDescriptors, bool aDelayedStart, \ + uint32_t aMaxSize, uint32_t* aSizeUsed, \ + mozilla::ipc::ChildToParentStreamActorManager* aManager) override { \ + _to Serialize(aParams, aFileDescriptors, aDelayedStart, aMaxSize, \ + aSizeUsed, aManager); \ + } \ + \ + virtual bool Deserialize(const mozilla::ipc::InputStreamParams& aParams, \ + const FileDescriptorArray& aFileDescriptors) \ + override { \ + return _to Deserialize(aParams, aFileDescriptors); \ + } + +#define NS_FORWARD_SAFE_NSIIPCSERIALIZABLEINPUTSTREAM(_to) \ + virtual void Serialize( \ + mozilla::ipc::InputStreamParams& aParams, \ + FileDescriptorArray& aFileDescriptors, bool aDelayedStart, \ + uint32_t aMaxSize, uint32_t* aSizeUsed, \ + mozilla::ipc::ParentToChildStreamActorManager* aManager) override { \ + if (_to) { \ + _to->Serialize(aParams, aFileDescriptors, aDelayedStart, aMaxSize, \ + aSizeUsed, aManager); \ + } \ + } \ + \ + virtual void Serialize( \ + mozilla::ipc::InputStreamParams& aParams, \ + FileDescriptorArray& aFileDescriptors, bool aDelayedStart, \ + uint32_t aMaxSize, uint32_t* aSizeUsed, \ + mozilla::ipc::ChildToParentStreamActorManager* aManager) override { \ + if (_to) { \ + _to->Serialize(aParams, aFileDescriptors, aDelayedStart, aMaxSize, \ + aSizeUsed, aManager); \ + } \ + } \ + \ + virtual bool Deserialize(const mozilla::ipc::InputStreamParams& aParams, \ + const FileDescriptorArray& aFileDescriptors) \ + override { \ + return _to ? _to->Deserialize(aParams, aFileDescriptors) : false; \ + } + +#endif // mozilla_ipc_nsIIPCSerializableInputStream_h |