Chromium Code Reviews| 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 |