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