/* -*- 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/nsIContentChild.h" #include "mozilla/dom/PContentParent.h" #include "mozilla/dom/File.h" #include "mozilla/ipc/FileDescriptorSetChild.h" #include "mozilla/ipc/FileDescriptorSetParent.h" #include "mozilla/ipc/InputStreamUtils.h" #include "mozilla/ipc/IPCStreamDestination.h" #include "mozilla/ipc/IPCStreamSource.h" #include "mozilla/ipc/PBackgroundChild.h" #include "mozilla/ipc/PBackgroundParent.h" #include "mozilla/Unused.h" #include "nsIAsyncInputStream.h" #include "nsIAsyncOutputStream.h" #include "nsIPipe.h" #include "nsNetCID.h" #include "nsStreamUtils.h" using namespace mozilla::dom; namespace mozilla { namespace ipc { namespace { void AssertValidValueToTake(const IPCStream& aVal) { MOZ_ASSERT(aVal.type() == IPCStream::TIPCRemoteStream || aVal.type() == IPCStream::TInputStreamParamsWithFds); } void AssertValidValueToTake(const OptionalIPCStream& aVal) { MOZ_ASSERT(aVal.type() == OptionalIPCStream::Tvoid_t || aVal.type() == OptionalIPCStream::TIPCStream); if (aVal.type() == OptionalIPCStream::TIPCStream) { AssertValidValueToTake(aVal.get_IPCStream()); } } // 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, M* aManager) { MOZ_RELEASE_ASSERT(aStream); MOZ_ASSERT(aManager); aValue = InputStreamParamsWithFds(); InputStreamParamsWithFds& streamWithFds = aValue.get_InputStreamParamsWithFds(); AutoTArray fds; aStream->Serialize(streamWithFds.stream(), fds); if (streamWithFds.stream().type() == InputStreamParams::T__None) { MOZ_CRASH("Serialize failed!"); } if (fds.IsEmpty()) { streamWithFds.optionalFds() = void_t(); } else { PFileDescriptorSetChild* fdSet = aManager->SendPFileDescriptorSetConstructor(fds[0]); for (uint32_t i = 1; i < fds.Length(); ++i) { Unused << fdSet->SendAddFileDescriptor(fds[i]); } streamWithFds.optionalFds() = fdSet; } return true; } template bool SerializeInputStreamWithFdsParent(nsIIPCSerializableInputStream* aStream, IPCStream& aValue, M* aManager) { MOZ_RELEASE_ASSERT(aStream); MOZ_ASSERT(aManager); aValue = InputStreamParamsWithFds(); InputStreamParamsWithFds& streamWithFds = aValue.get_InputStreamParamsWithFds(); AutoTArray fds; aStream->Serialize(streamWithFds.stream(), fds); if (streamWithFds.stream().type() == InputStreamParams::T__None) { MOZ_CRASH("Serialize failed!"); } streamWithFds.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) { streamWithFds.optionalFds() = fdSet; } } return true; } template bool SerializeInputStream(nsIInputStream* aStream, IPCStream& aValue, M* aManager) { MOZ_ASSERT(aStream); MOZ_ASSERT(aManager); // 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); IPCRemoteStream remoteStream; remoteStream.stream() = IPCStreamSource::Create(asyncStream, aManager); aValue = remoteStream; return true; } template bool SerializeInputStreamChild(nsIInputStream* aStream, M* aManager, IPCStream* aValue, OptionalIPCStream* aOptionalValue) { MOZ_ASSERT(aStream); MOZ_ASSERT(aManager); MOZ_ASSERT(aValue || aOptionalValue); // If a stream is known to be larger than 1MB, prefer sending it in chunks. const uint64_t kTooLargeStream = 1024 * 1024; nsCOMPtr serializable = do_QueryInterface(aStream); // ExpectedSerializedLength() returns the length of the stream if serialized. // This is useful to decide if we want to continue using the serialization // directly, or if it's better to use IPCStream. uint64_t expectedLength = serializable ? serializable->ExpectedSerializedLength().valueOr(0) : 0; if (serializable && expectedLength < kTooLargeStream) { if (aValue) { return SerializeInputStreamWithFdsChild(serializable, *aValue, aManager); } return SerializeInputStreamWithFdsChild(serializable, *aOptionalValue, aManager); } if (aValue) { return SerializeInputStream(aStream, *aValue, aManager); } return SerializeInputStream(aStream, *aOptionalValue, aManager); } template bool SerializeInputStreamParent(nsIInputStream* aStream, M* aManager, IPCStream* aValue, OptionalIPCStream* aOptionalValue) { MOZ_ASSERT(aStream); MOZ_ASSERT(aManager); MOZ_ASSERT(aValue || aOptionalValue); // If a stream is known to be larger than 1MB, prefer sending it in chunks. const uint64_t kTooLargeStream = 1024 * 1024; nsCOMPtr serializable = do_QueryInterface(aStream); uint64_t expectedLength = serializable ? serializable->ExpectedSerializedLength().valueOr(0) : 0; if (serializable && expectedLength < kTooLargeStream) { if (aValue) { return SerializeInputStreamWithFdsParent(serializable, *aValue, aManager); } return SerializeInputStreamWithFdsParent(serializable, *aOptionalValue, aManager); } if (aValue) { return SerializeInputStream(aStream, *aValue, aManager); } return SerializeInputStream(aStream, *aOptionalValue, aManager); } void CleanupIPCStream(IPCStream& aValue, bool aConsumedByIPC) { if (aValue.type() == IPCStream::T__None) { return; } if (aValue.type() == IPCStream::TInputStreamParamsWithFds) { InputStreamParamsWithFds& streamWithFds = aValue.get_InputStreamParamsWithFds(); // Cleanup file descriptors if necessary if (streamWithFds.optionalFds().type() == OptionalFileDescriptorSet::TPFileDescriptorSetChild) { AutoTArray fds; auto fdSetActor = static_cast( streamWithFds.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 << fdSetActor->Send__delete__(fdSetActor); } } else if (streamWithFds.optionalFds().type() == OptionalFileDescriptorSet::TPFileDescriptorSetParent) { AutoTArray fds; auto fdSetActor = static_cast( streamWithFds.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 << fdSetActor->Send__delete__(fdSetActor); } } return; } MOZ_ASSERT(aValue.type() == IPCStream::TIPCRemoteStream); IPCRemoteStreamType& remoteInputStream = aValue.get_IPCRemoteStream().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 (!aConsumedByIPC) { source->StartDestroy(); return; } // If the source stream was taken to be sent to the other side, then we need // to start it before forgetting about it. source->Start(); } void CleanupIPCStream(OptionalIPCStream& aValue, bool aConsumedByIPC) { if (aValue.type() == OptionalIPCStream::Tvoid_t) { return; } CleanupIPCStream(aValue.get_IPCStream(), aConsumedByIPC); } // 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) { if (aValue.type() == IPCStream::TIPCRemoteStream) { const IPCRemoteStreamType& remoteInputStream = aValue.get_IPCRemoteStream().stream(); if (remoteInputStream.type() == IPCRemoteStreamType::TPChildToParentStreamParent) { auto sendStream = IPCStreamDestination::Cast(remoteInputStream.get_PChildToParentStreamParent()); return sendStream->TakeReader(); } MOZ_ASSERT(remoteInputStream.type() == IPCRemoteStreamType::TPParentToChildStreamChild); auto sendStream = IPCStreamDestination::Cast(remoteInputStream.get_PParentToChildStreamChild()); return sendStream->TakeReader(); } // Note, we explicitly do not support deserializing the PChildToParentStream actor on // the child side nor the PParentToChildStream actor on the parent side. MOZ_ASSERT(aValue.type() == IPCStream::TInputStreamParamsWithFds); const InputStreamParamsWithFds& streamWithFds = aValue.get_InputStreamParamsWithFds(); AutoTArray fds; if (streamWithFds.optionalFds().type() == OptionalFileDescriptorSet::TPFileDescriptorSetParent) { auto fdSetActor = static_cast( streamWithFds.optionalFds().get_PFileDescriptorSetParent()); MOZ_ASSERT(fdSetActor); fdSetActor->ForgetFileDescriptors(fds); MOZ_ASSERT(!fds.IsEmpty()); if (!fdSetActor->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 (streamWithFds.optionalFds().type() == OptionalFileDescriptorSet::TPFileDescriptorSetChild) { auto fdSetActor = static_cast( streamWithFds.optionalFds().get_PFileDescriptorSetChild()); MOZ_ASSERT(fdSetActor); fdSetActor->ForgetFileDescriptors(fds); MOZ_ASSERT(!fds.IsEmpty()); Unused << fdSetActor->Send__delete__(fdSetActor); } return InputStreamHelper::DeserializeInputStream(streamWithFds.stream(), fds); } already_AddRefed DeserializeIPCStream(const OptionalIPCStream& aValue) { if (aValue.type() == OptionalIPCStream::Tvoid_t) { return nullptr; } return DeserializeIPCStream(aValue.get_IPCStream()); } AutoIPCStream::AutoIPCStream() : mInlineValue(void_t()) , mValue(nullptr) , mOptionalValue(&mInlineValue) , mTaken(false) { } AutoIPCStream::AutoIPCStream(IPCStream& aTarget) : mInlineValue(void_t()) , mValue(&aTarget) , mOptionalValue(nullptr) , mTaken(false) { } AutoIPCStream::AutoIPCStream(OptionalIPCStream& aTarget) : mInlineValue(void_t()) , mValue(nullptr) , mOptionalValue(&aTarget) , mTaken(false) { *mOptionalValue = void_t(); } AutoIPCStream::~AutoIPCStream() { MOZ_ASSERT(mValue || mOptionalValue); if (mValue && IsSet()) { CleanupIPCStream(*mValue, mTaken); } else { CleanupIPCStream(*mOptionalValue, mTaken); } } 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)) { MOZ_CRASH("IPCStream creation failed!"); } if (mValue) { AssertValidValueToTake(*mValue); } else { AssertValidValueToTake(*mOptionalValue); } 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)) { MOZ_CRASH("IPCStream creation failed!"); } if (mValue) { AssertValidValueToTake(*mValue); } else { AssertValidValueToTake(*mOptionalValue); } 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)) { return false; } if (mValue) { AssertValidValueToTake(*mValue); } else { AssertValidValueToTake(*mOptionalValue); } 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)) { return false; } if (mValue) { AssertValidValueToTake(*mValue); } else { AssertValidValueToTake(*mOptionalValue); } return true; } bool AutoIPCStream::IsSet() const { MOZ_ASSERT(mValue || mOptionalValue); if (mValue) { return mValue->type() != IPCStream::T__None; } else { return mOptionalValue->type() != OptionalIPCStream::Tvoid_t && mOptionalValue->get_IPCStream().type() != IPCStream::T__None; } } IPCStream& AutoIPCStream::TakeValue() { MOZ_ASSERT(mValue || mOptionalValue); MOZ_ASSERT(!mTaken); MOZ_ASSERT(IsSet()); mTaken = true; if (mValue) { AssertValidValueToTake(*mValue); return *mValue; } IPCStream& value = mOptionalValue->get_IPCStream(); AssertValidValueToTake(value); return value; } OptionalIPCStream& AutoIPCStream::TakeOptionalValue() { MOZ_ASSERT(!mTaken); MOZ_ASSERT(!mValue); MOZ_ASSERT(mOptionalValue); mTaken = true; AssertValidValueToTake(*mOptionalValue); return *mOptionalValue; } } // namespace ipc } // namespace mozilla