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/mig.h>
8 #include <unistd.h>
9
10 #include <utility>
11
12 #include "base/apple/foundation_util.h"
13 #include "base/apple/mach_logging.h"
14 #include "base/containers/buffer_iterator.h"
15 #include "base/logging.h"
16 #include "base/mac/scoped_mach_msg_destroy.h"
17 #include "base/notreached.h"
18 #include "base/strings/stringprintf.h"
19
20 #if BUILDFLAG(IS_IOS)
21 #include "base/ios/sim_header_shims.h"
22 #else
23 #include <bsm/libbsm.h>
24 #include <servers/bootstrap.h>
25 #endif
26
27 namespace base {
28
29 namespace {
30
31 #if BUILDFLAG(IS_IOS)
32 static MachPortRendezvousClient* g_client = nullptr;
33 #else
34 // The name to use in the bootstrap server, formatted with the BaseBundleID and
35 // PID of the server.
36 constexpr char kBootstrapNameFormat[] = "%s.MachPortRendezvousServer.%d";
37 #endif
38
39 // This limit is arbitrary and can be safely increased in the future.
40 constexpr size_t kMaximumRendezvousPorts = 5;
41
42 enum MachRendezvousMsgId : mach_msg_id_t {
43 kMachRendezvousMsgIdRequest = 'mrzv',
44 kMachRendezvousMsgIdResponse = 'MRZV',
45 };
46
CalculateResponseSize(size_t num_ports)47 size_t CalculateResponseSize(size_t num_ports) {
48 return sizeof(mach_msg_base_t) +
49 (num_ports * sizeof(mach_msg_port_descriptor_t)) +
50 (num_ports * sizeof(MachPortsForRendezvous::key_type));
51 }
52
53 } // namespace
54
MachRendezvousPort(mach_port_t name,mach_msg_type_name_t disposition)55 MachRendezvousPort::MachRendezvousPort(mach_port_t name,
56 mach_msg_type_name_t disposition)
57 : name_(name), disposition_(disposition) {
58 DCHECK(disposition == MACH_MSG_TYPE_MOVE_RECEIVE ||
59 disposition == MACH_MSG_TYPE_MOVE_SEND ||
60 disposition == MACH_MSG_TYPE_MOVE_SEND_ONCE ||
61 disposition == MACH_MSG_TYPE_COPY_SEND ||
62 disposition == MACH_MSG_TYPE_MAKE_SEND ||
63 disposition == MACH_MSG_TYPE_MAKE_SEND_ONCE);
64 }
65
MachRendezvousPort(apple::ScopedMachSendRight send_right)66 MachRendezvousPort::MachRendezvousPort(apple::ScopedMachSendRight send_right)
67 : name_(send_right.release()), disposition_(MACH_MSG_TYPE_MOVE_SEND) {}
68
MachRendezvousPort(apple::ScopedMachReceiveRight receive_right)69 MachRendezvousPort::MachRendezvousPort(
70 apple::ScopedMachReceiveRight receive_right)
71 : name_(receive_right.release()),
72 disposition_(MACH_MSG_TYPE_MOVE_RECEIVE) {}
73
74 MachRendezvousPort::~MachRendezvousPort() = default;
75
Destroy()76 void MachRendezvousPort::Destroy() {
77 // Map the disposition to the type of right to deallocate.
78 mach_port_right_t right = 0;
79 switch (disposition_) {
80 case 0:
81 DCHECK(name_ == MACH_PORT_NULL);
82 return;
83 case MACH_MSG_TYPE_COPY_SEND:
84 case MACH_MSG_TYPE_MAKE_SEND:
85 case MACH_MSG_TYPE_MAKE_SEND_ONCE:
86 // Right is not owned, would be created by transit.
87 return;
88 case MACH_MSG_TYPE_MOVE_RECEIVE:
89 right = MACH_PORT_RIGHT_RECEIVE;
90 break;
91 case MACH_MSG_TYPE_MOVE_SEND:
92 right = MACH_PORT_RIGHT_SEND;
93 break;
94 case MACH_MSG_TYPE_MOVE_SEND_ONCE:
95 right = MACH_PORT_RIGHT_SEND_ONCE;
96 break;
97 default:
98 NOTREACHED() << "Leaking port name " << name_ << " with disposition "
99 << disposition_;
100 return;
101 }
102 kern_return_t kr = mach_port_mod_refs(mach_task_self(), name_, right, -1);
103 MACH_DCHECK(kr == KERN_SUCCESS, kr)
104 << "Failed to drop ref on port name " << name_;
105
106 name_ = MACH_PORT_NULL;
107 disposition_ = 0;
108 }
109
110 MachPortRendezvousServerBase::MachPortRendezvousServerBase() = default;
111 MachPortRendezvousServerBase::~MachPortRendezvousServerBase() = default;
112
HandleRequest()113 void MachPortRendezvousServerBase::HandleRequest() {
114 // Receive the request message, using the kernel audit token to ascertain the
115 // PID of the sender.
116 struct : mach_msg_header_t {
117 mach_msg_audit_trailer_t trailer;
118 } request{};
119 request.msgh_size = sizeof(request);
120 request.msgh_local_port = server_port_.get();
121
122 const mach_msg_option_t options =
123 MACH_RCV_MSG | MACH_RCV_TIMEOUT |
124 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) |
125 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT);
126
127 mach_msg_return_t mr = mach_msg(&request, options, 0, sizeof(request),
128 server_port_.get(), 0, MACH_PORT_NULL);
129 if (mr != KERN_SUCCESS) {
130 MACH_LOG(ERROR, mr) << "mach_msg receive";
131 return;
132 }
133
134 // Destroy the message in case of an early return, which will release
135 // any rights from a bad message. In the case of a disallowed sender,
136 // the destruction of the reply port will break them out of a mach_msg.
137 ScopedMachMsgDestroy scoped_message(&request);
138
139 if (request.msgh_id != kMachRendezvousMsgIdRequest ||
140 request.msgh_size != sizeof(mach_msg_header_t)) {
141 // Do not reply to messages that are unexpected.
142 return;
143 }
144
145 #if BUILDFLAG(IS_IOS)
146 MachPortsForRendezvous ports_to_send = PortsForPid(0);
147 #else
148 pid_t sender_pid = audit_token_to_pid(request.trailer.msgh_audit);
149 MachPortsForRendezvous ports_to_send = PortsForPid(sender_pid);
150 #endif
151
152 if (ports_to_send.empty()) {
153 return;
154 }
155
156 std::unique_ptr<uint8_t[]> response =
157 CreateReplyMessage(request.msgh_remote_port, ports_to_send);
158 auto* header = reinterpret_cast<mach_msg_header_t*>(response.get());
159
160 mr = mach_msg(header, MACH_SEND_MSG, header->msgh_size, 0, MACH_PORT_NULL,
161 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
162
163 if (mr == KERN_SUCCESS) {
164 scoped_message.Disarm();
165 } else {
166 MACH_LOG(ERROR, mr) << "mach_msg send";
167 }
168 }
169
CreateReplyMessage(mach_port_t reply_port,const MachPortsForRendezvous & ports)170 std::unique_ptr<uint8_t[]> MachPortRendezvousServerBase::CreateReplyMessage(
171 mach_port_t reply_port,
172 const MachPortsForRendezvous& ports) {
173 const size_t port_count = ports.size();
174 const size_t buffer_size = CalculateResponseSize(port_count);
175 auto buffer = std::make_unique<uint8_t[]>(buffer_size);
176 BufferIterator<uint8_t> iterator(buffer.get(), buffer_size);
177
178 auto* message = iterator.MutableObject<mach_msg_base_t>();
179 message->header.msgh_bits =
180 MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE) |
181 MACH_MSGH_BITS_COMPLEX;
182 message->header.msgh_size = checked_cast<mach_msg_size_t>(buffer_size);
183 message->header.msgh_remote_port = reply_port;
184 message->header.msgh_id = kMachRendezvousMsgIdResponse;
185 message->body.msgh_descriptor_count =
186 checked_cast<mach_msg_size_t>(port_count);
187
188 auto descriptors =
189 iterator.MutableSpan<mach_msg_port_descriptor_t>(port_count);
190 auto port_identifiers =
191 iterator.MutableSpan<MachPortsForRendezvous::key_type>(port_count);
192
193 auto port_it = ports.begin();
194 for (size_t i = 0; i < port_count; ++i, ++port_it) {
195 const MachRendezvousPort& port_for_rendezvous = port_it->second;
196 mach_msg_port_descriptor_t* descriptor = &descriptors[i];
197 descriptor->name = port_for_rendezvous.name();
198 descriptor->disposition = port_for_rendezvous.disposition();
199 descriptor->type = MACH_MSG_PORT_DESCRIPTOR;
200
201 port_identifiers[i] = port_it->first;
202 }
203
204 return buffer;
205 }
206
207 MachPortRendezvousServer::~MachPortRendezvousServer() = default;
208
209 #if BUILDFLAG(IS_IOS)
GetMachSendRight()210 apple::ScopedMachSendRight MachPortRendezvousServer::GetMachSendRight() {
211 return apple::RetainMachSendRight(send_right_.get());
212 }
213
MachPortRendezvousServer(const MachPortsForRendezvous & ports)214 MachPortRendezvousServer::MachPortRendezvousServer(
215 const MachPortsForRendezvous& ports)
216 : ports_(ports) {
217 DCHECK_LT(ports_.size(), kMaximumRendezvousPorts);
218 bool res = apple::CreateMachPort(&server_port_, &send_right_);
219 CHECK(res) << "Failed to create mach server port";
220 dispatch_source_ = std::make_unique<apple::DispatchSourceMach>(
221 "MachPortRendezvousServer", server_port_.get(), ^{
222 HandleRequest();
223 });
224 dispatch_source_->Resume();
225 }
226
PortsForPid(int pid)227 MachPortsForRendezvous MachPortRendezvousServer::PortsForPid(int pid) {
228 CHECK_EQ(pid, 0);
229 return ports_;
230 }
231
232 #else
233
234 // static
GetInstance()235 MachPortRendezvousServer* MachPortRendezvousServer::GetInstance() {
236 static auto* instance = new MachPortRendezvousServer();
237 return instance;
238 }
239
RegisterPortsForPid(pid_t pid,const MachPortsForRendezvous & ports)240 void MachPortRendezvousServer::RegisterPortsForPid(
241 pid_t pid,
242 const MachPortsForRendezvous& ports) {
243 lock_.AssertAcquired();
244 DCHECK_LT(ports.size(), kMaximumRendezvousPorts);
245 DCHECK(!ports.empty());
246
247 apple::ScopedDispatchObject<dispatch_source_t> exit_watcher(
248 dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC,
249 static_cast<uintptr_t>(pid), DISPATCH_PROC_EXIT,
250 dispatch_source_->Queue()));
251 dispatch_source_set_event_handler(exit_watcher.get(), ^{
252 OnClientExited(pid);
253 });
254 dispatch_resume(exit_watcher.get());
255
256 auto it =
257 client_data_.emplace(pid, ClientData{std::move(exit_watcher), ports});
258 DCHECK(it.second);
259 }
260
ClientData(apple::ScopedDispatchObject<dispatch_source_t> exit_watcher,MachPortsForRendezvous ports)261 MachPortRendezvousServer::ClientData::ClientData(
262 apple::ScopedDispatchObject<dispatch_source_t> exit_watcher,
263 MachPortsForRendezvous ports)
264 : exit_watcher(exit_watcher), ports(ports) {}
265
266 MachPortRendezvousServer::ClientData::ClientData(ClientData&&) = default;
267
268 MachPortRendezvousServer::ClientData::~ClientData() = default;
269
MachPortRendezvousServer()270 MachPortRendezvousServer::MachPortRendezvousServer() {
271 std::string bootstrap_name =
272 StringPrintf(kBootstrapNameFormat, apple::BaseBundleID(), getpid());
273 kern_return_t kr = bootstrap_check_in(
274 bootstrap_port, bootstrap_name.c_str(),
275 apple::ScopedMachReceiveRight::Receiver(server_port_).get());
276 BOOTSTRAP_CHECK(kr == KERN_SUCCESS, kr)
277 << "bootstrap_check_in " << bootstrap_name;
278 dispatch_source_ = std::make_unique<apple::DispatchSourceMach>(
279 bootstrap_name.c_str(), server_port_.get(), ^{
280 HandleRequest();
281 });
282 dispatch_source_->Resume();
283 }
284
PortsForPid(int pid)285 MachPortsForRendezvous MachPortRendezvousServer::PortsForPid(int pid) {
286 MachPortsForRendezvous ports_to_send;
287 AutoLock lock(lock_);
288 auto it = client_data_.find(pid);
289 if (it != client_data_.end()) {
290 ports_to_send = std::move(it->second.ports);
291 client_data_.erase(it);
292 }
293 return ports_to_send;
294 }
295
OnClientExited(pid_t pid)296 void MachPortRendezvousServer::OnClientExited(pid_t pid) {
297 MachPortsForRendezvous ports = PortsForPid(pid);
298 for (auto& pair : ports) {
299 pair.second.Destroy();
300 }
301 }
302 #endif
303
304 // static
GetInstance()305 MachPortRendezvousClient* MachPortRendezvousClient::GetInstance() {
306 #if BUILDFLAG(IS_IOS)
307 CHECK(g_client);
308 return g_client;
309 #else
310 static MachPortRendezvousClient* client = []() -> auto* {
311 auto* client = new MachPortRendezvousClient();
312 if (!client->AcquirePorts()) {
313 delete client;
314 client = nullptr;
315 }
316 return client;
317 }
318 ();
319 return client;
320 #endif
321 }
322
TakeSendRight(MachPortsForRendezvous::key_type key)323 apple::ScopedMachSendRight MachPortRendezvousClient::TakeSendRight(
324 MachPortsForRendezvous::key_type key) {
325 MachRendezvousPort port = PortForKey(key);
326 DCHECK(port.disposition() == 0 ||
327 port.disposition() == MACH_MSG_TYPE_PORT_SEND ||
328 port.disposition() == MACH_MSG_TYPE_PORT_SEND_ONCE);
329 return apple::ScopedMachSendRight(port.name());
330 }
331
TakeReceiveRight(MachPortsForRendezvous::key_type key)332 apple::ScopedMachReceiveRight MachPortRendezvousClient::TakeReceiveRight(
333 MachPortsForRendezvous::key_type key) {
334 MachRendezvousPort port = PortForKey(key);
335 DCHECK(port.disposition() == 0 ||
336 port.disposition() == MACH_MSG_TYPE_PORT_RECEIVE);
337 return apple::ScopedMachReceiveRight(port.name());
338 }
339
GetPortCount()340 size_t MachPortRendezvousClient::GetPortCount() {
341 AutoLock lock(lock_);
342 return ports_.size();
343 }
344
345 #if BUILDFLAG(IS_IOS)
346
Initialize(apple::ScopedMachSendRight server_port)347 bool MachPortRendezvousClient::Initialize(
348 apple::ScopedMachSendRight server_port) {
349 CHECK(!g_client);
350 g_client = new MachPortRendezvousClient();
351 if (!g_client->AcquirePorts(std::move(server_port))) {
352 delete g_client;
353 g_client = nullptr;
354 }
355 return true;
356 }
357
AcquirePorts(apple::ScopedMachSendRight server_port)358 bool MachPortRendezvousClient::AcquirePorts(
359 apple::ScopedMachSendRight server_port) {
360 AutoLock lock(lock_);
361 return SendRequest(std::move(server_port));
362 }
363
364 #else
365
366 // static
GetBootstrapName()367 std::string MachPortRendezvousClient::GetBootstrapName() {
368 return StringPrintf(kBootstrapNameFormat, apple::BaseBundleID(), getppid());
369 }
370
AcquirePorts()371 bool MachPortRendezvousClient::AcquirePorts() {
372 AutoLock lock(lock_);
373
374 apple::ScopedMachSendRight server_port;
375 std::string bootstrap_name = GetBootstrapName();
376 kern_return_t kr = bootstrap_look_up(
377 bootstrap_port, const_cast<char*>(bootstrap_name.c_str()),
378 apple::ScopedMachSendRight::Receiver(server_port).get());
379 if (kr != KERN_SUCCESS) {
380 BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_look_up " << bootstrap_name;
381 return false;
382 }
383
384 return SendRequest(std::move(server_port));
385 }
386 #endif
387
SendRequest(apple::ScopedMachSendRight server_port)388 bool MachPortRendezvousClient::SendRequest(
389 apple::ScopedMachSendRight server_port) {
390 const size_t buffer_size = CalculateResponseSize(kMaximumRendezvousPorts) +
391 sizeof(mach_msg_trailer_t);
392 auto buffer = std::make_unique<uint8_t[]>(buffer_size);
393 BufferIterator<uint8_t> iterator(buffer.get(), buffer_size);
394
395 // Perform a send and receive mach_msg.
396 auto* message = iterator.MutableObject<mach_msg_base_t>();
397 message->header.msgh_bits =
398 MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
399 // The |buffer_size| is used for receiving, since it includes space for the
400 // the entire reply and receiving trailer. But for the request being sent,
401 // the size is just an empty message.
402 message->header.msgh_size = sizeof(mach_msg_header_t);
403 message->header.msgh_remote_port = server_port.release();
404 message->header.msgh_local_port = mig_get_reply_port();
405 message->header.msgh_id = kMachRendezvousMsgIdRequest;
406
407 kern_return_t mr = mach_msg(
408 &message->header, MACH_SEND_MSG | MACH_RCV_MSG, message->header.msgh_size,
409 checked_cast<mach_msg_size_t>(buffer_size),
410 message->header.msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
411 if (mr != KERN_SUCCESS) {
412 MACH_LOG(ERROR, mr) << "mach_msg";
413 return false;
414 }
415
416 if (message->header.msgh_id != kMachRendezvousMsgIdResponse) {
417 // Check if the response contains a rendezvous reply. If there were no
418 // ports for this client, then the send right would have been destroyed.
419 if (message->header.msgh_id == MACH_NOTIFY_SEND_ONCE) {
420 return true;
421 }
422 return false;
423 }
424
425 const size_t port_count = message->body.msgh_descriptor_count;
426
427 auto descriptors = iterator.Span<mach_msg_port_descriptor_t>(port_count);
428 auto port_identifiers =
429 iterator.Span<MachPortsForRendezvous::key_type>(port_count);
430
431 if (descriptors.size() != port_identifiers.size()) {
432 // Ensure that the descriptors and keys are of the same size.
433 return false;
434 }
435
436 for (size_t i = 0; i < port_count; ++i) {
437 MachRendezvousPort rendezvous_port(descriptors[i].name,
438 descriptors[i].disposition);
439 ports_.emplace(port_identifiers[i], rendezvous_port);
440 }
441
442 return true;
443 }
444
PortForKey(MachPortsForRendezvous::key_type key)445 MachRendezvousPort MachPortRendezvousClient::PortForKey(
446 MachPortsForRendezvous::key_type key) {
447 AutoLock lock(lock_);
448 auto it = ports_.find(key);
449 MachRendezvousPort port;
450 if (it != ports_.end()) {
451 port = it->second;
452 ports_.erase(it);
453 }
454 return port;
455 }
456
457 MachPortRendezvousClient::MachPortRendezvousClient() = default;
458
459 MachPortRendezvousClient::~MachPortRendezvousClient() = default;
460
461 } // namespace base
462