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

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: 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
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 MACH_MSG_TIMEOUT_NONE);
205 if (mr == MACH_RCV_TIMED_OUT) {
206 break;
207 } else if (mr == MACH_RCV_TOO_LARGE) {
208 // Treating this as a hard error would allow for a denial-of-service
209 // attack against this class. Since the send right is published with
210 // the bootstrap server, anyone could send messages to this service.
211 // By sending messages that are larger than expected, mach_msg() can
212 // be made to fail.
213 MACH_LOG(WARNING, mr) << "MachMessageServer::Run";
214 } else if (mr != MACH_MSG_SUCCESS) {
215 MACH_LOG(ERROR, mr) << "MachMessageServer::Run";
216 return MACH_PORT_NULL;
217 }
218 }
219 break;
220 }
221
222 case EVFILT_WRITE:
223 // The write pipe is ready to be written to, or it’s at EOF. The former
224 // case is uninteresting, but a notification for this may be presented
225 // because the write pipe will be ready to be written to, at the latest,
226 // when the client reads its messages from the read side of the same
227 // pipe. Ignore that case. Multiple notifications for that situation
228 // will not be generated because edge triggering (EV_CLEAR) is used
229 // above.
230 DCHECK_EQ(implicit_cast<int>(event.ident), pipe_write);
231 if (event.flags & EV_EOF) {
232 // There are no readers attached to the write pipe. The client has
233 // closed its side of the pipe. There can be one last shot at
234 // receiving messages, in case the check-in message is delivered
235 // out of order, after the EOF notification.
236 blocking = false;
237 }
238 break;
239
240 default:
241 NOTREACHED();
242 break;
243 }
244 }
245
246 mach_port_t child_port = MACH_PORT_NULL;
247 std::swap(child_port_, child_port);
248 return child_port;
249 }
250
251 kern_return_t ChildPortHandshake::HandleChildPortCheckIn(
252 child_port_server_t server,
253 const child_port_token_t token,
254 mach_port_t port,
255 mach_msg_type_name_t right_type,
256 bool* destroy_complex_request) {
257 DCHECK_EQ(child_port_, kMachPortNull);
258
259 if (token != token_) {
260 // If the token’s not correct, someone’s attempting to spoof the legitimate
261 // client.
262 LOG(WARNING) << "ignoring incorrect token";
263 *destroy_complex_request = true;
264 } else {
265 checked_in_ = true;
266
267 if (right_type == MACH_MSG_TYPE_PORT_RECEIVE) {
268 // The message needs to carry a send right or a send-once right. This
269 // isn’t a strict requirement of the protocol, but users of this class
270 // expect a send right or a send-once right, both of which can be managed
271 // by base::mac::ScopedMachSendRight. It is invalid to store a receive
272 // right in that scoper.
273 LOG(WARNING) << "ignoring MACH_MSG_TYPE_PORT_RECEIVE";
274 *destroy_complex_request = true;
275 } else {
276 // Communicate the child port back to the RunServer().
277 // *destroy_complex_request is left at false, because RunServer() needs
278 // the right to remain intact. It gives ownership of the right to its
279 // caller.
280 child_port_ = port;
281 }
282 }
283
284 // This is a MIG simpleroutine, there is no reply message.
285 return MIG_NO_REPLY;
286 }
287
288 // static
289 void ChildPortHandshake::RunClientInternal_ReadPipe(int pipe_read,
290 child_port_token_t* token,
291 std::string* service_name) {
292 // Read the token from the pipe.
293 CheckedReadFD(pipe_read, token, sizeof(*token));
294
295 // Read the service name from the pipe.
296 uint32_t service_name_length;
297 CheckedReadFD(pipe_read, &service_name_length, sizeof(service_name_length));
298 DCHECK_LT(service_name_length,
299 implicit_cast<uint32_t>(BOOTSTRAP_MAX_NAME_LEN));
300
301 if (service_name_length > 0) {
302 service_name->resize(service_name_length);
303 CheckedReadFD(pipe_read, &(*service_name)[0], service_name_length);
304 }
305 }
306
307 // static
308 void ChildPortHandshake::RunClientInternal_SendCheckIn(
309 const std::string& service_name,
310 child_port_token_t token,
311 mach_port_t port,
312 mach_msg_type_name_t right_type) {
313 // Get a send right to the server by looking up the service with the bootstrap
314 // server by name.
315 mach_port_t server_port;
316 kern_return_t kr =
317 bootstrap_look_up(bootstrap_port, service_name.c_str(), &server_port);
318 BOOTSTRAP_CHECK(kr == BOOTSTRAP_SUCCESS, kr) << "bootstrap_look_up";
319 base::mac::ScopedMachSendRight server_port_owner(server_port);
320
321 // Check in with the server.
322 kr = child_port_check_in(server_port, token, port, right_type);
323 MACH_CHECK(kr == KERN_SUCCESS, kr) << "child_port_check_in";
324 }
325
326 // static
327 void ChildPortHandshake::RunClient(int pipe_read,
328 mach_port_t port,
329 mach_msg_type_name_t right_type) {
330 base::ScopedFD pipe_read_owner(pipe_read);
331
332 // Read the token and the service name from the read side of the pipe.
333 child_port_token_t token;
334 std::string service_name;
335 RunClientInternal_ReadPipe(pipe_read, &token, &service_name);
336
337 // Look up the server and check in with it by providing the token and port.
338 RunClientInternal_SendCheckIn(service_name, token, port, right_type);
339 }
340
341 } // namespace crashpad
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698