// Copyright 2019 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/mac/mach_port_rendezvous.h" #include #include #include "base/at_exit.h" #include "base/mac/foundation_util.h" #include "base/mac/mach_logging.h" #include "base/strings/stringprintf.h" #include "base/test/multiprocess_test.h" #include "base/test/test_timeouts.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" #include "testing/multiprocess_func_list.h" namespace base { namespace { constexpr MachPortsForRendezvous::key_type kTestPortKey = 'port'; } // namespace class MachPortRendezvousServerTest : public MultiProcessTest { public: void SetUp() override {} std::map& client_data() { return MachPortRendezvousServer::GetInstance()->client_data_; } private: ShadowingAtExitManager at_exit_; }; MULTIPROCESS_TEST_MAIN(TakeSendRight) { auto* rendezvous_client = MachPortRendezvousClient::GetInstance(); CHECK(rendezvous_client); CHECK_EQ(1u, rendezvous_client->GetPortCount()); mac::ScopedMachSendRight port = rendezvous_client->TakeSendRight(kTestPortKey); CHECK(port.is_valid()); mach_msg_base_t msg{}; msg.header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_COPY_SEND); msg.header.msgh_size = sizeof(msg); msg.header.msgh_remote_port = port.get(); msg.header.msgh_id = 'good'; kern_return_t kr = mach_msg(&msg.header, MACH_SEND_MSG, msg.header.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_msg"; return 0; } TEST_F(MachPortRendezvousServerTest, SendRight) { auto* server = MachPortRendezvousServer::GetInstance(); ASSERT_TRUE(server); mac::ScopedMachReceiveRight port; kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, mac::ScopedMachReceiveRight::Receiver(port).get()); ASSERT_EQ(kr, KERN_SUCCESS); MachRendezvousPort rendezvous_port(port.get(), MACH_MSG_TYPE_MAKE_SEND); Process child; { AutoLock lock(server->GetLock()); child = SpawnChild("TakeSendRight"); server->RegisterPortsForPid( child.Pid(), {std::make_pair(kTestPortKey, rendezvous_port)}); } struct : mach_msg_base_t { mach_msg_trailer_t trailer; } msg{}; kr = mach_msg(&msg.header, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, sizeof(msg), port.get(), TestTimeouts::action_timeout().InMilliseconds(), MACH_PORT_NULL); EXPECT_EQ(kr, KERN_SUCCESS) << mach_error_string(kr); EXPECT_EQ(msg.header.msgh_id, 'good'); int exit_code; ASSERT_TRUE(WaitForMultiprocessTestChildExit( child, TestTimeouts::action_timeout(), &exit_code)); EXPECT_EQ(0, exit_code); } MULTIPROCESS_TEST_MAIN(NoRights) { auto* rendezvous_client = MachPortRendezvousClient::GetInstance(); CHECK(rendezvous_client); CHECK_EQ(0u, rendezvous_client->GetPortCount()); return 0; } TEST_F(MachPortRendezvousServerTest, NoRights) { auto* server = MachPortRendezvousServer::GetInstance(); ASSERT_TRUE(server); Process child = SpawnChild("NoRights"); int exit_code; ASSERT_TRUE(WaitForMultiprocessTestChildExit( child, TestTimeouts::action_timeout(), &exit_code)); EXPECT_EQ(0, exit_code); } MULTIPROCESS_TEST_MAIN(Exit42) { _exit(42); } TEST_F(MachPortRendezvousServerTest, CleanupIfNoRendezvous) { auto* server = MachPortRendezvousServer::GetInstance(); ASSERT_TRUE(server); mac::ScopedMachReceiveRight port; kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, mac::ScopedMachReceiveRight::Receiver(port).get()); ASSERT_EQ(kr, KERN_SUCCESS); MachRendezvousPort rendezvous_port(port.get(), MACH_MSG_TYPE_MAKE_SEND); Process child; { AutoLock lock(server->GetLock()); child = SpawnChild("Exit42"); server->RegisterPortsForPid( child.Pid(), {std::make_pair(kTestPortKey, rendezvous_port)}); EXPECT_EQ(1u, client_data().size()); } int exit_code; ASSERT_TRUE(WaitForMultiprocessTestChildExit( child, TestTimeouts::action_timeout(), &exit_code)); EXPECT_EQ(42, exit_code); // There is no way to synchronize the test code with the asynchronous // delivery of the dispatch process-exit notification. Loop for a short // while for it to be delivered. auto start = TimeTicks::Now(); do { if (client_data().size() == 0) break; // Sleep is fine because dispatch will process the notification on one of // its workers. PlatformThread::Sleep(Milliseconds(10)); } while ((TimeTicks::Now() - start) < TestTimeouts::action_timeout()); EXPECT_EQ(0u, client_data().size()); } TEST_F(MachPortRendezvousServerTest, DestroyRight) { const struct { // How to create the port. bool insert_send_right; // Disposition for MachRendezvousPort. mach_port_right_t disposition; // After calling DestroyRight. bool is_dead_name; mach_port_urefs_t send_rights; } kCases[] = { {true, MACH_MSG_TYPE_MOVE_RECEIVE, true, 0}, {true, MACH_MSG_TYPE_MOVE_SEND, false, 0}, {true, MACH_MSG_TYPE_COPY_SEND, false, 1}, {true, MACH_MSG_TYPE_MAKE_SEND, false, 1}, {false, MACH_MSG_TYPE_MAKE_SEND, false, 0}, {true, MACH_MSG_TYPE_MAKE_SEND_ONCE, false, 1}, // It's not possible to test MOVE_SEND_ONCE since one cannot // insert_right MAKE_SEND_ONCE. }; for (size_t i = 0; i < std::size(kCases); ++i) { SCOPED_TRACE(base::StringPrintf("case %zu", i).c_str()); const auto& test = kCases[i]; // This test deliberately leaks Mach port rights. mach_port_t port; kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); ASSERT_EQ(kr, KERN_SUCCESS); if (test.insert_send_right) { kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); ASSERT_EQ(kr, KERN_SUCCESS); } MachRendezvousPort rendezvous_port(port, test.disposition); rendezvous_port.Destroy(); mach_port_type_t type = 0; kr = mach_port_type(mach_task_self(), port, &type); ASSERT_EQ(kr, KERN_SUCCESS); EXPECT_EQ(type == MACH_PORT_TYPE_DEAD_NAME, test.is_dead_name) << type; mach_port_urefs_t refs = 0; kr = mach_port_get_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, &refs); ASSERT_EQ(kr, KERN_SUCCESS); EXPECT_EQ(refs, test.send_rights); } } MULTIPROCESS_TEST_MAIN(FailToRendezvous) { // The rendezvous system uses the BaseBundleID to construct the bootstrap // server name, so changing it will result in a failure to look it up. base::mac::SetBaseBundleID("org.chromium.totallyfake"); CHECK_EQ(nullptr, base::MachPortRendezvousClient::GetInstance()); return 0; } TEST_F(MachPortRendezvousServerTest, FailToRendezvous) { auto* server = MachPortRendezvousServer::GetInstance(); ASSERT_TRUE(server); Process child = SpawnChild("FailToRendezvous"); int exit_code; ASSERT_TRUE(WaitForMultiprocessTestChildExit( child, TestTimeouts::action_timeout(), &exit_code)); EXPECT_EQ(0, exit_code); } } // namespace base