1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/mac/mach_port_rendezvous.h"
6
7 #include <mach/mach.h>
8
9 #include <utility>
10
11 #include "base/apple/foundation_util.h"
12 #include "base/apple/mach_logging.h"
13 #include "base/at_exit.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/test/multiprocess_test.h"
16 #include "base/test/test_timeouts.h"
17 #include "base/threading/platform_thread.h"
18 #include "base/time/time.h"
19 #include "testing/multiprocess_func_list.h"
20
21 namespace base {
22
23 namespace {
24
25 constexpr MachPortsForRendezvous::key_type kTestPortKey = 'port';
26
27 } // namespace
28
29 class MachPortRendezvousServerTest : public MultiProcessTest {
30 public:
SetUp()31 void SetUp() override {}
32
client_data()33 std::map<pid_t, MachPortRendezvousServer::ClientData>& client_data() {
34 return MachPortRendezvousServer::GetInstance()->client_data_;
35 }
36
37 private:
38 ShadowingAtExitManager at_exit_;
39 };
40
MULTIPROCESS_TEST_MAIN(TakeSendRight)41 MULTIPROCESS_TEST_MAIN(TakeSendRight) {
42 auto* rendezvous_client = MachPortRendezvousClient::GetInstance();
43 CHECK(rendezvous_client);
44
45 CHECK_EQ(1u, rendezvous_client->GetPortCount());
46
47 apple::ScopedMachSendRight port =
48 rendezvous_client->TakeSendRight(kTestPortKey);
49 CHECK(port.is_valid());
50
51 mach_msg_base_t msg{};
52 msg.header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_COPY_SEND);
53 msg.header.msgh_size = sizeof(msg);
54 msg.header.msgh_remote_port = port.get();
55 msg.header.msgh_id = 'good';
56
57 kern_return_t kr =
58 mach_msg(&msg.header, MACH_SEND_MSG, msg.header.msgh_size, 0,
59 MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
60 MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_msg";
61
62 return 0;
63 }
64
TEST_F(MachPortRendezvousServerTest,SendRight)65 TEST_F(MachPortRendezvousServerTest, SendRight) {
66 auto* server = MachPortRendezvousServer::GetInstance();
67 ASSERT_TRUE(server);
68
69 apple::ScopedMachReceiveRight port;
70 kern_return_t kr =
71 mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
72 apple::ScopedMachReceiveRight::Receiver(port).get());
73 ASSERT_EQ(kr, KERN_SUCCESS);
74
75 MachRendezvousPort rendezvous_port(port.get(), MACH_MSG_TYPE_MAKE_SEND);
76
77 Process child;
78 {
79 AutoLock lock(server->GetLock());
80 child = SpawnChild("TakeSendRight");
81 server->RegisterPortsForPid(
82 child.Pid(), {std::make_pair(kTestPortKey, rendezvous_port)});
83 }
84
85 struct : mach_msg_base_t {
86 mach_msg_trailer_t trailer;
87 } msg{};
88 kr = mach_msg(&msg.header, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, sizeof(msg),
89 port.get(), TestTimeouts::action_timeout().InMilliseconds(),
90 MACH_PORT_NULL);
91
92 EXPECT_EQ(kr, KERN_SUCCESS) << mach_error_string(kr);
93 EXPECT_EQ(msg.header.msgh_id, 'good');
94
95 int exit_code;
96 ASSERT_TRUE(WaitForMultiprocessTestChildExit(
97 child, TestTimeouts::action_timeout(), &exit_code));
98
99 EXPECT_EQ(0, exit_code);
100 }
101
MULTIPROCESS_TEST_MAIN(NoRights)102 MULTIPROCESS_TEST_MAIN(NoRights) {
103 auto* rendezvous_client = MachPortRendezvousClient::GetInstance();
104 CHECK(rendezvous_client);
105 CHECK_EQ(0u, rendezvous_client->GetPortCount());
106 return 0;
107 }
108
TEST_F(MachPortRendezvousServerTest,NoRights)109 TEST_F(MachPortRendezvousServerTest, NoRights) {
110 auto* server = MachPortRendezvousServer::GetInstance();
111 ASSERT_TRUE(server);
112
113 Process child = SpawnChild("NoRights");
114
115 int exit_code;
116 ASSERT_TRUE(WaitForMultiprocessTestChildExit(
117 child, TestTimeouts::action_timeout(), &exit_code));
118
119 EXPECT_EQ(0, exit_code);
120 }
121
MULTIPROCESS_TEST_MAIN(Exit42)122 MULTIPROCESS_TEST_MAIN(Exit42) {
123 _exit(42);
124 }
125
TEST_F(MachPortRendezvousServerTest,CleanupIfNoRendezvous)126 TEST_F(MachPortRendezvousServerTest, CleanupIfNoRendezvous) {
127 auto* server = MachPortRendezvousServer::GetInstance();
128 ASSERT_TRUE(server);
129
130 apple::ScopedMachReceiveRight port;
131 kern_return_t kr =
132 mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
133 apple::ScopedMachReceiveRight::Receiver(port).get());
134 ASSERT_EQ(kr, KERN_SUCCESS);
135
136 MachRendezvousPort rendezvous_port(port.get(), MACH_MSG_TYPE_MAKE_SEND);
137
138 Process child;
139 {
140 AutoLock lock(server->GetLock());
141 child = SpawnChild("Exit42");
142 server->RegisterPortsForPid(
143 child.Pid(), {std::make_pair(kTestPortKey, rendezvous_port)});
144
145 EXPECT_EQ(1u, client_data().size());
146 }
147
148 int exit_code;
149 ASSERT_TRUE(WaitForMultiprocessTestChildExit(
150 child, TestTimeouts::action_timeout(), &exit_code));
151
152 EXPECT_EQ(42, exit_code);
153
154 // There is no way to synchronize the test code with the asynchronous
155 // delivery of the dispatch process-exit notification. Loop for a short
156 // while for it to be delivered.
157 auto start = TimeTicks::Now();
158 do {
159 if (client_data().size() == 0)
160 break;
161 // Sleep is fine because dispatch will process the notification on one of
162 // its workers.
163 PlatformThread::Sleep(Milliseconds(10));
164 } while ((TimeTicks::Now() - start) < TestTimeouts::action_timeout());
165
166 EXPECT_EQ(0u, client_data().size());
167 }
168
TEST_F(MachPortRendezvousServerTest,DestroyRight)169 TEST_F(MachPortRendezvousServerTest, DestroyRight) {
170 const struct {
171 // How to create the port.
172 bool insert_send_right;
173
174 // Disposition for MachRendezvousPort.
175 mach_port_right_t disposition;
176
177 // After calling DestroyRight.
178 bool is_dead_name;
179 mach_port_urefs_t send_rights;
180 } kCases[] = {
181 {true, MACH_MSG_TYPE_MOVE_RECEIVE, true, 0},
182 {true, MACH_MSG_TYPE_MOVE_SEND, false, 0},
183 {true, MACH_MSG_TYPE_COPY_SEND, false, 1},
184 {true, MACH_MSG_TYPE_MAKE_SEND, false, 1},
185 {false, MACH_MSG_TYPE_MAKE_SEND, false, 0},
186 {true, MACH_MSG_TYPE_MAKE_SEND_ONCE, false, 1},
187 // It's not possible to test MOVE_SEND_ONCE since one cannot
188 // insert_right MAKE_SEND_ONCE.
189 };
190
191 for (size_t i = 0; i < std::size(kCases); ++i) {
192 SCOPED_TRACE(base::StringPrintf("case %zu", i).c_str());
193 const auto& test = kCases[i];
194
195 // This test deliberately leaks Mach port rights.
196 mach_port_t port;
197 kern_return_t kr =
198 mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
199 ASSERT_EQ(kr, KERN_SUCCESS);
200
201 if (test.insert_send_right) {
202 kr = mach_port_insert_right(mach_task_self(), port, port,
203 MACH_MSG_TYPE_MAKE_SEND);
204 ASSERT_EQ(kr, KERN_SUCCESS);
205 }
206
207 MachRendezvousPort rendezvous_port(port, test.disposition);
208 rendezvous_port.Destroy();
209
210 mach_port_type_t type = 0;
211 kr = mach_port_type(mach_task_self(), port, &type);
212 ASSERT_EQ(kr, KERN_SUCCESS);
213
214 EXPECT_EQ(type == MACH_PORT_TYPE_DEAD_NAME, test.is_dead_name) << type;
215
216 mach_port_urefs_t refs = 0;
217 kr =
218 mach_port_get_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, &refs);
219 ASSERT_EQ(kr, KERN_SUCCESS);
220 EXPECT_EQ(refs, test.send_rights);
221 }
222 }
223
MULTIPROCESS_TEST_MAIN(FailToRendezvous)224 MULTIPROCESS_TEST_MAIN(FailToRendezvous) {
225 // The rendezvous system uses the BaseBundleID to construct the bootstrap
226 // server name, so changing it will result in a failure to look it up.
227 base::apple::SetBaseBundleID("org.chromium.totallyfake");
228 CHECK_EQ(nullptr, base::MachPortRendezvousClient::GetInstance());
229 return 0;
230 }
231
TEST_F(MachPortRendezvousServerTest,FailToRendezvous)232 TEST_F(MachPortRendezvousServerTest, FailToRendezvous) {
233 auto* server = MachPortRendezvousServer::GetInstance();
234 ASSERT_TRUE(server);
235
236 Process child = SpawnChild("FailToRendezvous");
237
238 int exit_code;
239 ASSERT_TRUE(WaitForMultiprocessTestChildExit(
240 child, TestTimeouts::action_timeout(), &exit_code));
241
242 EXPECT_EQ(0, exit_code);
243 }
244
245 } // namespace base
246