| // 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 <stddef.h> |
| #include <stdint.h> |
| |
| #include <map> |
| |
| #include "base/command_line.h" |
| #include "base/mac/mac_util.h" |
| #include "base/mac/mach_logging.h" |
| #include "base/mac/scoped_mach_port.h" |
| #include "base/macros.h" |
| #include "base/memory/shared_memory.h" |
| #include "base/process/port_provider_mac.h" |
| #include "base/process/process_handle.h" |
| #include "base/sys_info.h" |
| #include "base/test/multiprocess_test.h" |
| #include "base/test/test_timeouts.h" |
| #include "ipc/test_util_mac.h" |
| #include "testing/multiprocess_func_list.h" |
| |
| namespace IPC { |
| |
| namespace { |
| |
| static const std::string g_service_switch_name = "service_name"; |
| |
| // 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_id = 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; |
| } |
| |
| // 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.get(), client_port.get(), MACH_MSG_TYPE_MAKE_SEND); |
| |
| // Send the task port for this process. |
| SendMachPort(server_port.get(), 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; |
| } |
| |
| class MockPortProvider : public base::PortProvider { |
| public: |
| MockPortProvider() {} |
| ~MockPortProvider() override {} |
| mach_port_t TaskForPid(base::ProcessHandle process) const override { |
| return MACH_PORT_NULL; |
| } |
| }; |
| |
| } // 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_.get()).release()); |
| client_task_port_.reset(ReceiveMachPort(server_port_.get()).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_; |
| |
| // Dummy port provider. |
| MockPortProvider port_provider_; |
| |
| 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) { |
| // Mach-based SharedMemory isn't support on OSX 10.6. |
| if (base::mac::IsOSSnowLeopard()) |
| return; |
| |
| SetUpChild("InsertRightClient"); |
| mach_msg_type_number_t original_name_count = GetActiveNameCount(); |
| IPC::AttachmentBrokerPrivilegedMac broker(&port_provider_); |
| |
| // Create some shared memory. |
| scoped_ptr<base::SharedMemory> shared_memory = |
| CreateAndPopulateSharedMemoryHandle(s_memory_size); |
| ASSERT_TRUE(shared_memory->handle().IsValid()); |
| |
| // Insert the memory object into the destination task, via an intermediate |
| // port. |
| IncrementMachRefCount(shared_memory->handle().GetMemoryObject(), |
| MACH_PORT_RIGHT_SEND); |
| mach_port_name_t inserted_memory_object = broker.CreateIntermediateMachPort( |
| client_task_port_.get(), base::mac::ScopedMachSendRight( |
| shared_memory->handle().GetMemoryObject())); |
| EXPECT_NE(inserted_memory_object, |
| static_cast<mach_port_name_t>(MACH_PORT_NULL)); |
| SendUInt32(client_port_.get(), 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.get())); |
| base::mac::ScopedMachSendRight memory_object( |
| ReceiveMachPort(inserted_port.get())); |
| 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) { |
| // Mach-based SharedMemory isn't support on OSX 10.6. |
| if (base::mac::IsOSSnowLeopard()) |
| return; |
| |
| SetUpChild("InsertSameRightTwiceClient"); |
| mach_msg_type_number_t original_name_count = GetActiveNameCount(); |
| IPC::AttachmentBrokerPrivilegedMac broker(&port_provider_); |
| |
| // Create some shared memory. |
| scoped_ptr<base::SharedMemory> shared_memory = |
| CreateAndPopulateSharedMemoryHandle(s_memory_size); |
| ASSERT_TRUE(shared_memory->handle().IsValid()); |
| |
| // Insert the memory object into the destination task, via an intermediate |
| // port, twice. |
| for (int i = 0; i < 2; ++i) { |
| IncrementMachRefCount(shared_memory->handle().GetMemoryObject(), |
| MACH_PORT_RIGHT_SEND); |
| mach_port_name_t inserted_memory_object = broker.CreateIntermediateMachPort( |
| client_task_port_.get(), |
| base::mac::ScopedMachSendRight( |
| shared_memory->handle().GetMemoryObject())); |
| EXPECT_NE(inserted_memory_object, |
| static_cast<mach_port_name_t>(MACH_PORT_NULL)); |
| SendUInt32(client_port_.get(), 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.get())); |
| base::mac::ScopedMachReceiveRight inserted_port2( |
| ReceiveUInt32(client_port.get())); |
| base::mac::ScopedMachSendRight memory_object( |
| ReceiveMachPort(inserted_port.get())); |
| base::mac::ScopedMachSendRight memory_object2( |
| ReceiveMachPort(inserted_port2.get())); |
| 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) { |
| // Mach-based SharedMemory isn't support on OSX 10.6. |
| if (base::mac::IsOSSnowLeopard()) |
| return; |
| |
| SetUpChild("InsertTwoRightsClient"); |
| mach_msg_type_number_t original_name_count = GetActiveNameCount(); |
| IPC::AttachmentBrokerPrivilegedMac broker(&port_provider_); |
| |
| 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 the memory object into the destination task, via an intermediate |
| // port. |
| IncrementMachRefCount(shared_memory->handle().GetMemoryObject(), |
| MACH_PORT_RIGHT_SEND); |
| mach_port_name_t inserted_memory_object = broker.CreateIntermediateMachPort( |
| client_task_port_.get(), |
| base::mac::ScopedMachSendRight( |
| shared_memory->handle().GetMemoryObject())); |
| EXPECT_NE(inserted_memory_object, |
| static_cast<mach_port_name_t>(MACH_PORT_NULL)); |
| SendUInt32(client_port_.get(), 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.get())); |
| base::mac::ScopedMachReceiveRight inserted_port2( |
| ReceiveUInt32(client_port.get())); |
| base::mac::ScopedMachSendRight memory_object( |
| ReceiveMachPort(inserted_port.get())); |
| base::mac::ScopedMachSendRight memory_object2( |
| ReceiveMachPort(inserted_port2.get())); |
| 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 |