| OLD | NEW |
| 1 // Copyright 2014 The Crashpad Authors. All rights reserved. | 1 // Copyright 2014 The Crashpad Authors. All rights reserved. |
| 2 // | 2 // |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 // you may not use this file except in compliance with 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 | 5 // You may obtain a copy of the License at |
| 6 // | 6 // |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 // | 8 // |
| 9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
| 13 // limitations under the License. | 13 // limitations under the License. |
| 14 | 14 |
| 15 #include "util/test/mac/mach_multiprocess.h" | 15 #include "util/test/mac/mach_multiprocess.h" |
| 16 | 16 |
| 17 #include <AvailabilityMacros.h> | 17 #include <AvailabilityMacros.h> |
| 18 #include <bsm/libbsm.h> | 18 #include <bsm/libbsm.h> |
| 19 #include <servers/bootstrap.h> | 19 #include <servers/bootstrap.h> |
| 20 #include <signal.h> | |
| 21 #include <stdlib.h> | |
| 22 #include <sys/wait.h> | |
| 23 | 20 |
| 24 #include <string> | 21 #include <string> |
| 25 | 22 |
| 26 #include "base/auto_reset.h" | 23 #include "base/auto_reset.h" |
| 27 #include "base/files/scoped_file.h" | |
| 28 #include "base/logging.h" | 24 #include "base/logging.h" |
| 29 #include "base/mac/scoped_mach_port.h" | 25 #include "base/mac/scoped_mach_port.h" |
| 30 #include "base/memory/scoped_ptr.h" | 26 #include "base/memory/scoped_ptr.h" |
| 31 #include "base/rand_util.h" | 27 #include "base/rand_util.h" |
| 32 #include "base/strings/stringprintf.h" | |
| 33 #include "gtest/gtest.h" | 28 #include "gtest/gtest.h" |
| 34 #include "util/mach/bootstrap.h" | 29 #include "util/mach/bootstrap.h" |
| 30 #include "util/misc/scoped_forbid_return.h" |
| 35 #include "util/test/errors.h" | 31 #include "util/test/errors.h" |
| 36 #include "util/test/mac/mach_errors.h" | 32 #include "util/test/mac/mach_errors.h" |
| 37 | 33 |
| 38 namespace { | 34 namespace { |
| 39 | 35 |
| 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. | 36 // The “hello” message contains a send right to the child process’ task port. |
| 50 struct SendHelloMessage : public mach_msg_base_t { | 37 struct SendHelloMessage : public mach_msg_base_t { |
| 51 mach_msg_port_descriptor_t port_descriptor; | 38 mach_msg_port_descriptor_t port_descriptor; |
| 52 }; | 39 }; |
| 53 | 40 |
| 54 struct ReceiveHelloMessage : public SendHelloMessage { | 41 struct ReceiveHelloMessage : public SendHelloMessage { |
| 55 mach_msg_audit_trailer_t audit_trailer; | 42 mach_msg_audit_trailer_t audit_trailer; |
| 56 }; | 43 }; |
| 57 | 44 |
| 58 } // namespace | 45 } // namespace |
| 59 | 46 |
| 60 namespace crashpad { | 47 namespace crashpad { |
| 61 namespace test { | 48 namespace test { |
| 62 | 49 |
| 63 using namespace testing; | 50 using namespace testing; |
| 64 | 51 |
| 65 namespace internal { | 52 namespace internal { |
| 66 | 53 |
| 67 struct MachMultiprocessInfo { | 54 struct MachMultiprocessInfo { |
| 68 MachMultiprocessInfo() | 55 MachMultiprocessInfo() |
| 69 : service_name(), | 56 : service_name(), |
| 70 pipe_c2p_read(-1), | |
| 71 pipe_c2p_write(-1), | |
| 72 pipe_p2c_read(-1), | |
| 73 pipe_p2c_write(-1), | |
| 74 child_pid(0), | |
| 75 read_pipe_fd(-1), | |
| 76 write_pipe_fd(-1), | |
| 77 local_port(MACH_PORT_NULL), | 57 local_port(MACH_PORT_NULL), |
| 78 remote_port(MACH_PORT_NULL), | 58 remote_port(MACH_PORT_NULL), |
| 79 child_task(MACH_PORT_NULL) {} | 59 child_task(MACH_PORT_NULL) {} |
| 80 | 60 |
| 81 std::string service_name; | 61 std::string service_name; |
| 82 base::ScopedFD pipe_c2p_read; // child to parent | |
| 83 base::ScopedFD pipe_c2p_write; // child to parent | |
| 84 base::ScopedFD pipe_p2c_read; // parent to child | |
| 85 base::ScopedFD pipe_p2c_write; // parent to child | |
| 86 pid_t child_pid; // valid only in parent | |
| 87 int read_pipe_fd; // pipe_c2p_read in parent, pipe_p2c_read in child | |
| 88 int write_pipe_fd; // pipe_p2c_write in parent, pipe_c2p_write in child | |
| 89 base::mac::ScopedMachReceiveRight local_port; | 62 base::mac::ScopedMachReceiveRight local_port; |
| 90 base::mac::ScopedMachSendRight remote_port; | 63 base::mac::ScopedMachSendRight remote_port; |
| 91 base::mac::ScopedMachSendRight child_task; // valid only in parent | 64 base::mac::ScopedMachSendRight child_task; // valid only in parent |
| 92 }; | 65 }; |
| 93 | 66 |
| 94 } // namespace internal | 67 } // namespace internal |
| 95 | 68 |
| 96 MachMultiprocess::MachMultiprocess() : info_(NULL) { | 69 MachMultiprocess::MachMultiprocess() : Multiprocess(), info_(NULL) { |
| 97 } | 70 } |
| 98 | 71 |
| 99 void MachMultiprocess::Run() { | 72 void MachMultiprocess::Run() { |
| 100 ASSERT_EQ(NULL, info_); | 73 ASSERT_EQ(NULL, info_); |
| 101 scoped_ptr<internal::MachMultiprocessInfo> info( | 74 scoped_ptr<internal::MachMultiprocessInfo> info( |
| 102 new internal::MachMultiprocessInfo); | 75 new internal::MachMultiprocessInfo); |
| 103 base::AutoReset<internal::MachMultiprocessInfo*> reset_info(&info_, | 76 base::AutoReset<internal::MachMultiprocessInfo*> reset_info(&info_, |
| 104 info.get()); | 77 info.get()); |
| 105 | 78 |
| 106 int pipe_fds_c2p[2]; | 79 return Multiprocess::Run(); |
| 107 int rv = pipe(pipe_fds_c2p); | 80 } |
| 108 ASSERT_EQ(0, rv) << ErrnoMessage("pipe"); | |
| 109 | 81 |
| 110 info_->pipe_c2p_read.reset(pipe_fds_c2p[0]); | 82 MachMultiprocess::~MachMultiprocess() { |
| 111 info_->pipe_c2p_write.reset(pipe_fds_c2p[1]); | 83 } |
| 112 | 84 |
| 113 int pipe_fds_p2c[2]; | 85 void MachMultiprocess::PreFork() { |
| 114 rv = pipe(pipe_fds_p2c); | 86 Multiprocess::PreFork(); |
| 115 ASSERT_EQ(0, rv) << ErrnoMessage("pipe"); | 87 if (testing::Test::HasFatalFailure()) { |
| 116 | 88 return; |
| 117 info_->pipe_p2c_read.reset(pipe_fds_p2c[0]); | 89 } |
| 118 info_->pipe_p2c_write.reset(pipe_fds_p2c[1]); | |
| 119 | 90 |
| 120 // Set up the parent port and register it with the bootstrap server before | 91 // Set up the parent port and register it with the bootstrap server before |
| 121 // forking, so that it’s guaranteed to be there when the child attempts to | 92 // forking, so that it’s guaranteed to be there when the child attempts to |
| 122 // look it up. | 93 // look it up. |
| 123 info_->service_name = "com.googlecode.crashpad.test.mach_multiprocess."; | 94 info_->service_name = "com.googlecode.crashpad.test.mach_multiprocess."; |
| 124 for (int index = 0; index < 16; ++index) { | 95 for (int index = 0; index < 16; ++index) { |
| 125 info_->service_name.append(1, base::RandInt('A', 'Z')); | 96 info_->service_name.append(1, base::RandInt('A', 'Z')); |
| 126 } | 97 } |
| 127 | 98 |
| 128 mach_port_t local_port; | 99 mach_port_t local_port; |
| 129 kern_return_t kr = | 100 kern_return_t kr = |
| 130 BootstrapCheckIn(bootstrap_port, info_->service_name, &local_port); | 101 BootstrapCheckIn(bootstrap_port, info_->service_name, &local_port); |
| 131 ASSERT_EQ(BOOTSTRAP_SUCCESS, kr) | 102 ASSERT_EQ(BOOTSTRAP_SUCCESS, kr) |
| 132 << BootstrapErrorMessage(kr, "bootstrap_check_in"); | 103 << BootstrapErrorMessage(kr, "bootstrap_check_in"); |
| 133 info_->local_port.reset(local_port); | 104 info_->local_port.reset(local_port); |
| 134 | |
| 135 pid_t pid = fork(); | |
| 136 ASSERT_GE(pid, 0) << ErrnoMessage("fork"); | |
| 137 | |
| 138 if (pid > 0) { | |
| 139 info_->child_pid = pid; | |
| 140 | |
| 141 RunParent(); | |
| 142 | |
| 143 // Waiting for the child happens here instead of in RunParent() because even | |
| 144 // if RunParent() returns early due to a gtest fatal assertion failure, the | |
| 145 // child should still be reaped. | |
| 146 | |
| 147 // This will make the parent hang up on the child as much as would be | |
| 148 // visible from the child’s perspective. The child’s side of the pipe will | |
| 149 // be broken, the child’s remote port will become a dead name, and an | |
| 150 // attempt by the child to look up the service will fail. If this weren’t | |
| 151 // done, the child might hang while waiting for a parent that has already | |
| 152 // triggered a fatal assertion failure to do something. | |
| 153 info.reset(); | |
| 154 info_ = NULL; | |
| 155 | |
| 156 int status; | |
| 157 pid_t wait_pid = waitpid(pid, &status, 0); | |
| 158 ASSERT_EQ(pid, wait_pid) << ErrnoMessage("waitpid"); | |
| 159 if (status != 0) { | |
| 160 std::string message; | |
| 161 if (WIFEXITED(status)) { | |
| 162 message = base::StringPrintf("Child exited with code %d", | |
| 163 WEXITSTATUS(status)); | |
| 164 } else if (WIFSIGNALED(status)) { | |
| 165 message = base::StringPrintf("Child terminated by signal %d (%s) %s", | |
| 166 WTERMSIG(status), | |
| 167 strsignal(WTERMSIG(status)), | |
| 168 WCOREDUMP(status) ? " (core dumped)" : ""); | |
| 169 } | |
| 170 ASSERT_EQ(0, status) << message; | |
| 171 } | |
| 172 } else { | |
| 173 RunChild(); | |
| 174 } | |
| 175 } | |
| 176 | |
| 177 MachMultiprocess::~MachMultiprocess() { | |
| 178 } | |
| 179 | |
| 180 pid_t MachMultiprocess::ChildPID() const { | |
| 181 EXPECT_NE(0, info_->child_pid); | |
| 182 return info_->child_pid; | |
| 183 } | |
| 184 | |
| 185 int MachMultiprocess::ReadPipeFD() const { | |
| 186 EXPECT_NE(-1, info_->read_pipe_fd); | |
| 187 return info_->read_pipe_fd; | |
| 188 } | |
| 189 | |
| 190 int MachMultiprocess::WritePipeFD() const { | |
| 191 EXPECT_NE(-1, info_->write_pipe_fd); | |
| 192 return info_->write_pipe_fd; | |
| 193 } | 105 } |
| 194 | 106 |
| 195 mach_port_t MachMultiprocess::LocalPort() const { | 107 mach_port_t MachMultiprocess::LocalPort() const { |
| 196 EXPECT_NE(static_cast<mach_port_t>(MACH_PORT_NULL), info_->local_port); | 108 EXPECT_NE(static_cast<mach_port_t>(MACH_PORT_NULL), info_->local_port); |
| 197 return info_->local_port; | 109 return info_->local_port; |
| 198 } | 110 } |
| 199 | 111 |
| 200 mach_port_t MachMultiprocess::RemotePort() const { | 112 mach_port_t MachMultiprocess::RemotePort() const { |
| 201 EXPECT_NE(static_cast<mach_port_t>(MACH_PORT_NULL), info_->remote_port); | 113 EXPECT_NE(static_cast<mach_port_t>(MACH_PORT_NULL), info_->remote_port); |
| 202 return info_->remote_port; | 114 return info_->remote_port; |
| 203 } | 115 } |
| 204 | 116 |
| 205 mach_port_t MachMultiprocess::ChildTask() const { | 117 mach_port_t MachMultiprocess::ChildTask() const { |
| 206 EXPECT_NE(static_cast<mach_port_t>(MACH_PORT_NULL), info_->child_task); | 118 EXPECT_NE(static_cast<mach_port_t>(MACH_PORT_NULL), info_->child_task); |
| 207 return info_->child_task; | 119 return info_->child_task; |
| 208 } | 120 } |
| 209 | 121 |
| 210 void MachMultiprocess::RunParent() { | 122 void MachMultiprocess::MultiprocessParent() { |
| 211 // The parent uses the read end of c2p and the write end of p2c. | |
| 212 info_->pipe_c2p_write.reset(); | |
| 213 info_->read_pipe_fd = info_->pipe_c2p_read.get(); | |
| 214 info_->pipe_p2c_read.reset(); | |
| 215 info_->write_pipe_fd = info_->pipe_p2c_write.get(); | |
| 216 | |
| 217 ReceiveHelloMessage message = {}; | 123 ReceiveHelloMessage message = {}; |
| 218 | 124 |
| 219 kern_return_t kr = | 125 kern_return_t kr = |
| 220 mach_msg(&message.header, | 126 mach_msg(&message.header, |
| 221 MACH_RCV_MSG | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | | 127 MACH_RCV_MSG | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | |
| 222 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT), | 128 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT), |
| 223 0, | 129 0, |
| 224 sizeof(message), | 130 sizeof(message), |
| 225 info_->local_port, | 131 info_->local_port, |
| 226 MACH_MSG_TIMEOUT_NONE, | 132 MACH_MSG_TIMEOUT_NONE, |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 292 // from the message body. | 198 // from the message body. |
| 293 info_->remote_port.reset(message.header.msgh_remote_port); | 199 info_->remote_port.reset(message.header.msgh_remote_port); |
| 294 info_->child_task.reset(message.port_descriptor.name); | 200 info_->child_task.reset(message.port_descriptor.name); |
| 295 | 201 |
| 296 // Verify that the child’s task port is what it purports to be. | 202 // Verify that the child’s task port is what it purports to be. |
| 297 int mach_pid; | 203 int mach_pid; |
| 298 kr = pid_for_task(info_->child_task, &mach_pid); | 204 kr = pid_for_task(info_->child_task, &mach_pid); |
| 299 ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "pid_for_task"); | 205 ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "pid_for_task"); |
| 300 ASSERT_EQ(ChildPID(), mach_pid); | 206 ASSERT_EQ(ChildPID(), mach_pid); |
| 301 | 207 |
| 302 Parent(); | 208 MachMultiprocessParent(); |
| 303 | 209 |
| 304 info_->remote_port.reset(); | 210 info_->remote_port.reset(); |
| 305 info_->local_port.reset(); | 211 info_->local_port.reset(); |
| 306 | |
| 307 info_->read_pipe_fd = -1; | |
| 308 info_->pipe_c2p_read.reset(); | |
| 309 info_->write_pipe_fd = -1; | |
| 310 info_->pipe_p2c_write.reset(); | |
| 311 } | 212 } |
| 312 | 213 |
| 313 void MachMultiprocess::RunChild() { | 214 void MachMultiprocess::MultiprocessChild() { |
| 314 ScopedNotReached must_not_leave_this_scope; | 215 ScopedForbidReturn forbid_return;; |
| 315 | 216 |
| 316 // local_port is not valid in the forked child process. | 217 // local_port is not valid in the forked child process. |
| 317 ignore_result(info_->local_port.release()); | 218 ignore_result(info_->local_port.release()); |
| 318 | 219 |
| 319 // The child uses the write end of c2p and the read end of p2c. | |
| 320 info_->pipe_c2p_read.reset(); | |
| 321 info_->write_pipe_fd = info_->pipe_c2p_write.get(); | |
| 322 info_->pipe_p2c_write.reset(); | |
| 323 info_->read_pipe_fd = info_->pipe_p2c_read.get(); | |
| 324 | |
| 325 mach_port_t local_port; | 220 mach_port_t local_port; |
| 326 kern_return_t kr = mach_port_allocate( | 221 kern_return_t kr = mach_port_allocate( |
| 327 mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &local_port); | 222 mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &local_port); |
| 328 ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_allocate"); | 223 ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_allocate"); |
| 329 info_->local_port.reset(local_port); | 224 info_->local_port.reset(local_port); |
| 330 | 225 |
| 331 // The remote port can be obtained from the bootstrap server. | 226 // The remote port can be obtained from the bootstrap server. |
| 332 mach_port_t remote_port; | 227 mach_port_t remote_port; |
| 333 kr = bootstrap_look_up( | 228 kr = bootstrap_look_up( |
| 334 bootstrap_port, info_->service_name.c_str(), &remote_port); | 229 bootstrap_port, info_->service_name.c_str(), &remote_port); |
| (...skipping 18 matching lines...) Expand all Loading... |
| 353 | 248 |
| 354 kr = mach_msg(&message.header, | 249 kr = mach_msg(&message.header, |
| 355 MACH_SEND_MSG, | 250 MACH_SEND_MSG, |
| 356 message.header.msgh_size, | 251 message.header.msgh_size, |
| 357 0, | 252 0, |
| 358 MACH_PORT_NULL, | 253 MACH_PORT_NULL, |
| 359 MACH_MSG_TIMEOUT_NONE, | 254 MACH_MSG_TIMEOUT_NONE, |
| 360 MACH_PORT_NULL); | 255 MACH_PORT_NULL); |
| 361 ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg"); | 256 ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg"); |
| 362 | 257 |
| 363 Child(); | 258 MachMultiprocessChild(); |
| 364 | 259 |
| 365 info_->remote_port.reset(); | 260 info_->remote_port.reset(); |
| 366 info_->local_port.reset(); | 261 info_->local_port.reset(); |
| 367 | 262 |
| 368 info_->write_pipe_fd = -1; | |
| 369 info_->pipe_c2p_write.reset(); | |
| 370 info_->read_pipe_fd = -1; | |
| 371 info_->pipe_p2c_read.reset(); | |
| 372 | |
| 373 if (Test::HasFailure()) { | 263 if (Test::HasFailure()) { |
| 374 // Trigger the ScopedNotReached destructor. | 264 // Trigger the ScopedForbidReturn destructor. |
| 375 return; | 265 return; |
| 376 } | 266 } |
| 377 | 267 |
| 378 exit(0); | 268 forbid_return.Disarm(); |
| 379 } | 269 } |
| 380 | 270 |
| 381 } // namespace test | 271 } // namespace test |
| 382 } // namespace crashpad | 272 } // namespace crashpad |
| OLD | NEW |