// 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 #include #include #include #include #include "base/command_line.h" #include "base/mac/mac_util.h" #include "base/mac/mach_logging.h" #include "base/mac/mach_port_util.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(buffer) + sizeof(mach_msg_header_t), &message, message_size); kern_return_t kr; kr = mach_msg(static_cast(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(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(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 CreateAndPopulateSharedMemoryHandle( size_t size) { base::SharedMemoryHandle shm(size); scoped_ptr 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 MapMemoryObject(mach_port_t memory_object, size_t size) { base::SharedMemoryHandle shm(memory_object, size, base::GetCurrentProcId()); scoped_ptr 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 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 = base::CreateIntermediateMachPort( client_task_port_.get(), base::mac::ScopedMachSendRight(shared_memory->handle().GetMemoryObject()), nullptr); EXPECT_NE(inserted_memory_object, static_cast(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 shared_memory(MapMemoryObject( memory_object.release(), AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size)); const char* start = static_cast(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 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 = base::CreateIntermediateMachPort( client_task_port_.get(), base::mac::ScopedMachSendRight( shared_memory->handle().GetMemoryObject()), nullptr); EXPECT_NE(inserted_memory_object, static_cast(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 shared_memory(MapMemoryObject( memory_object.release(), AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size)); char* start = static_cast(shared_memory->memory()); for (int i = 0; i < AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size; ++i) { DCHECK_EQ(start[i], 'a'); } scoped_ptr shared_memory2(MapMemoryObject( memory_object2.release(), AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size)); char* start2 = static_cast(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 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 = base::CreateIntermediateMachPort( client_task_port_.get(), base::mac::ScopedMachSendRight( shared_memory->handle().GetMemoryObject()), nullptr); EXPECT_NE(inserted_memory_object, static_cast(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 shared_memory(MapMemoryObject( memory_object.release(), AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size)); char* start = static_cast(shared_memory->memory()); for (int i = 0; i < AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size; ++i) { DCHECK_EQ(start[i], 'a'); } scoped_ptr shared_memory2(MapMemoryObject( memory_object2.release(), AttachmentBrokerPrivilegedMacMultiProcessTest::s_memory_size)); char* start2 = static_cast(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