Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(268)

Side by Side Diff: third_party/crashpad/crashpad/util/mach/child_port_handshake.cc

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

Powered by Google App Engine
This is Rietveld 408576698