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

Side by Side Diff: util/test/mac/mach_multiprocess.cc

Issue 482483002: Add MachMultiprocess and its test (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@bootstrap
Patch Set: Further refinements Created 6 years, 4 months 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/test/mac/mach_multiprocess.h ('k') | util/test/mac/mach_multiprocess_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/test/mac/mach_multiprocess.h"
16
17 #include <AvailabilityMacros.h>
18 #include <bsm/libbsm.h>
19 #include <servers/bootstrap.h>
20 #include <signal.h>
21 #include <stdlib.h>
22 #include <sys/wait.h>
23
24 #include <string>
25
26 #include "base/auto_reset.h"
27 #include "base/files/scoped_file.h"
28 #include "base/logging.h"
29 #include "base/mac/scoped_mach_port.h"
30 #include "base/memory/scoped_ptr.h"
31 #include "base/rand_util.h"
32 #include "base/strings/stringprintf.h"
33 #include "gtest/gtest.h"
34 #include "util/mach/bootstrap.h"
35 #include "util/test/errors.h"
36 #include "util/test/mac/mach_errors.h"
37
38 namespace {
39
40 class ScopedNotReached {
41 public:
42 ScopedNotReached() {}
43 ~ScopedNotReached() { abort(); }
44
45 private:
46 DISALLOW_COPY_AND_ASSIGN(ScopedNotReached);
47 };
48
49 // The “hello” message contains a send right to the child process’ task port.
50 struct SendHelloMessage : public mach_msg_base_t {
51 mach_msg_port_descriptor_t port_descriptor;
52 };
53
54 struct ReceiveHelloMessage : public SendHelloMessage {
55 mach_msg_audit_trailer_t audit_trailer;
56 };
57
58 } // namespace
59
60 namespace crashpad {
61 namespace test {
62
63 using namespace testing;
64
65 namespace internal {
66
67 struct MachMultiprocessInfo {
68 MachMultiprocessInfo()
69 : service_name(),
70 read_pipe(-1),
71 write_pipe(-1),
72 child_pid(0),
73 pipe_fd(-1),
74 local_port(MACH_PORT_NULL),
75 remote_port(MACH_PORT_NULL),
76 child_task(MACH_PORT_NULL) {}
77
78 std::string service_name;
79 base::ScopedFD read_pipe;
80 base::ScopedFD write_pipe;
81 pid_t child_pid; // valid only in parent
82 int pipe_fd; // read_pipe in parent, write_pipe in child
83 base::mac::ScopedMachReceiveRight local_port;
84 base::mac::ScopedMachSendRight remote_port;
85 base::mac::ScopedMachSendRight child_task; // valid only in parent
86 };
87
88 } // namespace internal
89
90 MachMultiprocess::MachMultiprocess() : info_(NULL) {
91 }
92
93 void MachMultiprocess::Run() {
94 ASSERT_EQ(NULL, info_);
95 scoped_ptr<internal::MachMultiprocessInfo> info(
96 new internal::MachMultiprocessInfo);
97 base::AutoReset<internal::MachMultiprocessInfo*> reset_info(&info_,
98 info.get());
99
100 int pipe_fds[2];
101 int rv = pipe(pipe_fds);
102 ASSERT_EQ(0, rv) << ErrnoMessage("pipe");
103
104 info_->read_pipe.reset(pipe_fds[0]);
105 info_->write_pipe.reset(pipe_fds[1]);
106
107 // Set up the parent port and register it with the bootstrap server before
108 // forking, so that it’s guaranteed to be there when the child attempts to
109 // look it up.
110 info_->service_name = "com.googlecode.crashpad.test.mach_multiprocess.";
111 for (int index = 0; index < 16; ++index) {
112 info_->service_name.append(1, base::RandInt('A', 'Z'));
113 }
114
115 mach_port_t local_port;
116 kern_return_t kr =
117 BootstrapCheckIn(bootstrap_port, info_->service_name, &local_port);
118 ASSERT_EQ(BOOTSTRAP_SUCCESS, kr)
119 << BootstrapErrorMessage(kr, "bootstrap_check_in");
120 info_->local_port.reset(local_port);
121
122 pid_t pid = fork();
123 ASSERT_GE(pid, 0) << ErrnoMessage("fork");
124
125 if (pid > 0) {
126 info_->child_pid = pid;
127
128 RunParent();
129
130 // Waiting for the child happens here instead of in RunParent() because even
131 // if RunParent() returns early due to a gtest fatal assertion failure, the
132 // child should still be reaped.
133
134 // This will make the parent hang up on the child as much as would be
135 // visible from the child’s perspective. The child’s side of the pipe will
136 // be broken, the child’s remote port will become a dead name, and an
137 // attempt by the child to look up the service will fail. If this weren’t
138 // done, the child might hang while waiting for a parent that has already
139 // triggered a fatal assertion failure to do something.
140 info.reset();
141 info_ = NULL;
142
143 int status;
144 pid_t wait_pid = waitpid(pid, &status, 0);
145 ASSERT_EQ(pid, wait_pid) << ErrnoMessage("waitpid");
146 if (status != 0) {
147 std::string message;
148 if (WIFEXITED(status)) {
149 message = base::StringPrintf("Child exited with code %d",
150 WEXITSTATUS(status));
151 } else if (WIFSIGNALED(status)) {
152 message = base::StringPrintf("Child terminated by signal %d (%s) %s",
153 WTERMSIG(status),
154 strsignal(WTERMSIG(status)),
155 WCOREDUMP(status) ? " (core dumped)" : "");
156 }
157 ASSERT_EQ(0, status) << message;
158 }
159 } else {
160 RunChild();
161 }
162 }
163
164 MachMultiprocess::~MachMultiprocess() {
165 }
166
167 pid_t MachMultiprocess::ChildPID() const {
168 EXPECT_NE(0, info_->child_pid);
169 return info_->child_pid;
170 }
171
172 int MachMultiprocess::PipeFD() const {
173 EXPECT_NE(-1, info_->pipe_fd);
174 return info_->pipe_fd;
175 }
176
177 mach_port_t MachMultiprocess::LocalPort() const {
178 EXPECT_NE(static_cast<mach_port_t>(MACH_PORT_NULL), info_->local_port);
179 return info_->local_port;
180 }
181
182 mach_port_t MachMultiprocess::RemotePort() const {
183 EXPECT_NE(static_cast<mach_port_t>(MACH_PORT_NULL), info_->remote_port);
184 return info_->remote_port;
185 }
186
187 mach_port_t MachMultiprocess::ChildTask() const {
188 EXPECT_NE(static_cast<mach_port_t>(MACH_PORT_NULL), info_->child_task);
189 return info_->child_task;
190 }
191
192 void MachMultiprocess::RunParent() {
193 // The parent uses the read end of the pipe.
194 info_->write_pipe.reset();
195 info_->pipe_fd = info_->read_pipe.get();
196
197 ReceiveHelloMessage message = {};
198
199 kern_return_t kr =
200 mach_msg(&message.header,
201 MACH_RCV_MSG | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) |
202 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT),
203 0,
204 sizeof(message),
205 info_->local_port,
206 MACH_MSG_TIMEOUT_NONE,
207 MACH_PORT_NULL);
208 ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg");
209
210 // Comb through the entire message, checking every field against its expected
211 // value.
212 EXPECT_EQ(MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MOVE_SEND) |
213 MACH_MSGH_BITS_COMPLEX,
214 message.header.msgh_bits);
215 ASSERT_EQ(sizeof(SendHelloMessage), message.header.msgh_size);
216 EXPECT_EQ(info_->local_port, message.header.msgh_local_port);
217 ASSERT_EQ(1u, message.body.msgh_descriptor_count);
218 EXPECT_EQ(static_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_MOVE_SEND),
219 message.port_descriptor.disposition);
220 ASSERT_EQ(static_cast<mach_msg_descriptor_type_t>(MACH_MSG_PORT_DESCRIPTOR),
221 message.port_descriptor.type);
222 ASSERT_EQ(static_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0),
223 message.audit_trailer.msgh_trailer_type);
224 ASSERT_EQ(sizeof(message.audit_trailer),
225 message.audit_trailer.msgh_trailer_size);
226 EXPECT_EQ(0u, message.audit_trailer.msgh_seqno);
227
228 // Check the audit trailer’s values for sanity. This is a little bit of
229 // overkill, but because the service was registered with the bootstrap server
230 // and other processes will be able to look it up and send messages to it,
231 // these checks disambiguate genuine failures later on in the test from those
232 // that would occur if an errant process sends a message to this service.
233 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
234 uid_t audit_auid;
235 uid_t audit_euid;
236 gid_t audit_egid;
237 uid_t audit_ruid;
238 gid_t audit_rgid;
239 pid_t audit_pid;
240 au_asid_t audit_asid;
241 audit_token_to_au32(message.audit_trailer.msgh_audit,
242 &audit_auid,
243 &audit_euid,
244 &audit_egid,
245 &audit_ruid,
246 &audit_rgid,
247 &audit_pid,
248 &audit_asid,
249 NULL);
250 #else
251 uid_t audit_auid = audit_token_to_auid(message.audit_trailer.msgh_audit);
252 uid_t audit_euid = audit_token_to_euid(message.audit_trailer.msgh_audit);
253 gid_t audit_egid = audit_token_to_egid(message.audit_trailer.msgh_audit);
254 uid_t audit_ruid = audit_token_to_ruid(message.audit_trailer.msgh_audit);
255 gid_t audit_rgid = audit_token_to_rgid(message.audit_trailer.msgh_audit);
256 pid_t audit_pid = audit_token_to_pid(message.audit_trailer.msgh_audit);
257 au_asid_t audit_asid = audit_token_to_asid(message.audit_trailer.msgh_audit);
258 #endif
259 EXPECT_EQ(geteuid(), audit_euid);
260 EXPECT_EQ(getegid(), audit_egid);
261 EXPECT_EQ(getuid(), audit_ruid);
262 EXPECT_EQ(getgid(), audit_rgid);
263 ASSERT_EQ(ChildPID(), audit_pid);
264
265 auditinfo_addr_t audit_info;
266 int rv = getaudit_addr(&audit_info, sizeof(audit_info));
267 ASSERT_EQ(0, rv) << ErrnoMessage("getaudit_addr");
268 EXPECT_EQ(audit_info.ai_auid, audit_auid);
269 EXPECT_EQ(audit_info.ai_asid, audit_asid);
270
271 // Retrieve the remote port from the message header, and the child’s task port
272 // from the message body.
273 info_->remote_port.reset(message.header.msgh_remote_port);
274 info_->child_task.reset(message.port_descriptor.name);
275
276 // Verify that the child’s task port is what it purports to be.
277 int mach_pid;
278 kr = pid_for_task(info_->child_task, &mach_pid);
279 ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "pid_for_task");
280 ASSERT_EQ(ChildPID(), mach_pid);
281
282 Parent();
283
284 info_->remote_port.reset();
285 info_->local_port.reset();
286
287 info_->pipe_fd = -1;
288 info_->read_pipe.reset();
289 }
290
291 void MachMultiprocess::RunChild() {
292 ScopedNotReached must_not_leave_this_scope;
293
294 // local_port is not valid in the forked child process.
295 ignore_result(info_->local_port.release());
296
297 // The child uses the write end of the pipe.
298 info_->read_pipe.reset();
299 info_->pipe_fd = info_->write_pipe.get();
300
301 mach_port_t local_port;
302 kern_return_t kr = mach_port_allocate(
303 mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &local_port);
304 ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_allocate");
305 info_->local_port.reset(local_port);
306
307 // The remote port can be obtained from the bootstrap server.
308 mach_port_t remote_port;
309 kr = bootstrap_look_up(
310 bootstrap_port, info_->service_name.c_str(), &remote_port);
311 ASSERT_EQ(BOOTSTRAP_SUCCESS, kr)
312 << BootstrapErrorMessage(kr, "bootstrap_look_up");
313 info_->remote_port.reset(remote_port);
314
315 // The “hello” message will provide the parent with its remote port, a send
316 // right to the child task’s local port receive right. It will also carry a
317 // send right to the child task’s task port.
318 SendHelloMessage message = {};
319 message.header.msgh_bits =
320 MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND) |
321 MACH_MSGH_BITS_COMPLEX;
322 message.header.msgh_size = sizeof(message);
323 message.header.msgh_remote_port = info_->remote_port;
324 message.header.msgh_local_port = info_->local_port;
325 message.body.msgh_descriptor_count = 1;
326 message.port_descriptor.name = mach_task_self();
327 message.port_descriptor.disposition = MACH_MSG_TYPE_COPY_SEND;
328 message.port_descriptor.type = MACH_MSG_PORT_DESCRIPTOR;
329
330 kr = mach_msg(&message.header,
331 MACH_SEND_MSG,
332 message.header.msgh_size,
333 0,
334 MACH_PORT_NULL,
335 MACH_MSG_TIMEOUT_NONE,
336 MACH_PORT_NULL);
337 ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg");
338
339 Child();
340
341 info_->remote_port.reset();
342 info_->local_port.reset();
343
344 info_->pipe_fd = -1;
345 info_->write_pipe.reset();
346
347 if (Test::HasFailure()) {
348 // Trigger the ScopedNotReached destructor.
349 return;
350 }
351
352 exit(0);
353 }
354
355 } // namespace test
356 } // namespace crashpad
OLDNEW
« no previous file with comments | « util/test/mac/mach_multiprocess.h ('k') | util/test/mac/mach_multiprocess_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698