diff options
author | amistry <amistry@chromium.org> | 2016-03-16 17:53:46 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-03-17 00:54:57 +0000 |
commit | ba362cd27ec5b4410bd992daccc88a164988384a (patch) | |
tree | 8c4ef53df62869d9e4e79458c605da15af1d9344 /mojo | |
parent | 2668bea689fd4a5ce07011903bca095aaf67bb07 (diff) | |
download | chromium_src-ba362cd27ec5b4410bd992daccc88a164988384a.zip chromium_src-ba362cd27ec5b4410bd992daccc88a164988384a.tar.gz chromium_src-ba362cd27ec5b4410bd992daccc88a164988384a.tar.bz2 |
[mojo-edk] Add support for transferring mach ports.
This change adds the ability to transfer mach ports over Mojo. Mach ports can
either be wrapped using CreatePlatformHandleWrapper() or a Mojo shared buffer
can be created using CreateSharedBufferWrapper(). For now, Mojo shared buffers
created using MojoCreateSharedBuffer() (or MojoCreateDataPipe()) will still
use posix shared memory.
BUG=582468
Review URL: https://codereview.chromium.org/1712143002
Cr-Commit-Position: refs/heads/master@{#381615}
Diffstat (limited to 'mojo')
-rw-r--r-- | mojo/edk/embedder/embedder.cc | 7 | ||||
-rw-r--r-- | mojo/edk/embedder/embedder.h | 11 | ||||
-rw-r--r-- | mojo/edk/embedder/embedder_unittest.cc | 134 | ||||
-rw-r--r-- | mojo/edk/embedder/platform_handle.cc | 8 | ||||
-rw-r--r-- | mojo/edk/embedder/platform_handle.h | 7 | ||||
-rw-r--r-- | mojo/edk/embedder/platform_shared_buffer.cc | 5 | ||||
-rw-r--r-- | mojo/edk/system/BUILD.gn | 7 | ||||
-rw-r--r-- | mojo/edk/system/channel.cc | 88 | ||||
-rw-r--r-- | mojo/edk/system/channel.h | 24 | ||||
-rw-r--r-- | mojo/edk/system/channel_posix.cc | 38 | ||||
-rw-r--r-- | mojo/edk/system/core.cc | 6 | ||||
-rw-r--r-- | mojo/edk/system/core.h | 7 | ||||
-rw-r--r-- | mojo/edk/system/mach_port_relay.cc | 155 | ||||
-rw-r--r-- | mojo/edk/system/mach_port_relay.h | 94 | ||||
-rw-r--r-- | mojo/edk/system/node_channel.cc | 166 | ||||
-rw-r--r-- | mojo/edk/system/node_channel.h | 37 | ||||
-rw-r--r-- | mojo/edk/system/node_controller.cc | 91 | ||||
-rw-r--r-- | mojo/edk/system/node_controller.h | 21 | ||||
-rw-r--r-- | mojo/edk/test/mojo_test_base.cc | 43 | ||||
-rw-r--r-- | mojo/edk/test/multiprocess_test_helper.cc | 6 | ||||
-rw-r--r-- | mojo/edk/test/multiprocess_test_helper.h | 2 | ||||
-rw-r--r-- | mojo/mojo_edk.gyp | 6 |
22 files changed, 913 insertions, 50 deletions
diff --git a/mojo/edk/embedder/embedder.cc b/mojo/edk/embedder/embedder.cc index d471c3a..793b708 100644 --- a/mojo/edk/embedder/embedder.cc +++ b/mojo/edk/embedder/embedder.cc @@ -119,6 +119,13 @@ void ShutdownIPCSupport() { base::Unretained(internal::g_process_delegate))); } +#if defined(OS_MACOSX) && !defined(OS_IOS) +void SetMachPortProvider(base::PortProvider* port_provider) { + DCHECK(port_provider); + internal::g_core->SetMachPortProvider(port_provider); +} +#endif + ScopedMessagePipeHandle CreateMessagePipe( ScopedPlatformHandle platform_handle) { CHECK(internal::g_process_delegate); diff --git a/mojo/edk/embedder/embedder.h b/mojo/edk/embedder/embedder.h index b96d847..3eadc36 100644 --- a/mojo/edk/embedder/embedder.h +++ b/mojo/edk/embedder/embedder.h @@ -20,6 +20,10 @@ #include "mojo/edk/system/system_impl_export.h" #include "mojo/public/cpp/system/message_pipe.h" +namespace base { +class PortProvider; +} + namespace mojo { namespace edk { @@ -138,6 +142,13 @@ MOJO_SYSTEM_IMPL_EXPORT void InitIPCSupport( // |OnShutdownComplete()| method is invoked. MOJO_SYSTEM_IMPL_EXPORT void ShutdownIPCSupport(); +#if defined(OS_MACOSX) && !defined(OS_IOS) +// Set the |base::PortProvider| for this process. Can be called on any thread, +// but must be set in the root process before any Mach ports can be transferred. +MOJO_SYSTEM_IMPL_EXPORT void SetMachPortProvider( + base::PortProvider* port_provider); +#endif + // Creates a message pipe over an arbitrary platform channel. The other end of // the channel must also be passed to this function. Either endpoint can be in // any process. diff --git a/mojo/edk/embedder/embedder_unittest.cc b/mojo/edk/embedder/embedder_unittest.cc index b5927b3..c781f42 100644 --- a/mojo/edk/embedder/embedder_unittest.cc +++ b/mojo/edk/embedder/embedder_unittest.cc @@ -13,6 +13,7 @@ #include "base/bind.h" #include "base/command_line.h" #include "base/logging.h" +#include "base/macros.h" #include "base/memory/shared_memory.h" #include "base/message_loop/message_loop.h" #include "base/synchronization/waitable_event.h" @@ -376,6 +377,139 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessSharedMemoryClient, EmbedderTest, EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(sb1)); } +#if defined(OS_MACOSX) && !defined(OS_IOS) +TEST_F(EmbedderTest, MultiprocessMachSharedMemory) { + RUN_CHILD_ON_PIPE(MultiprocessSharedMemoryClient, server_mp) + // 1. Create a Mach base::SharedMemory object and create a mojo shared + // buffer from it. + base::SharedMemoryCreateOptions options; + options.size = 123; + options.type = base::SharedMemoryHandle::MACH; + base::SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.Create(options)); + base::SharedMemoryHandle shm_handle = base::SharedMemory::DuplicateHandle( + shared_memory.handle()); + MojoHandle sb1; + ASSERT_EQ(MOJO_RESULT_OK, + CreateSharedBufferWrapper(shm_handle, 123, false, &sb1)); + + // 2. Map |sb1| and write something into it. + char* buffer = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(sb1, 0, 123, reinterpret_cast<void**>(&buffer), 0)); + ASSERT_TRUE(buffer); + memcpy(buffer, kHelloWorld, sizeof(kHelloWorld)); + + // 3. Duplicate |sb1| into |sb2| and pass to |server_mp|. + MojoHandle sb2 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoDuplicateBufferHandle(sb1, 0, &sb2)); + EXPECT_NE(MOJO_HANDLE_INVALID, sb2); + WriteMessageWithHandles(server_mp, "hello", &sb2, 1); + + // 4. Read a message from |server_mp|. + EXPECT_EQ("bye", ReadMessage(server_mp)); + + // 5. Expect that the contents of the shared buffer have changed. + EXPECT_EQ(kByeWorld, std::string(buffer)); + + // 6. Map the original base::SharedMemory and expect it contains the + // expected value. + ASSERT_TRUE(shared_memory.Map(123)); + EXPECT_EQ(kByeWorld, + std::string(static_cast<char*>(shared_memory.memory()))); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(sb1)); + END_CHILD() +} + +const base::SharedMemoryHandle::Type kTestHandleTypes[] = { + base::SharedMemoryHandle::MACH, + base::SharedMemoryHandle::POSIX, + base::SharedMemoryHandle::POSIX, + base::SharedMemoryHandle::MACH, +}; + +// Test that we can mix file descriptor and mach port handles. +TEST_F(EmbedderTest, MultiprocessMixMachAndFds) { + const size_t kShmSize = 1234; + RUN_CHILD_ON_PIPE(MultiprocessMixMachAndFdsClient, server_mp) + // 1. Create the base::SharedMemory objects and mojo handles from them. + MojoHandle platform_handles[arraysize(kTestHandleTypes)]; + for (size_t i = 0; i < arraysize(kTestHandleTypes); i++) { + const auto type = kTestHandleTypes[i]; + base::SharedMemoryCreateOptions options; + options.size = kShmSize; + options.type = type; + base::SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.Create(options)); + base::SharedMemoryHandle shm_handle = base::SharedMemory::DuplicateHandle( + shared_memory.handle()); + ScopedPlatformHandle scoped_handle; + if (type == base::SharedMemoryHandle::POSIX) + scoped_handle.reset(PlatformHandle(shm_handle.GetFileDescriptor().fd)); + else + scoped_handle.reset(PlatformHandle(shm_handle.GetMemoryObject())); + ASSERT_EQ(MOJO_RESULT_OK, CreatePlatformHandleWrapper( + std::move(scoped_handle), platform_handles + i)); + + // Map the shared memory object and write the type into it. 'P' for POSIX, + // and 'M' for Mach. + ASSERT_TRUE(shared_memory.Map(kShmSize)); + static_cast<char*>(shared_memory.memory())[0] = + type == base::SharedMemoryHandle::POSIX ? 'P' : 'M'; + } + + // 2. Send all the handles to the child. + WriteMessageWithHandles(server_mp, "hello", platform_handles, + arraysize(kTestHandleTypes)); + + // 3. Read a message from |server_mp|. + EXPECT_EQ("bye", ReadMessage(server_mp)); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessMixMachAndFdsClient, EmbedderTest, + client_mp) { + const int kNumHandles = 4; + const size_t kShmSize = 1234; + MojoHandle platform_handles[kNumHandles]; + + // 1. Read from |client_mp|, which should have a message containing + // |kNumHandles| handles. + EXPECT_EQ("hello", + ReadMessageWithHandles(client_mp, platform_handles, kNumHandles)); + + // 2. Extract each handle, map it, and verify the type. + for (int i = 0; i < kNumHandles; i++) { + ScopedPlatformHandle scoped_handle; + ASSERT_EQ(MOJO_RESULT_OK, + PassWrappedPlatformHandle(platform_handles[i], &scoped_handle)); + base::SharedMemoryHandle shm_handle; + char type = 0; + if (scoped_handle.get().type == PlatformHandle::Type::POSIX) { + shm_handle = base::SharedMemoryHandle(scoped_handle.release().handle, + false); + type = 'P'; + } else { + shm_handle = base::SharedMemoryHandle(scoped_handle.release().port, + kShmSize, base::GetCurrentProcId()); + type = 'M'; + } + + // Verify the type order. + EXPECT_EQ(kTestHandleTypes[i], shm_handle.GetType()); + + base::SharedMemory shared_memory(shm_handle, false); + ASSERT_TRUE(shared_memory.Map(kShmSize)); + EXPECT_EQ(type, static_cast<char*>(shared_memory.memory())[0]); + } + + // 3. Say bye! + WriteMessage(client_mp, "bye"); +} + +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + // TODO(vtl): Test immediate write & close. // TODO(vtl): Test broken-connection cases. diff --git a/mojo/edk/embedder/platform_handle.cc b/mojo/edk/embedder/platform_handle.cc index 62dc850..5709b1e 100644 --- a/mojo/edk/embedder/platform_handle.cc +++ b/mojo/edk/embedder/platform_handle.cc @@ -29,10 +29,10 @@ void PlatformHandle::CloseIfNecessary() { handle = -1; } #if defined(OS_MACOSX) && !defined(OS_IOS) - else { - kern_return_t rv = mach_port_deallocate(mach_task_self(), port); - DPCHECK(rv == KERN_SUCCESS); - port = MACH_PORT_NULL; + else if (type == Type::MACH) { + kern_return_t rv = mach_port_deallocate(mach_task_self(), port); + DPCHECK(rv == KERN_SUCCESS); + port = MACH_PORT_NULL; } #endif // defined(OS_MACOSX) && !defined(OS_IOS) #elif defined(OS_WIN) diff --git a/mojo/edk/embedder/platform_handle.h b/mojo/edk/embedder/platform_handle.h index 1c0c752..3c945c6 100644 --- a/mojo/edk/embedder/platform_handle.h +++ b/mojo/edk/embedder/platform_handle.h @@ -30,7 +30,7 @@ struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandle { bool is_valid() const { #if defined(OS_MACOSX) && !defined(OS_IOS) - if (type == Type::MACH) + if (type == Type::MACH || type == Type::MACH_NAME) return port != MACH_PORT_NULL; #endif return handle != -1; @@ -40,6 +40,11 @@ struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandle { POSIX, #if defined(OS_MACOSX) && !defined(OS_IOS) MACH, + // MACH_NAME isn't a real Mach port. But rather the "name" of one that can + // be resolved to a real port later. This distinction is needed so that the + // "port" doesn't try to be closed if CloseIfNecessary() is called. Having + // this also allows us to do checks in other places. + MACH_NAME, #endif }; Type type = Type::POSIX; diff --git a/mojo/edk/embedder/platform_shared_buffer.cc b/mojo/edk/embedder/platform_shared_buffer.cc index 9516634..f3f9b70 100644 --- a/mojo/edk/embedder/platform_shared_buffer.cc +++ b/mojo/edk/embedder/platform_shared_buffer.cc @@ -210,11 +210,6 @@ void PlatformSharedBuffer::InitFromSharedMemoryHandle( base::SharedMemoryHandle handle) { DCHECK(!shared_memory_); -#if defined(OS_MACOSX) && !defined(OS_IOS) - // TODO(crbug.com/582468): Support Mach shared memory. - CHECK(handle.GetType() == base::SharedMemoryHandle::POSIX); -#endif - // TODO(crbug.com/556587): Support read-only handles. shared_memory_.reset(new base::SharedMemory(handle, false)); } diff --git a/mojo/edk/system/BUILD.gn b/mojo/edk/system/BUILD.gn index 1bd597f..5d83bb6 100644 --- a/mojo/edk/system/BUILD.gn +++ b/mojo/edk/system/BUILD.gn @@ -105,6 +105,13 @@ component("system") { # which is uninteresting. } + if (is_mac && !is_ios) { + sources += [ + "mach_port_relay.cc", + "mach_port_relay.h", + ] + } + allow_circular_includes_from = [ "//mojo/edk/embedder" ] } diff --git a/mojo/edk/system/channel.cc b/mojo/edk/system/channel.cc index e74b900..67c7398 100644 --- a/mojo/edk/system/channel.cc +++ b/mojo/edk/system/channel.cc @@ -8,11 +8,16 @@ #include <algorithm> #include <limits> +#include <utility> #include "base/macros.h" #include "base/memory/aligned_memory.h" #include "mojo/edk/embedder/platform_handle.h" +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "base/mac/mach_logging.h" +#endif + namespace mojo { namespace edk { @@ -43,6 +48,12 @@ Channel::Message::Message(size_t payload_size, #if defined(OS_WIN) // On Windows we serialize platform handles into the extra header space. extra_header_size = max_handles_ * sizeof(PlatformHandle); +#elif defined(OS_MACOSX) && !defined(OS_IOS) + // On OSX, some of the platform handles may be mach ports, which are + // serialised into the message buffer. Since there could be a mix of fds and + // mach ports, we store the mach ports as an <index, port> pair (of uint32_t), + // so that the original ordering of handles can be re-created. + extra_header_size = max_handles * sizeof(MachPortsEntry); #endif // Pad extra header data to be aliged to |kChannelMessageAlignment| bytes. if (extra_header_size % kChannelMessageAlignment) { @@ -78,14 +89,19 @@ Channel::Message::Message(size_t payload_size, static_cast<uint16_t>(sizeof(Header) + extra_header_size); #endif -#if defined(OS_WIN) if (max_handles_ > 0) { +#if defined(OS_WIN) handles_ = reinterpret_cast<PlatformHandle*>(mutable_extra_header()); // Initialize all handles to invalid values. for (size_t i = 0; i < max_handles_; ++i) handles()[i] = PlatformHandle(); - } +#elif defined(OS_MACOSX) && !defined(OS_IOS) + mach_ports_ = reinterpret_cast<MachPortsEntry*>(mutable_extra_header()); + // Initialize all handles to invalid values. + for (size_t i = 0; i < max_handles_; ++i) + mach_ports_[i] = {0, static_cast<uint32_t>(MACH_PORT_NULL)}; #endif + } } Channel::Message::~Message() { @@ -100,9 +116,9 @@ Channel::Message::~Message() { // static Channel::MessagePtr Channel::Message::Deserialize(const void* data, size_t data_num_bytes) { -#if !defined(OS_WIN) +#if !defined(OS_WIN) && !(defined(OS_MACOSX) && !defined(OS_IOS)) // We only serialize messages into other messages when performing message - // relay on Windows. + // relay on Windows and OSX. NOTREACHED(); return nullptr; #else @@ -123,16 +139,19 @@ Channel::MessagePtr Channel::Message::Deserialize(const void* data, } uint32_t extra_header_size = header->num_header_bytes - sizeof(Header); +#if defined(OS_WIN) uint32_t max_handles = extra_header_size / sizeof(PlatformHandle); +#elif defined(OS_MACOSX) && !defined(OS_IOS) + uint32_t max_handles = extra_header_size / sizeof(MachPortsEntry); +#endif if (header->num_handles > max_handles) { - DLOG(ERROR) << "Decoding invalid message:" << header->num_handles << " > " - << max_handles; + DLOG(ERROR) << "Decoding invalid message:" << header->num_handles + << " > " << max_handles; return nullptr; } - MessagePtr message( - new Message(data_num_bytes - header->num_header_bytes, max_handles)); - + MessagePtr message(new Message(data_num_bytes - header->num_header_bytes, + max_handles)); DCHECK_EQ(message->data_num_bytes(), data_num_bytes); DCHECK_EQ(message->extra_header_size(), extra_header_size); DCHECK_EQ(message->header_->num_header_bytes, header->num_header_bytes); @@ -184,8 +203,10 @@ bool Channel::Message::has_mach_ports() const { return false; for (const auto& handle : (*handle_vector_)) { - if (handle.type == PlatformHandle::Type::MACH) + if (handle.type == PlatformHandle::Type::MACH || + handle.type == PlatformHandle::Type::MACH_NAME) { return true; + } } return false; } @@ -217,6 +238,21 @@ void Channel::Message::SetHandles(ScopedPlatformHandleVectorPtr new_handles) { std::swap(handle_vector_, new_handles); #endif // defined(OS_WIN) #endif // defined(OS_CHROMEOS) || defined(OS_ANDROID) + +#if defined(OS_MACOSX) && !defined(OS_IOS) + size_t mach_port_index = 0; + for (size_t i = 0; i < max_handles_; ++i) + mach_ports_[i] = {0, static_cast<uint32_t>(MACH_PORT_NULL)}; + for (size_t i = 0; i < handle_vector_->size(); i++) { + if ((*handle_vector_)[i].type == PlatformHandle::Type::MACH || + (*handle_vector_)[i].type == PlatformHandle::Type::MACH_NAME) { + mach_port_t port = (*handle_vector_)[i].port; + mach_ports_[mach_port_index].index = i; + mach_ports_[mach_port_index].mach_port = port; + mach_port_index++; + } + } +#endif } ScopedPlatformHandleVectorPtr Channel::Message::TakeHandles() { @@ -227,7 +263,39 @@ ScopedPlatformHandleVectorPtr Channel::Message::TakeHandles() { new PlatformHandleVector(header_->num_handles)); for (size_t i = 0; i < header_->num_handles; ++i) std::swap(moved_handles->at(i), handles()[i]); + header_->num_handles = 0; return moved_handles; +#elif defined(OS_MACOSX) && !defined(OS_IOS) + for (size_t i = 0; i < max_handles_; ++i) + mach_ports_[i] = {0, static_cast<uint32_t>(MACH_PORT_NULL)}; + header_->num_handles = 0; + return std::move(handle_vector_); +#else + header_->num_handles = 0; + return std::move(handle_vector_); +#endif +} + +ScopedPlatformHandleVectorPtr Channel::Message::TakeHandlesForTransport() { +#if defined(OS_WIN) + // Not necessary on Windows. + NOTREACHED(); + return nullptr; +#elif defined(OS_MACOSX) && !defined(OS_IOS) + if (handle_vector_) { + for (auto it = handle_vector_->begin(); it != handle_vector_->end(); ) { + if (it->type == PlatformHandle::Type::MACH || + it->type == PlatformHandle::Type::MACH_NAME) { + // For Mach port names, we can can just leak them. They're not real + // ports anyways. For real ports, they're leaked because this is a child + // process and the remote process will take ownership. + it = handle_vector_->erase(it); + } else { + ++it; + } + } + } + return std::move(handle_vector_); #else return std::move(handle_vector_); #endif diff --git a/mojo/edk/system/channel.h b/mojo/edk/system/channel.h index 54b6412..9932c9c 100644 --- a/mojo/edk/system/channel.h +++ b/mojo/edk/system/channel.h @@ -66,6 +66,17 @@ class Channel : public base::RefCountedThreadSafe<Channel> { char padding[6]; #endif // defined(OS_CHROMEOS) || defined(OS_ANDROID) }; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + struct MachPortsEntry { + uint16_t index; + uint32_t mach_port; + static_assert(sizeof(mach_port_t) <= sizeof(uint32_t), + "mach_port_t must be no larger than uint32_t"); + }; + static_assert(sizeof(MachPortsEntry) == 6, + "sizeof(MachPortsEntry) must be 6 bytes"); +#endif #pragma pack(pop) // Allocates and owns a buffer for message data with enough capacity for @@ -111,6 +122,11 @@ class Channel : public base::RefCountedThreadSafe<Channel> { // handles(). void SetHandles(ScopedPlatformHandleVectorPtr new_handles); ScopedPlatformHandleVectorPtr TakeHandles(); + // Version of TakeHandles that returns a vector of platform handles suitable + // for transfer over an underlying OS mechanism. i.e. file descriptors over + // a unix domain socket. Any handle that cannot be transferred this way, + // such as Mach ports, will be removed. + ScopedPlatformHandleVectorPtr TakeHandlesForTransport(); #if defined(OS_WIN) // Prepares the handles in this message for use in a different process. @@ -131,13 +147,17 @@ class Channel : public base::RefCountedThreadSafe<Channel> { Header* header_; #if defined(OS_WIN) - // On Windows, handles are serialized in the data buffer along with the - // rest of the payload. + // On Windows, handles are serialised into the extra header section. PlatformHandle* handles_ = nullptr; #else ScopedPlatformHandleVectorPtr handle_vector_; #endif +#if defined(OS_MACOSX) && !defined(OS_IOS) + // On OSX, handles are serialised into the extra header section. + MachPortsEntry* mach_ports_ = nullptr; +#endif + DISALLOW_COPY_AND_ASSIGN(Message); }; diff --git a/mojo/edk/system/channel_posix.cc b/mojo/edk/system/channel_posix.cc index 2576eac..bc759e0 100644 --- a/mojo/edk/system/channel_posix.cc +++ b/mojo/edk/system/channel_posix.cc @@ -36,7 +36,7 @@ class MessageView { MessageView(Channel::MessagePtr message, size_t offset) : message_(std::move(message)), offset_(offset), - handles_(message_->TakeHandles()) { + handles_(message_->TakeHandlesForTransport()) { DCHECK_GT(message_->data_num_bytes(), offset_); } @@ -136,6 +136,41 @@ class ChannelPosix : public Channel, size_t num_handles, const void* extra_header, size_t extra_header_size) override { +#if defined(OS_MACOSX) && !defined(OS_IOS) + // On OSX, we can have mach ports which are located in the extra header + // section. + using MachPortsEntry = Channel::Message::MachPortsEntry; + CHECK(extra_header_size >= num_handles * sizeof(MachPortsEntry)); + size_t num_mach_ports = 0; + const MachPortsEntry* mach_ports = + reinterpret_cast<const MachPortsEntry*>(extra_header); + for (size_t i = 0; i < num_handles; i++) { + if (mach_ports[i].mach_port != MACH_PORT_NULL) + num_mach_ports++; + } + CHECK(num_mach_ports <= num_handles); + if (incoming_platform_handles_.size() + num_mach_ports < num_handles) + return nullptr; + + ScopedPlatformHandleVectorPtr handles( + new PlatformHandleVector(num_handles)); + for (size_t i = 0, mach_port_index = 0; i < num_handles; ++i) { + if (mach_port_index < num_mach_ports && + mach_ports[mach_port_index].index == i) { + (*handles)[i] = PlatformHandle( + static_cast<mach_port_t>(mach_ports[mach_port_index].mach_port)); + CHECK((*handles)[i].type == PlatformHandle::Type::MACH); + // These are actually just Mach port names until they're resolved from + // the remote process. + (*handles)[i].type = PlatformHandle::Type::MACH_NAME; + mach_port_index++; + } else { + CHECK(!incoming_platform_handles_.empty()); + (*handles)[i] = incoming_platform_handles_.front(); + incoming_platform_handles_.pop_front(); + } + } +#else if (incoming_platform_handles_.size() < num_handles) return nullptr; @@ -145,6 +180,7 @@ class ChannelPosix : public Channel, (*handles)[i] = incoming_platform_handles_.front(); incoming_platform_handles_.pop_front(); } +#endif return handles; } diff --git a/mojo/edk/system/core.cc b/mojo/edk/system/core.cc index d436307..49cb242 100644 --- a/mojo/edk/system/core.cc +++ b/mojo/edk/system/core.cc @@ -99,6 +99,12 @@ void Core::InitChild(ScopedPlatformHandle platform_handle) { GetNodeController()->ConnectToParent(std::move(platform_handle)); } +void Core::SetMachPortProvider(base::PortProvider* port_provider) { +#if defined(OS_MACOSX) && !defined(OS_IOS) + GetNodeController()->CreateMachPortRelay(port_provider); +#endif +} + MojoHandle Core::AddDispatcher(scoped_refptr<Dispatcher> dispatcher) { base::AutoLock lock(handles_lock_); return handles_.AddDispatcher(dispatcher); diff --git a/mojo/edk/system/core.h b/mojo/edk/system/core.h index 11aff94..18da0ba 100644 --- a/mojo/edk/system/core.h +++ b/mojo/edk/system/core.h @@ -27,6 +27,10 @@ #include "mojo/public/c/system/types.h" #include "mojo/public/cpp/system/message_pipe.h" +namespace base { +class PortProvider; +} + namespace mojo { namespace edk { @@ -67,6 +71,9 @@ class MOJO_SYSTEM_IMPL_EXPORT Core { // associated with |token|. ScopedMessagePipeHandle CreateChildMessagePipe(const std::string& token); + // Sets the mach port provider for this process. + void SetMachPortProvider(base::PortProvider* port_provider); + MojoHandle AddDispatcher(scoped_refptr<Dispatcher> dispatcher); // Adds new dispatchers for non-message-pipe handles received in a message. diff --git a/mojo/edk/system/mach_port_relay.cc b/mojo/edk/system/mach_port_relay.cc new file mode 100644 index 0000000..2dd40e5 --- /dev/null +++ b/mojo/edk/system/mach_port_relay.cc @@ -0,0 +1,155 @@ +// Copyright 2016 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 "mojo/edk/system/mach_port_relay.h" + +#include <mach/mach.h> + +#include <utility> + +#include "base/logging.h" +#include "base/mac/mach_port_util.h" +#include "base/mac/scoped_mach_port.h" +#include "base/process/process.h" +#include "mojo/edk/embedder/platform_handle_vector.h" + +namespace mojo { +namespace edk { + +// static +bool MachPortRelay::ReceivePorts(PlatformHandleVector* handles) { + DCHECK(handles); + + for (size_t i = 0; i < handles->size(); i++) { + PlatformHandle* handle = handles->data() + i; + DCHECK(handle->type != PlatformHandle::Type::MACH); + if (handle->type != PlatformHandle::Type::MACH_NAME) + continue; + + base::mac::ScopedMachReceiveRight message_port(handle->port); + base::mac::ScopedMachSendRight received_port( + base::ReceiveMachPort(message_port.get())); + if (received_port.get() == MACH_PORT_NULL) { + handle->port = MACH_PORT_NULL; + LOG(ERROR) << "Error receiving mach port"; + return false; + } + + handle->port = received_port.release(); + handle->type = PlatformHandle::Type::MACH; + } + + return true; +} + +MachPortRelay::MachPortRelay(base::PortProvider* port_provider) + : port_provider_(port_provider) { + DCHECK(port_provider); + port_provider_->AddObserver(this); +} + +MachPortRelay::~MachPortRelay() { + port_provider_->RemoveObserver(this); +} + +bool MachPortRelay::SendPortsToProcess(Channel::Message* message, + base::ProcessHandle process) { + DCHECK(message); + mach_port_t task_port = port_provider_->TaskForPid(process); + if (task_port == MACH_PORT_NULL) + return false; + + size_t num_sent = 0; + bool error = false; + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + // Message should have handles, otherwise there's no point in calling this + // function. + DCHECK(handles); + for (size_t i = 0; i < handles->size(); i++) { + PlatformHandle* handle = &(*handles)[i]; + DCHECK(handle->type != PlatformHandle::Type::MACH_NAME); + if (handle->type != PlatformHandle::Type::MACH) + continue; + + mach_port_name_t intermediate_port; + DCHECK(handle->port != MACH_PORT_NULL); + intermediate_port = base::CreateIntermediateMachPort( + task_port, base::mac::ScopedMachSendRight(handle->port), nullptr); + if (intermediate_port == MACH_PORT_NULL) { + handle->port = MACH_PORT_NULL; + error = true; + break; + } + handle->port = intermediate_port; + handle->type = PlatformHandle::Type::MACH_NAME; + num_sent++; + } + DCHECK(error || num_sent); + message->SetHandles(std::move(handles)); + + return !error; +} + +bool MachPortRelay::ExtractPortRights(Channel::Message* message, + base::ProcessHandle process) { + DCHECK(message); + + mach_port_t task_port = port_provider_->TaskForPid(process); + if (task_port == MACH_PORT_NULL) + return false; + + size_t num_received = 0; + bool error = false; + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + // Message should have handles, otherwise there's no point in calling this + // function. + DCHECK(handles); + for (size_t i = 0; i < handles->size(); i++) { + PlatformHandle* handle = handles->data() + i; + DCHECK(handle->type != PlatformHandle::Type::MACH); + if (handle->type != PlatformHandle::Type::MACH_NAME) + continue; + + mach_port_t extracted_right = MACH_PORT_NULL; + mach_msg_type_name_t extracted_right_type; + kern_return_t kr = + mach_port_extract_right(task_port, handle->port, + MACH_MSG_TYPE_MOVE_SEND, + &extracted_right, &extracted_right_type); + if (kr != KERN_SUCCESS) { + error = true; + break; + } + + DCHECK_EQ(static_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND), + extracted_right_type); + handle->port = extracted_right; + handle->type = PlatformHandle::Type::MACH; + num_received++; + } + DCHECK(error || num_received); + message->SetHandles(std::move(handles)); + + return !error; +} + +void MachPortRelay::AddObserver(Observer* observer) { + base::AutoLock locker(observers_lock_); + bool inserted = observers_.insert(observer).second; + DCHECK(inserted); +} + +void MachPortRelay::RemoveObserver(Observer* observer) { + base::AutoLock locker(observers_lock_); + observers_.erase(observer); +} + +void MachPortRelay::OnReceivedTaskPort(base::ProcessHandle process) { + base::AutoLock locker(observers_lock_); + for (const auto observer : observers_) + observer->OnProcessReady(process); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/mach_port_relay.h b/mojo/edk/system/mach_port_relay.h new file mode 100644 index 0000000..87bc56c --- /dev/null +++ b/mojo/edk/system/mach_port_relay.h @@ -0,0 +1,94 @@ +// Copyright 2016 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 MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_ +#define MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_ + +#include <set> + +#include "base/macros.h" +#include "base/process/port_provider_mac.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/channel.h" + +namespace mojo { +namespace edk { + +// The MachPortRelay is used by a privileged process, usually the root process, +// to manipulate Mach ports in a child process. Ports can be added to and +// extracted from a child process that has registered itself with the +// |base::PortProvider| used by this class. +class MachPortRelay : public base::PortProvider::Observer { + public: + class Observer { + public: + // Called by the MachPortRelay to notify observers that a new process is + // ready for Mach ports to be sent/received. There are no guarantees about + // the thread this is called on, including the presence of a MessageLoop. + // Implementations must not call AddObserver() or RemoveObserver() during + // this function, as doing so will deadlock. + virtual void OnProcessReady(base::ProcessHandle process) = 0; + }; + + // Used by a child process to receive Mach ports from a sender (privileged) + // process. Each Mach port in |handles| is interpreted as an intermediate Mach + // port. It replaces each Mach port with the final Mach port received from the + // intermediate port. This method takes ownership of the intermediate Mach + // port and gives ownership of the final Mach port to the caller. Any handles + // that are not Mach ports will remain unchanged, and the number and ordering + // of handles is preserved. + // Returns |false| on failure and there is no guarantee about whether a Mach + // port is intermediate or final. + // + // See SendPortsToProcess() for the definition of intermediate and final Mach + // ports. + static bool ReceivePorts(PlatformHandleVector* handles); + + explicit MachPortRelay(base::PortProvider* port_provider); + ~MachPortRelay() override; + + // Sends the Mach ports attached to |message| to |process|. + // For each Mach port attached to |message|, a new Mach port, the intermediate + // port, is created in |process|. The message's Mach port is then sent over + // this intermediate port and the message is modified to refer to the name of + // the intermediate port. The Mach port received over the intermediate port in + // the child is referred to as the final Mach port. + // Returns |false| on failure and |message| may contain a mix of actual Mach + // ports and names. + bool SendPortsToProcess(Channel::Message* message, + base::ProcessHandle process); + + // Extracts the Mach ports attached to |message| from |process|. + // Any Mach ports attached to |message| are names and not actual Mach ports + // that are valid in this process. For each of those Mach port names, a send + // right is extracted from |process| and the port name is replaced with the + // send right. + // Returns |false| on failure and |message| may contain a mix of actual Mach + // ports and names. + bool ExtractPortRights(Channel::Message* message, + base::ProcessHandle process); + + // Observer interface. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + base::PortProvider* port_provider() const { return port_provider_; } + + private: + // base::PortProvider::Observer implementation. + void OnReceivedTaskPort(base::ProcessHandle process) override; + + base::PortProvider* const port_provider_; + + base::Lock observers_lock_; + std::set<Observer*> observers_; + + DISALLOW_COPY_AND_ASSIGN(MachPortRelay); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_ diff --git a/mojo/edk/system/node_channel.cc b/mojo/edk/system/node_channel.cc index 0e2ef52..7e76fe4 100644 --- a/mojo/edk/system/node_channel.cc +++ b/mojo/edk/system/node_channel.cc @@ -8,9 +8,15 @@ #include <limits> #include <sstream> +#include "base/bind.h" +#include "base/location.h" #include "base/logging.h" #include "mojo/edk/system/channel.h" +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "mojo/edk/system/mach_port_relay.h" +#endif + namespace mojo { namespace edk { @@ -32,7 +38,7 @@ enum class MessageType : uint32_t { REQUEST_PORT_MERGE, REQUEST_INTRODUCTION, INTRODUCE, -#if defined(OS_WIN) +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) RELAY_PORTS_MESSAGE, #endif }; @@ -98,7 +104,7 @@ struct IntroductionData { ports::NodeName name; }; -#if defined(OS_WIN) +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) // This struct is followed by the full payload of a message to be relayed. struct RelayPortsMessageData { ports::NodeName destination; @@ -117,7 +123,7 @@ Channel::MessagePtr CreateMessage(MessageType type, header->padding = 0; *out_data = reinterpret_cast<DataType*>(&header[1]); return message; -}; +} template <typename DataType> void GetMessagePayload(const void* bytes, DataType** out_data) { @@ -152,12 +158,24 @@ void NodeChannel::GetPortsMessageData(Channel::Message* message, } void NodeChannel::Start() { +#if defined(OS_MACOSX) && !defined(OS_IOS) + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (relay) + relay->AddObserver(this); +#endif + base::AutoLock lock(channel_lock_); DCHECK(channel_); channel_->Start(); } void NodeChannel::ShutDown() { +#if defined(OS_MACOSX) && !defined(OS_IOS) + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (relay) + relay->RemoveObserver(this); +#endif + base::AutoLock lock(channel_lock_); if (channel_) { channel_->ShutDown(); @@ -303,9 +321,10 @@ void NodeChannel::Introduce(const ports::NodeName& name, WriteChannelMessage(std::move(message)); } -#if defined(OS_WIN) +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) void NodeChannel::RelayPortsMessage(const ports::NodeName& destination, Channel::MessagePtr message) { +#if defined(OS_WIN) DCHECK(message->has_handles()); // Note that this is only used on Windows, and on Windows all platform @@ -325,9 +344,27 @@ void NodeChannel::RelayPortsMessage(const ports::NodeName& destination, ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); handles->clear(); +#else + DCHECK(message->has_mach_ports()); + + // On OSX, the handles are extracted from the relayed message and attached to + // the wrapper. The broker then takes the handles attached to the wrapper and + // moves them back to the relayed message. This is necessary because the + // message may contain fds which need to be attached to the outer message so + // that they can be transferred to the broker. + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + size_t num_bytes = sizeof(RelayPortsMessageData) + message->data_num_bytes(); + RelayPortsMessageData* data; + Channel::MessagePtr relay_message = CreateMessage( + MessageType::RELAY_PORTS_MESSAGE, num_bytes, handles->size(), &data); + data->destination = destination; + memcpy(data + 1, message->data(), message->data_num_bytes()); + relay_message->SetHandles(std::move(handles)); +#endif // defined(OS_WIN) + WriteChannelMessage(std::move(relay_message)); } -#endif +#endif // defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) NodeChannel::NodeChannel(Delegate* delegate, ScopedPlatformHandle platform_handle, @@ -361,7 +398,19 @@ void NodeChannel::OnChannelMessage(const void* payload, } } } -#endif +#elif defined(OS_MACOSX) && !defined(OS_IOS) + // If we're not the root, receive any mach ports from the message. If we're + // the root, the only message containing mach ports should be a + // RELAY_PORTS_MESSAGE. + { + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (handles && !relay) { + if (!MachPortRelay::ReceivePorts(handles.get())) { + LOG(ERROR) << "Error receiving mach ports."; + } + } + } +#endif // defined(OS_WIN) const Header* header = static_cast<const Header*>(payload); switch (header->type) { @@ -484,7 +533,7 @@ void NodeChannel::OnChannelMessage(const void* payload, break; } -#if defined(OS_WIN) +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) case MessageType::RELAY_PORTS_MESSAGE: { base::ProcessHandle from_process; { @@ -500,6 +549,24 @@ void NodeChannel::OnChannelMessage(const void* payload, DLOG(ERROR) << "Dropping invalid relay message."; break; } +#if defined(OS_MACOSX) && !defined(OS_IOS) + message->SetHandles(std::move(handles)); + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (!relay) { + LOG(ERROR) << "Receiving mach ports without a port relay from " + << remote_node_name_ << ". Dropping message."; + break; + } + { + base::AutoLock lock(pending_mach_messages_lock_); + if (relay->port_provider()->TaskForPid(from_process) == + MACH_PORT_NULL) { + pending_relay_messages_.push( + std::make_pair(data->destination, std::move(message))); + break; + } + } +#endif delegate_->OnRelayPortsMessage(remote_node_name_, from_process, data->destination, std::move(message)); break; @@ -526,6 +593,58 @@ void NodeChannel::OnChannelError() { delegate_->OnChannelError(node_name); } +#if defined(OS_MACOSX) && !defined(OS_IOS) +void NodeChannel::OnProcessReady(base::ProcessHandle process) { + io_task_runner_->PostTask(FROM_HERE, base::Bind( + &NodeChannel::ProcessPendingMessagesWithMachPorts, this)); +} + +void NodeChannel::ProcessPendingMessagesWithMachPorts() { + MachPortRelay* relay = delegate_->GetMachPortRelay(); + DCHECK(relay); + + base::ProcessHandle remote_process_handle; + { + base::AutoLock lock(remote_process_handle_lock_); + remote_process_handle = remote_process_handle_; + } + PendingMessageQueue pending_writes; + PendingRelayMessageQueue pending_relays; + { + base::AutoLock lock(pending_mach_messages_lock_); + pending_writes.swap(pending_write_messages_); + pending_relays.swap(pending_relay_messages_); + } + DCHECK(pending_writes.empty() && pending_relays.empty()); + + while (!pending_writes.empty()) { + Channel::MessagePtr message = std::move(pending_writes.front()); + pending_writes.pop(); + if (!relay->SendPortsToProcess(message.get(), remote_process_handle)) { + LOG(ERROR) << "Error on sending mach ports. Remote process is likely " + << "gone. Dropping message."; + return; + } + + base::AutoLock lock(channel_lock_); + if (!channel_) { + DLOG(ERROR) << "Dropping message on closed channel."; + break; + } else { + channel_->Write(std::move(message)); + } + } + + while (!pending_relays.empty()) { + ports::NodeName destination = pending_relays.front().first; + Channel::MessagePtr message = std::move(pending_relays.front().second); + pending_relays.pop(); + delegate_->OnRelayPortsMessage(remote_node_name_, remote_process_handle, + destination, std::move(message)); + } +} +#endif + void NodeChannel::WriteChannelMessage(Channel::MessagePtr message) { #if defined(OS_WIN) // Map handles to the destination process. Note: only messages from a @@ -550,6 +669,39 @@ void NodeChannel::WriteChannelMessage(Channel::MessagePtr message) { } } } +#elif defined(OS_MACOSX) && !defined(OS_IOS) + // On OSX, we need to transfer mach ports to the destination process before + // transferring the message itself. + if (message->has_mach_ports()) { + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (relay) { + base::ProcessHandle remote_process_handle; + { + base::AutoLock lock(remote_process_handle_lock_); + // Expect that the receiving node is a child. + DCHECK(remote_process_handle_ != base::kNullProcessHandle); + remote_process_handle = remote_process_handle_; + } + { + base::AutoLock lock(pending_mach_messages_lock_); + if (relay->port_provider()->TaskForPid(remote_process_handle) == + MACH_PORT_NULL) { + // It is also possible for TaskForPid() to return MACH_PORT_NULL when + // the process has started, then died. In that case, the queued + // message will never be processed. But that's fine since we're about + // to die anyway. + pending_write_messages_.push(std::move(message)); + return; + } + } + + if (!relay->SendPortsToProcess(message.get(), remote_process_handle)) { + LOG(ERROR) << "Error on sending mach ports. Remote process is likely " + << "gone. Dropping message."; + return; + } + } + } #endif base::AutoLock lock(channel_lock_); diff --git a/mojo/edk/system/node_channel.h b/mojo/edk/system/node_channel.h index 7dc1050..c8a97ca 100644 --- a/mojo/edk/system/node_channel.h +++ b/mojo/edk/system/node_channel.h @@ -5,7 +5,9 @@ #ifndef MOJO_EDK_SYSTEM_NODE_CHANNEL_H_ #define MOJO_EDK_SYSTEM_NODE_CHANNEL_H_ +#include <queue> #include <unordered_map> +#include <utility> #include "base/macros.h" #include "base/memory/ref_counted.h" @@ -18,12 +20,20 @@ #include "mojo/edk/system/channel.h" #include "mojo/edk/system/ports/name.h" +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "mojo/edk/system/mach_port_relay.h" +#endif + namespace mojo { namespace edk { // Wraps a Channel to send and receive Node control messages. class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>, - public Channel::Delegate { + public Channel::Delegate +#if defined(OS_MACOSX) && !defined(OS_IOS) + , public MachPortRelay::Observer +#endif + { public: class Delegate { public: @@ -52,7 +62,7 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>, virtual void OnIntroduce(const ports::NodeName& from_node, const ports::NodeName& name, ScopedPlatformHandle channel_handle) = 0; -#if defined(OS_WIN) +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) virtual void OnRelayPortsMessage(const ports::NodeName& from_node, base::ProcessHandle from_process, const ports::NodeName& destination, @@ -60,6 +70,10 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>, #endif virtual void OnChannelError(const ports::NodeName& node) = 0; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + virtual MachPortRelay* GetMachPortRelay() = 0; +#endif }; static scoped_refptr<NodeChannel> Create( @@ -106,7 +120,7 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>, void Introduce(const ports::NodeName& name, ScopedPlatformHandle channel_handle); -#if defined(OS_WIN) +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) // Relay the message to the specified node via this channel. This is used to // pass windows handles between two processes that do not have permission to // duplicate handles into the other's address space. The relay process is @@ -118,6 +132,10 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>, private: friend class base::RefCountedThreadSafe<NodeChannel>; + using PendingMessageQueue = std::queue<Channel::MessagePtr>; + using PendingRelayMessageQueue = + std::queue<std::pair<ports::NodeName, Channel::MessagePtr>>; + NodeChannel(Delegate* delegate, ScopedPlatformHandle platform_handle, scoped_refptr<base::TaskRunner> io_task_runner); @@ -129,6 +147,13 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>, ScopedPlatformHandleVectorPtr handles) override; void OnChannelError() override; +#if defined(OS_MACOSX) && !defined(OS_IOS) + // MachPortRelay::Observer: + void OnProcessReady(base::ProcessHandle process) override; + + void ProcessPendingMessagesWithMachPorts(); +#endif + void WriteChannelMessage(Channel::MessagePtr message); Delegate* const delegate_; @@ -143,6 +168,12 @@ class NodeChannel : public base::RefCountedThreadSafe<NodeChannel>, base::Lock remote_process_handle_lock_; base::ProcessHandle remote_process_handle_ = base::kNullProcessHandle; +#if defined(OS_MACOSX) && !defined(OS_IOS) + base::Lock pending_mach_messages_lock_; + PendingMessageQueue pending_write_messages_; + PendingRelayMessageQueue pending_relay_messages_; +#endif + DISALLOW_COPY_AND_ASSIGN(NodeChannel); }; diff --git a/mojo/edk/system/node_controller.cc b/mojo/edk/system/node_controller.cc index 1472f03..3733c6c 100644 --- a/mojo/edk/system/node_controller.cc +++ b/mojo/edk/system/node_controller.cc @@ -22,6 +22,10 @@ #include "mojo/edk/system/core.h" #include "mojo/edk/system/ports_message.h" +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "mojo/edk/system/mach_port_relay.h" +#endif + namespace mojo { namespace edk { @@ -107,6 +111,15 @@ NodeController::NodeController(Core* core) DVLOG(1) << "Initializing node " << name_; } +#if defined(OS_MACOSX) && !defined(OS_IOS) +void NodeController::CreateMachPortRelay( + base::PortProvider* port_provider) { + base::AutoLock lock(mach_port_relay_lock_); + DCHECK(!mach_port_relay_); + mach_port_relay_.reset(new MachPortRelay(port_provider)); +} +#endif + void NodeController::SetIOTaskRunner( scoped_refptr<base::TaskRunner> task_runner) { io_task_runner_ = task_runner; @@ -278,14 +291,16 @@ void NodeController::ConnectToParentOnIOThread( ScopedPlatformHandle platform_handle) { DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); - base::AutoLock lock(parent_lock_); - DCHECK(parent_name_ == ports::kInvalidNodeName); + { + base::AutoLock lock(parent_lock_); + DCHECK(parent_name_ == ports::kInvalidNodeName); - // At this point we don't know the parent's name, so we can't yet insert it - // into our |peers_| map. That will happen as soon as we receive an - // AcceptChild message from them. - bootstrap_parent_channel_ = - NodeChannel::Create(this, std::move(platform_handle), io_task_runner_); + // At this point we don't know the parent's name, so we can't yet insert it + // into our |peers_| map. That will happen as soon as we receive an + // AcceptChild message from them. + bootstrap_parent_channel_ = + NodeChannel::Create(this, std::move(platform_handle), io_task_runner_); + } bootstrap_parent_channel_->Start(); } @@ -406,7 +421,28 @@ void NodeController::SendPeerMessage(const ports::NodeName& name, return; } } -#endif +#elif defined(OS_MACOSX) && !defined(OS_IOS) + if (channel_message->has_mach_ports()) { + // Messages containing Mach ports are always routed through the broker, even + // if the broker process is the intended recipient. + bool use_broker = false; + { + base::AutoLock lock(parent_lock_); + use_broker = (bootstrap_parent_channel_ || + parent_name_ != ports::kInvalidNodeName); + } + if (use_broker) { + scoped_refptr<NodeChannel> broker = GetBrokerChannel(); + if (broker) { + broker->RelayPortsMessage(name, std::move(channel_message)); + } else { + base::AutoLock lock(broker_lock_); + pending_relay_messages_[name].emplace(std::move(channel_message)); + } + return; + } + } +#endif // defined(OS_WIN) if (peer) { peer->PortsMessage(std::move(channel_message)); @@ -659,7 +695,7 @@ void NodeController::OnBrokerClientAdded(const ports::NodeName& from_node, } // This should have come from our own broker. - if(GetBrokerChannel() != GetPeerChannel(from_node)) { + if (GetBrokerChannel() != GetPeerChannel(from_node)) { DLOG(ERROR) << "BrokerClientAdded from non-broker node " << from_node; return; } @@ -727,7 +763,7 @@ void NodeController::OnAcceptBrokerClient(const ports::NodeName& from_node, pending_broker_clients.pop(); } -#if defined(OS_WIN) +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) // Have the broker relay any messages we have waiting. for (auto& entry : pending_relay_messages) { const ports::NodeName& destination = entry.first; @@ -836,11 +872,13 @@ void NodeController::OnIntroduce(const ports::NodeName& from_node, AddPeer(name, channel, true /* start_channel */); } -#if defined(OS_WIN) +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) void NodeController::OnRelayPortsMessage(const ports::NodeName& from_node, base::ProcessHandle from_process, const ports::NodeName& destination, Channel::MessagePtr message) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + if (GetBrokerChannel()) { // Only the broker should be asked to relay a message. LOG(ERROR) << "Non-broker refusing to relay message."; @@ -851,6 +889,7 @@ void NodeController::OnRelayPortsMessage(const ports::NodeName& from_node, // The parent should always know which process this came from. DCHECK(from_process != base::kNullProcessHandle); +#if defined(OS_WIN) // Rewrite the handles to this (the parent) process. If the message is // destined for another child process, the handles will be rewritten to that // process before going out (see NodeChannel::WriteChannelMessage). @@ -862,6 +901,22 @@ void NodeController::OnRelayPortsMessage(const ports::NodeName& from_node, message->num_handles())) { DLOG(ERROR) << "Failed to relay one or more handles."; } +#else + MachPortRelay* relay = GetMachPortRelay(); + if (!relay) { + LOG(ERROR) << "Receiving Mach ports without a port relay from " + << from_node << ". Dropping message."; + return; + } + if (!relay->ExtractPortRights(message.get(), from_process)) { + // NodeChannel should ensure that MachPortRelay is ready for the remote + // process. At this point, if the port extraction failed, either something + // went wrong in the mach stuff, or the remote process died. + LOG(ERROR) << "Error on receiving Mach ports " << from_node + << ". Dropping message."; + return; + } +#endif // defined(OS_WIN) if (destination == name_) { // Great, we can deliver this message locally. @@ -888,6 +943,20 @@ void NodeController::OnChannelError(const ports::NodeName& from_node) { } } +#if defined(OS_MACOSX) && !defined(OS_IOS) +MachPortRelay* NodeController::GetMachPortRelay() { + { + base::AutoLock lock(parent_lock_); + // Return null if we're not the root. + if (bootstrap_parent_channel_ || parent_name_ != ports::kInvalidNodeName) + return nullptr; + } + + base::AutoLock lock(mach_port_relay_lock_); + return mach_port_relay_.get(); +} +#endif + void NodeController::DestroyOnIOThreadShutdown() { destroy_on_io_thread_shutdown_ = true; } diff --git a/mojo/edk/system/node_controller.h b/mojo/edk/system/node_controller.h index 75d2892..34c2644 100644 --- a/mojo/edk/system/node_controller.h +++ b/mojo/edk/system/node_controller.h @@ -25,11 +25,16 @@ #include "mojo/edk/system/ports/node.h" #include "mojo/edk/system/ports/node_delegate.h" +namespace base { +class PortProvider; +} + namespace mojo { namespace edk { class Broker; class Core; +class MachPortRelay; class PortsMessage; // The owner of ports::Node which facilitates core EDK implementation. All @@ -56,6 +61,11 @@ class NodeController : public ports::NodeDelegate, return io_task_runner_; } +#if defined(OS_MACOSX) && !defined(OS_IOS) + // Create the relay used to transfer mach ports between processes. + void CreateMachPortRelay(base::PortProvider* port_provider); +#endif + // Called exactly once, shortly after construction, and before any other // methods are called on this object. void SetIOTaskRunner(scoped_refptr<base::TaskRunner> io_task_runner); @@ -162,13 +172,16 @@ class NodeController : public ports::NodeDelegate, void OnIntroduce(const ports::NodeName& from_node, const ports::NodeName& name, ScopedPlatformHandle channel_handle) override; -#if defined(OS_WIN) +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) void OnRelayPortsMessage(const ports::NodeName& from_node, base::ProcessHandle from_process, const ports::NodeName& destination, Channel::MessagePtr message) override; #endif void OnChannelError(const ports::NodeName& from_node) override; +#if defined(OS_MACOSX) && !defined(OS_IOS) + MachPortRelay* GetMachPortRelay() override; +#endif // Marks this NodeController for destruction when the IO thread shuts down. // This is used in case Core is torn down before the IO thread. Must only be @@ -258,6 +271,12 @@ class NodeController : public ports::NodeDelegate, scoped_ptr<Broker> broker_; #endif +#if defined(OS_MACOSX) && !defined(OS_IOS) + base::Lock mach_port_relay_lock_; + // Relay for transferring mach ports to/from children. + scoped_ptr<MachPortRelay> mach_port_relay_; +#endif + DISALLOW_COPY_AND_ASSIGN(NodeController); }; diff --git a/mojo/edk/test/mojo_test_base.cc b/mojo/edk/test/mojo_test_base.cc index 7ec067a..4b044f6 100644 --- a/mojo/edk/test/mojo_test_base.cc +++ b/mojo/edk/test/mojo_test_base.cc @@ -13,11 +13,29 @@ #include "mojo/public/c/system/functions.h" #include "testing/gtest/include/gtest/gtest.h" +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "base/mac/mach_port_broker.h" +#endif + namespace mojo { namespace edk { namespace test { + +#if defined(OS_MACOSX) && !defined(OS_IOS) +namespace { +base::MachPortBroker* g_mach_broker = nullptr; +} +#endif + MojoTestBase::MojoTestBase() { +#if defined(OS_MACOSX) && !defined(OS_IOS) + if (!g_mach_broker) { + g_mach_broker = new base::MachPortBroker("mojo_test"); + CHECK(g_mach_broker->Init()); + SetMachPortProvider(g_mach_broker); + } +#endif } MojoTestBase::~MojoTestBase() {} @@ -31,12 +49,22 @@ MojoTestBase::ClientController& MojoTestBase::StartClient( MojoTestBase::ClientController::ClientController(const std::string& client_name, MojoTestBase* test) - : test_(test) + : test_(test) { #if !defined(OS_IOS) - , - pipe_(helper_.StartChild(client_name)) +#if defined(OS_MACOSX) + // This lock needs to be held while launching the child because the Mach port + // broker only allows task ports to be received from known child processes. + // However, it can only know the child process's pid after the child has + // launched. To prevent a race where the child process sends its task port + // before the pid has been registered, the lock needs to be held over both + // launch and child pid registration. + base::AutoLock lock(g_mach_broker->GetLock()); +#endif + pipe_ = helper_.StartChild(client_name); +#if defined(OS_MACOSX) + g_mach_broker->AddPlaceholderForPid(helper_.test_child().Handle()); +#endif #endif -{ } MojoTestBase::ClientController::~ClientController() { @@ -47,7 +75,12 @@ MojoTestBase::ClientController::~ClientController() { int MojoTestBase::ClientController::WaitForShutdown() { was_shutdown_ = true; #if !defined(OS_IOS) - return helper_.WaitForChildShutdown(); + int retval = helper_.WaitForChildShutdown(); +#if defined(OS_MACOSX) + base::AutoLock lock(g_mach_broker->GetLock()); + g_mach_broker->InvalidatePid(helper_.test_child().Handle()); +#endif + return retval; #else NOTREACHED(); return 1; diff --git a/mojo/edk/test/multiprocess_test_helper.cc b/mojo/edk/test/multiprocess_test_helper.cc index efc4e6b..7098934 100644 --- a/mojo/edk/test/multiprocess_test_helper.cc +++ b/mojo/edk/test/multiprocess_test_helper.cc @@ -27,6 +27,8 @@ #if defined(OS_WIN) #include "base/win/windows_version.h" +#elif defined(OS_MACOSX) && !defined(OS_IOS) +#include "base/mac/mach_port_broker.h" #endif namespace mojo { @@ -148,6 +150,10 @@ void MultiprocessTestHelper::ChildSetup() { ->GetSwitchValueASCII(kMojoPrimordialPipeToken); CHECK(!primordial_pipe_token.empty()); +#if defined(OS_MACOSX) && !defined(OS_IOS) + CHECK(base::MachPortBroker::ChildSendTaskPortToParent("mojo_test")); +#endif + SetParentPipeHandle( PlatformChannelPair::PassClientHandleFromParentProcess( *base::CommandLine::ForCurrentProcess())); diff --git a/mojo/edk/test/multiprocess_test_helper.h b/mojo/edk/test/multiprocess_test_helper.h index 7ee6024..b3bed6b 100644 --- a/mojo/edk/test/multiprocess_test_helper.h +++ b/mojo/edk/test/multiprocess_test_helper.h @@ -55,6 +55,8 @@ class MultiprocessTestHelper { // |EXPECT_TRUE(WaitForChildTestShutdown());|. bool WaitForChildTestShutdown(); + const base::Process& test_child() const { return test_child_; } + // Used by macros in mojo/edk/test/mojo_test_base.h to support multiprocess // test client initialization. static void ChildSetup(); diff --git a/mojo/mojo_edk.gyp b/mojo/mojo_edk.gyp index f95cde4..db57fc5 100644 --- a/mojo/mojo_edk.gyp +++ b/mojo/mojo_edk.gyp @@ -146,6 +146,12 @@ # uninteresting. 'msvs_disabled_warnings': [ 4324 ], }], + ['OS=="mac" and OS!="ios"', { + 'sources': [ + 'edk/system/mach_port_relay.cc', + 'edk/system/mach_port_relay.h', + ], + }], ], }, { |