From 4c8ab874ba9529bbe768c707c300a41a7f7c3c23 Mon Sep 17 00:00:00 2001 From: Ben Kelly Date: Sun, 15 May 2016 10:32:09 -0700 Subject: [PATCH] Bug 1093357 P2 Add an RAII type to safely handle serialized stream actors. r=jld * * * Bug 1093357 P2 interdiff 001 address review feedback * * * Bug 1093357 P2 interdiff 002 refactor to IPCStream type --- ipc/glue/IPCStream.ipdlh | 34 +++ ipc/glue/IPCStreamUtils.cpp | 462 ++++++++++++++++++++++++++++++++++++ ipc/glue/IPCStreamUtils.h | 183 ++++++++++++++ ipc/glue/moz.build | 3 + 4 files changed, 682 insertions(+) create mode 100644 ipc/glue/IPCStream.ipdlh create mode 100644 ipc/glue/IPCStreamUtils.cpp create mode 100644 ipc/glue/IPCStreamUtils.h diff --git a/ipc/glue/IPCStream.ipdlh b/ipc/glue/IPCStream.ipdlh new file mode 100644 index 000000000000..bf17df628cd5 --- /dev/null +++ b/ipc/glue/IPCStream.ipdlh @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 PSendStream; +include InputStreamParams; + +namespace mozilla { +namespace ipc { + +// Do not use this directly. See IPCStream below. +struct InputStreamParamsWithFds +{ + InputStreamParams stream; + OptionalFileDescriptorSet optionalFds; +}; + +// Use IPCStream or OptionalIPCStream in your ipdl to represent serialized +// nsIInputStreams. Then use AutoIPCStream from IPCStreamUtils.h to perform +// the serialization. +union IPCStream +{ + InputStreamParamsWithFds; + PSendStream; +}; + +union OptionalIPCStream +{ + IPCStream; + void_t; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/ipc/glue/IPCStreamUtils.cpp b/ipc/glue/IPCStreamUtils.cpp new file mode 100644 index 000000000000..8c83a41b6225 --- /dev/null +++ b/ipc/glue/IPCStreamUtils.cpp @@ -0,0 +1,462 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/PContentChild.h" +#include "mozilla/dom/File.h" +#include "mozilla/ipc/FileDescriptorSetChild.h" +#include "mozilla/ipc/FileDescriptorSetParent.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ipc/SendStream.h" +#include "nsIAsyncInputStream.h" + +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 +void +SerializeInputStreamWithFdsChild(nsIInputStream* aStream, + IPCStream& aValue, + M* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + + // First attempt simple stream serialization + nsCOMPtr serializable = + do_QueryInterface(aStream); + if (!serializable) { + MOZ_CRASH("Input stream is not serializable!"); + } + + aValue = InputStreamParamsWithFds(); + InputStreamParamsWithFds& streamWithFds = + aValue.get_InputStreamParamsWithFds(); + + AutoTArray fds; + serializable->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; + } +} + +template +void +SerializeInputStreamWithFdsParent(nsIInputStream* aStream, + IPCStream& aValue, + M* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + + // First attempt simple stream serialization + nsCOMPtr serializable = + do_QueryInterface(aStream); + if (!serializable) { + MOZ_CRASH("Input stream is not serializable!"); + } + + aValue = InputStreamParamsWithFds(); + InputStreamParamsWithFds& streamWithFds = + aValue.get_InputStreamParamsWithFds(); + + AutoTArray fds; + serializable->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; + } + } +} + +template +void +SerializeInputStream(nsIInputStream* aStream, IPCStream& aValue, M* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + + // First attempt simple stream serialization + nsCOMPtr serializable = + do_QueryInterface(aStream); + if (serializable) { + SerializeInputStreamWithFdsChild(aStream, aValue, aManager); + return; + } + + // As a fallback, attempt to stream the data across using a SendStream + // actor. This will fail for blocking streams. + nsCOMPtr asyncStream = do_QueryInterface(aStream); + aValue = SendStreamChild::Create(asyncStream, aManager); + + if (!aValue.get_PSendStreamChild()) { + MOZ_CRASH("SendStream creation failed!"); + } +} + +template +void +SerializeInputStream(nsIInputStream* aStream, OptionalIPCStream& aValue, + M* aManager) +{ + if (!aStream) { + aValue = void_t(); + return; + } + + aValue = IPCStream(); + SerializeInputStream(aStream, aValue.get_IPCStream(), + 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); + + if (!aConsumedByIPC) { + Unused << fdSetActor->Send__delete__(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); + + } else if (streamWithFds.optionalFds().type() == + OptionalFileDescriptorSet::TPFileDescriptorSetParent) { + + AutoTArray fds; + + auto fdSetActor = static_cast( + streamWithFds.optionalFds().get_PFileDescriptorSetParent()); + MOZ_ASSERT(fdSetActor); + + if (!aConsumedByIPC) { + Unused << fdSetActor->Send__delete__(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); + } + + return; + } + + MOZ_ASSERT(aValue.type() == IPCStream::TPSendStreamChild); + + auto sendStream = + static_cast(aValue.get_PSendStreamChild()); + + if (!aConsumedByIPC) { + sendStream->StartDestroy(); + return; + } + + // If the SendStream was taken to be sent to the parent, then we need to + // start it before forgetting about it. + sendStream->Start(); +} + +void +CleanupIPCStream(OptionalIPCStream& aValue, bool aConsumedByIPC) +{ + if (aValue.type() == OptionalIPCStream::Tvoid_t) { + return; + } + + CleanupIPCStream(aValue.get_IPCStream(), aConsumedByIPC); +} + +} // anonymous namespace + +already_AddRefed +DeserializeIPCStream(const IPCStream& aValue) +{ + if (aValue.type() == IPCStream::TPSendStreamParent) { + auto sendStream = + static_cast(aValue.get_PSendStreamParent()); + return sendStream->TakeReader(); + } + + // Note, we explicitly do not support deserializing the PSendStream actor on + // the child side. It can only be sent from child to parent. + 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 DeserializeInputStream(streamWithFds.stream(), fds); +} + +already_AddRefed +DeserializeIPCStream(const OptionalIPCStream& aValue) +{ + if (aValue.type() == OptionalIPCStream::Tvoid_t) { + return nullptr; + } + + return DeserializeIPCStream(aValue.get_IPCStream()); +} + +namespace { + +void +AssertValidValueToTake(const IPCStream& aVal) +{ + MOZ_ASSERT(aVal.type() == IPCStream::TPSendStreamChild || + 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()); + } +} + +} // anonymous namespace + +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); + } +} + +void +AutoIPCStream::Serialize(nsIInputStream* aStream, PContentChild* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + if (mValue) { + SerializeInputStream(aStream, *mValue, aManager); + AssertValidValueToTake(*mValue); + } else { + SerializeInputStream(aStream, *mOptionalValue, aManager); + AssertValidValueToTake(*mOptionalValue); + } +} + +void +AutoIPCStream::Serialize(nsIInputStream* aStream, PBackgroundChild* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + if (mValue) { + SerializeInputStream(aStream, *mValue, aManager); + AssertValidValueToTake(*mValue); + } else { + SerializeInputStream(aStream, *mOptionalValue, aManager); + AssertValidValueToTake(*mOptionalValue); + } +} + +void +AutoIPCStream::Serialize(nsIInputStream* aStream, dom::PContentParent* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + if (mValue) { + SerializeInputStreamWithFdsParent(aStream, *mValue, aManager); + AssertValidValueToTake(*mValue); + } else { + SerializeInputStreamWithFdsParent(aStream, *mOptionalValue, aManager); + AssertValidValueToTake(*mOptionalValue); + } +} + +void +AutoIPCStream::Serialize(nsIInputStream* aStream, PBackgroundParent* aManager) +{ + MOZ_ASSERT(aStream); + MOZ_ASSERT(aManager); + MOZ_ASSERT(mValue || mOptionalValue); + MOZ_ASSERT(!mTaken); + MOZ_ASSERT(!IsSet()); + + if (mValue) { + SerializeInputStreamWithFdsParent(aStream, *mValue, aManager); + AssertValidValueToTake(*mValue); + } else { + SerializeInputStreamWithFdsParent(aStream, *mOptionalValue, aManager); + AssertValidValueToTake(*mOptionalValue); + } +} + +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 diff --git a/ipc/glue/IPCStreamUtils.h b/ipc/glue/IPCStreamUtils.h new file mode 100644 index 000000000000..d444469e4609 --- /dev/null +++ b/ipc/glue/IPCStreamUtils.h @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsIInputStream.h" + +namespace mozilla { + +namespace dom { +class PContentChild; +class PContentParent; +} + +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 +DeserializeIPCStream(const IPCStream& aValue); + +already_AddRefed +DeserializeIPCStream(const OptionalIPCStream& aValue); + +// RAII helper class that serializes an nsIInputStream into an IPCStream struct. +// Any file descriptor or PSendStream 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 stream = DeserializeIPCStream(aIPCStream); +// // Do something with stream... +// +// // You can also serialize streams from parent-to-child as long as +// // they don't require PSendStream 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 stream = DeserializeIPCStream(aStuff.stream()); +// /* do something with the nsIInputStream */ +// } +// +// The AutoIPCStream class also supports OptionalIPCStream 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 PSendStream 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 final +{ + OptionalIPCStream mInlineValue; + IPCStream* mValue; + OptionalIPCStream* mOptionalValue; + bool mTaken; + + bool + IsSet() const; + +public: + // Implicitly create an OptionalIPCStream value. Either + // TakeValue() or TakeOptionalValue() can be used. + AutoIPCStream(); + + // 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); + + // Wrap an existing OptionalIPCStream. Either TakeValue() + // or TakeOptionalValue can be used. + explicit AutoIPCStream(OptionalIPCStream& aTarget); + + ~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. + void + Serialize(nsIInputStream* aStream, dom::PContentChild* 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. + void + Serialize(nsIInputStream* aStream, PBackgroundChild* aManager); + + // Serialize the input stream. A PSendStream cannot be used when going + // from parent-to-child. + void + Serialize(nsIInputStream* aStream, dom::PContentParent* aManager); + + // Serialize the input stream. A PSendStream cannot be used when going + // from parent-to-child. + void + Serialize(nsIInputStream* aStream, PBackgroundParent* 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 parent + IPCStream& + TakeValue(); + + // Get the OptionalIPCStream 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 parent + OptionalIPCStream& + TakeOptionalValue(); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_IPCStreamUtils_h diff --git a/ipc/glue/moz.build b/ipc/glue/moz.build index fa2af19126b3..248d2a30ce96 100644 --- a/ipc/glue/moz.build +++ b/ipc/glue/moz.build @@ -23,6 +23,7 @@ EXPORTS.mozilla.ipc += [ 'GeckoChildProcessHost.h', 'InputStreamUtils.h', 'IOThreadChild.h', + 'IPCStreamUtils.h', 'MessageChannel.h', 'MessageLink.h', 'Neutering.h', @@ -117,6 +118,7 @@ UNIFIED_SOURCES += [ 'FileDescriptorUtils.cpp', 'InputStreamUtils.cpp', 'IPCMessageUtils.cpp', + 'IPCStreamUtils.cpp', 'MessageChannel.cpp', 'MessageLink.cpp', 'MessagePump.cpp', @@ -158,6 +160,7 @@ LOCAL_INCLUDES += [ IPDL_SOURCES = [ 'InputStreamParams.ipdlh', + 'IPCStream.ipdlh', 'PBackground.ipdl', 'PBackgroundSharedTypes.ipdlh', 'PBackgroundTest.ipdl',