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

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

Issue 756603003: Add ChildPortHandshake and its test (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@child_port_server
Patch Set: Rebase Created 6 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
« no previous file with comments | « util/mach/child_port_handshake.h ('k') | util/mach/child_port_handshake_test.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 <fcntl.h>
19 #include <pthread.h>
20 #include <servers/bootstrap.h>
21 #include <sys/event.h>
22 #include <sys/time.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25
26 #include <algorithm>
27
28 #include "base/logging.h"
29 #include "base/mac/mach_logging.h"
30 #include "base/mac/scoped_mach_port.h"
31 #include "base/posix/eintr_wrapper.h"
32 #include "base/rand_util.h"
33 #include "base/strings/stringprintf.h"
34 #include "util/file/fd_io.h"
35 #include "util/mach/child_port.h"
36 #include "util/mach/mach_extensions.h"
37 #include "util/mach/mach_message_server.h"
38
39 namespace crashpad {
40
41 ChildPortHandshake::ChildPortHandshake()
42 : token_(0),
43 pipe_read_(),
44 pipe_write_(),
45 child_port_(MACH_PORT_NULL),
46 checked_in_(false) {
47 int pipe_fds[2];
48 int rv = pipe(pipe_fds);
49 PCHECK(rv == 0) << "pipe";
50
51 pipe_read_.reset(pipe_fds[0]);
52 pipe_write_.reset(pipe_fds[1]);
53
54 // SIGPIPE is undesirable when writing to this pipe. Allow broken-pipe writes
55 // to fail with EPIPE instead.
56 PCHECK(fcntl(pipe_write_.get(), F_SETNOSIGPIPE, 1) == 0) << "fcntl";
57 }
58
59 ChildPortHandshake::~ChildPortHandshake() {
60 }
61
62 int ChildPortHandshake::ReadPipeFD() const {
63 DCHECK_NE(pipe_read_.get(), -1);
64 return pipe_read_.get();
65 }
66
67 mach_port_t ChildPortHandshake::RunServer() {
68 DCHECK_NE(pipe_read_.get(), -1);
69 pipe_read_.reset();
70
71 // Transfer ownership of the write pipe into this method’s scope.
72 base::ScopedFD pipe_write_owner(pipe_write_.release());
73
74 // Initialize the token and share it with the client via the pipe.
75 token_ = base::RandUint64();
76 int pipe_write = pipe_write_owner.get();
77 if (!LoggingWriteFD(pipe_write, &token_, sizeof(token_))) {
78 return MACH_PORT_NULL;
79 }
80
81 // Create a unique name for the bootstrap service mapping. Make it unguessable
82 // to prevent outsiders from grabbing the name first, which would cause
83 // bootstrap_check_in() to fail.
84 uint64_t thread_id;
85 errno = pthread_threadid_np(pthread_self(), &thread_id);
86 PCHECK(errno == 0) << "pthread_threadid_np";
87 std::string service_name = base::StringPrintf(
88 "com.googlecode.crashpad.child_port_handshake.%d.%llu.%016llx",
89 getpid(),
90 thread_id,
91 base::RandUint64());
92 DCHECK_LT(service_name.size(), implicit_cast<size_t>(BOOTSTRAP_MAX_NAME_LEN));
93
94 // Check the new service in with the bootstrap server, obtaining a receive
95 // right for it.
96 mach_port_t server_port;
97 kern_return_t kr =
98 bootstrap_check_in(bootstrap_port, service_name.c_str(), &server_port);
99 BOOTSTRAP_CHECK(kr == BOOTSTRAP_SUCCESS, kr) << "bootstrap_check_in";
100 base::mac::ScopedMachReceiveRight server_port_owner(server_port);
101
102 // Share the service name with the client via the pipe.
103 uint32_t service_name_length = service_name.size();
104 if (!LoggingWriteFD(
105 pipe_write, &service_name_length, sizeof(service_name_length))) {
106 return MACH_PORT_NULL;
107 }
108
109 if (!LoggingWriteFD(pipe_write, service_name.c_str(), service_name_length)) {
110 return MACH_PORT_NULL;
111 }
112
113 // A kqueue cannot monitor a raw Mach receive right with EVFILT_MACHPORT. It
114 // requires a port set. Create a new port set and add the receive right to it.
115 mach_port_t server_port_set;
116 kr = mach_port_allocate(
117 mach_task_self(), MACH_PORT_RIGHT_PORT_SET, &server_port_set);
118 MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_allocate";
119 base::mac::ScopedMachPortSet server_port_set_owner(server_port_set);
120
121 kr = mach_port_insert_member(mach_task_self(), server_port, server_port_set);
122 MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_member";
123
124 // Set up a kqueue to monitor both the server’s receive right and the write
125 // side of the pipe. Messages from the client will be received via the receive
126 // right, and the pipe will show EOF if the client closes its read side
127 // prematurely.
128 base::ScopedFD kq(kqueue());
129 PCHECK(kq != -1) << "kqueue";
130
131 struct kevent changelist[2];
132 EV_SET(&changelist[0],
133 server_port_set,
134 EVFILT_MACHPORT,
135 EV_ADD | EV_CLEAR,
136 0,
137 0,
138 nullptr);
139 EV_SET(&changelist[1],
140 pipe_write,
141 EVFILT_WRITE,
142 EV_ADD | EV_CLEAR,
143 0,
144 0,
145 nullptr);
146 int rv = HANDLE_EINTR(
147 kevent(kq.get(), changelist, arraysize(changelist), nullptr, 0, nullptr));
148 PCHECK(rv != -1) << "kevent";
149
150 ChildPortServer child_port_server(this);
151
152 bool blocking = true;
153 DCHECK(!checked_in_);
154 while (!checked_in_) {
155 DCHECK_EQ(child_port_, kMachPortNull);
156
157 // Get a kevent from the kqueue. Block while waiting for an event unless the
158 // write pipe has arrived at EOF, in which case the kevent() should be
159 // nonblocking. Although the client sends its check-in message before
160 // closing the read side of the pipe, this organization allows the events to
161 // be delivered out of order and the check-in message will still be
162 // processed.
163 struct kevent event;
164 const timespec nonblocking_timeout = {};
165 const timespec* timeout = blocking ? nullptr : &nonblocking_timeout;
166 rv = HANDLE_EINTR(kevent(kq.get(), nullptr, 0, &event, 1, timeout));
167 PCHECK(rv != -1) << "kevent";
168
169 if (rv == 0) {
170 // Non-blocking kevent() with no events to return.
171 DCHECK(!blocking);
172 LOG(WARNING) << "no client check-in";
173 return MACH_PORT_NULL;
174 }
175
176 DCHECK_EQ(rv, 1);
177
178 if (event.flags & EV_ERROR) {
179 // kevent() may have put its error here.
180 errno = event.data;
181 PLOG(FATAL) << "kevent";
182 }
183
184 switch (event.filter) {
185 case EVFILT_MACHPORT: {
186 // There’s something to receive on the port set.
187 DCHECK_EQ(event.ident, server_port_set);
188
189 // Run the message server in an inner loop instead of using
190 // MachMessageServer::kPersistent. This allows the loop to exit as soon
191 // as child_port_ is set, even if other messages are queued. This needs
192 // to drain all messages, because the use of edge triggering (EV_CLEAR)
193 // means that if more than one message is in the queue when kevent()
194 // returns, no more notifications will be generated.
195 while (!checked_in_) {
196 // If a proper message is received from child_port_check_in(),
197 // this will call HandleChildPortCheckIn().
198 mach_msg_return_t mr =
199 MachMessageServer::Run(&child_port_server,
200 server_port_set,
201 MACH_MSG_OPTION_NONE,
202 MachMessageServer::kOneShot,
203 MachMessageServer::kNonblocking,
204 MachMessageServer::kReceiveLargeIgnore,
205 MACH_MSG_TIMEOUT_NONE);
206 if (mr == MACH_RCV_TIMED_OUT) {
207 break;
208 } else if (mr != MACH_MSG_SUCCESS) {
209 MACH_LOG(ERROR, mr) << "MachMessageServer::Run";
210 return MACH_PORT_NULL;
211 }
212 }
213 break;
214 }
215
216 case EVFILT_WRITE:
217 // The write pipe is ready to be written to, or it’s at EOF. The former
218 // case is uninteresting, but a notification for this may be presented
219 // because the write pipe will be ready to be written to, at the latest,
220 // when the client reads its messages from the read side of the same
221 // pipe. Ignore that case. Multiple notifications for that situation
222 // will not be generated because edge triggering (EV_CLEAR) is used
223 // above.
224 DCHECK_EQ(implicit_cast<int>(event.ident), pipe_write);
225 if (event.flags & EV_EOF) {
226 // There are no readers attached to the write pipe. The client has
227 // closed its side of the pipe. There can be one last shot at
228 // receiving messages, in case the check-in message is delivered
229 // out of order, after the EOF notification.
230 blocking = false;
231 }
232 break;
233
234 default:
235 NOTREACHED();
236 break;
237 }
238 }
239
240 mach_port_t child_port = MACH_PORT_NULL;
241 std::swap(child_port_, child_port);
242 return child_port;
243 }
244
245 kern_return_t ChildPortHandshake::HandleChildPortCheckIn(
246 child_port_server_t server,
247 const child_port_token_t token,
248 mach_port_t port,
249 mach_msg_type_name_t right_type,
250 bool* destroy_complex_request) {
251 DCHECK_EQ(child_port_, kMachPortNull);
252
253 if (token != token_) {
254 // If the token’s not correct, someone’s attempting to spoof the legitimate
255 // client.
256 LOG(WARNING) << "ignoring incorrect token";
257 *destroy_complex_request = true;
258 } else {
259 checked_in_ = true;
260
261 if (right_type == MACH_MSG_TYPE_PORT_RECEIVE) {
262 // The message needs to carry a send right or a send-once right. This
263 // isn’t a strict requirement of the protocol, but users of this class
264 // expect a send right or a send-once right, both of which can be managed
265 // by base::mac::ScopedMachSendRight. It is invalid to store a receive
266 // right in that scoper.
267 LOG(WARNING) << "ignoring MACH_MSG_TYPE_PORT_RECEIVE";
268 *destroy_complex_request = true;
269 } else {
270 // Communicate the child port back to the RunServer().
271 // *destroy_complex_request is left at false, because RunServer() needs
272 // the right to remain intact. It gives ownership of the right to its
273 // caller.
274 child_port_ = port;
275 }
276 }
277
278 // This is a MIG simpleroutine, there is no reply message.
279 return MIG_NO_REPLY;
280 }
281
282 // static
283 void ChildPortHandshake::RunClient(int pipe_read,
284 mach_port_t port,
285 mach_msg_type_name_t right_type) {
286 base::ScopedFD pipe_read_owner(pipe_read);
287
288 // Read the token and the service name from the read side of the pipe.
289 child_port_token_t token;
290 std::string service_name;
291 RunClientInternal_ReadPipe(pipe_read, &token, &service_name);
292
293 // Look up the server and check in with it by providing the token and port.
294 RunClientInternal_SendCheckIn(service_name, token, port, right_type);
295 }
296
297 // static
298 void ChildPortHandshake::RunClientInternal_ReadPipe(int pipe_read,
299 child_port_token_t* token,
300 std::string* service_name) {
301 // Read the token from the pipe.
302 CheckedReadFD(pipe_read, token, sizeof(*token));
303
304 // Read the service name from the pipe.
305 uint32_t service_name_length;
306 CheckedReadFD(pipe_read, &service_name_length, sizeof(service_name_length));
307 DCHECK_LT(service_name_length,
308 implicit_cast<uint32_t>(BOOTSTRAP_MAX_NAME_LEN));
309
310 if (service_name_length > 0) {
311 service_name->resize(service_name_length);
312 CheckedReadFD(pipe_read, &(*service_name)[0], service_name_length);
313 }
314 }
315
316 // static
317 void ChildPortHandshake::RunClientInternal_SendCheckIn(
318 const std::string& service_name,
319 child_port_token_t token,
320 mach_port_t port,
321 mach_msg_type_name_t right_type) {
322 // Get a send right to the server by looking up the service with the bootstrap
323 // server by name.
324 mach_port_t server_port;
325 kern_return_t kr =
326 bootstrap_look_up(bootstrap_port, service_name.c_str(), &server_port);
327 BOOTSTRAP_CHECK(kr == BOOTSTRAP_SUCCESS, kr) << "bootstrap_look_up";
328 base::mac::ScopedMachSendRight server_port_owner(server_port);
329
330 // Check in with the server.
331 kr = child_port_check_in(server_port, token, port, right_type);
332 MACH_CHECK(kr == KERN_SUCCESS, kr) << "child_port_check_in";
333 }
334
335 } // namespace crashpad
OLDNEW
« no previous file with comments | « util/mach/child_port_handshake.h ('k') | util/mach/child_port_handshake_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698