// 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 #include #include #include #include #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/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 base { namespace { // Gets the current and maximum protection levels of the memory region. // Returns whether the operation was successful. // |current| and |max| are output variables only populated on success. bool GetProtections(void* address, size_t size, int* current, int* max) { vm_region_info_t region_info; mach_vm_address_t mem_address = reinterpret_cast(address); mach_vm_size_t mem_size = size; vm_region_basic_info_64 basic_info; region_info = reinterpret_cast(&basic_info); vm_region_flavor_t flavor = VM_REGION_BASIC_INFO_64; memory_object_name_t memory_object; mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; kern_return_t kr = mach_vm_region(mach_task_self(), &mem_address, &mem_size, flavor, region_info, &count, &memory_object); if (kr != KERN_SUCCESS) { MACH_LOG(ERROR, kr) << "Failed to get region info."; return false; } *current = basic_info.protection; *max = basic_info.max_protection; return true; } // Creates a new SharedMemory with the given |size|, filled with 'a'. scoped_ptr CreateSharedMemory(int size) { SharedMemoryHandle shm(size); if (!shm.IsValid()) { LOG(ERROR) << "Failed to make SharedMemoryHandle"; return nullptr; } scoped_ptr shared_memory(new SharedMemory(shm, false)); shared_memory->Map(size); memset(shared_memory->memory(), 'a', size); return shared_memory; } 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 { mach_msg_header_t header; mach_msg_body_t body; mach_msg_port_descriptor_t data; mach_msg_trailer_t trailer; }; // Makes the current process into a Mach Server with the given |service_name|. mach_port_t 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 port; } // Returns the mach port for the Mach Server with the given |service_name|. mach_port_t 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 server_port; } mach_port_t 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 client_port; } // Blocks until a mach message is sent to |server_port|. This mach message // must contain a mach port. Returns that mach port. mach_port_t 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, // message buffer MACH_RCV_MSG, // option indicating service 0, // send size recv_hdr->msgh_size, // size of header + body port_to_listen_on, // receive name MACH_MSG_TIMEOUT_NONE, // no timeout, wait forever MACH_PORT_NULL); // no notification port MACH_CHECK(kr == KERN_SUCCESS, kr) << "ReceiveMachPort"; mach_port_t other_task_port = recv_msg.data.name; return 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; mach_msg_header_t* send_hdr; send_hdr = &(send_msg.header); send_hdr->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX; send_hdr->msgh_size = sizeof(send_msg); send_hdr->msgh_remote_port = receiving_port; send_hdr->msgh_local_port = MACH_PORT_NULL; send_hdr->msgh_reserved = 0; send_hdr->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_hdr, // message buffer MACH_SEND_MSG, // option indicating send send_hdr->msgh_size, // size of header + body 0, // receive limit MACH_PORT_NULL, // receive name MACH_MSG_TIMEOUT_NONE, // no timeout, wait forever MACH_PORT_NULL); // no notification port MACH_CHECK(kr == KERN_SUCCESS, kr) << "SendMachPort"; } std::string CreateRandomServiceName() { return StringPrintf("SharedMemoryMacMultiProcessTest.%llu", RandUint64()); } // Sets up the mach communication ports with the server. Returns a port to which // the server will send mach objects. mach_port_t CommonChildProcessSetUp() { CommandLine cmd_line = *CommandLine::ForCurrentProcess(); std::string service_name = cmd_line.GetSwitchValueASCII(g_service_switch_name); mac::ScopedMachSendRight server_port(LookupServer(service_name.c_str())); mach_port_t client_port = MakeReceivingPort(); // Send the port that this process is listening on to the server. SendMachPort(server_port.get(), client_port, MACH_MSG_TYPE_MAKE_SEND); return client_port; } // 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; } } // namespace class SharedMemoryMacMultiProcessTest : public MultiProcessTest { public: SharedMemoryMacMultiProcessTest() {} CommandLine MakeCmdLine(const std::string& procname) override { 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())); child_process_ = SpawnChild(name); client_port_.reset(ReceiveMachPort(server_port_.get())); } 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. mac::ScopedMachReceiveRight server_port_; // A port on which the child process listens for mach messages from the main // process. mac::ScopedMachSendRight client_port_; base::Process child_process_; DISALLOW_COPY_AND_ASSIGN(SharedMemoryMacMultiProcessTest); }; // Tests that content written to shared memory in the server process can be read // by the child process. TEST_F(SharedMemoryMacMultiProcessTest, MachBasedSharedMemory) { // Mach-based SharedMemory isn't support on OSX 10.6. if (mac::IsOSSnowLeopard()) return; SetUpChild("MachBasedSharedMemoryClient"); scoped_ptr shared_memory(CreateSharedMemory(s_memory_size)); // Send the underlying memory object to the client process. SendMachPort(client_port_.get(), shared_memory->handle().GetMemoryObject(), MACH_MSG_TYPE_COPY_SEND); int rv = -1; ASSERT_TRUE(child_process_.WaitForExitWithTimeout( TestTimeouts::action_timeout(), &rv)); EXPECT_EQ(0, rv); } MULTIPROCESS_TEST_MAIN(MachBasedSharedMemoryClient) { mac::ScopedMachReceiveRight client_port(CommonChildProcessSetUp()); // The next mach port should be for a memory object. mach_port_t memory_object = ReceiveMachPort(client_port.get()); SharedMemoryHandle shm(memory_object, SharedMemoryMacMultiProcessTest::s_memory_size, GetCurrentProcId()); SharedMemory shared_memory(shm, false); shared_memory.Map(SharedMemoryMacMultiProcessTest::s_memory_size); const char* start = static_cast(shared_memory.memory()); for (int i = 0; i < SharedMemoryMacMultiProcessTest::s_memory_size; ++i) { DCHECK_EQ(start[i], 'a'); } return 0; } // Tests that mapping shared memory with an offset works correctly. TEST_F(SharedMemoryMacMultiProcessTest, MachBasedSharedMemoryWithOffset) { // Mach-based SharedMemory isn't support on OSX 10.6. if (mac::IsOSSnowLeopard()) return; SetUpChild("MachBasedSharedMemoryWithOffsetClient"); SharedMemoryHandle shm(s_memory_size); ASSERT_TRUE(shm.IsValid()); SharedMemory shared_memory(shm, false); shared_memory.Map(s_memory_size); size_t page_size = SysInfo::VMAllocationGranularity(); char* start = static_cast(shared_memory.memory()); memset(start, 'a', page_size); memset(start + page_size, 'b', page_size); memset(start + 2 * page_size, 'c', page_size); // Send the underlying memory object to the client process. SendMachPort( client_port_.get(), shm.GetMemoryObject(), MACH_MSG_TYPE_COPY_SEND); int rv = -1; ASSERT_TRUE(child_process_.WaitForExitWithTimeout( TestTimeouts::action_timeout(), &rv)); EXPECT_EQ(0, rv); } MULTIPROCESS_TEST_MAIN(MachBasedSharedMemoryWithOffsetClient) { mac::ScopedMachReceiveRight client_port(CommonChildProcessSetUp()); // The next mach port should be for a memory object. mach_port_t memory_object = ReceiveMachPort(client_port.get()); SharedMemoryHandle shm(memory_object, SharedMemoryMacMultiProcessTest::s_memory_size, GetCurrentProcId()); SharedMemory shared_memory(shm, false); size_t page_size = SysInfo::VMAllocationGranularity(); shared_memory.MapAt(page_size, 2 * page_size); const char* start = static_cast(shared_memory.memory()); for (size_t i = 0; i < page_size; ++i) { DCHECK_EQ(start[i], 'b'); } for (size_t i = page_size; i < 2 * page_size; ++i) { DCHECK_EQ(start[i], 'c'); } return 0; } // Tests that duplication and closing has the right effect on Mach reference // counts. TEST_F(SharedMemoryMacMultiProcessTest, MachDuplicateAndClose) { // Mach-based SharedMemory isn't support on OSX 10.6. if (mac::IsOSSnowLeopard()) return; mach_msg_type_number_t active_name_count = GetActiveNameCount(); // Making a new SharedMemoryHandle increments the name count. SharedMemoryHandle shm(s_memory_size); ASSERT_TRUE(shm.IsValid()); EXPECT_EQ(active_name_count + 1, GetActiveNameCount()); // Duplicating the SharedMemoryHandle increments the ref count, but doesn't // make a new name. shm.Duplicate(); EXPECT_EQ(active_name_count + 1, GetActiveNameCount()); // Closing the SharedMemoryHandle decrements the ref count. The first time has // no effect. shm.Close(); EXPECT_EQ(active_name_count + 1, GetActiveNameCount()); // Closing the SharedMemoryHandle decrements the ref count. The second time // destroys the port. shm.Close(); EXPECT_EQ(active_name_count, GetActiveNameCount()); } // Tests that Mach shared memory can be mapped and unmapped. TEST_F(SharedMemoryMacMultiProcessTest, MachUnmapMap) { // Mach-based SharedMemory isn't support on OSX 10.6. if (mac::IsOSSnowLeopard()) return; mach_msg_type_number_t active_name_count = GetActiveNameCount(); scoped_ptr shared_memory = CreateSharedMemory(s_memory_size); ASSERT_TRUE(shared_memory->Unmap()); ASSERT_TRUE(shared_memory->Map(s_memory_size)); shared_memory.reset(); EXPECT_EQ(active_name_count, GetActiveNameCount()); } // Tests that passing a SharedMemoryHandle to a SharedMemory object also passes // ownership, and that destroying the SharedMemory closes the SharedMemoryHandle // as well. TEST_F(SharedMemoryMacMultiProcessTest, MachSharedMemoryTakesOwnership) { // Mach-based SharedMemory isn't support on OSX 10.6. if (mac::IsOSSnowLeopard()) return; mach_msg_type_number_t active_name_count = GetActiveNameCount(); // Making a new SharedMemoryHandle increments the name count. SharedMemoryHandle shm(s_memory_size); ASSERT_TRUE(shm.IsValid()); EXPECT_EQ(active_name_count + 1, GetActiveNameCount()); // Name count doesn't change when mapping the memory. scoped_ptr shared_memory(new SharedMemory(shm, false)); shared_memory->Map(s_memory_size); EXPECT_EQ(active_name_count + 1, GetActiveNameCount()); // Destroying the SharedMemory object frees the resource. shared_memory.reset(); EXPECT_EQ(active_name_count, GetActiveNameCount()); } // Tests that the read-only flag works. TEST_F(SharedMemoryMacMultiProcessTest, MachReadOnly) { // Mach-based SharedMemory isn't support on OSX 10.6. if (mac::IsOSSnowLeopard()) return; scoped_ptr shared_memory(CreateSharedMemory(s_memory_size)); SharedMemoryHandle shm2 = shared_memory->handle().Duplicate(); ASSERT_TRUE(shm2.IsValid()); SharedMemory shared_memory2(shm2, true); shared_memory2.Map(s_memory_size); ASSERT_DEATH(memset(shared_memory2.memory(), 'b', s_memory_size), ""); } // Tests that the method ShareToProcess() works. TEST_F(SharedMemoryMacMultiProcessTest, MachShareToProcess) { // Mach-based SharedMemory isn't support on OSX 10.6. if (mac::IsOSSnowLeopard()) return; mach_msg_type_number_t active_name_count = GetActiveNameCount(); { scoped_ptr shared_memory(CreateSharedMemory(s_memory_size)); SharedMemoryHandle shm2; ASSERT_TRUE(shared_memory->ShareToProcess(GetCurrentProcId(), &shm2)); ASSERT_TRUE(shm2.IsValid()); SharedMemory shared_memory2(shm2, true); shared_memory2.Map(s_memory_size); ASSERT_EQ(0, memcmp(shared_memory->memory(), shared_memory2.memory(), s_memory_size)); } EXPECT_EQ(active_name_count, GetActiveNameCount()); } // Tests that the method ShareReadOnlyToProcess() creates a memory object that // is read only. TEST_F(SharedMemoryMacMultiProcessTest, MachShareToProcessReadonly) { // Mach-based SharedMemory isn't support on OSX 10.6. if (mac::IsOSSnowLeopard()) return; scoped_ptr shared_memory(CreateSharedMemory(s_memory_size)); // Check the protection levels. int current_prot, max_prot; ASSERT_TRUE(GetProtections(shared_memory->memory(), shared_memory->mapped_size(), ¤t_prot, &max_prot)); ASSERT_EQ(VM_PROT_READ | VM_PROT_WRITE, current_prot); ASSERT_EQ(VM_PROT_READ | VM_PROT_WRITE, max_prot); // Make a new memory object. SharedMemoryHandle shm2; ASSERT_TRUE(shared_memory->ShareReadOnlyToProcess(GetCurrentProcId(), &shm2)); ASSERT_TRUE(shm2.IsValid()); // Mapping with |readonly| set to |false| should fail. SharedMemory shared_memory2(shm2, false); shared_memory2.Map(s_memory_size); ASSERT_EQ(nullptr, shared_memory2.memory()); // Now trying mapping with |readonly| set to |true|. SharedMemory shared_memory3(shm2.Duplicate(), true); shared_memory3.Map(s_memory_size); ASSERT_NE(nullptr, shared_memory3.memory()); // Check the protection levels. ASSERT_TRUE(GetProtections(shared_memory3.memory(), shared_memory3.mapped_size(), ¤t_prot, &max_prot)); ASSERT_EQ(VM_PROT_READ, current_prot); ASSERT_EQ(VM_PROT_READ, max_prot); // The memory should still be readonly, since the underlying memory object // is readonly. ASSERT_DEATH(memset(shared_memory2.memory(), 'b', s_memory_size), ""); } // Tests that the method ShareReadOnlyToProcess() doesn't leak. TEST_F(SharedMemoryMacMultiProcessTest, MachShareToProcessReadonlyLeak) { // Mach-based SharedMemory isn't support on OSX 10.6. if (mac::IsOSSnowLeopard()) return; mach_msg_type_number_t active_name_count = GetActiveNameCount(); { scoped_ptr shared_memory(CreateSharedMemory(s_memory_size)); SharedMemoryHandle shm2; ASSERT_TRUE( shared_memory->ShareReadOnlyToProcess(GetCurrentProcId(), &shm2)); ASSERT_TRUE(shm2.IsValid()); // Intentionally map with |readonly| set to |false|. SharedMemory shared_memory2(shm2, false); shared_memory2.Map(s_memory_size); } EXPECT_EQ(active_name_count, GetActiveNameCount()); } } // namespace base