954 lines
30 KiB
C++
954 lines
30 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "base/process_util.h"
|
|
#include "base/task.h"
|
|
|
|
#ifdef XP_UNIX
|
|
# include <errno.h>
|
|
#endif
|
|
#include <type_traits>
|
|
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
|
|
|
#include "mozilla/ipc/ProtocolMessageUtils.h"
|
|
#include "mozilla/ipc/ProtocolUtils.h"
|
|
|
|
#include "mozilla/ipc/MessageChannel.h"
|
|
#include "mozilla/ipc/IPDLParamTraits.h"
|
|
#include "mozilla/StaticMutex.h"
|
|
#if defined(DEBUG) || defined(FUZZING)
|
|
# include "mozilla/Tokenizer.h"
|
|
#endif
|
|
#include "mozilla/Unused.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "prtime.h"
|
|
|
|
#if defined(MOZ_SANDBOX) && defined(XP_WIN)
|
|
# include "mozilla/sandboxTarget.h"
|
|
#endif
|
|
|
|
#if defined(XP_WIN)
|
|
# include "aclapi.h"
|
|
# include "sddl.h"
|
|
#endif
|
|
|
|
#ifdef FUZZING_SNAPSHOT
|
|
# include "mozilla/fuzzing/IPCFuzzController.h"
|
|
#endif
|
|
|
|
using namespace IPC;
|
|
|
|
using base::GetCurrentProcId;
|
|
using base::ProcessHandle;
|
|
using base::ProcessId;
|
|
|
|
namespace mozilla {
|
|
namespace ipc {
|
|
|
|
/* static */
|
|
EndpointProcInfo EndpointProcInfo::Current() {
|
|
return EndpointProcInfo{.mPid = GetCurrentProcId(),
|
|
.mChildID = XRE_GetChildID()};
|
|
}
|
|
|
|
/* static */
|
|
IPCResult IPCResult::FailImpl(NotNull<IProtocol*> actor, const char* where,
|
|
const char* why) {
|
|
// Calls top-level protocol to handle the error.
|
|
nsPrintfCString errorMsg("%s %s\n", where, why);
|
|
actor->GetIPCChannel()->Listener()->ProcessingError(
|
|
HasResultCodes::MsgProcessingError, errorMsg.get());
|
|
|
|
#if defined(DEBUG) && !defined(FUZZING)
|
|
// We do not expect IPC_FAIL to ever happen in normal operations. If this
|
|
// happens in DEBUG, we most likely see some behavior during a test we should
|
|
// really investigate.
|
|
nsPrintfCString crashMsg(
|
|
"Use IPC_FAIL only in an "
|
|
"unrecoverable, unexpected state: %s",
|
|
errorMsg.get());
|
|
// We already leak the same information potentially on child process failures
|
|
// even in release, and here we are only in DEBUG.
|
|
MOZ_CRASH_UNSAFE(crashMsg.get());
|
|
#else
|
|
return IPCResult(false);
|
|
#endif
|
|
}
|
|
|
|
/* static */
|
|
IPCResult IPCResult::FailForTesting(NotNull<IProtocol*> actor,
|
|
const char* where, const char* why) {
|
|
return IPCResult(false);
|
|
}
|
|
|
|
void AnnotateSystemError() {
|
|
uint32_t error = 0;
|
|
#if defined(XP_WIN)
|
|
error = ::GetLastError();
|
|
#else
|
|
error = errno;
|
|
#endif
|
|
if (error) {
|
|
CrashReporter::RecordAnnotationU32(
|
|
CrashReporter::Annotation::IPCSystemError, error);
|
|
}
|
|
}
|
|
|
|
#if defined(XP_MACOSX)
|
|
void AnnotateCrashReportWithErrno(CrashReporter::Annotation tag, int error) {
|
|
CrashReporter::RecordAnnotationU32(tag, static_cast<uint32_t>(error));
|
|
}
|
|
#endif // defined(XP_MACOSX)
|
|
|
|
#if defined(DEBUG) || defined(FUZZING)
|
|
// If aTopLevelProtocol matches any token in aFilter, return true.
|
|
//
|
|
// aTopLevelProtocol is a protocol name, without the "Parent" / "Child" suffix.
|
|
// aSide indicates whether we're logging parent-side or child-side activity.
|
|
//
|
|
// aFilter is a list of protocol names separated by commas and/or
|
|
// spaces. These may include the "Child" / "Parent" suffix, or omit
|
|
// the suffix to log activity on both sides.
|
|
//
|
|
// This overload is for testability; application code should use the single-
|
|
// argument version (defined in the ProtocolUtils.h) which takes the filter from
|
|
// the environment.
|
|
bool LoggingEnabledFor(const char* aTopLevelProtocol, Side aSide,
|
|
const char* aFilter) {
|
|
if (!aFilter) {
|
|
return false;
|
|
}
|
|
if (strcmp(aFilter, "1") == 0) {
|
|
return true;
|
|
}
|
|
|
|
const char kDelimiters[] = ", ";
|
|
Tokenizer tokens(aFilter, kDelimiters);
|
|
Tokenizer::Token t;
|
|
while (tokens.Next(t)) {
|
|
if (t.Type() == Tokenizer::TOKEN_WORD) {
|
|
auto filter = t.AsString();
|
|
|
|
// Since aTopLevelProtocol never includes the "Parent" / "Child" suffix,
|
|
// this will only occur when filter doesn't include it either, meaning
|
|
// that we should log activity on both sides.
|
|
if (filter == aTopLevelProtocol) {
|
|
return true;
|
|
}
|
|
|
|
if (aSide == ParentSide &&
|
|
StringEndsWith(filter, nsDependentCString("Parent")) &&
|
|
Substring(filter, 0, filter.Length() - 6) == aTopLevelProtocol) {
|
|
return true;
|
|
}
|
|
|
|
if (aSide == ChildSide &&
|
|
StringEndsWith(filter, nsDependentCString("Child")) &&
|
|
Substring(filter, 0, filter.Length() - 5) == aTopLevelProtocol) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif // defined(DEBUG) || defined(FUZZING)
|
|
|
|
void LogMessageForProtocol(const char* aTopLevelProtocol,
|
|
base::ProcessId aOtherPid,
|
|
const char* aContextDescription, uint32_t aMessageId,
|
|
MessageDirection aDirection) {
|
|
nsPrintfCString logMessage(
|
|
"[time: %" PRId64 "][%" PRIPID "%s%" PRIPID "] [%s] %s %s\n", PR_Now(),
|
|
base::GetCurrentProcId(),
|
|
aDirection == MessageDirection::eReceiving ? "<-" : "->", aOtherPid,
|
|
aTopLevelProtocol, aContextDescription,
|
|
StringFromIPCMessageType(aMessageId));
|
|
#ifdef ANDROID
|
|
__android_log_write(ANDROID_LOG_INFO, "GeckoIPC", logMessage.get());
|
|
#endif
|
|
fputs(logMessage.get(), stderr);
|
|
}
|
|
|
|
void ProtocolErrorBreakpoint(const char* aMsg) {
|
|
// Bugs that generate these error messages can be tough to
|
|
// reproduce. Log always in the hope that someone finds the error
|
|
// message.
|
|
printf_stderr("IPDL protocol error: %s\n", aMsg);
|
|
}
|
|
|
|
void PickleFatalError(const char* aMsg, IProtocol* aActor) {
|
|
if (aActor) {
|
|
aActor->FatalError(aMsg);
|
|
} else {
|
|
FatalError(aMsg, false);
|
|
}
|
|
}
|
|
|
|
void FatalError(const char* aMsg, bool aIsParent) {
|
|
#ifndef FUZZING
|
|
ProtocolErrorBreakpoint(aMsg);
|
|
#endif
|
|
|
|
nsAutoCString formattedMessage("IPDL error: \"");
|
|
formattedMessage.AppendASCII(aMsg);
|
|
if (aIsParent) {
|
|
// We're going to crash the parent process because at this time
|
|
// there's no other really nice way of getting a minidump out of
|
|
// this process if we're off the main thread.
|
|
formattedMessage.AppendLiteral("\". Intentionally crashing.");
|
|
NS_ERROR(formattedMessage.get());
|
|
CrashReporter::RecordAnnotationCString(
|
|
CrashReporter::Annotation::IPCFatalErrorMsg, aMsg);
|
|
AnnotateSystemError();
|
|
#ifndef FUZZING
|
|
MOZ_CRASH("IPC FatalError in the parent process!");
|
|
#endif
|
|
} else {
|
|
formattedMessage.AppendLiteral("\". abort()ing as a result.");
|
|
#ifndef FUZZING
|
|
MOZ_CRASH_UNSAFE(formattedMessage.get());
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void LogicError(const char* aMsg) { MOZ_CRASH_UNSAFE(aMsg); }
|
|
|
|
void ActorIdReadError(const char* aActorDescription) {
|
|
#ifndef FUZZING
|
|
MOZ_CRASH_UNSAFE_PRINTF("Error deserializing id for %s", aActorDescription);
|
|
#endif
|
|
}
|
|
|
|
void BadActorIdError(const char* aActorDescription) {
|
|
nsPrintfCString message("bad id for %s", aActorDescription);
|
|
ProtocolErrorBreakpoint(message.get());
|
|
}
|
|
|
|
void ActorLookupError(const char* aActorDescription) {
|
|
nsPrintfCString message("could not lookup id for %s", aActorDescription);
|
|
ProtocolErrorBreakpoint(message.get());
|
|
}
|
|
|
|
void MismatchedActorTypeError(const char* aActorDescription) {
|
|
nsPrintfCString message("actor that should be of type %s has different type",
|
|
aActorDescription);
|
|
ProtocolErrorBreakpoint(message.get());
|
|
}
|
|
|
|
void UnionTypeReadError(const char* aUnionName) {
|
|
MOZ_CRASH_UNSAFE_PRINTF("error deserializing type of union %s", aUnionName);
|
|
}
|
|
|
|
void ArrayLengthReadError(const char* aElementName) {
|
|
MOZ_CRASH_UNSAFE_PRINTF("error deserializing length of %s[]", aElementName);
|
|
}
|
|
|
|
void SentinelReadError(const char* aClassName) {
|
|
MOZ_CRASH_UNSAFE_PRINTF("incorrect sentinel when reading %s", aClassName);
|
|
}
|
|
|
|
ActorLifecycleProxy::ActorLifecycleProxy(IProtocol* aActor) : mActor(aActor) {
|
|
MOZ_ASSERT(mActor);
|
|
MOZ_ASSERT(mActor->CanSend(),
|
|
"Cannot create LifecycleProxy for non-connected actor!");
|
|
|
|
// Record that we've taken our first reference to our actor.
|
|
mActor->ActorAlloc();
|
|
}
|
|
|
|
WeakActorLifecycleProxy* ActorLifecycleProxy::GetWeakProxy() {
|
|
if (!mWeakProxy) {
|
|
mWeakProxy = new WeakActorLifecycleProxy(this);
|
|
}
|
|
return mWeakProxy;
|
|
}
|
|
|
|
ActorLifecycleProxy::~ActorLifecycleProxy() {
|
|
if (mWeakProxy) {
|
|
mWeakProxy->mProxy = nullptr;
|
|
mWeakProxy = nullptr;
|
|
}
|
|
|
|
// When the LifecycleProxy's lifetime has come to an end, it means that the
|
|
// actor should have its `Dealloc` method called on it. In a well-behaved
|
|
// actor, this will release the IPC-held reference to the actor.
|
|
//
|
|
// If the actor has already died before the `LifecycleProxy`, the `IProtocol`
|
|
// destructor below will clear our reference to it, preventing us from
|
|
// performing a use-after-free here.
|
|
if (!mActor) {
|
|
return;
|
|
}
|
|
|
|
// Clear our actor's state back to inactive, and then invoke ActorDealloc.
|
|
MOZ_ASSERT(mActor->mLinkStatus == LinkStatus::Destroyed,
|
|
"Deallocating non-destroyed actor!");
|
|
mActor->mLifecycleProxy = nullptr;
|
|
mActor->mLinkStatus = LinkStatus::Inactive;
|
|
mActor->ActorDealloc();
|
|
mActor = nullptr;
|
|
}
|
|
|
|
WeakActorLifecycleProxy::WeakActorLifecycleProxy(ActorLifecycleProxy* aProxy)
|
|
: mProxy(aProxy), mActorEventTarget(GetCurrentSerialEventTarget()) {}
|
|
|
|
WeakActorLifecycleProxy::~WeakActorLifecycleProxy() {
|
|
MOZ_DIAGNOSTIC_ASSERT(!mProxy, "Destroyed before mProxy was cleared?");
|
|
}
|
|
|
|
IProtocol* WeakActorLifecycleProxy::Get() const {
|
|
MOZ_DIAGNOSTIC_ASSERT(mActorEventTarget->IsOnCurrentThread());
|
|
return mProxy ? mProxy->Get() : nullptr;
|
|
}
|
|
|
|
WeakActorLifecycleProxy* IProtocol::GetWeakLifecycleProxy() {
|
|
return mLifecycleProxy ? mLifecycleProxy->GetWeakProxy() : nullptr;
|
|
}
|
|
|
|
IProtocol::~IProtocol() {
|
|
// If the actor still has a lifecycle proxy when it is being torn down, it
|
|
// means that IPC was not given control over the lifecycle of the actor
|
|
// correctly. Usually this means that the actor was destroyed while IPC is
|
|
// calling a message handler for it, and the actor incorrectly frees itself
|
|
// during that operation.
|
|
//
|
|
// As this happens unfortunately frequently, due to many odd protocols in
|
|
// Gecko, simply emit a warning and clear the weak backreference from our
|
|
// LifecycleProxy back to us.
|
|
if (mLifecycleProxy) {
|
|
MOZ_ASSERT(mLinkStatus != LinkStatus::Inactive);
|
|
NS_WARNING(
|
|
nsPrintfCString("Actor destructor for '%s%s' called before IPC "
|
|
"lifecycle complete!\n"
|
|
"References to this actor may unexpectedly dangle!",
|
|
GetProtocolName(), StringFromIPCSide(GetSide()))
|
|
.get());
|
|
|
|
mLifecycleProxy->mActor = nullptr;
|
|
mLifecycleProxy = nullptr;
|
|
}
|
|
}
|
|
|
|
// The following methods either directly forward to the toplevel protocol, or
|
|
// almost directly do.
|
|
IProtocol* IProtocol::Lookup(ActorId aId) { return mToplevel->Lookup(aId); }
|
|
|
|
Shmem IProtocol::CreateSharedMemory(size_t aSize, bool aUnsafe) {
|
|
return mToplevel->CreateSharedMemory(aSize, aUnsafe);
|
|
}
|
|
Shmem::Segment* IProtocol::LookupSharedMemory(Shmem::id_t aId) {
|
|
return mToplevel->LookupSharedMemory(aId);
|
|
}
|
|
bool IProtocol::IsTrackingSharedMemory(const Shmem::Segment* aSegment) {
|
|
return mToplevel->IsTrackingSharedMemory(aSegment);
|
|
}
|
|
bool IProtocol::DestroySharedMemory(Shmem& aShmem) {
|
|
return mToplevel->DestroySharedMemory(aShmem);
|
|
}
|
|
|
|
MessageChannel* IProtocol::GetIPCChannel() {
|
|
return mToplevel->GetIPCChannel();
|
|
}
|
|
const MessageChannel* IProtocol::GetIPCChannel() const {
|
|
return mToplevel->GetIPCChannel();
|
|
}
|
|
|
|
nsISerialEventTarget* IProtocol::GetActorEventTarget() {
|
|
return GetIPCChannel()->GetWorkerEventTarget();
|
|
}
|
|
|
|
void IProtocol::FatalError(const char* const aErrorMsg) {
|
|
HandleFatalError(aErrorMsg);
|
|
}
|
|
|
|
void IProtocol::HandleFatalError(const char* aErrorMsg) {
|
|
if (IProtocol* manager = Manager()) {
|
|
manager->HandleFatalError(aErrorMsg);
|
|
return;
|
|
}
|
|
|
|
mozilla::ipc::FatalError(aErrorMsg, mSide == ParentSide);
|
|
if (CanSend()) {
|
|
GetIPCChannel()->InduceConnectionError();
|
|
}
|
|
}
|
|
|
|
bool IProtocol::AllocShmem(size_t aSize, Shmem* aOutMem) {
|
|
if (!CanSend()) {
|
|
NS_WARNING(
|
|
"Shmem not allocated. Cannot communicate with the other actor.");
|
|
return false;
|
|
}
|
|
|
|
*aOutMem = CreateSharedMemory(aSize, false);
|
|
return aOutMem->IsReadable();
|
|
}
|
|
|
|
bool IProtocol::AllocUnsafeShmem(size_t aSize, Shmem* aOutMem) {
|
|
if (!CanSend()) {
|
|
NS_WARNING(
|
|
"Shmem not allocated. Cannot communicate with the other actor.");
|
|
return false;
|
|
}
|
|
|
|
*aOutMem = CreateSharedMemory(aSize, true);
|
|
return aOutMem->IsReadable();
|
|
}
|
|
|
|
bool IProtocol::DeallocShmem(Shmem& aMem) {
|
|
bool ok = DestroySharedMemory(aMem);
|
|
#ifdef DEBUG
|
|
if (!ok) {
|
|
if (mSide == ChildSide) {
|
|
FatalError("bad Shmem");
|
|
} else {
|
|
NS_WARNING("bad Shmem");
|
|
}
|
|
return false;
|
|
}
|
|
#endif // DEBUG
|
|
aMem.forget();
|
|
return ok;
|
|
}
|
|
|
|
void IProtocol::SetManager(IRefCountedProtocol* aManager) {
|
|
MOZ_RELEASE_ASSERT(!mManager || mManager == aManager);
|
|
mManager = aManager;
|
|
mToplevel = aManager->mToplevel;
|
|
}
|
|
|
|
bool IProtocol::SetManagerAndRegister(IRefCountedProtocol* aManager,
|
|
ActorId aId) {
|
|
MOZ_RELEASE_ASSERT(mLinkStatus == LinkStatus::Inactive,
|
|
"Actor must be inactive to SetManagerAndRegister");
|
|
|
|
// Set to `false` if the actor is to be torn down after registration.
|
|
bool success = true;
|
|
|
|
// Set the manager prior to registering so registering properly inherits
|
|
// the manager's event target.
|
|
SetManager(aManager);
|
|
|
|
mId = aId == kNullActorId ? mToplevel->NextId() : aId;
|
|
while (mToplevel->mActorMap.Contains(mId)) {
|
|
// The ID already existing is an error case, but we want to proceed with
|
|
// registration so that we can tear down the actor cleanly - generate a new
|
|
// ID for that case.
|
|
NS_WARNING("Actor already exists with the selected ID!");
|
|
mId = mToplevel->NextId();
|
|
success = false;
|
|
}
|
|
|
|
RefPtr<ActorLifecycleProxy> proxy = ActorConnected();
|
|
mToplevel->mActorMap.InsertOrUpdate(mId, proxy);
|
|
MOZ_ASSERT(proxy->Get() == this);
|
|
|
|
UntypedManagedContainer* container =
|
|
aManager->GetManagedActors(GetProtocolId());
|
|
if (container) {
|
|
container->Insert(this);
|
|
} else {
|
|
NS_WARNING("Manager does not manage actors with this ProtocolId");
|
|
success = false;
|
|
}
|
|
|
|
// If our manager is already dying, mark ourselves as doomed as well.
|
|
if (aManager && aManager->mLinkStatus != LinkStatus::Connected) {
|
|
mLinkStatus = LinkStatus::Doomed;
|
|
if (aManager->mLinkStatus != LinkStatus::Doomed) {
|
|
// Our manager is already fully dead, make sure we call
|
|
// `ActorDisconnected`.
|
|
success = false;
|
|
}
|
|
}
|
|
|
|
// If setting the manager failed, call `ActorDisconnected` and return false.
|
|
if (!success) {
|
|
ActorDisconnected(FailedConstructor);
|
|
MOZ_ASSERT(mLinkStatus == LinkStatus::Destroyed);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void IProtocol::UnlinkManager() {
|
|
mToplevel = nullptr;
|
|
mManager = nullptr;
|
|
}
|
|
|
|
bool IProtocol::ChannelSend(UniquePtr<IPC::Message> aMsg,
|
|
IPC::Message::seqno_t* aSeqno) {
|
|
if (CanSend()) {
|
|
// NOTE: This send call failing can only occur during toplevel channel
|
|
// teardown. As this is an async call, this isn't reasonable to predict or
|
|
// respond to, so just drop the message on the floor silently.
|
|
GetIPCChannel()->Send(std::move(aMsg), aSeqno);
|
|
return true;
|
|
}
|
|
|
|
WarnMessageDiscarded(aMsg.get());
|
|
return false;
|
|
}
|
|
|
|
bool IProtocol::ChannelSend(UniquePtr<IPC::Message> aMsg,
|
|
UniquePtr<IPC::Message>* aReply) {
|
|
if (CanSend()) {
|
|
return GetIPCChannel()->Send(std::move(aMsg), aReply);
|
|
}
|
|
|
|
WarnMessageDiscarded(aMsg.get());
|
|
return false;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void IProtocol::WarnMessageDiscarded(IPC::Message* aMsg) {
|
|
NS_WARNING(nsPrintfCString("IPC message '%s' discarded: actor cannot send",
|
|
aMsg->name())
|
|
.get());
|
|
}
|
|
#endif
|
|
|
|
uint32_t IProtocol::AllManagedActorsCount() const {
|
|
uint32_t total = 0;
|
|
for (ProtocolId id : ManagedProtocolIds()) {
|
|
total += GetManagedActors(id)->Count();
|
|
}
|
|
return total;
|
|
}
|
|
|
|
already_AddRefed<ActorLifecycleProxy> IProtocol::ActorConnected() {
|
|
if (mLinkStatus != LinkStatus::Inactive) {
|
|
return nullptr;
|
|
}
|
|
|
|
#ifdef FUZZING_SNAPSHOT
|
|
fuzzing::IPCFuzzController::instance().OnActorConnected(this);
|
|
#endif
|
|
|
|
mLinkStatus = LinkStatus::Connected;
|
|
|
|
MOZ_ASSERT(!mLifecycleProxy, "double-connecting live actor");
|
|
RefPtr<ActorLifecycleProxy> proxy = new ActorLifecycleProxy(this);
|
|
mLifecycleProxy = proxy;
|
|
return proxy.forget();
|
|
}
|
|
|
|
void IProtocol::ActorDisconnected(ActorDestroyReason aWhy) {
|
|
MOZ_ASSERT(mLifecycleProxy, "destroying zombie actor");
|
|
// If the actor has already been marked as `Destroyed`, there's nothing to do.
|
|
if (mLinkStatus != LinkStatus::Connected &&
|
|
mLinkStatus != LinkStatus::Doomed) {
|
|
return;
|
|
}
|
|
|
|
// Mark the entire subtree as doomed so that no further messages can be
|
|
// sent/recieved, and newly created managed actors are immediately marked as
|
|
// doomed on creation.
|
|
DoomSubtree();
|
|
|
|
// Perform the steps to fully destroy an actor after it has been unregistered
|
|
// from its manager.
|
|
auto doActorDestroy = [toplevel = mToplevel, ipcChannel = GetIPCChannel()](
|
|
IProtocol* actor, ActorDestroyReason why) {
|
|
MOZ_ASSERT(actor->mLinkStatus == LinkStatus::Doomed,
|
|
"Actor must be doomed when calling doActorDestroy");
|
|
MOZ_ASSERT(actor->AllManagedActorsCount() == 0,
|
|
"All managed actors must have been destroyed first");
|
|
|
|
// Mark the actor as Destroyed, ensuring we can't re-enter `ActorDestroy`,
|
|
// even if an callback spins a nested event loop.
|
|
actor->mLinkStatus = LinkStatus::Destroyed;
|
|
|
|
#ifdef FUZZING_SNAPSHOT
|
|
fuzzing::IPCFuzzController::instance().OnActorDestroyed(actor);
|
|
#endif
|
|
|
|
ActorId id = actor->mId;
|
|
if (IProtocol* manager = actor->Manager()) {
|
|
auto entry = toplevel->mActorMap.Lookup(id);
|
|
MOZ_DIAGNOSTIC_ASSERT(entry && *entry == actor->GetLifecycleProxy(),
|
|
"ID must be present and reference this actor");
|
|
entry.Remove();
|
|
if (auto* container = manager->GetManagedActors(actor->GetProtocolId())) {
|
|
container->EnsureRemoved(actor);
|
|
}
|
|
}
|
|
|
|
actor->RejectPendingResponses(ResponseRejectReason::ActorDestroyed);
|
|
actor->ActorDestroy(why);
|
|
};
|
|
|
|
// Hold all ActorLifecycleProxy instances for managed actors until we return.
|
|
nsTArray<RefPtr<ActorLifecycleProxy>> proxyHolder;
|
|
proxyHolder.AppendElement(GetLifecycleProxy());
|
|
|
|
// Invoke `ActorDestroy` for all managed actors in the subtree. These are
|
|
// handled one at a time, so that new actors which are potentially registered
|
|
// during `ActorDestroy` callbacks are not missed.
|
|
ActorDestroyReason subtreeWhy = aWhy;
|
|
if (aWhy == Deletion || aWhy == FailedConstructor) {
|
|
subtreeWhy = AncestorDeletion;
|
|
}
|
|
while (IProtocol* actor = PeekManagedActor()) {
|
|
// If the selected actor manages other actors, destroy those first.
|
|
while (IProtocol* inner = actor->PeekManagedActor()) {
|
|
actor = inner;
|
|
}
|
|
|
|
proxyHolder.AppendElement(actor->GetLifecycleProxy());
|
|
doActorDestroy(actor, subtreeWhy);
|
|
}
|
|
|
|
// Destroy ourselves if we were not not otherwise destroyed while destroying
|
|
// managed actors.
|
|
if (mLinkStatus == LinkStatus::Doomed) {
|
|
doActorDestroy(this, aWhy);
|
|
}
|
|
}
|
|
|
|
void IProtocol::DoomSubtree() {
|
|
MOZ_ASSERT(
|
|
mLinkStatus == LinkStatus::Connected || mLinkStatus == LinkStatus::Doomed,
|
|
"Invalid link status for SetDoomed");
|
|
for (ProtocolId id : ManagedProtocolIds()) {
|
|
for (IProtocol* actor : *GetManagedActors(id)) {
|
|
actor->DoomSubtree();
|
|
}
|
|
}
|
|
mLinkStatus = LinkStatus::Doomed;
|
|
}
|
|
|
|
IProtocol* IProtocol::PeekManagedActor() const {
|
|
for (ProtocolId id : ManagedProtocolIds()) {
|
|
const UntypedManagedContainer& container = *GetManagedActors(id);
|
|
if (!container.IsEmpty()) {
|
|
// Return the last element first, to reduce the copying required when
|
|
// removing it.
|
|
return *(container.end() - 1);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
IToplevelProtocol::IToplevelProtocol(const char* aName, ProtocolId aProtoId,
|
|
Side aSide)
|
|
: IRefCountedProtocol(aProtoId, aSide),
|
|
mOtherPid(base::kInvalidProcessId),
|
|
mLastLocalId(kNullActorId),
|
|
mChannel(aName, this) {
|
|
mToplevel = this;
|
|
}
|
|
|
|
void IToplevelProtocol::SetOtherEndpointProcInfo(
|
|
EndpointProcInfo aOtherProcInfo) {
|
|
mOtherPid = aOtherProcInfo.mPid;
|
|
mOtherChildID = aOtherProcInfo.mChildID;
|
|
}
|
|
|
|
bool IToplevelProtocol::Open(ScopedPort aPort, const nsID& aMessageChannelId,
|
|
EndpointProcInfo aOtherProcInfo,
|
|
nsISerialEventTarget* aEventTarget) {
|
|
SetOtherEndpointProcInfo(aOtherProcInfo);
|
|
return GetIPCChannel()->Open(std::move(aPort), mSide, aMessageChannelId,
|
|
aEventTarget);
|
|
}
|
|
|
|
bool IToplevelProtocol::Open(IToplevelProtocol* aTarget,
|
|
nsISerialEventTarget* aEventTarget,
|
|
mozilla::ipc::Side aSide) {
|
|
SetOtherEndpointProcInfo(EndpointProcInfo::Current());
|
|
aTarget->SetOtherEndpointProcInfo(EndpointProcInfo::Current());
|
|
return GetIPCChannel()->Open(aTarget->GetIPCChannel(), aEventTarget, aSide);
|
|
}
|
|
|
|
bool IToplevelProtocol::OpenOnSameThread(IToplevelProtocol* aTarget,
|
|
Side aSide) {
|
|
SetOtherEndpointProcInfo(EndpointProcInfo::Current());
|
|
aTarget->SetOtherEndpointProcInfo(EndpointProcInfo::Current());
|
|
return GetIPCChannel()->OpenOnSameThread(aTarget->GetIPCChannel(), aSide);
|
|
}
|
|
|
|
void IToplevelProtocol::NotifyImpendingShutdown() {
|
|
if (CanRecv()) {
|
|
GetIPCChannel()->NotifyImpendingShutdown();
|
|
}
|
|
}
|
|
|
|
void IToplevelProtocol::Close() { GetIPCChannel()->Close(); }
|
|
|
|
void IToplevelProtocol::SetReplyTimeoutMs(int32_t aTimeoutMs) {
|
|
GetIPCChannel()->SetReplyTimeoutMs(aTimeoutMs);
|
|
}
|
|
|
|
bool IToplevelProtocol::IsOnCxxStack() const {
|
|
return GetIPCChannel()->IsOnCxxStack();
|
|
}
|
|
|
|
int64_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.
|
|
MOZ_RELEASE_ASSERT(mozilla::Abs(mLastLocalId) < MSG_ROUTING_CONTROL - 1,
|
|
"actor id overflow");
|
|
return (GetSide() == ChildSide) ? --mLastLocalId : ++mLastLocalId;
|
|
}
|
|
|
|
IProtocol* IToplevelProtocol::Lookup(ActorId aId) {
|
|
if (auto entry = mActorMap.Lookup(aId)) {
|
|
return entry.Data()->Get();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Shmem IToplevelProtocol::CreateSharedMemory(size_t aSize, bool aUnsafe) {
|
|
auto shmemBuilder = Shmem::Builder(aSize);
|
|
if (!shmemBuilder) {
|
|
return {};
|
|
}
|
|
auto [createdMessage, shmem] =
|
|
shmemBuilder.Build(NextId(), aUnsafe, MSG_ROUTING_CONTROL);
|
|
if (!createdMessage) {
|
|
return {};
|
|
}
|
|
Unused << GetIPCChannel()->Send(std::move(createdMessage));
|
|
|
|
MOZ_ASSERT(!mShmemMap.Contains(shmem.Id()),
|
|
"Don't insert with an existing ID");
|
|
mShmemMap.InsertOrUpdate(shmem.Id(), shmem.GetSegment());
|
|
return shmem;
|
|
}
|
|
|
|
Shmem::Segment* IToplevelProtocol::LookupSharedMemory(Shmem::id_t aId) {
|
|
auto entry = mShmemMap.Lookup(aId);
|
|
return entry ? entry.Data().get() : nullptr;
|
|
}
|
|
|
|
bool IToplevelProtocol::IsTrackingSharedMemory(const Shmem::Segment* segment) {
|
|
for (const auto& shmem : mShmemMap.Values()) {
|
|
if (segment == shmem) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IToplevelProtocol::DestroySharedMemory(Shmem& shmem) {
|
|
Shmem::id_t aId = shmem.Id();
|
|
if (!LookupSharedMemory(aId)) {
|
|
return false;
|
|
}
|
|
|
|
UniquePtr<Message> descriptor = shmem.MkDestroyedMessage(MSG_ROUTING_CONTROL);
|
|
|
|
MOZ_ASSERT(mShmemMap.Contains(aId),
|
|
"Attempting to remove an ID not in the shmem map");
|
|
mShmemMap.Remove(aId);
|
|
|
|
MessageChannel* channel = GetIPCChannel();
|
|
if (!channel->CanSend()) {
|
|
return true;
|
|
}
|
|
|
|
return descriptor && channel->Send(std::move(descriptor));
|
|
}
|
|
|
|
void IToplevelProtocol::DeallocShmems() { mShmemMap.Clear(); }
|
|
|
|
bool IToplevelProtocol::ShmemCreated(const Message& aMsg) {
|
|
Shmem::id_t id;
|
|
RefPtr<Shmem::Segment> segment(Shmem::OpenExisting(aMsg, &id, true));
|
|
if (!segment) {
|
|
return false;
|
|
}
|
|
MOZ_ASSERT(!mShmemMap.Contains(id), "Don't insert with an existing ID");
|
|
mShmemMap.InsertOrUpdate(id, std::move(segment));
|
|
return true;
|
|
}
|
|
|
|
bool IToplevelProtocol::ShmemDestroyed(const Message& aMsg) {
|
|
Shmem::id_t id;
|
|
MessageReader reader(aMsg);
|
|
if (!IPC::ReadParam(&reader, &id)) {
|
|
return false;
|
|
}
|
|
reader.EndRead();
|
|
|
|
mShmemMap.Remove(id);
|
|
return true;
|
|
}
|
|
|
|
IPDLResolverInner::IPDLResolverInner(UniquePtr<IPC::Message> aReply,
|
|
IProtocol* aActor)
|
|
: mReply(std::move(aReply)),
|
|
mWeakProxy(aActor->GetLifecycleProxy()->GetWeakProxy()) {}
|
|
|
|
void IPDLResolverInner::ResolveOrReject(
|
|
bool aResolve, FunctionRef<void(IPC::Message*, IProtocol*)> aWrite) {
|
|
MOZ_ASSERT(mWeakProxy);
|
|
MOZ_ASSERT(mWeakProxy->ActorEventTarget()->IsOnCurrentThread());
|
|
MOZ_ASSERT(mReply);
|
|
|
|
UniquePtr<IPC::Message> reply = std::move(mReply);
|
|
|
|
IProtocol* actor = mWeakProxy->Get();
|
|
if (!actor) {
|
|
NS_WARNING(nsPrintfCString("Not resolving response '%s': actor is dead",
|
|
reply->name())
|
|
.get());
|
|
return;
|
|
}
|
|
|
|
IPC::MessageWriter writer(*reply, actor);
|
|
WriteIPDLParam(&writer, actor, aResolve);
|
|
aWrite(reply.get(), actor);
|
|
|
|
actor->ChannelSend(std::move(reply));
|
|
}
|
|
|
|
void IPDLResolverInner::Destroy() {
|
|
if (mReply) {
|
|
NS_PROXY_DELETE_TO_EVENT_TARGET(IPDLResolverInner,
|
|
mWeakProxy->ActorEventTarget());
|
|
} else {
|
|
// If we've already been consumed, just delete without proxying. This avoids
|
|
// leaking the resolver if the actor's thread is already dead.
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
IPDLResolverInner::~IPDLResolverInner() {
|
|
if (mReply) {
|
|
NS_WARNING(
|
|
nsPrintfCString(
|
|
"Rejecting reply '%s': resolver dropped without being called",
|
|
mReply->name())
|
|
.get());
|
|
ResolveOrReject(false, [](IPC::Message* aMessage, IProtocol* aActor) {
|
|
IPC::MessageWriter writer(*aMessage, aActor);
|
|
ResponseRejectReason reason = ResponseRejectReason::ResolverDestroyed;
|
|
WriteIPDLParam(&writer, aActor, reason);
|
|
});
|
|
}
|
|
}
|
|
|
|
bool IPDLAsyncReturnsCallbacks::EntryKey::operator==(
|
|
const EntryKey& aOther) const {
|
|
return mSeqno == aOther.mSeqno && mType == aOther.mType;
|
|
}
|
|
|
|
bool IPDLAsyncReturnsCallbacks::EntryKey::operator<(
|
|
const EntryKey& aOther) const {
|
|
return mSeqno < aOther.mSeqno ||
|
|
(mSeqno == aOther.mSeqno && mType < aOther.mType);
|
|
}
|
|
|
|
void IPDLAsyncReturnsCallbacks::AddCallback(IPC::Message::seqno_t aSeqno,
|
|
msgid_t aType, Callback aResolve,
|
|
RejectCallback aReject) {
|
|
Entry entry{{aSeqno, aType}, std::move(aResolve), std::move(aReject)};
|
|
MOZ_ASSERT(!mMap.ContainsSorted(entry));
|
|
mMap.InsertElementSorted(std::move(entry));
|
|
}
|
|
|
|
auto IPDLAsyncReturnsCallbacks::GotReply(IProtocol* aActor,
|
|
const IPC::Message& aMessage)
|
|
-> Result {
|
|
// Check if we have an entry for the given seqno and message type.
|
|
EntryKey key{aMessage.seqno(), aMessage.type()};
|
|
size_t index = mMap.BinaryIndexOf(key);
|
|
if (index == nsTArray<Entry>::NoIndex) {
|
|
return MsgProcessingError;
|
|
}
|
|
|
|
// Move the callbacks out of the map, as we will now be handling it.
|
|
Entry entry = std::move(mMap[index]);
|
|
mMap.RemoveElementAt(index);
|
|
MOZ_ASSERT(entry == key);
|
|
|
|
// Deserialize the message which was serialized by IPDLResolverInner.
|
|
IPC::MessageReader reader{aMessage, aActor};
|
|
bool resolve = false;
|
|
if (!IPC::ReadParam(&reader, &resolve)) {
|
|
entry.mReject(ResponseRejectReason::HandlerRejected);
|
|
return MsgValueError;
|
|
}
|
|
|
|
if (resolve) {
|
|
// Hand off resolve-case deserialization & success to the callback.
|
|
Result rv = entry.mResolve(&reader);
|
|
if (rv != MsgProcessed) {
|
|
// If deserialization failed, we need to call the reject handler.
|
|
entry.mReject(ResponseRejectReason::HandlerRejected);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
ResponseRejectReason reason;
|
|
if (!IPC::ReadParam(&reader, &reason)) {
|
|
entry.mReject(ResponseRejectReason::HandlerRejected);
|
|
return MsgValueError;
|
|
}
|
|
reader.EndRead();
|
|
|
|
entry.mReject(reason);
|
|
return MsgProcessed;
|
|
}
|
|
|
|
void IPDLAsyncReturnsCallbacks::RejectPendingResponses(
|
|
ResponseRejectReason aReason) {
|
|
nsTArray<Entry> pending = std::move(mMap);
|
|
for (auto& entry : pending) {
|
|
entry.mReject(aReason);
|
|
}
|
|
}
|
|
|
|
} // namespace ipc
|
|
} // namespace mozilla
|
|
|
|
namespace IPC {
|
|
|
|
void ParamTraits<mozilla::ipc::IProtocol*>::Write(MessageWriter* aWriter,
|
|
const paramType& aParam) {
|
|
MOZ_RELEASE_ASSERT(aWriter->GetActor(),
|
|
"Cannot serialize managed actors without an actor");
|
|
|
|
mozilla::ipc::ActorId id = mozilla::ipc::IProtocol::kNullActorId;
|
|
if (aParam) {
|
|
id = aParam->Id();
|
|
MOZ_RELEASE_ASSERT(id != mozilla::ipc::IProtocol::kNullActorId,
|
|
"Actor has ID of 0?");
|
|
MOZ_RELEASE_ASSERT(aParam->CanSend(),
|
|
"Actor must still be open when sending");
|
|
MOZ_RELEASE_ASSERT(
|
|
aWriter->GetActor()->GetIPCChannel() == aParam->GetIPCChannel(),
|
|
"Actor must be from the same tree as the actor it is being sent over");
|
|
}
|
|
|
|
IPC::WriteParam(aWriter, id);
|
|
}
|
|
|
|
bool ParamTraits<mozilla::ipc::IProtocol*>::Read(MessageReader* aReader,
|
|
paramType* aResult) {
|
|
MOZ_RELEASE_ASSERT(aReader->GetActor(),
|
|
"Cannot serialize managed actors without an actor");
|
|
|
|
mozilla::ipc::ActorId id;
|
|
if (!IPC::ReadParam(aReader, &id)) {
|
|
return false;
|
|
}
|
|
|
|
if (id == mozilla::ipc::IProtocol::kNullActorId) {
|
|
*aResult = nullptr;
|
|
return true;
|
|
}
|
|
|
|
*aResult = aReader->GetActor()->Lookup(id);
|
|
return *aResult != nullptr;
|
|
}
|
|
|
|
} // namespace IPC
|