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/containers/buffer_iterator.h"
13 #include "base/logging.h"
14 #include "base/mac/foundation_util.h"
15 #include "base/mac/mach_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(mac::ScopedMachSendRight send_right)62 MachRendezvousPort::MachRendezvousPort(mac::ScopedMachSendRight send_right)
63 : name_(send_right.release()), disposition_(MACH_MSG_TYPE_MOVE_SEND) {}
64
MachRendezvousPort(mac::ScopedMachReceiveRight receive_right)65 MachRendezvousPort::MachRendezvousPort(
66 mac::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 ScopedDispatchObject<dispatch_source_t> exit_watcher(dispatch_source_create(
120 DISPATCH_SOURCE_TYPE_PROC, static_cast<uintptr_t>(pid),
121 DISPATCH_PROC_EXIT, dispatch_source_->queue()));
122 dispatch_source_set_event_handler(exit_watcher, ^{
123 OnClientExited(pid);
124 });
125 dispatch_resume(exit_watcher);
126
127 auto it =
128 client_data_.emplace(pid, ClientData{std::move(exit_watcher), ports});
129 DCHECK(it.second);
130 }
131
ClientData(ScopedDispatchObject<dispatch_source_t> exit_watcher,MachPortsForRendezvous ports)132 MachPortRendezvousServer::ClientData::ClientData(
133 ScopedDispatchObject<dispatch_source_t> exit_watcher,
134 MachPortsForRendezvous ports)
135 : exit_watcher(exit_watcher), ports(ports) {}
136
137 MachPortRendezvousServer::ClientData::ClientData(ClientData&&) = default;
138
139 MachPortRendezvousServer::ClientData::~ClientData() = default;
140
MachPortRendezvousServer()141 MachPortRendezvousServer::MachPortRendezvousServer() {
142 std::string bootstrap_name =
143 StringPrintf(kBootstrapNameFormat, mac::BaseBundleID(), getpid());
144 kern_return_t kr = bootstrap_check_in(
145 bootstrap_port, bootstrap_name.c_str(),
146 mac::ScopedMachReceiveRight::Receiver(server_port_).get());
147 BOOTSTRAP_CHECK(kr == KERN_SUCCESS, kr)
148 << "bootstrap_check_in " << bootstrap_name;
149
150 dispatch_source_ = std::make_unique<DispatchSourceMach>(
151 bootstrap_name.c_str(), server_port_.get(), ^{
152 HandleRequest();
153 });
154 dispatch_source_->Resume();
155 }
156
~MachPortRendezvousServer()157 MachPortRendezvousServer::~MachPortRendezvousServer() {}
158
HandleRequest()159 void MachPortRendezvousServer::HandleRequest() {
160 // Receive the request message, using the kernel audit token to ascertain the
161 // PID of the sender.
162 struct : mach_msg_header_t {
163 mach_msg_audit_trailer_t trailer;
164 } request{};
165 request.msgh_size = sizeof(request);
166 request.msgh_local_port = server_port_.get();
167
168 const mach_msg_option_t options =
169 MACH_RCV_MSG | MACH_RCV_TIMEOUT |
170 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) |
171 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT);
172
173 mach_msg_return_t mr = mach_msg(&request, options, 0, sizeof(request),
174 server_port_.get(), 0, MACH_PORT_NULL);
175 if (mr != KERN_SUCCESS) {
176 MACH_LOG(ERROR, mr) << "mach_msg receive";
177 return;
178 }
179
180 // Destroy the message in case of an early return, which will release
181 // any rights from a bad message. In the case of a disallowed sender,
182 // the destruction of the reply port will break them out of a mach_msg.
183 ScopedMachMsgDestroy scoped_message(&request);
184
185 if (request.msgh_id != kMachRendezvousMsgIdRequest ||
186 request.msgh_size != sizeof(mach_msg_header_t)) {
187 // Do not reply to messages that are unexpected.
188 return;
189 }
190
191 pid_t sender_pid = audit_token_to_pid(request.trailer.msgh_audit);
192 MachPortsForRendezvous ports_to_send = PortsForPid(sender_pid);
193 if (ports_to_send.empty()) {
194 return;
195 }
196
197 std::unique_ptr<uint8_t[]> response =
198 CreateReplyMessage(request.msgh_remote_port, ports_to_send);
199 auto* header = reinterpret_cast<mach_msg_header_t*>(response.get());
200
201 mr = mach_msg(header, MACH_SEND_MSG, header->msgh_size, 0, MACH_PORT_NULL,
202 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
203
204 if (mr == KERN_SUCCESS) {
205 scoped_message.Disarm();
206 } else {
207 MACH_LOG(ERROR, mr) << "mach_msg send";
208 }
209 }
210
PortsForPid(pid_t pid)211 MachPortsForRendezvous MachPortRendezvousServer::PortsForPid(pid_t pid) {
212 MachPortsForRendezvous ports_to_send;
213 AutoLock lock(lock_);
214 auto it = client_data_.find(pid);
215 if (it != client_data_.end()) {
216 ports_to_send = std::move(it->second.ports);
217 client_data_.erase(it);
218 }
219 return ports_to_send;
220 }
221
CreateReplyMessage(mach_port_t reply_port,const MachPortsForRendezvous & ports)222 std::unique_ptr<uint8_t[]> MachPortRendezvousServer::CreateReplyMessage(
223 mach_port_t reply_port,
224 const MachPortsForRendezvous& ports) {
225 const size_t port_count = ports.size();
226 const size_t buffer_size = CalculateResponseSize(port_count);
227 auto buffer = std::make_unique<uint8_t[]>(buffer_size);
228 BufferIterator<uint8_t> iterator(buffer.get(), buffer_size);
229
230 auto* message = iterator.MutableObject<mach_msg_base_t>();
231 message->header.msgh_bits =
232 MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE) |
233 MACH_MSGH_BITS_COMPLEX;
234 message->header.msgh_size = checked_cast<mach_msg_size_t>(buffer_size);
235 message->header.msgh_remote_port = reply_port;
236 message->header.msgh_id = kMachRendezvousMsgIdResponse;
237 message->body.msgh_descriptor_count =
238 checked_cast<mach_msg_size_t>(port_count);
239
240 auto descriptors =
241 iterator.MutableSpan<mach_msg_port_descriptor_t>(port_count);
242 auto port_identifiers =
243 iterator.MutableSpan<MachPortsForRendezvous::key_type>(port_count);
244
245 auto port_it = ports.begin();
246 for (size_t i = 0; i < port_count; ++i, ++port_it) {
247 const MachRendezvousPort& port_for_rendezvous = port_it->second;
248 mach_msg_port_descriptor_t* descriptor = &descriptors[i];
249 descriptor->name = port_for_rendezvous.name();
250 descriptor->disposition = port_for_rendezvous.disposition();
251 descriptor->type = MACH_MSG_PORT_DESCRIPTOR;
252
253 port_identifiers[i] = port_it->first;
254 }
255
256 return buffer;
257 }
258
OnClientExited(pid_t pid)259 void MachPortRendezvousServer::OnClientExited(pid_t pid) {
260 MachPortsForRendezvous ports = PortsForPid(pid);
261 for (auto& pair : ports) {
262 pair.second.Destroy();
263 }
264 }
265
266 // static
GetInstance()267 MachPortRendezvousClient* MachPortRendezvousClient::GetInstance() {
268 static MachPortRendezvousClient* client = []() -> auto* {
269 auto* client = new MachPortRendezvousClient();
270 if (!client->AcquirePorts()) {
271 delete client;
272 client = nullptr;
273 }
274 return client;
275 }
276 ();
277 return client;
278 }
279
TakeSendRight(MachPortsForRendezvous::key_type key)280 mac::ScopedMachSendRight MachPortRendezvousClient::TakeSendRight(
281 MachPortsForRendezvous::key_type key) {
282 MachRendezvousPort port = PortForKey(key);
283 DCHECK(port.disposition() == 0 ||
284 port.disposition() == MACH_MSG_TYPE_PORT_SEND ||
285 port.disposition() == MACH_MSG_TYPE_PORT_SEND_ONCE);
286 return mac::ScopedMachSendRight(port.name());
287 }
288
TakeReceiveRight(MachPortsForRendezvous::key_type key)289 mac::ScopedMachReceiveRight MachPortRendezvousClient::TakeReceiveRight(
290 MachPortsForRendezvous::key_type key) {
291 MachRendezvousPort port = PortForKey(key);
292 DCHECK(port.disposition() == 0 ||
293 port.disposition() == MACH_MSG_TYPE_PORT_RECEIVE);
294 return mac::ScopedMachReceiveRight(port.name());
295 }
296
GetPortCount()297 size_t MachPortRendezvousClient::GetPortCount() {
298 AutoLock lock(lock_);
299 return ports_.size();
300 }
301
302 // static
GetBootstrapName()303 std::string MachPortRendezvousClient::GetBootstrapName() {
304 return StringPrintf(kBootstrapNameFormat, mac::BaseBundleID(), getppid());
305 }
306
AcquirePorts()307 bool MachPortRendezvousClient::AcquirePorts() {
308 AutoLock lock(lock_);
309
310 mac::ScopedMachSendRight server_port;
311 std::string bootstrap_name = GetBootstrapName();
312 kern_return_t kr = bootstrap_look_up(
313 bootstrap_port, const_cast<char*>(bootstrap_name.c_str()),
314 mac::ScopedMachSendRight::Receiver(server_port).get());
315 if (kr != KERN_SUCCESS) {
316 BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_look_up " << bootstrap_name;
317 return false;
318 }
319
320 return SendRequest(std::move(server_port));
321 }
322
SendRequest(mac::ScopedMachSendRight server_port)323 bool MachPortRendezvousClient::SendRequest(
324 mac::ScopedMachSendRight server_port) {
325 const size_t buffer_size = CalculateResponseSize(kMaximumRendezvousPorts) +
326 sizeof(mach_msg_trailer_t);
327 auto buffer = std::make_unique<uint8_t[]>(buffer_size);
328 BufferIterator<uint8_t> iterator(buffer.get(), buffer_size);
329
330 // Perform a send and receive mach_msg.
331 auto* message = iterator.MutableObject<mach_msg_base_t>();
332 message->header.msgh_bits =
333 MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
334 // The |buffer_size| is used for receiving, since it includes space for the
335 // the entire reply and receiving trailer. But for the request being sent,
336 // the size is just an empty message.
337 message->header.msgh_size = sizeof(mach_msg_header_t);
338 message->header.msgh_remote_port = server_port.release();
339 message->header.msgh_local_port = mig_get_reply_port();
340 message->header.msgh_id = kMachRendezvousMsgIdRequest;
341
342 kern_return_t mr = mach_msg(
343 &message->header, MACH_SEND_MSG | MACH_RCV_MSG, message->header.msgh_size,
344 checked_cast<mach_msg_size_t>(buffer_size),
345 message->header.msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
346 if (mr != KERN_SUCCESS) {
347 MACH_LOG(ERROR, mr) << "mach_msg";
348 return false;
349 }
350
351 if (message->header.msgh_id != kMachRendezvousMsgIdResponse) {
352 // Check if the response contains a rendezvous reply. If there were no
353 // ports for this client, then the send right would have been destroyed.
354 if (message->header.msgh_id == MACH_NOTIFY_SEND_ONCE) {
355 return true;
356 }
357 return false;
358 }
359
360 const size_t port_count = message->body.msgh_descriptor_count;
361
362 auto descriptors = iterator.Span<mach_msg_port_descriptor_t>(port_count);
363 auto port_identifiers =
364 iterator.Span<MachPortsForRendezvous::key_type>(port_count);
365
366 if (descriptors.size() != port_identifiers.size()) {
367 // Ensure that the descriptors and keys are of the same size.
368 return false;
369 }
370
371 for (size_t i = 0; i < port_count; ++i) {
372 MachRendezvousPort rendezvous_port(descriptors[i].name,
373 descriptors[i].disposition);
374 ports_.emplace(port_identifiers[i], rendezvous_port);
375 }
376
377 return true;
378 }
379
PortForKey(MachPortsForRendezvous::key_type key)380 MachRendezvousPort MachPortRendezvousClient::PortForKey(
381 MachPortsForRendezvous::key_type key) {
382 AutoLock lock(lock_);
383 auto it = ports_.find(key);
384 MachRendezvousPort port;
385 if (it != ports_.end()) {
386 port = it->second;
387 ports_.erase(it);
388 }
389 return port;
390 }
391
392 MachPortRendezvousClient::MachPortRendezvousClient() = default;
393
394 MachPortRendezvousClient::~MachPortRendezvousClient() = default;
395
396 } // namespace base
397