| // 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 <mach/mach.h> |
| #include <mach/mach_vm.h> |
| #include <servers/bootstrap.h> |
| |
| #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 "base/unguessable_token.h" |
| #include "starboard/memory.h" |
| #include "starboard/types.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<mach_vm_address_t>(address); |
| mach_vm_size_t mem_size = size; |
| vm_region_basic_info_64 basic_info; |
| |
| region_info = reinterpret_cast<vm_region_recurse_info_t>(&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'. |
| std::unique_ptr<SharedMemory> CreateSharedMemory(int size) { |
| SharedMemoryHandle shm(size, UnguessableToken::Create()); |
| if (!shm.IsValid()) { |
| LOG(ERROR) << "Failed to make SharedMemoryHandle"; |
| return nullptr; |
| } |
| std::unique_ptr<SharedMemory> 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) { |
| SetUpChild("MachBasedSharedMemoryClient"); |
| |
| std::unique_ptr<SharedMemory> 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, |
| UnguessableToken::Create()); |
| SharedMemory shared_memory(shm, false); |
| shared_memory.Map(SharedMemoryMacMultiProcessTest::s_memory_size); |
| const char* start = static_cast<const char*>(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) { |
| SetUpChild("MachBasedSharedMemoryWithOffsetClient"); |
| |
| SharedMemoryHandle shm(s_memory_size, UnguessableToken::Create()); |
| 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<char*>(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, |
| UnguessableToken::Create()); |
| SharedMemory shared_memory(shm, false); |
| size_t page_size = SysInfo::VMAllocationGranularity(); |
| shared_memory.MapAt(page_size, 2 * page_size); |
| const char* start = static_cast<const char*>(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_msg_type_number_t active_name_count = GetActiveNameCount(); |
| |
| // Making a new SharedMemoryHandle increments the name count. |
| SharedMemoryHandle shm(s_memory_size, UnguessableToken::Create()); |
| 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_msg_type_number_t active_name_count = GetActiveNameCount(); |
| |
| std::unique_ptr<SharedMemory> 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_msg_type_number_t active_name_count = GetActiveNameCount(); |
| |
| // Making a new SharedMemoryHandle increments the name count. |
| SharedMemoryHandle shm(s_memory_size, UnguessableToken::Create()); |
| ASSERT_TRUE(shm.IsValid()); |
| EXPECT_EQ(active_name_count + 1, GetActiveNameCount()); |
| |
| // Name count doesn't change when mapping the memory. |
| std::unique_ptr<SharedMemory> 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) { |
| std::unique_ptr<SharedMemory> 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 duplication of the underlying handle works. |
| TEST_F(SharedMemoryMacMultiProcessTest, MachDuplicate) { |
| mach_msg_type_number_t active_name_count = GetActiveNameCount(); |
| |
| { |
| std::unique_ptr<SharedMemory> 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_EQ(0, memcmp(shared_memory->memory(), |
| shared_memory2.memory(), s_memory_size)); |
| } |
| |
| EXPECT_EQ(active_name_count, GetActiveNameCount()); |
| } |
| |
| // Tests that the method GetReadOnlyHandle() creates a memory object that |
| // is read only. |
| TEST_F(SharedMemoryMacMultiProcessTest, MachReadonly) { |
| std::unique_ptr<SharedMemory> 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 = shared_memory->GetReadOnlyHandle(); |
| ASSERT_TRUE(shm2.IsValid()); |
| EXPECT_EQ(shared_memory->handle().GetGUID(), shm2.GetGUID()); |
| |
| // 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 GetReadOnlyHandle() doesn't leak. |
| TEST_F(SharedMemoryMacMultiProcessTest, MachReadonlyLeak) { |
| mach_msg_type_number_t active_name_count = GetActiveNameCount(); |
| |
| { |
| std::unique_ptr<SharedMemory> shared_memory( |
| CreateSharedMemory(s_memory_size)); |
| |
| SharedMemoryHandle shm2 = shared_memory->GetReadOnlyHandle(); |
| 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 |