/* -*- 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/ipc/PBackgroundChild.h" #include "mozilla/ipc/PBackgroundParent.h" #include "mozilla/Unused.h" #include "nsNetCID.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 bool SerializeInputStreamWithFdsChild(nsIIPCSerializableInputStream* aStream, IPCStream& aValue, bool aDelayedStart, M* aManager) { MOZ_RELEASE_ASSERT(aStream); MOZ_ASSERT(aManager); AutoTArray fds; aStream->Serialize(aValue.stream(), fds, aDelayedStart, aManager); 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 bool SerializeInputStreamWithFdsParent(nsIIPCSerializableInputStream* aStream, IPCStream& aValue, bool aDelayedStart, M* aManager) { MOZ_RELEASE_ASSERT(aStream); MOZ_ASSERT(aManager); AutoTArray fds; aStream->Serialize(aValue.stream(), fds, aDelayedStart, aManager); 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 bool SerializeInputStream(nsIInputStream* aStream, IPCStream& aValue, M* aManager, bool aDelayedStart) { MOZ_ASSERT(aStream); 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(aStream, &length)) { length = -1; } // As a fallback, attempt to stream the data across using a IPCStream // actor. For blocking streams, create a nonblocking pipe instead, nsCOMPtr asyncStream = do_QueryInterface(aStream); if (!asyncStream) { const uint32_t kBufferSize = 32768; // matches IPCStream buffer size. nsCOMPtr sink; nsresult rv = NS_NewPipe2(getter_AddRefs(asyncStream), getter_AddRefs(sink), true, false, kBufferSize, UINT32_MAX); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } nsCOMPtr target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); rv = NS_AsyncCopy(aStream, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS, kBufferSize); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } } MOZ_ASSERT(asyncStream); IPCRemoteStreamParams remoteStream; remoteStream.delayedStart() = aDelayedStart; remoteStream.stream() = IPCStreamSource::Create(asyncStream, aManager); remoteStream.length() = length; aValue.stream() = remoteStream; aValue.optionalFds() = void_t(); return true; } template bool SerializeInputStreamChild(nsIInputStream* aStream, M* aManager, IPCStream* aValue, OptionalIPCStream* aOptionalValue, bool aDelayedStart) { MOZ_ASSERT(aStream); MOZ_ASSERT(aManager); MOZ_ASSERT(aValue || aOptionalValue); nsCOMPtr serializable = do_QueryInterface(aStream); if (serializable) { if (aValue) { return SerializeInputStreamWithFdsChild(serializable, *aValue, aDelayedStart, aManager); } return SerializeInputStreamWithFdsChild(serializable, *aOptionalValue, aDelayedStart, aManager); } if (aValue) { return SerializeInputStream(aStream, *aValue, aManager, aDelayedStart); } return SerializeInputStream(aStream, *aOptionalValue, aManager, aDelayedStart); } template bool SerializeInputStreamParent(nsIInputStream* aStream, M* aManager, IPCStream* aValue, OptionalIPCStream* aOptionalValue, bool aDelayedStart) { MOZ_ASSERT(aStream); MOZ_ASSERT(aManager); MOZ_ASSERT(aValue || aOptionalValue); nsCOMPtr serializable = do_QueryInterface(aStream); if (serializable) { if (aValue) { return SerializeInputStreamWithFdsParent(serializable, *aValue, aDelayedStart, aManager); } return SerializeInputStreamWithFdsParent(serializable, *aOptionalValue, aDelayedStart, aManager); } if (aValue) { return SerializeInputStream(aStream, *aValue, aManager, aDelayedStart); } return SerializeInputStream(aStream, *aOptionalValue, aManager, aDelayedStart); } void ActivateAndCleanupIPCStream(IPCStream& aValue, bool aConsumedByIPC, bool aDelayedStart) { // Cleanup file descriptors if necessary if (aValue.optionalFds().type() == OptionalFileDescriptorSet::TPFileDescriptorSetChild) { AutoTArray fds; auto fdSetActor = static_cast( 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 fds; auto fdSetActor = static_cast( 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(OptionalIPCStream& aValue, bool aConsumedByIPC, bool aDelayedStart) { if (aValue.type() == OptionalIPCStream::Tvoid_t) { return; } ActivateAndCleanupIPCStream(aValue.get_IPCStream(), aConsumedByIPC, aDelayedStart); } // Returns false if the serialization should not proceed. This means that the // inputStream is null. bool NormalizeOptionalValue(nsIInputStream* aStream, IPCStream* aValue, OptionalIPCStream* aOptionalValue) { if (aValue) { // if aStream is null, we will crash when serializing. return true; } if (!aStream) { *aOptionalValue = void_t(); return false; } *aOptionalValue = IPCStream(); return true; } } // anonymous namespace already_AddRefed 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 fds; if (aValue.optionalFds().type() == OptionalFileDescriptorSet::TPFileDescriptorSetParent) { auto fdSetActor = static_cast( 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( 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 DeserializeIPCStream( const OptionalIPCStream& aValue) { if (aValue.type() == OptionalIPCStream::Tvoid_t) { return nullptr; } return DeserializeIPCStream(aValue.get_IPCStream()); } AutoIPCStream::AutoIPCStream(bool aDelayedStart) : mInlineValue(void_t()), mValue(nullptr), mOptionalValue(&mInlineValue), mTaken(false), mDelayedStart(aDelayedStart) {} AutoIPCStream::AutoIPCStream(IPCStream& aTarget, bool aDelayedStart) : mInlineValue(void_t()), mValue(&aTarget), mOptionalValue(nullptr), mTaken(false), mDelayedStart(aDelayedStart) {} AutoIPCStream::AutoIPCStream(OptionalIPCStream& aTarget, bool aDelayedStart) : mInlineValue(void_t()), mValue(nullptr), mOptionalValue(&aTarget), mTaken(false), mDelayedStart(aDelayedStart) { *mOptionalValue = void_t(); } AutoIPCStream::~AutoIPCStream() { MOZ_ASSERT(mValue || mOptionalValue); if (mValue && IsSet()) { ActivateAndCleanupIPCStream(*mValue, mTaken, mDelayedStart); } else { ActivateAndCleanupIPCStream(*mOptionalValue, mTaken, mDelayedStart); } } bool AutoIPCStream::Serialize(nsIInputStream* aStream, dom::nsIContentChild* 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; } if (!SerializeInputStreamChild(aStream, aManager, mValue, mOptionalValue, mDelayedStart)) { MOZ_CRASH("IPCStream creation failed!"); } return true; } bool AutoIPCStream::Serialize(nsIInputStream* aStream, dom::nsIContentParent* 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; } 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->type() != OptionalIPCStream::Tvoid_t && mOptionalValue->get_IPCStream().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->get_IPCStream(); return value; } OptionalIPCStream& AutoIPCStream::TakeOptionalValue() { MOZ_ASSERT(!mTaken); MOZ_ASSERT(!mValue); MOZ_ASSERT(mOptionalValue); mTaken = true; return *mOptionalValue; } void IPDLParamTraits::Write(IPC::Message* aMsg, IProtocol* aActor, nsIInputStream* aParam) { mozilla::ipc::AutoIPCStream autoStream; 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->GetProtocolTypeId()) { case PContentMsgStart: if (actor->GetSide() == mozilla::ipc::ParentSide) { ok = autoStream.Serialize( aParam, static_cast(actor)); } else { MOZ_RELEASE_ASSERT(actor->GetSide() == mozilla::ipc::ChildSide); ok = autoStream.Serialize( aParam, static_cast(actor)); } found = true; break; case PBackgroundMsgStart: if (actor->GetSide() == mozilla::ipc::ParentSide) { ok = autoStream.Serialize( aParam, static_cast(actor)); } else { MOZ_RELEASE_ASSERT(actor->GetSide() == mozilla::ipc::ChildSide); ok = autoStream.Serialize( aParam, static_cast(actor)); } found = true; 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()); } bool IPDLParamTraits::Read(const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, RefPtr* aResult) { mozilla::ipc::OptionalIPCStream ipcStream; if (!ReadIPDLParam(aMsg, aIter, aActor, &ipcStream)) { return false; } *aResult = mozilla::ipc::DeserializeIPCStream(ipcStream); return true; } } // namespace ipc } // namespace mozilla