1 // Copyright 2014 The Crashpad Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "util/mach/child_port_handshake.h"
16
17 #include <errno.h>
18 #include <pthread.h>
19 #include <stdint.h>
20 #include <sys/event.h>
21 #include <sys/socket.h>
22 #include <sys/time.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25
26 #include <algorithm>
27 #include <utility>
28
29 #include "base/check_op.h"
30 #include "base/logging.h"
31 #include "base/mac/mach_logging.h"
32 #include "base/mac/scoped_mach_port.h"
33 #include "base/notreached.h"
34 #include "base/posix/eintr_wrapper.h"
35 #include "base/rand_util.h"
36 #include "base/stl_util.h"
37 #include "base/strings/stringprintf.h"
38 #include "util/file/file_io.h"
39 #include "util/mach/bootstrap.h"
40 #include "util/mach/child_port.h"
41 #include "util/mach/child_port_server.h"
42 #include "util/mach/mach_extensions.h"
43 #include "util/mach/mach_message.h"
44 #include "util/mach/mach_message_server.h"
45 #include "util/misc/implicit_cast.h"
46 #include "util/misc/random_string.h"
47
48 namespace crashpad {
49 namespace {
50
51 class ChildPortHandshakeServer final : public ChildPortServer::Interface {
52 public:
53 ChildPortHandshakeServer();
54 ~ChildPortHandshakeServer();
55
56 mach_port_t RunServer(base::ScopedFD server_write_fd,
57 ChildPortHandshake::PortRightType port_right_type);
58
59 private:
60 // ChildPortServer::Interface:
61 kern_return_t HandleChildPortCheckIn(child_port_server_t server,
62 child_port_token_t token,
63 mach_port_t port,
64 mach_msg_type_name_t right_type,
65 const mach_msg_trailer_t* trailer,
66 bool* destroy_request) override;
67
68 child_port_token_t token_;
69 mach_port_t port_;
70 mach_msg_type_name_t right_type_;
71 bool checked_in_;
72
73 DISALLOW_COPY_AND_ASSIGN(ChildPortHandshakeServer);
74 };
75
ChildPortHandshakeServer()76 ChildPortHandshakeServer::ChildPortHandshakeServer()
77 : token_(0),
78 port_(MACH_PORT_NULL),
79 right_type_(MACH_MSG_TYPE_PORT_NONE),
80 checked_in_(false) {
81 }
82
~ChildPortHandshakeServer()83 ChildPortHandshakeServer::~ChildPortHandshakeServer() {
84 }
85
RunServer(base::ScopedFD server_write_fd,ChildPortHandshake::PortRightType port_right_type)86 mach_port_t ChildPortHandshakeServer::RunServer(
87 base::ScopedFD server_write_fd,
88 ChildPortHandshake::PortRightType port_right_type) {
89 DCHECK_EQ(port_, kMachPortNull);
90 DCHECK(!checked_in_);
91 DCHECK(server_write_fd.is_valid());
92
93 // Initialize the token and share it with the client via the pipe.
94 token_ = base::RandUint64();
95 if (!LoggingWriteFile(server_write_fd.get(), &token_, sizeof(token_))) {
96 LOG(WARNING) << "no client check-in";
97 return MACH_PORT_NULL;
98 }
99
100 // Create a unique name for the bootstrap service mapping. Make it unguessable
101 // to prevent outsiders from grabbing the name first, which would cause
102 // bootstrap_check_in() to fail.
103 uint64_t thread_id;
104 errno = pthread_threadid_np(pthread_self(), &thread_id);
105 PCHECK(errno == 0) << "pthread_threadid_np";
106 std::string service_name = base::StringPrintf(
107 "org.chromium.crashpad.child_port_handshake.%d.%llu.%s",
108 getpid(),
109 thread_id,
110 RandomString().c_str());
111
112 // Check the new service in with the bootstrap server, obtaining a receive
113 // right for it.
114 base::mac::ScopedMachReceiveRight server_port(BootstrapCheckIn(service_name));
115 CHECK(server_port.is_valid());
116
117 // Share the service name with the client via the pipe.
118 uint32_t service_name_length = service_name.size();
119 if (!LoggingWriteFile(server_write_fd.get(),
120 &service_name_length,
121 sizeof(service_name_length))) {
122 LOG(WARNING) << "no client check-in";
123 return MACH_PORT_NULL;
124 }
125
126 if (!LoggingWriteFile(
127 server_write_fd.get(), service_name.c_str(), service_name_length)) {
128 LOG(WARNING) << "no client check-in";
129 return MACH_PORT_NULL;
130 }
131
132 // Prior to macOS 10.12, a kqueue cannot monitor a raw Mach receive right with
133 // EVFILT_MACHPORT. It requires a port set. Compare 10.11.6
134 // xnu-3248.60.10/osfmk/ipc/ipc_pset.c filt_machportattach(), which requires
135 // MACH_PORT_RIGHT_PORT_SET, to 10.12.0 xnu-3789.1.32/osfmk/ipc/ipc_pset.c
136 // filt_machportattach(), which also handles MACH_PORT_TYPE_RECEIVE. Create a
137 // new port set and add the receive right to it.
138 base::mac::ScopedMachPortSet server_port_set(
139 NewMachPort(MACH_PORT_RIGHT_PORT_SET));
140 CHECK(server_port_set.is_valid());
141
142 kern_return_t kr = mach_port_insert_member(
143 mach_task_self(), server_port.get(), server_port_set.get());
144 MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_member";
145
146 // Set up a kqueue to monitor both the server’s receive right and the write
147 // side of the pipe. Messages from the client will be received via the receive
148 // right, and the pipe will show EOF if the client closes its read side
149 // prematurely.
150 base::ScopedFD kq(kqueue());
151 PCHECK(kq != -1) << "kqueue";
152
153 struct kevent changelist[2];
154 EV_SET(&changelist[0],
155 server_port_set.get(),
156 EVFILT_MACHPORT,
157 EV_ADD | EV_CLEAR,
158 0,
159 0,
160 nullptr);
161 EV_SET(&changelist[1],
162 server_write_fd.get(),
163 EVFILT_WRITE,
164 EV_ADD | EV_CLEAR,
165 0,
166 0,
167 nullptr);
168 int rv = HANDLE_EINTR(kevent(
169 kq.get(), changelist, base::size(changelist), nullptr, 0, nullptr));
170 PCHECK(rv != -1) << "kevent";
171
172 ChildPortServer child_port_server(this);
173
174 bool blocking = true;
175 DCHECK(!checked_in_);
176 while (!checked_in_) {
177 DCHECK_EQ(port_, kMachPortNull);
178
179 // Get a kevent from the kqueue. Block while waiting for an event unless the
180 // write pipe has arrived at EOF, in which case the kevent() should be
181 // nonblocking. Although the client sends its check-in message before
182 // closing the read side of the pipe, this organization allows the events to
183 // be delivered out of order and the check-in message will still be
184 // processed.
185 struct kevent event;
186 constexpr timespec nonblocking_timeout = {};
187 const timespec* timeout = blocking ? nullptr : &nonblocking_timeout;
188 rv = HANDLE_EINTR(kevent(kq.get(), nullptr, 0, &event, 1, timeout));
189 PCHECK(rv != -1) << "kevent";
190
191 if (rv == 0) {
192 // Non-blocking kevent() with no events to return.
193 DCHECK(!blocking);
194 LOG(WARNING) << "no client check-in";
195 return MACH_PORT_NULL;
196 }
197
198 DCHECK_EQ(rv, 1);
199
200 if (event.flags & EV_ERROR) {
201 // kevent() may have put its error here.
202 errno = event.data;
203 PLOG(FATAL) << "kevent";
204 }
205
206 switch (event.filter) {
207 case EVFILT_MACHPORT: {
208 // There’s something to receive on the port set.
209 DCHECK_EQ(event.ident, server_port_set.get());
210
211 // Run the message server in an inner loop instead of using
212 // MachMessageServer::kPersistent. This allows the loop to exit as soon
213 // as child_port_ is set, even if other messages are queued. This needs
214 // to drain all messages, because the use of edge triggering (EV_CLEAR)
215 // means that if more than one message is in the queue when kevent()
216 // returns, no more notifications will be generated.
217 while (!checked_in_) {
218 // If a proper message is received from child_port_check_in(),
219 // this will call HandleChildPortCheckIn().
220 mach_msg_return_t mr =
221 MachMessageServer::Run(&child_port_server,
222 server_port_set.get(),
223 MACH_MSG_OPTION_NONE,
224 MachMessageServer::kOneShot,
225 MachMessageServer::kReceiveLargeIgnore,
226 kMachMessageTimeoutNonblocking);
227 if (mr == MACH_RCV_TIMED_OUT) {
228 break;
229 } else if (mr != MACH_MSG_SUCCESS) {
230 MACH_LOG(ERROR, mr) << "MachMessageServer::Run";
231 return MACH_PORT_NULL;
232 }
233 }
234 break;
235 }
236
237 case EVFILT_WRITE:
238 // The write pipe is ready to be written to, or it’s at EOF. The former
239 // case is uninteresting, but a notification for this may be presented
240 // because the write pipe will be ready to be written to, at the latest,
241 // when the client reads its messages from the read side of the same
242 // pipe. Ignore that case. Multiple notifications for that situation
243 // will not be generated because edge triggering (EV_CLEAR) is used
244 // above.
245 DCHECK_EQ(implicit_cast<int>(event.ident), server_write_fd.get());
246 if (event.flags & EV_EOF) {
247 // There are no readers attached to the write pipe. The client has
248 // closed its side of the pipe. There can be one last shot at
249 // receiving messages, in case the check-in message is delivered
250 // out of order, after the EOF notification.
251 blocking = false;
252 }
253 break;
254
255 default:
256 NOTREACHED();
257 break;
258 }
259 }
260
261 if (port_ == MACH_PORT_NULL) {
262 return MACH_PORT_NULL;
263 }
264
265 bool mismatch = false;
266 switch (port_right_type) {
267 case ChildPortHandshake::PortRightType::kReceiveRight:
268 if (right_type_ != MACH_MSG_TYPE_PORT_RECEIVE) {
269 LOG(ERROR) << "expected receive right, observed " << right_type_;
270 mismatch = true;
271 }
272 break;
273 case ChildPortHandshake::PortRightType::kSendRight:
274 if (right_type_ != MACH_MSG_TYPE_PORT_SEND &&
275 right_type_ != MACH_MSG_TYPE_PORT_SEND_ONCE) {
276 LOG(ERROR) << "expected send or send-once right, observed "
277 << right_type_;
278 mismatch = true;
279 }
280 break;
281 }
282
283 if (mismatch) {
284 MachMessageDestroyReceivedPort(port_, right_type_);
285 port_ = MACH_PORT_NULL;
286 return MACH_PORT_NULL;
287 }
288
289 mach_port_t port = MACH_PORT_NULL;
290 std::swap(port_, port);
291 return port;
292 }
293
HandleChildPortCheckIn(child_port_server_t server,const child_port_token_t token,mach_port_t port,mach_msg_type_name_t right_type,const mach_msg_trailer_t * trailer,bool * destroy_request)294 kern_return_t ChildPortHandshakeServer::HandleChildPortCheckIn(
295 child_port_server_t server,
296 const child_port_token_t token,
297 mach_port_t port,
298 mach_msg_type_name_t right_type,
299 const mach_msg_trailer_t* trailer,
300 bool* destroy_request) {
301 DCHECK_EQ(port_, kMachPortNull);
302 DCHECK(!checked_in_);
303
304 if (token != token_) {
305 // If the token’s not correct, someone’s attempting to spoof the legitimate
306 // client.
307 LOG(WARNING) << "ignoring incorrect token";
308 *destroy_request = true;
309 } else {
310 checked_in_ = true;
311
312 if (right_type != MACH_MSG_TYPE_PORT_RECEIVE &&
313 right_type != MACH_MSG_TYPE_PORT_SEND &&
314 right_type != MACH_MSG_TYPE_PORT_SEND_ONCE) {
315 // The message needs to carry a receive, send, or send-once right.
316 LOG(ERROR) << "invalid right type " << right_type;
317 *destroy_request = true;
318 } else {
319 // Communicate the child port and right type back to the RunServer().
320 // *destroy_request is left at false, because RunServer() needs the right
321 // to remain intact. It gives ownership of the right to its caller.
322 port_ = port;
323 right_type_ = right_type;
324 }
325 }
326
327 // This is a MIG simpleroutine, there is no reply message.
328 return MIG_NO_REPLY;
329 }
330
331 } // namespace
332
ChildPortHandshake()333 ChildPortHandshake::ChildPortHandshake()
334 : client_read_fd_(),
335 server_write_fd_() {
336 // Use socketpair() instead of pipe(). There is no way to suppress SIGPIPE on
337 // pipes in Mac OS X 10.6, because the F_SETNOSIGPIPE fcntl() command was not
338 // introduced until 10.7.
339 int pipe_fds[2];
340 PCHECK(socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_fds) == 0)
341 << "socketpair";
342
343 client_read_fd_.reset(pipe_fds[0]);
344 server_write_fd_.reset(pipe_fds[1]);
345
346 // Simulate pipe() semantics by shutting down the “wrong” sides of the socket.
347 PCHECK(shutdown(server_write_fd_.get(), SHUT_RD) == 0) << "shutdown SHUT_RD";
348 PCHECK(shutdown(client_read_fd_.get(), SHUT_WR) == 0) << "shutdown SHUT_WR";
349
350 // SIGPIPE is undesirable when writing to this pipe. Allow broken-pipe writes
351 // to fail with EPIPE instead.
352 constexpr int value = 1;
353 PCHECK(setsockopt(server_write_fd_.get(),
354 SOL_SOCKET,
355 SO_NOSIGPIPE,
356 &value,
357 sizeof(value)) == 0) << "setsockopt";
358 }
359
~ChildPortHandshake()360 ChildPortHandshake::~ChildPortHandshake() {
361 }
362
ClientReadFD()363 base::ScopedFD ChildPortHandshake::ClientReadFD() {
364 DCHECK(client_read_fd_.is_valid());
365 return std::move(client_read_fd_);
366 }
367
ServerWriteFD()368 base::ScopedFD ChildPortHandshake::ServerWriteFD() {
369 DCHECK(server_write_fd_.is_valid());
370 return std::move(server_write_fd_);
371 }
372
RunServer(PortRightType port_right_type)373 mach_port_t ChildPortHandshake::RunServer(PortRightType port_right_type) {
374 client_read_fd_.reset();
375 return RunServerForFD(std::move(server_write_fd_), port_right_type);
376 }
377
RunClient(mach_port_t port,mach_msg_type_name_t right_type)378 bool ChildPortHandshake::RunClient(mach_port_t port,
379 mach_msg_type_name_t right_type) {
380 server_write_fd_.reset();
381 return RunClientForFD(std::move(client_read_fd_), port, right_type);
382 }
383
384 // static
RunServerForFD(base::ScopedFD server_write_fd,PortRightType port_right_type)385 mach_port_t ChildPortHandshake::RunServerForFD(base::ScopedFD server_write_fd,
386 PortRightType port_right_type) {
387 ChildPortHandshakeServer server;
388 return server.RunServer(std::move(server_write_fd), port_right_type);
389 }
390
391 // static
RunClientForFD(base::ScopedFD client_read_fd,mach_port_t port,mach_msg_type_name_t right_type)392 bool ChildPortHandshake::RunClientForFD(base::ScopedFD client_read_fd,
393 mach_port_t port,
394 mach_msg_type_name_t right_type) {
395 DCHECK(client_read_fd.is_valid());
396
397 // Read the token and the service name from the read side of the pipe.
398 child_port_token_t token;
399 std::string service_name;
400 if (!RunClientInternal_ReadPipe(
401 client_read_fd.get(), &token, &service_name)) {
402 return false;
403 }
404
405 // Look up the server and check in with it by providing the token and port.
406 return RunClientInternal_SendCheckIn(service_name, token, port, right_type);
407 }
408
409 // static
RunClientInternal_ReadPipe(int client_read_fd,child_port_token_t * token,std::string * service_name)410 bool ChildPortHandshake::RunClientInternal_ReadPipe(int client_read_fd,
411 child_port_token_t* token,
412 std::string* service_name) {
413 // Read the token from the pipe.
414 if (!LoggingReadFileExactly(client_read_fd, token, sizeof(*token))) {
415 return false;
416 }
417
418 // Read the service name from the pipe.
419 uint32_t service_name_length;
420 if (!LoggingReadFileExactly(
421 client_read_fd, &service_name_length, sizeof(service_name_length))) {
422 return false;
423 }
424
425 service_name->resize(service_name_length);
426 if (!service_name->empty() &&
427 !LoggingReadFileExactly(
428 client_read_fd, &(*service_name)[0], service_name_length)) {
429 return false;
430 }
431
432 return true;
433 }
434
435 // static
RunClientInternal_SendCheckIn(const std::string & service_name,child_port_token_t token,mach_port_t port,mach_msg_type_name_t right_type)436 bool ChildPortHandshake::RunClientInternal_SendCheckIn(
437 const std::string& service_name,
438 child_port_token_t token,
439 mach_port_t port,
440 mach_msg_type_name_t right_type) {
441 // Get a send right to the server by looking up the service with the bootstrap
442 // server by name.
443 base::mac::ScopedMachSendRight server_port(BootstrapLookUp(service_name));
444 if (server_port == kMachPortNull) {
445 return false;
446 }
447
448 // Check in with the server.
449 kern_return_t kr = child_port_check_in(
450 server_port.get(), token, port, right_type);
451 if (kr != KERN_SUCCESS) {
452 MACH_LOG(ERROR, kr) << "child_port_check_in";
453 return false;
454 }
455
456 return true;
457 }
458
459 } // namespace crashpad
460