OLD | NEW |
---|---|
(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/rand_util.h" | |
31 #include "base/strings/stringprintf.h" | |
32 #include "gtest/gtest.h" | |
33 #include "util/mach/bootstrap.h" | |
34 #include "util/test/errors.h" | |
35 #include "util/test/mac/mach_errors.h" | |
36 | |
37 namespace { | |
38 | |
39 class ScopedNotReached { | |
40 public: | |
41 ScopedNotReached() {} | |
42 ~ScopedNotReached() { abort(); } | |
43 | |
44 private: | |
45 DISALLOW_COPY_AND_ASSIGN(ScopedNotReached); | |
46 }; | |
47 | |
48 } // namespace | |
49 | |
50 namespace crashpad { | |
51 namespace test { | |
52 | |
53 using namespace testing; | |
54 | |
55 MachMultiprocess::MachMultiprocess() | |
56 : child_pid_(0), | |
57 pipe_fd_(-1), | |
58 local_port_(MACH_PORT_NULL), | |
59 remote_port_(MACH_PORT_NULL), | |
60 child_task_(MACH_PORT_NULL) { | |
61 } | |
62 | |
63 void MachMultiprocess::Run() { | |
64 int pipe_fds[2]; | |
65 int rv = pipe(pipe_fds); | |
66 ASSERT_EQ(0, rv) << ErrnoMessage("pipe"); | |
67 | |
68 base::ScopedFD read_pipe(pipe_fds[0]); | |
69 base::ScopedFD write_pipe(pipe_fds[1]); | |
70 | |
71 // Set up the parent port and register it with the bootstrap server before | |
72 // forking, so that it’s guaranteed to be there when the child attempts to | |
73 // look it up. | |
74 std::string service_name = "com.googlecode.crashpad.test.mach_multiprocess."; | |
75 for (int index = 0; index < 16; ++index) { | |
76 service_name.append(1, base::RandInt('A', 'Z')); | |
77 } | |
78 | |
79 mach_port_t local_port; | |
80 kern_return_t kr = | |
81 BootstrapCheckIn(bootstrap_port, service_name, &local_port); | |
82 ASSERT_EQ(BOOTSTRAP_SUCCESS, kr) | |
83 << BootstrapErrorMessage(kr, "bootstrap_check_in"); | |
84 base::mac::ScopedMachReceiveRight local_port_owner(local_port); | |
85 | |
86 pid_t pid = fork(); | |
87 ASSERT_GE(pid, 0) << ErrnoMessage("fork"); | |
88 | |
89 // The “hello” message contains a send right to the child process’ task port. | |
90 struct SendHelloMessage : public mach_msg_base_t { | |
91 mach_msg_port_descriptor_t port_descriptor; | |
92 }; | |
93 | |
94 struct ReceiveHelloMessage : public SendHelloMessage { | |
95 mach_msg_audit_trailer_t audit_trailer; | |
96 }; | |
97 | |
98 if (pid > 0) { | |
Robert Sesek
2014/08/19 17:27:51
Split this into private helper routines DoParent()
Mark Mentovai
2014/08/20 00:08:48
rsesek wrote:
| |
99 // Parent. | |
100 base::AutoReset<pid_t> reset_child_pid(&child_pid_, pid); | |
101 | |
102 // The parent uses the read end of the pipe. | |
103 write_pipe.reset(); | |
104 base::AutoReset<int> reset_pipe_fd(&pipe_fd_, read_pipe.get()); | |
105 | |
106 base::AutoReset<mach_port_t> reset_local_port(&local_port_, | |
107 local_port_owner); | |
108 | |
109 ReceiveHelloMessage message = {}; | |
110 | |
111 kr = mach_msg(&message.header, | |
112 MACH_RCV_MSG | | |
113 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | | |
114 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT), | |
115 0, | |
116 sizeof(message), | |
117 local_port_, | |
118 MACH_MSG_TIMEOUT_NONE, | |
119 MACH_PORT_NULL); | |
120 ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg"); | |
121 | |
122 // Comb through the entire message, checking every field against its | |
Robert Sesek
2014/08/19 17:27:51
This might be excessive, but that's fine.
| |
123 // expected value. | |
124 EXPECT_EQ(MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MOVE_SEND) | | |
125 MACH_MSGH_BITS_COMPLEX, | |
126 message.header.msgh_bits); | |
127 ASSERT_EQ(sizeof(SendHelloMessage), message.header.msgh_size); | |
128 EXPECT_EQ(local_port_, message.header.msgh_local_port); | |
129 ASSERT_EQ(1u, message.body.msgh_descriptor_count); | |
130 EXPECT_EQ(static_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_MOVE_SEND), | |
131 message.port_descriptor.disposition); | |
132 ASSERT_EQ(static_cast<mach_msg_descriptor_type_t>(MACH_MSG_PORT_DESCRIPTOR), | |
133 message.port_descriptor.type); | |
134 ASSERT_EQ(static_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0), | |
135 message.audit_trailer.msgh_trailer_type); | |
136 ASSERT_EQ(sizeof(message.audit_trailer), | |
137 message.audit_trailer.msgh_trailer_size); | |
138 EXPECT_EQ(0u, message.audit_trailer.msgh_seqno); | |
139 | |
140 // Check the audit trailer’s values for sanity. | |
141 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8 | |
142 uid_t audit_auid; | |
143 uid_t audit_euid; | |
144 gid_t audit_egid; | |
145 uid_t audit_ruid; | |
146 gid_t audit_rgid; | |
147 pid_t audit_pid; | |
148 au_asid_t audit_asid; | |
149 audit_token_to_au32(message.audit_trailer.msgh_audit, | |
150 &audit_auid, | |
151 &audit_euid, | |
152 &audit_egid, | |
153 &audit_ruid, | |
154 &audit_rgid, | |
155 &audit_pid, | |
156 &audit_asid, | |
157 NULL); | |
158 #else | |
159 uid_t audit_auid = audit_token_to_auid(message.audit_trailer.msgh_audit); | |
160 uid_t audit_euid = audit_token_to_euid(message.audit_trailer.msgh_audit); | |
161 gid_t audit_egid = audit_token_to_egid(message.audit_trailer.msgh_audit); | |
162 uid_t audit_ruid = audit_token_to_ruid(message.audit_trailer.msgh_audit); | |
163 gid_t audit_rgid = audit_token_to_rgid(message.audit_trailer.msgh_audit); | |
164 pid_t audit_pid = audit_token_to_pid(message.audit_trailer.msgh_audit); | |
165 au_asid_t audit_asid = | |
166 audit_token_to_asid(message.audit_trailer.msgh_audit); | |
167 #endif | |
168 EXPECT_EQ(geteuid(), audit_euid); | |
169 EXPECT_EQ(getegid(), audit_egid); | |
170 EXPECT_EQ(getuid(), audit_ruid); | |
171 EXPECT_EQ(getgid(), audit_rgid); | |
172 ASSERT_EQ(pid, audit_pid); | |
173 | |
174 auditinfo_addr_t audit_info; | |
175 rv = getaudit_addr(&audit_info, sizeof(audit_info)); | |
176 ASSERT_EQ(0, rv) << ErrnoMessage("getaudit_addr"); | |
177 EXPECT_EQ(audit_info.ai_auid, audit_auid); | |
178 EXPECT_EQ(audit_info.ai_asid, audit_asid); | |
179 | |
180 // Retrieve the remote port from the message header, and the child’s task | |
181 // port from the message body. | |
182 base::mac::ScopedMachSendRight remote_port_owner( | |
183 message.header.msgh_remote_port); | |
184 base::mac::ScopedMachSendRight child_task_owner( | |
185 message.port_descriptor.name); | |
186 base::AutoReset<mach_port_t> reset_remote_port(&remote_port_, | |
187 remote_port_owner); | |
188 base::AutoReset<mach_port_t> reset_child_task(&child_task_, | |
189 child_task_owner); | |
190 | |
191 // Verify that the child’s task port is what it purports to be. | |
192 int mach_pid; | |
193 kr = pid_for_task(child_task_, &mach_pid); | |
194 ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "pid_for_task"); | |
195 ASSERT_EQ(pid, mach_pid); | |
196 | |
197 Parent(); | |
198 | |
199 remote_port_ = MACH_PORT_NULL; | |
Robert Sesek
2014/08/19 17:27:51
Do you need to do this? Won't the AutoReset do it
Mark Mentovai
2014/08/20 00:08:48
rsesek wrote:
| |
200 local_port_ = MACH_PORT_NULL; | |
201 remote_port_owner.reset(); | |
202 local_port_owner.reset(); | |
203 | |
204 pipe_fd_ = -1; | |
205 read_pipe.reset(); | |
206 | |
207 int status; | |
208 pid_t wait_pid = waitpid(pid, &status, 0); | |
209 ASSERT_EQ(pid, wait_pid) << ErrnoMessage("waitpid"); | |
210 if (status != 0) { | |
211 std::string message; | |
212 if (WIFEXITED(status)) { | |
213 message = base::StringPrintf("Child exited with code %d", | |
214 WEXITSTATUS(status)); | |
215 } else if (WIFSIGNALED(status)) { | |
216 message = base::StringPrintf("Child terminated by signal %d (%s) %s", | |
217 WTERMSIG(status), | |
218 strsignal(WTERMSIG(status)), | |
219 WCOREDUMP(status) ? " (core dumped)" : ""); | |
220 } | |
221 ASSERT_EQ(0, status) << message; | |
222 } | |
223 } else { | |
224 // Child. | |
225 ScopedNotReached must_not_leave_this_scope; | |
226 | |
227 // local_port is not valid in the forked child process. | |
228 ignore_result(local_port_owner.release()); | |
229 local_port = MACH_PORT_NULL; | |
230 | |
231 // The child uses the write end of the pipe. | |
232 read_pipe.reset(); | |
233 base::AutoReset<int> reset_pipe_fd(&pipe_fd_, write_pipe.get()); | |
234 | |
235 kr = mach_port_allocate( | |
236 mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &local_port); | |
237 ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_allocate"); | |
238 local_port_owner.reset(local_port); | |
239 base::AutoReset<mach_port_t> reset_local_port(&local_port_, | |
240 local_port_owner); | |
241 | |
242 // The remote port can be obtained from the bootstrap server. | |
243 mach_port_t remote_port; | |
244 kr = bootstrap_look_up(bootstrap_port, service_name.c_str(), &remote_port); | |
Robert Sesek
2014/08/19 17:27:51
This doesn't require that stupid const_cast<>?
Mark Mentovai
2014/08/20 00:08:48
rsesek wrote:
| |
245 ASSERT_EQ(BOOTSTRAP_SUCCESS, kr) | |
246 << BootstrapErrorMessage(kr, "bootstrap_look_up"); | |
247 base::mac::ScopedMachSendRight remote_port_owner(remote_port); | |
248 base::AutoReset<mach_port_t> reset_remote_port(&remote_port_, | |
249 remote_port_owner); | |
250 | |
251 // The “hello” message will provide the parent with its remote port, a send | |
252 // right to the child task’s local port receive right. It will also carry a | |
253 // send right to the child task’s task port. | |
254 SendHelloMessage message = {}; | |
255 message.header.msgh_bits = | |
256 MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND) | | |
257 MACH_MSGH_BITS_COMPLEX; | |
258 message.header.msgh_size = sizeof(message); | |
259 message.header.msgh_remote_port = remote_port_; | |
260 message.header.msgh_local_port = local_port_; | |
261 message.body.msgh_descriptor_count = 1; | |
262 message.port_descriptor.name = mach_task_self(); | |
263 message.port_descriptor.disposition = MACH_MSG_TYPE_COPY_SEND; | |
264 message.port_descriptor.type = MACH_MSG_PORT_DESCRIPTOR; | |
265 | |
266 kr = mach_msg(&message.header, | |
267 MACH_SEND_MSG, | |
268 message.header.msgh_size, | |
269 0, | |
270 MACH_PORT_NULL, | |
271 MACH_MSG_TIMEOUT_NONE, | |
272 MACH_PORT_NULL); | |
273 ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg"); | |
274 | |
275 Child(); | |
276 | |
277 remote_port_ = MACH_PORT_NULL; | |
278 local_port_ = MACH_PORT_NULL; | |
279 remote_port_owner.reset(); | |
280 local_port_owner.reset(); | |
281 | |
282 pipe_fd_ = -1; | |
283 write_pipe.reset(); | |
284 | |
285 if (Test::HasFailure()) { | |
286 // Trigger the ScopedNotReached destructor. | |
287 return; | |
288 } | |
289 | |
290 exit(0); | |
291 } | |
292 } | |
293 | |
294 MachMultiprocess::~MachMultiprocess() { | |
295 } | |
296 | |
297 pid_t MachMultiprocess::ChildPID() const { | |
298 DCHECK_NE(child_pid_, 0); | |
299 return child_pid_; | |
300 } | |
301 | |
302 int MachMultiprocess::PipeFD() const { | |
303 DCHECK_NE(pipe_fd_, -1); | |
304 return pipe_fd_; | |
305 } | |
306 | |
307 mach_port_t MachMultiprocess::LocalPort() const { | |
308 DCHECK_NE(local_port_, static_cast<mach_port_t>(MACH_PORT_NULL)); | |
309 return local_port_; | |
310 } | |
311 | |
312 mach_port_t MachMultiprocess::RemotePort() const { | |
313 DCHECK_NE(remote_port_, static_cast<mach_port_t>(MACH_PORT_NULL)); | |
314 return remote_port_; | |
315 } | |
316 | |
317 mach_port_t MachMultiprocess::ChildTask() const { | |
318 DCHECK_NE(child_task_, static_cast<mach_port_t>(MACH_PORT_NULL)); | |
319 return child_task_; | |
320 } | |
321 | |
322 } // namespace test | |
323 } // namespace crashpad | |
OLD | NEW |