246 lines
7.2 KiB
C++
246 lines
7.2 KiB
C++
// 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 <mach/mach.h>
|
|
|
|
#include <utility>
|
|
|
|
#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<pid_t, MachPortRendezvousServer::ClientData>& 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
|