Chromium Code Reviews| Index: util/test/mac/mach_multiprocess.cc |
| diff --git a/util/test/mac/mach_multiprocess.cc b/util/test/mac/mach_multiprocess.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..1b3e9d2fa1ed8ba9d0cb48da7a674dd00ac41dc3 |
| --- /dev/null |
| +++ b/util/test/mac/mach_multiprocess.cc |
| @@ -0,0 +1,323 @@ |
| +// Copyright 2014 The Crashpad Authors. All rights reserved. |
| +// |
| +// Licensed under the Apache License, Version 2.0 (the "License"); |
| +// you may not use this file except in compliance with the License. |
| +// You may obtain a copy of the License at |
| +// |
| +// http://www.apache.org/licenses/LICENSE-2.0 |
| +// |
| +// Unless required by applicable law or agreed to in writing, software |
| +// distributed under the License is distributed on an "AS IS" BASIS, |
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| +// See the License for the specific language governing permissions and |
| +// limitations under the License. |
| + |
| +#include "util/test/mac/mach_multiprocess.h" |
| + |
| +#include <AvailabilityMacros.h> |
| +#include <bsm/libbsm.h> |
| +#include <servers/bootstrap.h> |
| +#include <signal.h> |
| +#include <stdlib.h> |
| +#include <sys/wait.h> |
| + |
| +#include <string> |
| + |
| +#include "base/auto_reset.h" |
| +#include "base/files/scoped_file.h" |
| +#include "base/logging.h" |
| +#include "base/mac/scoped_mach_port.h" |
| +#include "base/rand_util.h" |
| +#include "base/strings/stringprintf.h" |
| +#include "gtest/gtest.h" |
| +#include "util/mach/bootstrap.h" |
| +#include "util/test/errors.h" |
| +#include "util/test/mac/mach_errors.h" |
| + |
| +namespace { |
| + |
| +class ScopedNotReached { |
| + public: |
| + ScopedNotReached() {} |
| + ~ScopedNotReached() { abort(); } |
| + |
| + private: |
| + DISALLOW_COPY_AND_ASSIGN(ScopedNotReached); |
| +}; |
| + |
| +} // namespace |
| + |
| +namespace crashpad { |
| +namespace test { |
| + |
| +using namespace testing; |
| + |
| +MachMultiprocess::MachMultiprocess() |
| + : child_pid_(0), |
| + pipe_fd_(-1), |
| + local_port_(MACH_PORT_NULL), |
| + remote_port_(MACH_PORT_NULL), |
| + child_task_(MACH_PORT_NULL) { |
| +} |
| + |
| +void MachMultiprocess::Run() { |
| + int pipe_fds[2]; |
| + int rv = pipe(pipe_fds); |
| + ASSERT_EQ(0, rv) << ErrnoMessage("pipe"); |
| + |
| + base::ScopedFD read_pipe(pipe_fds[0]); |
| + base::ScopedFD write_pipe(pipe_fds[1]); |
| + |
| + // Set up the parent port and register it with the bootstrap server before |
| + // forking, so that it’s guaranteed to be there when the child attempts to |
| + // look it up. |
| + std::string service_name = "com.googlecode.crashpad.test.mach_multiprocess."; |
| + for (int index = 0; index < 16; ++index) { |
| + service_name.append(1, base::RandInt('A', 'Z')); |
| + } |
| + |
| + mach_port_t local_port; |
| + kern_return_t kr = |
| + BootstrapCheckIn(bootstrap_port, service_name, &local_port); |
| + ASSERT_EQ(BOOTSTRAP_SUCCESS, kr) |
| + << BootstrapErrorMessage(kr, "bootstrap_check_in"); |
| + base::mac::ScopedMachReceiveRight local_port_owner(local_port); |
| + |
| + pid_t pid = fork(); |
| + ASSERT_GE(pid, 0) << ErrnoMessage("fork"); |
| + |
| + // The “hello” message contains a send right to the child process’ task port. |
| + struct SendHelloMessage : public mach_msg_base_t { |
| + mach_msg_port_descriptor_t port_descriptor; |
| + }; |
| + |
| + struct ReceiveHelloMessage : public SendHelloMessage { |
| + mach_msg_audit_trailer_t audit_trailer; |
| + }; |
| + |
| + 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:
|
| + // Parent. |
| + base::AutoReset<pid_t> reset_child_pid(&child_pid_, pid); |
| + |
| + // The parent uses the read end of the pipe. |
| + write_pipe.reset(); |
| + base::AutoReset<int> reset_pipe_fd(&pipe_fd_, read_pipe.get()); |
| + |
| + base::AutoReset<mach_port_t> reset_local_port(&local_port_, |
| + local_port_owner); |
| + |
| + ReceiveHelloMessage message = {}; |
| + |
| + kr = mach_msg(&message.header, |
| + MACH_RCV_MSG | |
| + MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | |
| + MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT), |
| + 0, |
| + sizeof(message), |
| + local_port_, |
| + MACH_MSG_TIMEOUT_NONE, |
| + MACH_PORT_NULL); |
| + ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg"); |
| + |
| + // 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.
|
| + // expected value. |
| + EXPECT_EQ(MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MOVE_SEND) | |
| + MACH_MSGH_BITS_COMPLEX, |
| + message.header.msgh_bits); |
| + ASSERT_EQ(sizeof(SendHelloMessage), message.header.msgh_size); |
| + EXPECT_EQ(local_port_, message.header.msgh_local_port); |
| + ASSERT_EQ(1u, message.body.msgh_descriptor_count); |
| + EXPECT_EQ(static_cast<mach_msg_type_name_t>(MACH_MSG_TYPE_MOVE_SEND), |
| + message.port_descriptor.disposition); |
| + ASSERT_EQ(static_cast<mach_msg_descriptor_type_t>(MACH_MSG_PORT_DESCRIPTOR), |
| + message.port_descriptor.type); |
| + ASSERT_EQ(static_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0), |
| + message.audit_trailer.msgh_trailer_type); |
| + ASSERT_EQ(sizeof(message.audit_trailer), |
| + message.audit_trailer.msgh_trailer_size); |
| + EXPECT_EQ(0u, message.audit_trailer.msgh_seqno); |
| + |
| + // Check the audit trailer’s values for sanity. |
| +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8 |
| + uid_t audit_auid; |
| + uid_t audit_euid; |
| + gid_t audit_egid; |
| + uid_t audit_ruid; |
| + gid_t audit_rgid; |
| + pid_t audit_pid; |
| + au_asid_t audit_asid; |
| + audit_token_to_au32(message.audit_trailer.msgh_audit, |
| + &audit_auid, |
| + &audit_euid, |
| + &audit_egid, |
| + &audit_ruid, |
| + &audit_rgid, |
| + &audit_pid, |
| + &audit_asid, |
| + NULL); |
| +#else |
| + uid_t audit_auid = audit_token_to_auid(message.audit_trailer.msgh_audit); |
| + uid_t audit_euid = audit_token_to_euid(message.audit_trailer.msgh_audit); |
| + gid_t audit_egid = audit_token_to_egid(message.audit_trailer.msgh_audit); |
| + uid_t audit_ruid = audit_token_to_ruid(message.audit_trailer.msgh_audit); |
| + gid_t audit_rgid = audit_token_to_rgid(message.audit_trailer.msgh_audit); |
| + pid_t audit_pid = audit_token_to_pid(message.audit_trailer.msgh_audit); |
| + au_asid_t audit_asid = |
| + audit_token_to_asid(message.audit_trailer.msgh_audit); |
| +#endif |
| + EXPECT_EQ(geteuid(), audit_euid); |
| + EXPECT_EQ(getegid(), audit_egid); |
| + EXPECT_EQ(getuid(), audit_ruid); |
| + EXPECT_EQ(getgid(), audit_rgid); |
| + ASSERT_EQ(pid, audit_pid); |
| + |
| + auditinfo_addr_t audit_info; |
| + rv = getaudit_addr(&audit_info, sizeof(audit_info)); |
| + ASSERT_EQ(0, rv) << ErrnoMessage("getaudit_addr"); |
| + EXPECT_EQ(audit_info.ai_auid, audit_auid); |
| + EXPECT_EQ(audit_info.ai_asid, audit_asid); |
| + |
| + // Retrieve the remote port from the message header, and the child’s task |
| + // port from the message body. |
| + base::mac::ScopedMachSendRight remote_port_owner( |
| + message.header.msgh_remote_port); |
| + base::mac::ScopedMachSendRight child_task_owner( |
| + message.port_descriptor.name); |
| + base::AutoReset<mach_port_t> reset_remote_port(&remote_port_, |
| + remote_port_owner); |
| + base::AutoReset<mach_port_t> reset_child_task(&child_task_, |
| + child_task_owner); |
| + |
| + // Verify that the child’s task port is what it purports to be. |
| + int mach_pid; |
| + kr = pid_for_task(child_task_, &mach_pid); |
| + ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "pid_for_task"); |
| + ASSERT_EQ(pid, mach_pid); |
| + |
| + Parent(); |
| + |
| + 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:
|
| + local_port_ = MACH_PORT_NULL; |
| + remote_port_owner.reset(); |
| + local_port_owner.reset(); |
| + |
| + pipe_fd_ = -1; |
| + read_pipe.reset(); |
| + |
| + int status; |
| + pid_t wait_pid = waitpid(pid, &status, 0); |
| + ASSERT_EQ(pid, wait_pid) << ErrnoMessage("waitpid"); |
| + if (status != 0) { |
| + std::string message; |
| + if (WIFEXITED(status)) { |
| + message = base::StringPrintf("Child exited with code %d", |
| + WEXITSTATUS(status)); |
| + } else if (WIFSIGNALED(status)) { |
| + message = base::StringPrintf("Child terminated by signal %d (%s) %s", |
| + WTERMSIG(status), |
| + strsignal(WTERMSIG(status)), |
| + WCOREDUMP(status) ? " (core dumped)" : ""); |
| + } |
| + ASSERT_EQ(0, status) << message; |
| + } |
| + } else { |
| + // Child. |
| + ScopedNotReached must_not_leave_this_scope; |
| + |
| + // local_port is not valid in the forked child process. |
| + ignore_result(local_port_owner.release()); |
| + local_port = MACH_PORT_NULL; |
| + |
| + // The child uses the write end of the pipe. |
| + read_pipe.reset(); |
| + base::AutoReset<int> reset_pipe_fd(&pipe_fd_, write_pipe.get()); |
| + |
| + kr = mach_port_allocate( |
| + mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &local_port); |
| + ASSERT_EQ(KERN_SUCCESS, kr) << MachErrorMessage(kr, "mach_port_allocate"); |
| + local_port_owner.reset(local_port); |
| + base::AutoReset<mach_port_t> reset_local_port(&local_port_, |
| + local_port_owner); |
| + |
| + // The remote port can be obtained from the bootstrap server. |
| + mach_port_t remote_port; |
| + 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:
|
| + ASSERT_EQ(BOOTSTRAP_SUCCESS, kr) |
| + << BootstrapErrorMessage(kr, "bootstrap_look_up"); |
| + base::mac::ScopedMachSendRight remote_port_owner(remote_port); |
| + base::AutoReset<mach_port_t> reset_remote_port(&remote_port_, |
| + remote_port_owner); |
| + |
| + // The “hello” message will provide the parent with its remote port, a send |
| + // right to the child task’s local port receive right. It will also carry a |
| + // send right to the child task’s task port. |
| + SendHelloMessage message = {}; |
| + message.header.msgh_bits = |
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND) | |
| + MACH_MSGH_BITS_COMPLEX; |
| + message.header.msgh_size = sizeof(message); |
| + message.header.msgh_remote_port = remote_port_; |
| + message.header.msgh_local_port = local_port_; |
| + message.body.msgh_descriptor_count = 1; |
| + message.port_descriptor.name = mach_task_self(); |
| + message.port_descriptor.disposition = MACH_MSG_TYPE_COPY_SEND; |
| + message.port_descriptor.type = MACH_MSG_PORT_DESCRIPTOR; |
| + |
| + kr = mach_msg(&message.header, |
| + MACH_SEND_MSG, |
| + message.header.msgh_size, |
| + 0, |
| + MACH_PORT_NULL, |
| + MACH_MSG_TIMEOUT_NONE, |
| + MACH_PORT_NULL); |
| + ASSERT_EQ(MACH_MSG_SUCCESS, kr) << MachErrorMessage(kr, "mach_msg"); |
| + |
| + Child(); |
| + |
| + remote_port_ = MACH_PORT_NULL; |
| + local_port_ = MACH_PORT_NULL; |
| + remote_port_owner.reset(); |
| + local_port_owner.reset(); |
| + |
| + pipe_fd_ = -1; |
| + write_pipe.reset(); |
| + |
| + if (Test::HasFailure()) { |
| + // Trigger the ScopedNotReached destructor. |
| + return; |
| + } |
| + |
| + exit(0); |
| + } |
| +} |
| + |
| +MachMultiprocess::~MachMultiprocess() { |
| +} |
| + |
| +pid_t MachMultiprocess::ChildPID() const { |
| + DCHECK_NE(child_pid_, 0); |
| + return child_pid_; |
| +} |
| + |
| +int MachMultiprocess::PipeFD() const { |
| + DCHECK_NE(pipe_fd_, -1); |
| + return pipe_fd_; |
| +} |
| + |
| +mach_port_t MachMultiprocess::LocalPort() const { |
| + DCHECK_NE(local_port_, static_cast<mach_port_t>(MACH_PORT_NULL)); |
| + return local_port_; |
| +} |
| + |
| +mach_port_t MachMultiprocess::RemotePort() const { |
| + DCHECK_NE(remote_port_, static_cast<mach_port_t>(MACH_PORT_NULL)); |
| + return remote_port_; |
| +} |
| + |
| +mach_port_t MachMultiprocess::ChildTask() const { |
| + DCHECK_NE(child_task_, static_cast<mach_port_t>(MACH_PORT_NULL)); |
| + return child_task_; |
| +} |
| + |
| +} // namespace test |
| +} // namespace crashpad |