diff options
author | erikchen <erikchen@chromium.org> | 2015-10-06 12:59:39 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-10-06 20:00:21 +0000 |
commit | b3481e2f5f20d01c55c9f6d5f5b8b65d1d42c223 (patch) | |
tree | 6d61aba7d7d8f8e3fcff50bf1f82a9797db97635 | |
parent | 8fd6c67f04a47afdd66ad4a6ca5f23fa5a1f04a7 (diff) | |
download | chromium_src-b3481e2f5f20d01c55c9f6d5f5b8b65d1d42c223.zip chromium_src-b3481e2f5f20d01c55c9f6d5f5b8b65d1d42c223.tar.gz chromium_src-b3481e2f5f20d01c55c9f6d5f5b8b65d1d42c223.tar.bz2 |
ipc: Write privileged and unprivileged attachment brokers for Mac.
This is in preparation for brokering mach ports on Mac.
BUG=535711
Review URL: https://codereview.chromium.org/1376473005
Cr-Commit-Position: refs/heads/master@{#352672}
-rw-r--r-- | ipc/BUILD.gn | 5 | ||||
-rw-r--r-- | ipc/attachment_broker_messages.h | 31 | ||||
-rw-r--r-- | ipc/attachment_broker_privileged_mac.cc | 275 | ||||
-rw-r--r-- | ipc/attachment_broker_privileged_mac.h | 87 | ||||
-rw-r--r-- | ipc/attachment_broker_privileged_mac_unittest.cc | 476 | ||||
-rw-r--r-- | ipc/attachment_broker_unprivileged_mac.cc | 100 | ||||
-rw-r--r-- | ipc/attachment_broker_unprivileged_mac.h | 41 | ||||
-rw-r--r-- | ipc/ipc.gyp | 1 | ||||
-rw-r--r-- | ipc/ipc.gypi | 4 |
9 files changed, 1020 insertions, 0 deletions
diff --git a/ipc/BUILD.gn b/ipc/BUILD.gn index 4327bd1..ae64490 100644 --- a/ipc/BUILD.gn +++ b/ipc/BUILD.gn @@ -11,10 +11,14 @@ component("ipc") { "attachment_broker_messages.h", "attachment_broker_privileged.cc", "attachment_broker_privileged.h", + "attachment_broker_privileged_mac.cc", + "attachment_broker_privileged_mac.h", "attachment_broker_privileged_win.cc", "attachment_broker_privileged_win.h", "attachment_broker_unprivileged.cc", "attachment_broker_unprivileged.h", + "attachment_broker_unprivileged_mac.cc", + "attachment_broker_unprivileged_mac.h", "attachment_broker_unprivileged_win.cc", "attachment_broker_unprivileged_win.h", "brokerable_attachment.cc", @@ -138,6 +142,7 @@ if (!is_android) { test("ipc_tests") { sources = [ + "attachment_broker_privileged_mac_unittest.cc", "attachment_broker_privileged_win_unittest.cc", "attachment_broker_unprivileged_win_unittest.cc", "ipc_channel_posix_unittest.cc", diff --git a/ipc/attachment_broker_messages.h b/ipc/attachment_broker_messages.h index f0e103d..a6301c2 100644 --- a/ipc/attachment_broker_messages.h +++ b/ipc/attachment_broker_messages.h @@ -14,6 +14,10 @@ #include "ipc/handle_attachment_win.h" #endif // defined(OS_WIN) +#if defined(OS_MACOSX) +#include "ipc/mach_port_attachment_mac.h" +#endif // defined(OS_MACOSX) + // ---------------------------------------------------------------------------- // Serialization of structs. // ---------------------------------------------------------------------------- @@ -30,6 +34,14 @@ IPC_STRUCT_TRAITS_BEGIN(IPC::internal::HandleAttachmentWin::WireFormat) IPC_STRUCT_TRAITS_END() #endif // defined(OS_WIN) +#if defined(OS_MACOSX) +IPC_STRUCT_TRAITS_BEGIN(IPC::internal::MachPortAttachmentMac::WireFormat) + IPC_STRUCT_TRAITS_MEMBER(mach_port) + IPC_STRUCT_TRAITS_MEMBER(destination_process) + IPC_STRUCT_TRAITS_MEMBER(attachment_id) +IPC_STRUCT_TRAITS_END() +#endif // defined(OS_MACOSX) + #undef IPC_MESSAGE_EXPORT #define IPC_MESSAGE_EXPORT IPC_EXPORT #define IPC_MESSAGE_START AttachmentBrokerMsgStart @@ -47,6 +59,16 @@ IPC_MESSAGE_CONTROL1( IPC::internal::HandleAttachmentWin::WireFormat /* wire_format */) #endif // defined(OS_WIN) +#if defined(OS_MACOSX) +// Sent from a broker process to a non-broker process to indicate that an OSX +// Mach port has been duplicated. Contains all information necessary for the +// non-broker process to translate a BrokerAttachment::AttachmentId to a +// BrokerAttachment. +IPC_MESSAGE_CONTROL1( + AttachmentBrokerMsg_MachPortHasBeenDuplicated, + IPC::internal::MachPortAttachmentMac::WireFormat /* wire_format */) +#endif // defined(OS_MACOSX) + // ---------------------------------------------------------------------------- // Messages sent from a non-broker process to a broker process. // ---------------------------------------------------------------------------- @@ -59,3 +81,12 @@ IPC_MESSAGE_CONTROL1( AttachmentBrokerMsg_DuplicateWinHandle, IPC::internal::HandleAttachmentWin::WireFormat /* wire_format */) #endif // defined(OS_WIN) + +#if defined(OS_MACOSX) +// Sent from a non-broker process to a broker process to request the duplication +// of a Mach port into a different process (possibly the broker process, or even +// the original process). +IPC_MESSAGE_CONTROL1( + AttachmentBrokerMsg_DuplicateMachPort, + IPC::internal::MachPortAttachmentMac::WireFormat /* wire_format */) +#endif // defined(OS_MACOSX) diff --git a/ipc/attachment_broker_privileged_mac.cc b/ipc/attachment_broker_privileged_mac.cc new file mode 100644 index 0000000..7399326 --- /dev/null +++ b/ipc/attachment_broker_privileged_mac.cc @@ -0,0 +1,275 @@ +// Copyright 2015 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 "ipc/attachment_broker_privileged_mac.h" + +#include "base/mac/scoped_mach_port.h" +#include "base/memory/shared_memory.h" +#include "base/process/process.h" +#include "ipc/attachment_broker_messages.h" +#include "ipc/brokerable_attachment.h" +#include "ipc/ipc_channel.h" +#include "ipc/mach_port_attachment_mac.h" + +namespace { + +// Struct for sending a complex Mach message. +struct MachSendComplexMessage { + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_port_descriptor_t data; +}; + +// Sends a Mach port to |endpoint|. Assumes that |endpoint| is a send once +// right. Takes ownership of |endpoint|. +kern_return_t SendMachPort(mach_port_t endpoint, + mach_port_t port_to_send, + int disposition) { + MachSendComplexMessage send_msg; + send_msg.header.msgh_bits = + MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0) | MACH_MSGH_BITS_COMPLEX; + send_msg.header.msgh_size = sizeof(send_msg); + send_msg.header.msgh_remote_port = endpoint; + send_msg.header.msgh_local_port = MACH_PORT_NULL; + send_msg.header.msgh_reserved = 0; + send_msg.header.msgh_id = 0; + send_msg.body.msgh_descriptor_count = 1; + send_msg.data.name = port_to_send; + send_msg.data.disposition = disposition; + send_msg.data.type = MACH_MSG_PORT_DESCRIPTOR; + + return mach_msg(&send_msg.header, + MACH_SEND_MSG | MACH_SEND_TIMEOUT, + send_msg.header.msgh_size, + 0, // receive limit + MACH_PORT_NULL, // receive name + 0, // timeout + MACH_PORT_NULL); // notification port +} + +} // namespace + +namespace IPC { + +AttachmentBrokerPrivilegedMac::AttachmentBrokerPrivilegedMac() + : port_provider_(nullptr) {} + +AttachmentBrokerPrivilegedMac::~AttachmentBrokerPrivilegedMac() {} + +void AttachmentBrokerPrivilegedMac::SetPortProvider( + base::PortProvider* port_provider) { + CHECK(!port_provider_); + port_provider_ = port_provider; +} + +bool AttachmentBrokerPrivilegedMac::SendAttachmentToProcess( + const BrokerableAttachment* attachment, + base::ProcessId destination_process) { + switch (attachment->GetBrokerableType()) { + case BrokerableAttachment::MACH_PORT: { + const internal::MachPortAttachmentMac* mach_port_attachment = + static_cast<const internal::MachPortAttachmentMac*>(attachment); + MachPortWireFormat wire_format = + mach_port_attachment->GetWireFormat(destination_process); + MachPortWireFormat new_wire_format = + DuplicateMachPort(wire_format, base::Process::Current().Pid()); + if (new_wire_format.mach_port == 0) + return false; + RouteDuplicatedMachPort(new_wire_format); + return true; + } + default: + NOTREACHED(); + return false; + } + return false; +} + +bool AttachmentBrokerPrivilegedMac::OnMessageReceived(const Message& msg) { + bool handled = true; + switch (msg.type()) { + IPC_MESSAGE_HANDLER_GENERIC(AttachmentBrokerMsg_DuplicateMachPort, + OnDuplicateMachPort(msg)) + IPC_MESSAGE_UNHANDLED(handled = false) + } + return handled; +} + +void AttachmentBrokerPrivilegedMac::OnDuplicateMachPort( + const IPC::Message& message) { + AttachmentBrokerMsg_DuplicateMachPort::Param param; + if (!AttachmentBrokerMsg_DuplicateMachPort::Read(&message, ¶m)) + return; + IPC::internal::MachPortAttachmentMac::WireFormat wire_format = + base::get<0>(param); + + if (wire_format.destination_process == base::kNullProcessId) { + LogError(NO_DESTINATION); + return; + } + + MachPortWireFormat new_wire_format = + DuplicateMachPort(wire_format, message.get_sender_pid()); + RouteDuplicatedMachPort(new_wire_format); +} + +void AttachmentBrokerPrivilegedMac::RouteDuplicatedMachPort( + const MachPortWireFormat& wire_format) { + // This process is the destination. + if (wire_format.destination_process == base::Process::Current().Pid()) { + scoped_refptr<BrokerableAttachment> attachment( + new internal::MachPortAttachmentMac(wire_format)); + HandleReceivedAttachment(attachment); + return; + } + + // Another process is the destination. + base::ProcessId dest = wire_format.destination_process; + Sender* sender = GetSenderWithProcessId(dest); + if (!sender) { + // Assuming that this message was not sent from a malicious process, the + // channel endpoint that would have received this message will block + // forever. + LOG(ERROR) << "Failed to deliver brokerable attachment to process with id: " + << dest; + LogError(DESTINATION_NOT_FOUND); + return; + } + + LogError(DESTINATION_FOUND); + sender->Send(new AttachmentBrokerMsg_MachPortHasBeenDuplicated(wire_format)); +} + +AttachmentBrokerPrivilegedMac::MachPortWireFormat +AttachmentBrokerPrivilegedMac::DuplicateMachPort( + const MachPortWireFormat& wire_format, + base::ProcessId source_pid) { + // If the source is the destination, just increment the ref count. + if (source_pid == wire_format.destination_process) { + mach_port_t task_port = + port_provider_->TaskForPid(wire_format.destination_process); + kern_return_t kr = mach_port_mod_refs(task_port, wire_format.mach_port, + MACH_PORT_RIGHT_SEND, 1); + if (kr != KERN_SUCCESS) { + // TODO(erikchen): UMA metric. + return CopyWireFormat(wire_format, MACH_PORT_NULL); + } + return wire_format; + } + + // Acquire a send right to the memory object. + base::mac::ScopedMachSendRight memory_object( + AcquireSendRight(source_pid, wire_format.mach_port)); + if (!memory_object) + return CopyWireFormat(wire_format, MACH_PORT_NULL); + + mach_port_t task_port = + port_provider_->TaskForPid(wire_format.destination_process); + mach_port_name_t inserted_memory_object = + InsertIndirectMachPort(task_port, memory_object); + return CopyWireFormat(wire_format, inserted_memory_object); +} + +mach_port_name_t AttachmentBrokerPrivilegedMac::InsertIndirectMachPort( + mach_port_t task_port, + mach_port_t port_to_insert) { + DCHECK_NE(mach_task_self(), task_port); + + // Make a port with receive rights in the destination task. + mach_port_name_t endpoint; + kern_return_t kr = + mach_port_allocate(task_port, MACH_PORT_RIGHT_RECEIVE, &endpoint); + if (kr != KERN_SUCCESS) { + // TODO(erikchen): UMA metric. + return MACH_PORT_NULL; + } + + // Change its message queue limit so that it accepts one message. + mach_port_limits limits = {}; + limits.mpl_qlimit = 1; + kr = mach_port_set_attributes(task_port, endpoint, MACH_PORT_LIMITS_INFO, + reinterpret_cast<mach_port_info_t>(&limits), + MACH_PORT_LIMITS_INFO_COUNT); + if (kr != KERN_SUCCESS) { + // TODO(erikchen): UMA metric. + mach_port_deallocate(task_port, endpoint); + return MACH_PORT_NULL; + } + + // Get a send right. + mach_port_t send_once_right; + mach_msg_type_name_t send_right_type; + kr = + mach_port_extract_right(task_port, endpoint, MACH_MSG_TYPE_MAKE_SEND_ONCE, + &send_once_right, &send_right_type); + if (kr != KERN_SUCCESS) { + // TODO(erikchen): UMA metric. + mach_port_deallocate(task_port, endpoint); + return MACH_PORT_NULL; + } + DCHECK_EQ(static_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND_ONCE), + send_right_type); + + // This call takes ownership of |send_once_right|. + kr = SendMachPort(send_once_right, port_to_insert, MACH_MSG_TYPE_COPY_SEND); + if (kr != KERN_SUCCESS) { + // TODO(erikchen): UMA metric. + mach_port_deallocate(task_port, endpoint); + return MACH_PORT_NULL; + } + + // Endpoint is intentionally leaked into the destination task. An IPC must be + // sent to the destination task so that it can clean up this port. + return endpoint; +} + +base::mac::ScopedMachSendRight AttachmentBrokerPrivilegedMac::AcquireSendRight( + base::ProcessId pid, + mach_port_name_t named_right) { + if (pid == base::GetCurrentProcId()) { + kern_return_t kr = mach_port_mod_refs(mach_task_self(), named_right, + MACH_PORT_RIGHT_SEND, 1); + if (kr != KERN_SUCCESS) + return base::mac::ScopedMachSendRight(MACH_PORT_NULL); + return base::mac::ScopedMachSendRight(named_right); + } + + mach_port_t task_port = port_provider_->TaskForPid(pid); + return ExtractNamedRight(task_port, named_right); +} + +base::mac::ScopedMachSendRight AttachmentBrokerPrivilegedMac::ExtractNamedRight( + mach_port_t task_port, + mach_port_name_t named_right) { + 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, named_right, MACH_MSG_TYPE_COPY_SEND, + &extracted_right, &extracted_right_type); + if (kr != KERN_SUCCESS) + return base::mac::ScopedMachSendRight(MACH_PORT_NULL); + + DCHECK_EQ(static_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_PORT_SEND), + extracted_right_type); + + // Decrement the reference count of the send right from the source process. + kr = mach_port_mod_refs(task_port, named_right, MACH_PORT_RIGHT_SEND, -1); + if (kr != KERN_SUCCESS) { + // TODO(erikchen): UMA metric. + // Failure does not actually affect attachment brokering, so there's no need + // to return |MACH_PORT_NULL|. + } + + return base::mac::ScopedMachSendRight(extracted_right); +} + +AttachmentBrokerPrivilegedMac::MachPortWireFormat +AttachmentBrokerPrivilegedMac::CopyWireFormat( + const MachPortWireFormat& wire_format, + uint32_t mach_port) { + return MachPortWireFormat(mach_port, wire_format.destination_process, + wire_format.attachment_id); +} + +} // namespace IPC diff --git a/ipc/attachment_broker_privileged_mac.h b/ipc/attachment_broker_privileged_mac.h new file mode 100644 index 0000000..24c8b0c --- /dev/null +++ b/ipc/attachment_broker_privileged_mac.h @@ -0,0 +1,87 @@ +// Copyright 2015 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 IPC_ATTACHMENT_BROKER_PRIVILEGED_MAC_H_ +#define IPC_ATTACHMENT_BROKER_PRIVILEGED_MAC_H_ + +#include <mach/mach.h> + +#include "base/gtest_prod_util.h" +#include "base/mac/scoped_mach_port.h" +#include "base/process/port_provider_mac.h" +#include "ipc/attachment_broker_privileged.h" +#include "ipc/ipc_export.h" +#include "ipc/mach_port_attachment_mac.h" + +namespace IPC { + +// This class is a concrete subclass of AttachmentBrokerPrivileged for the +// OSX platform. +class IPC_EXPORT AttachmentBrokerPrivilegedMac + : public AttachmentBrokerPrivileged { + public: + AttachmentBrokerPrivilegedMac(); + ~AttachmentBrokerPrivilegedMac() override; + + // The port provider must live as long as the AttachmentBrokerPrivilegedMac. A + // port provider must be set before any attachment brokering occurs. + void SetPortProvider(base::PortProvider* port_provider); + + // IPC::AttachmentBroker overrides. + bool SendAttachmentToProcess(const BrokerableAttachment* attachment, + base::ProcessId destination_process) override; + + // IPC::Listener overrides. + bool OnMessageReceived(const Message& message) override; + + private: + FRIEND_TEST_ALL_PREFIXES(AttachmentBrokerPrivilegedMacMultiProcessTest, + InsertRight); + FRIEND_TEST_ALL_PREFIXES(AttachmentBrokerPrivilegedMacMultiProcessTest, + InsertSameRightTwice); + FRIEND_TEST_ALL_PREFIXES(AttachmentBrokerPrivilegedMacMultiProcessTest, + InsertTwoRights); + using MachPortWireFormat = internal::MachPortAttachmentMac::WireFormat; + // IPC message handlers. + void OnDuplicateMachPort(const Message& message); + + // Duplicates the Mach port referenced from |wire_format| from + // |source_process| into |wire_format|'s destination process. + MachPortWireFormat DuplicateMachPort(const MachPortWireFormat& wire_format, + base::ProcessId source_process); + + // Returns the name of the inserted right of a port, which contains a queued + // message with |port_to_insert|. Returns |MACH_PORT_NULL| on failure. + // |task_port| must be for a different task, and |port_to_insert| is a port + // right in the current task. + mach_port_name_t InsertIndirectMachPort(mach_port_t task_port, + mach_port_t port_to_insert); + + // Acquire a send right to a named right in |pid|. + // Returns MACH_PORT_NULL on error. + base::mac::ScopedMachSendRight AcquireSendRight(base::ProcessId pid, + mach_port_name_t named_right); + + // Extracts a copy of the send right to |named_right| from |task_port|. + // Returns MACH_PORT_NULL on error. + base::mac::ScopedMachSendRight ExtractNamedRight( + mach_port_t task_port, + mach_port_name_t named_right); + + // Copies an existing |wire_format|, but substitutes in a different mach port. + MachPortWireFormat CopyWireFormat(const MachPortWireFormat& wire_format, + uint32_t mach_port); + + // If the mach port's destination is this process, queue it and notify the + // observers. Otherwise, send it in an IPC to its destination. + void RouteDuplicatedMachPort(const MachPortWireFormat& wire_format); + + base::PortProvider* port_provider_; + + DISALLOW_COPY_AND_ASSIGN(AttachmentBrokerPrivilegedMac); +}; + +} // namespace IPC + +#endif // IPC_ATTACHMENT_BROKER_PRIVILEGED_MAC_H_ diff --git a/ipc/attachment_broker_privileged_mac_unittest.cc b/ipc/attachment_broker_privileged_mac_unittest.cc new file mode 100644 index 0000000..b6992aa --- /dev/null +++ b/ipc/attachment_broker_privileged_mac_unittest.cc @@ -0,0 +1,476 @@ +// Copyright 2015 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 "ipc/attachment_broker_privileged_mac.h" + +#include <mach/mach.h> +#include <mach/mach_vm.h> +#include <servers/bootstrap.h> + +#include <map> + +#include "base/command_line.h" +#include "base/mac/mach_logging.h" +#include "base/mac/scoped_mach_port.h" +#include "base/memory/shared_memory.h" +#include "base/process/port_provider_mac.h" +#include "base/process/process_handle.h" +#include "base/rand_util.h" +#include "base/strings/stringprintf.h" +#include "base/sys_info.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "testing/multiprocess_func_list.h" + +namespace IPC { + +namespace { + +static const std::string g_service_switch_name = "service_name"; + +// Structs used to pass a mach port from client to server. +struct MachSendPortMessage { + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_port_descriptor_t data; +}; +struct MachReceivePortMessage : public MachSendPortMessage { + mach_msg_trailer_t trailer; +}; + +// Makes the current process into a Mach Server with the given |service_name|. +base::mac::ScopedMachSendRight BecomeMachServer(const char* service_name) { + mach_port_t port; + kern_return_t kr = bootstrap_check_in(bootstrap_port, service_name, &port); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "BecomeMachServer"; + return base::mac::ScopedMachSendRight(port); +} + +// Returns the mach port for the Mach Server with the given |service_name|. +base::mac::ScopedMachSendRight LookupServer(const char* service_name) { + mach_port_t server_port; + kern_return_t kr = + bootstrap_look_up(bootstrap_port, service_name, &server_port); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "LookupServer"; + return base::mac::ScopedMachSendRight(server_port); +} + +base::mac::ScopedMachReceiveRight MakeReceivingPort() { + mach_port_t client_port; + kern_return_t kr = + mach_port_allocate(mach_task_self(), // our task is acquiring + MACH_PORT_RIGHT_RECEIVE, // a new receive right + &client_port); // with this name + MACH_CHECK(kr == KERN_SUCCESS, kr) << "MakeReceivingPort"; + return base::mac::ScopedMachReceiveRight(client_port); +} + +// Blocks until a mach message is sent to |server_port|. This mach message +// must contain a mach port. Returns that mach port. +base::mac::ScopedMachSendRight ReceiveMachPort(mach_port_t port_to_listen_on) { + MachReceivePortMessage recv_msg; + mach_msg_header_t* recv_hdr = &recv_msg.header; + recv_hdr->msgh_local_port = port_to_listen_on; + recv_hdr->msgh_size = sizeof(recv_msg); + kern_return_t kr = + mach_msg(recv_hdr, MACH_RCV_MSG, 0, recv_hdr->msgh_size, + port_to_listen_on, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "ReceiveMachPort"; + mach_port_t other_task_port = recv_msg.data.name; + return base::mac::ScopedMachSendRight(other_task_port); +} + +// Passes a copy of the send right of |port_to_send| to |receiving_port|. +void SendMachPort(mach_port_t receiving_port, + mach_port_t port_to_send, + int disposition) { + MachSendPortMessage send_msg; + send_msg.header.msgh_bits = + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX; + send_msg.header.msgh_size = sizeof(send_msg); + send_msg.header.msgh_remote_port = receiving_port; + send_msg.header.msgh_local_port = MACH_PORT_NULL; + send_msg.header.msgh_reserved = 0; + send_msg.header.msgh_id = 0; + send_msg.body.msgh_descriptor_count = 1; + send_msg.data.name = port_to_send; + send_msg.data.disposition = disposition; + send_msg.data.type = MACH_MSG_PORT_DESCRIPTOR; + int kr = mach_msg(&send_msg.header, MACH_SEND_MSG, send_msg.header.msgh_size, + 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "SendMachPort"; +} + +// Sends a uint32_t to a mach port. +void SendUInt32(mach_port_t port, uint32_t message) { + int message_size = sizeof(uint32_t); + int total_size = message_size + sizeof(mach_msg_header_t); + void* buffer = malloc(total_size); + mach_msg_header_t* header = (mach_msg_header_t*)buffer; + header->msgh_remote_port = port; + header->msgh_local_port = MACH_PORT_NULL; + header->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); + header->msgh_reserved = 0; + header->msgh_size = total_size; + memcpy(static_cast<char*>(buffer) + sizeof(mach_msg_header_t), &message, + message_size); + + kern_return_t kr; + kr = mach_msg(static_cast<mach_msg_header_t*>(buffer), MACH_SEND_MSG, + total_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "SendUInt32"; + free(buffer); +} + +// Receives a uint32_t from a mach port. +uint32_t ReceiveUInt32(mach_port_t listening_port) { + int message_size = sizeof(uint32_t); + int total_size = + message_size + sizeof(mach_msg_header_t) + sizeof(mach_msg_trailer_t); + int options = MACH_RCV_MSG; + void* buffer = malloc(total_size); + + int kr = + mach_msg(static_cast<mach_msg_header_t*>(buffer), options, 0, total_size, + listening_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "ReceiveUInt32"; + + uint32_t response; + memcpy(&response, static_cast<char*>(buffer) + sizeof(mach_msg_header_t), + message_size); + + free(buffer); + return response; +} + +std::string CreateRandomServiceName() { + return base::StringPrintf( + "AttachmentBrokerPrivilegedMacMultiProcessTest.%llu", base::RandUint64()); +} + +// The number of active names in the current task's port name space. +mach_msg_type_number_t GetActiveNameCount() { + mach_port_name_array_t name_array; + mach_msg_type_number_t names_count; + mach_port_type_array_t type_array; + mach_msg_type_number_t types_count; + kern_return_t kr = mach_port_names(mach_task_self(), &name_array, + &names_count, &type_array, &types_count); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "GetActiveNameCount"; + return names_count; +} + +// Sets up the mach communication ports with the server. Returns a port to which +// the server will send mach objects. +// |original_name_count| is an output variable that describes the number of +// active names in this task before the task port is shared with the server. +base::mac::ScopedMachReceiveRight CommonChildProcessSetUp( + mach_msg_type_number_t* original_name_count) { + base::CommandLine cmd_line = *base::CommandLine::ForCurrentProcess(); + std::string service_name = + cmd_line.GetSwitchValueASCII(g_service_switch_name); + base::mac::ScopedMachSendRight server_port( + LookupServer(service_name.c_str())); + base::mac::ScopedMachReceiveRight client_port(MakeReceivingPort()); + + // |server_port| is a newly allocated right which will be deallocated once + // this method returns. + *original_name_count = GetActiveNameCount() - 1; + + // Send the port that this process is listening on to the server. + SendMachPort(server_port, client_port, MACH_MSG_TYPE_MAKE_SEND); + + // Send the task port for this process. + SendMachPort(server_port, mach_task_self(), MACH_MSG_TYPE_COPY_SEND); + return client_port; +} + +// Creates a new shared memory region populated with 'a'. +scoped_ptr<base::SharedMemory> CreateAndPopulateSharedMemoryHandle( + size_t size) { + base::SharedMemoryHandle shm(size); + scoped_ptr<base::SharedMemory> shared_memory( + new base::SharedMemory(shm, false)); + shared_memory->Map(size); + memset(shared_memory->memory(), 'a', size); + return shared_memory; +} + +// Create a shared memory region from a memory object. The returned object takes +// ownership of |memory_object|. +scoped_ptr<base::SharedMemory> MapMemoryObject(mach_port_t memory_object, + size_t size) { + base::SharedMemoryHandle shm(memory_object, size, base::GetCurrentProcId()); + scoped_ptr<base::SharedMemory> shared_memory( + new base::SharedMemory(shm, false)); + shared_memory->Map(size); + return shared_memory; +} + +} // namespace + +class AttachmentBrokerPrivilegedMacMultiProcessTest + : public base::MultiProcessTest { + public: + AttachmentBrokerPrivilegedMacMultiProcessTest() {} + + base::CommandLine MakeCmdLine(const std::string& procname) override { + base::CommandLine command_line = MultiProcessTest::MakeCmdLine(procname); + // Pass the service name to the child process. + command_line.AppendSwitchASCII(g_service_switch_name, service_name_); + return command_line; + } + + void SetUpChild(const std::string& name) { + // Make a random service name so that this test doesn't conflict with other + // similar tests. + service_name_ = CreateRandomServiceName(); + server_port_.reset(BecomeMachServer(service_name_.c_str()).release()); + child_process_ = SpawnChild(name); + client_port_.reset(ReceiveMachPort(server_port_).release()); + client_task_port_.reset(ReceiveMachPort(server_port_).release()); + } + + static const int s_memory_size = 99999; + + protected: + std::string service_name_; + + // A port on which the main process listens for mach messages from the child + // process. + base::mac::ScopedMachReceiveRight server_port_; + + // A port on which the child process listens for mach messages from the main + // process. + base::mac::ScopedMachSendRight client_port_; + + // Child process's task port. + base::mac::ScopedMachSendRight client_task_port_; + + base::Process child_process_; + DISALLOW_COPY_AND_ASSIGN(AttachmentBrokerPrivilegedMacMultiProcessTest); +}; + +// The attachment broker inserts a right for a memory object into the +// destination task. +TEST_F(AttachmentBrokerPrivilegedMacMultiProcessTest, InsertRight) { + SetUpChild("InsertRightClient"); + mach_msg_type_number_t original_name_count = GetActiveNameCount(); + IPC::AttachmentBrokerPrivilegedMac broker; + + // Create some shared memory. + scoped_ptr<base::SharedMemory> shared_memory = + CreateAndPopulateSharedMemoryHandle(s_memory_size); + ASSERT_TRUE(shared_memory->handle().IsValid()); + + // Insert it indirectly into the destination task. + mach_port_name_t inserted_memory_object = broker.InsertIndirectMachPort( + client_task_port_, shared_memory->handle().GetMemoryObject()); + EXPECT_NE(inserted_memory_object, + static_cast<mach_port_name_t>(MACH_PORT_NULL)); + SendUInt32(client_port_, inserted_memory_object); + + // Check that no names have been leaked. + shared_memory.reset(); + EXPECT_EQ(original_name_count, GetActiveNameCount()); + + int rv = -1; + ASSERT_TRUE(child_process_.WaitForExitWithTimeout( + TestTimeouts::action_timeout(), &rv)); + EXPECT_EQ(0, rv); +} + +MULTIPROCESS_TEST_MAIN(InsertRightClient) { + mach_msg_type_number_t original_name_count = 0; + base::mac::ScopedMachReceiveRight client_port( + CommonChildProcessSetUp(&original_name_count).release()); + base::mac::ScopedMachReceiveRight inserted_port(ReceiveUInt32(client_port)); + base::mac::ScopedMachSendRight memory_object(ReceiveMachPort(inserted_port)); + inserted_port.reset(); + + // The server should have inserted a right into this process. + EXPECT_EQ(original_name_count + 1, GetActiveNameCount()); + + // Map the memory object and check its contents. + scoped_ptr<base::SharedMemory> shared_memory(MapMemoryObject( + memory_object.release(), + AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size)); + const char* start = static_cast<const char*>(shared_memory->memory()); + for (int i = 0; + i < AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size; ++i) { + DCHECK_EQ(start[i], 'a'); + } + + // Check that no names have been leaked. + shared_memory.reset(); + EXPECT_EQ(original_name_count, GetActiveNameCount()); + + return 0; +} + +// The attachment broker inserts the right for a memory object into the +// destination task twice. +TEST_F(AttachmentBrokerPrivilegedMacMultiProcessTest, InsertSameRightTwice) { + SetUpChild("InsertSameRightTwiceClient"); + mach_msg_type_number_t original_name_count = GetActiveNameCount(); + IPC::AttachmentBrokerPrivilegedMac broker; + + // Create some shared memory. + scoped_ptr<base::SharedMemory> shared_memory = + CreateAndPopulateSharedMemoryHandle(s_memory_size); + ASSERT_TRUE(shared_memory->handle().IsValid()); + + // Insert it indirectly into the destination task, twice. + for (int i = 0; i < 2; ++i) { + mach_port_name_t inserted_memory_object = broker.InsertIndirectMachPort( + client_task_port_, shared_memory->handle().GetMemoryObject()); + EXPECT_NE(inserted_memory_object, + static_cast<mach_port_name_t>(MACH_PORT_NULL)); + SendUInt32(client_port_, inserted_memory_object); + } + + // Check that no names have been leaked. + shared_memory.reset(); + EXPECT_EQ(original_name_count, GetActiveNameCount()); + + int rv = -1; + ASSERT_TRUE(child_process_.WaitForExitWithTimeout( + TestTimeouts::action_timeout(), &rv)); + EXPECT_EQ(0, rv); +} + +MULTIPROCESS_TEST_MAIN(InsertSameRightTwiceClient) { + mach_msg_type_number_t original_name_count = 0; + base::mac::ScopedMachReceiveRight client_port( + CommonChildProcessSetUp(&original_name_count).release()); + + // Receive two memory objects. + base::mac::ScopedMachReceiveRight inserted_port(ReceiveUInt32(client_port)); + base::mac::ScopedMachReceiveRight inserted_port2(ReceiveUInt32(client_port)); + base::mac::ScopedMachSendRight memory_object(ReceiveMachPort(inserted_port)); + base::mac::ScopedMachSendRight memory_object2( + ReceiveMachPort(inserted_port2)); + inserted_port.reset(); + inserted_port2.reset(); + + // Both rights are for the same Mach port, so only one new name should appear. + EXPECT_EQ(original_name_count + 1, GetActiveNameCount()); + + // Map both memory objects and check their contents. + scoped_ptr<base::SharedMemory> shared_memory(MapMemoryObject( + memory_object.release(), + AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size)); + char* start = static_cast<char*>(shared_memory->memory()); + for (int i = 0; + i < AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size; ++i) { + DCHECK_EQ(start[i], 'a'); + } + + scoped_ptr<base::SharedMemory> shared_memory2(MapMemoryObject( + memory_object2.release(), + AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size)); + char* start2 = static_cast<char*>(shared_memory2->memory()); + for (int i = 0; + i < AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size; ++i) { + DCHECK_EQ(start2[i], 'a'); + } + + // Check that the contents of both regions are shared. + start[0] = 'b'; + DCHECK_EQ(start2[0], 'b'); + + // After releasing one shared memory region, the name count shouldn't change, + // since another reference exists. + shared_memory.reset(); + EXPECT_EQ(original_name_count + 1, GetActiveNameCount()); + + // After releasing the second shared memory region, the name count should be + // as if no names were ever inserted + shared_memory2.reset(); + EXPECT_EQ(original_name_count, GetActiveNameCount()); + + return 0; +} + +// The attachment broker inserts the rights for two memory objects into the +// destination task. +TEST_F(AttachmentBrokerPrivilegedMacMultiProcessTest, InsertTwoRights) { + SetUpChild("InsertTwoRightsClient"); + mach_msg_type_number_t original_name_count = GetActiveNameCount(); + IPC::AttachmentBrokerPrivilegedMac broker; + + for (int i = 0; i < 2; ++i) { + // Create some shared memory. + scoped_ptr<base::SharedMemory> shared_memory = + CreateAndPopulateSharedMemoryHandle(s_memory_size); + ASSERT_TRUE(shared_memory->handle().IsValid()); + + // Insert it indirectly into the destination task. + mach_port_name_t inserted_memory_object = broker.InsertIndirectMachPort( + client_task_port_, shared_memory->handle().GetMemoryObject()); + EXPECT_NE(inserted_memory_object, + static_cast<mach_port_name_t>(MACH_PORT_NULL)); + SendUInt32(client_port_, inserted_memory_object); + } + + // Check that no names have been leaked. + EXPECT_EQ(original_name_count, GetActiveNameCount()); + + int rv = -1; + ASSERT_TRUE(child_process_.WaitForExitWithTimeout( + TestTimeouts::action_timeout(), &rv)); + EXPECT_EQ(0, rv); +} + +MULTIPROCESS_TEST_MAIN(InsertTwoRightsClient) { + mach_msg_type_number_t original_name_count = 0; + base::mac::ScopedMachReceiveRight client_port( + CommonChildProcessSetUp(&original_name_count).release()); + + // Receive two memory objects. + base::mac::ScopedMachReceiveRight inserted_port(ReceiveUInt32(client_port)); + base::mac::ScopedMachReceiveRight inserted_port2(ReceiveUInt32(client_port)); + base::mac::ScopedMachSendRight memory_object(ReceiveMachPort(inserted_port)); + base::mac::ScopedMachSendRight memory_object2( + ReceiveMachPort(inserted_port2)); + inserted_port.reset(); + inserted_port2.reset(); + + // There should be two new names to reflect the two new shared memory regions. + EXPECT_EQ(original_name_count + 2, GetActiveNameCount()); + + // Map both memory objects and check their contents. + scoped_ptr<base::SharedMemory> shared_memory(MapMemoryObject( + memory_object.release(), + AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size)); + char* start = static_cast<char*>(shared_memory->memory()); + for (int i = 0; + i < AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size; ++i) { + DCHECK_EQ(start[i], 'a'); + } + + scoped_ptr<base::SharedMemory> shared_memory2(MapMemoryObject( + memory_object2.release(), + AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size)); + char* start2 = static_cast<char*>(shared_memory2->memory()); + for (int i = 0; + i < AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size; ++i) { + DCHECK_EQ(start2[i], 'a'); + } + + // Check that the contents of both regions are not shared. + start[0] = 'b'; + DCHECK_EQ(start2[0], 'a'); + + // After releasing one shared memory region, the name count should decrement. + shared_memory.reset(); + EXPECT_EQ(original_name_count + 1, GetActiveNameCount()); + shared_memory2.reset(); + EXPECT_EQ(original_name_count, GetActiveNameCount()); + + return 0; +} + +} // namespace IPC diff --git a/ipc/attachment_broker_unprivileged_mac.cc b/ipc/attachment_broker_unprivileged_mac.cc new file mode 100644 index 0000000..b4ced08 --- /dev/null +++ b/ipc/attachment_broker_unprivileged_mac.cc @@ -0,0 +1,100 @@ +// Copyright 2015 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 "ipc/attachment_broker_unprivileged_mac.h" + +#include <mach/mach.h> + +#include "base/mac/scoped_mach_port.h" +#include "base/process/process.h" +#include "ipc/attachment_broker_messages.h" +#include "ipc/brokerable_attachment.h" +#include "ipc/ipc_sender.h" +#include "ipc/mach_port_attachment_mac.h" + +namespace { + +// Struct for receiving a complex message. +struct MachReceiveComplexMessage { + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_port_descriptor_t data; + mach_msg_trailer_t trailer; +}; + +// Receives a Mach port from |port_to_listen_on|, which should have exactly one +// queued message. Returns |MACH_PORT_NULL| on any error. +base::mac::ScopedMachSendRight ReceiveMachPort(mach_port_t port_to_listen_on) { + MachReceiveComplexMessage recv_msg; + mach_msg_header_t* recv_hdr = &recv_msg.header; + recv_hdr->msgh_local_port = port_to_listen_on; + recv_hdr->msgh_size = sizeof(recv_msg); + + kern_return_t kr = + mach_msg(recv_hdr, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, + recv_hdr->msgh_size, port_to_listen_on, 0, MACH_PORT_NULL); + if (kr != KERN_SUCCESS) + return base::mac::ScopedMachSendRight(MACH_PORT_NULL); + if (recv_msg.header.msgh_id != 0) + return base::mac::ScopedMachSendRight(MACH_PORT_NULL); + return base::mac::ScopedMachSendRight(recv_msg.data.name); +} + +} // namespace + +namespace IPC { + +AttachmentBrokerUnprivilegedMac::AttachmentBrokerUnprivilegedMac() {} + +AttachmentBrokerUnprivilegedMac::~AttachmentBrokerUnprivilegedMac() {} + +bool AttachmentBrokerUnprivilegedMac::SendAttachmentToProcess( + const BrokerableAttachment* attachment, + base::ProcessId destination_process) { + switch (attachment->GetBrokerableType()) { + case BrokerableAttachment::MACH_PORT: { + const internal::MachPortAttachmentMac* mach_port_attachment = + static_cast<const internal::MachPortAttachmentMac*>(attachment); + internal::MachPortAttachmentMac::WireFormat format = + mach_port_attachment->GetWireFormat(destination_process); + return get_sender()->Send( + new AttachmentBrokerMsg_DuplicateMachPort(format)); + } + default: + NOTREACHED(); + return false; + } + return false; +} + +bool AttachmentBrokerUnprivilegedMac::OnMessageReceived(const Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(AttachmentBrokerUnprivilegedMac, msg) + IPC_MESSAGE_HANDLER(AttachmentBrokerMsg_MachPortHasBeenDuplicated, + OnMachPortHasBeenDuplicated) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void AttachmentBrokerUnprivilegedMac::OnMachPortHasBeenDuplicated( + const IPC::internal::MachPortAttachmentMac::WireFormat& wire_format) { + // The IPC message was intended for a different process. Ignore it. + if (wire_format.destination_process != base::Process::Current().Pid()) { + // TODO(erikchen): UMA metric. + return; + } + + base::mac::ScopedMachReceiveRight message_port(wire_format.mach_port); + base::mac::ScopedMachSendRight memory_object(ReceiveMachPort(message_port)); + IPC::internal::MachPortAttachmentMac::WireFormat translated_wire_format( + memory_object.release(), wire_format.destination_process, + wire_format.attachment_id); + + scoped_refptr<BrokerableAttachment> attachment( + new IPC::internal::MachPortAttachmentMac(translated_wire_format)); + HandleReceivedAttachment(attachment); +} + +} // namespace IPC diff --git a/ipc/attachment_broker_unprivileged_mac.h b/ipc/attachment_broker_unprivileged_mac.h new file mode 100644 index 0000000..58c4f87 --- /dev/null +++ b/ipc/attachment_broker_unprivileged_mac.h @@ -0,0 +1,41 @@ +// Copyright 2015 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 IPC_ATTACHMENT_BROKER_UNPRIVILEGED_MAC_H_ +#define IPC_ATTACHMENT_BROKER_UNPRIVILEGED_MAC_H_ + +#include "ipc/attachment_broker_unprivileged.h" +#include "ipc/ipc_export.h" +#include "ipc/mach_port_attachment_mac.h" + +namespace IPC { + +class BrokerableAttachment; + +// This class is an implementation of AttachmentBroker for the OSX platform +// for non-privileged processes. +class IPC_EXPORT AttachmentBrokerUnprivilegedMac + : public IPC::AttachmentBrokerUnprivileged { + public: + AttachmentBrokerUnprivilegedMac(); + ~AttachmentBrokerUnprivilegedMac() override; + + // IPC::AttachmentBroker overrides. + bool SendAttachmentToProcess(const BrokerableAttachment* attachment, + base::ProcessId destination_process) override; + + // IPC::Listener overrides. + bool OnMessageReceived(const Message& message) override; + + private: + // IPC message handlers. + void OnMachPortHasBeenDuplicated( + const IPC::internal::MachPortAttachmentMac::WireFormat& wire_format); + + DISALLOW_COPY_AND_ASSIGN(AttachmentBrokerUnprivilegedMac); +}; + +} // namespace IPC + +#endif // IPC_ATTACHMENT_BROKER_UNPRIVILEGED_MAC_H_ diff --git a/ipc/ipc.gyp b/ipc/ipc.gyp index 7924ec6..ad178ab 100644 --- a/ipc/ipc.gyp +++ b/ipc/ipc.gyp @@ -52,6 +52,7 @@ '..' ], 'sources': [ + 'attachment_broker_privileged_mac_unittest.cc', 'attachment_broker_privileged_win_unittest.cc', 'attachment_broker_unprivileged_win_unittest.cc', 'ipc_channel_posix_unittest.cc', diff --git a/ipc/ipc.gypi b/ipc/ipc.gypi index 696b34a..44fcdab 100644 --- a/ipc/ipc.gypi +++ b/ipc/ipc.gypi @@ -16,10 +16,14 @@ 'attachment_broker_messages.h', 'attachment_broker_privileged.cc', 'attachment_broker_privileged.h', + 'attachment_broker_privileged_mac.cc', + 'attachment_broker_privileged_mac.h', 'attachment_broker_privileged_win.cc', 'attachment_broker_privileged_win.h', 'attachment_broker_unprivileged.cc', 'attachment_broker_unprivileged.h', + 'attachment_broker_unprivileged_mac.cc', + 'attachment_broker_unprivileged_mac.h', 'attachment_broker_unprivileged_win.cc', 'attachment_broker_unprivileged_win.h', 'brokerable_attachment.cc', |