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/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 |
OLD | NEW |