Add ChildPortHandshake and its test.

ChildPortHandshake is the most generic system yet to allow child
processes to provide their parents with Mach rights. These are
ordinarily expected to be send rights to the children’s own task ports,
or send rights to servers that the children hold receive rights to.

This updates DEPS to pull mini_chromium 1d3523dbda93, which includes
base::mac::ScopedMachPortSet.

TEST=util_test ChildPortHandshake.*
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/756603003
This commit is contained in:
Mark Mentovai 2014-11-25 14:56:05 -05:00
parent 79b4434c81
commit 85c9318597
5 changed files with 753 additions and 1 deletions

2
DEPS
View File

@ -28,7 +28,7 @@ deps = {
'46282cedf40ff7fe803be4af357b9d59050f02e4', # svn r1977
'crashpad/third_party/mini_chromium/mini_chromium':
Var('chromium_git') + '/chromium/mini_chromium@' +
'eeb3b6a4f020dbfc34fe8bc42844796fccc60d5b',
'1d3523dbda93d792ef9e65d42e58e877f1ac01eb',
}
hooks = [

View File

@ -0,0 +1,335 @@
// 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/mach/child_port_handshake.h"
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <servers/bootstrap.h>
#include <sys/event.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_port.h"
#include "base/posix/eintr_wrapper.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "util/file/fd_io.h"
#include "util/mach/child_port.h"
#include "util/mach/mach_extensions.h"
#include "util/mach/mach_message_server.h"
namespace crashpad {
ChildPortHandshake::ChildPortHandshake()
: token_(0),
pipe_read_(),
pipe_write_(),
child_port_(MACH_PORT_NULL),
checked_in_(false) {
int pipe_fds[2];
int rv = pipe(pipe_fds);
PCHECK(rv == 0) << "pipe";
pipe_read_.reset(pipe_fds[0]);
pipe_write_.reset(pipe_fds[1]);
// SIGPIPE is undesirable when writing to this pipe. Allow broken-pipe writes
// to fail with EPIPE instead.
PCHECK(fcntl(pipe_write_.get(), F_SETNOSIGPIPE, 1) == 0) << "fcntl";
}
ChildPortHandshake::~ChildPortHandshake() {
}
int ChildPortHandshake::ReadPipeFD() const {
DCHECK_NE(pipe_read_.get(), -1);
return pipe_read_.get();
}
mach_port_t ChildPortHandshake::RunServer() {
DCHECK_NE(pipe_read_.get(), -1);
pipe_read_.reset();
// Transfer ownership of the write pipe into this methods scope.
base::ScopedFD pipe_write_owner(pipe_write_.release());
// Initialize the token and share it with the client via the pipe.
token_ = base::RandUint64();
int pipe_write = pipe_write_owner.get();
if (!LoggingWriteFD(pipe_write, &token_, sizeof(token_))) {
return MACH_PORT_NULL;
}
// Create a unique name for the bootstrap service mapping. Make it unguessable
// to prevent outsiders from grabbing the name first, which would cause
// bootstrap_check_in() to fail.
uint64_t thread_id;
errno = pthread_threadid_np(pthread_self(), &thread_id);
PCHECK(errno == 0) << "pthread_threadid_np";
std::string service_name = base::StringPrintf(
"com.googlecode.crashpad.child_port_handshake.%d.%llu.%016llx",
getpid(),
thread_id,
base::RandUint64());
DCHECK_LT(service_name.size(), implicit_cast<size_t>(BOOTSTRAP_MAX_NAME_LEN));
// Check the new service in with the bootstrap server, obtaining a receive
// right for it.
mach_port_t server_port;
kern_return_t kr =
bootstrap_check_in(bootstrap_port, service_name.c_str(), &server_port);
BOOTSTRAP_CHECK(kr == BOOTSTRAP_SUCCESS, kr) << "bootstrap_check_in";
base::mac::ScopedMachReceiveRight server_port_owner(server_port);
// Share the service name with the client via the pipe.
uint32_t service_name_length = service_name.size();
if (!LoggingWriteFD(
pipe_write, &service_name_length, sizeof(service_name_length))) {
return MACH_PORT_NULL;
}
if (!LoggingWriteFD(pipe_write, service_name.c_str(), service_name_length)) {
return MACH_PORT_NULL;
}
// A kqueue cannot monitor a raw Mach receive right with EVFILT_MACHPORT. It
// requires a port set. Create a new port set and add the receive right to it.
mach_port_t server_port_set;
kr = mach_port_allocate(
mach_task_self(), MACH_PORT_RIGHT_PORT_SET, &server_port_set);
MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_allocate";
base::mac::ScopedMachPortSet server_port_set_owner(server_port_set);
kr = mach_port_insert_member(mach_task_self(), server_port, server_port_set);
MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_member";
// Set up a kqueue to monitor both the servers receive right and the write
// side of the pipe. Messages from the client will be received via the receive
// right, and the pipe will show EOF if the client closes its read side
// prematurely.
base::ScopedFD kq(kqueue());
PCHECK(kq != -1) << "kqueue";
struct kevent changelist[2];
EV_SET(&changelist[0],
server_port_set,
EVFILT_MACHPORT,
EV_ADD | EV_CLEAR,
0,
0,
nullptr);
EV_SET(&changelist[1],
pipe_write,
EVFILT_WRITE,
EV_ADD | EV_CLEAR,
0,
0,
nullptr);
int rv = HANDLE_EINTR(
kevent(kq.get(), changelist, arraysize(changelist), nullptr, 0, nullptr));
PCHECK(rv != -1) << "kevent";
ChildPortServer child_port_server(this);
bool blocking = true;
DCHECK(!checked_in_);
while (!checked_in_) {
DCHECK_EQ(child_port_, kMachPortNull);
// Get a kevent from the kqueue. Block while waiting for an event unless the
// write pipe has arrived at EOF, in which case the kevent() should be
// nonblocking. Although the client sends its check-in message before
// closing the read side of the pipe, this organization allows the events to
// be delivered out of order and the check-in message will still be
// processed.
struct kevent event;
const timespec nonblocking_timeout = {};
const timespec* timeout = blocking ? nullptr : &nonblocking_timeout;
rv = HANDLE_EINTR(kevent(kq.get(), nullptr, 0, &event, 1, timeout));
PCHECK(rv != -1) << "kevent";
if (rv == 0) {
// Non-blocking kevent() with no events to return.
DCHECK(!blocking);
LOG(WARNING) << "no client check-in";
return MACH_PORT_NULL;
}
DCHECK_EQ(rv, 1);
if (event.flags & EV_ERROR) {
// kevent() may have put its error here.
errno = event.data;
PLOG(FATAL) << "kevent";
}
switch (event.filter) {
case EVFILT_MACHPORT: {
// Theres something to receive on the port set.
DCHECK_EQ(event.ident, server_port_set);
// Run the message server in an inner loop instead of using
// MachMessageServer::kPersistent. This allows the loop to exit as soon
// as child_port_ is set, even if other messages are queued. This needs
// to drain all messages, because the use of edge triggering (EV_CLEAR)
// means that if more than one message is in the queue when kevent()
// returns, no more notifications will be generated.
while (!checked_in_) {
// If a proper message is received from child_port_check_in(),
// this will call HandleChildPortCheckIn().
mach_msg_return_t mr =
MachMessageServer::Run(&child_port_server,
server_port_set,
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kNonblocking,
MachMessageServer::kReceiveLargeIgnore,
MACH_MSG_TIMEOUT_NONE);
if (mr == MACH_RCV_TIMED_OUT) {
break;
} else if (mr != MACH_MSG_SUCCESS) {
MACH_LOG(ERROR, mr) << "MachMessageServer::Run";
return MACH_PORT_NULL;
}
}
break;
}
case EVFILT_WRITE:
// The write pipe is ready to be written to, or its at EOF. The former
// case is uninteresting, but a notification for this may be presented
// because the write pipe will be ready to be written to, at the latest,
// when the client reads its messages from the read side of the same
// pipe. Ignore that case. Multiple notifications for that situation
// will not be generated because edge triggering (EV_CLEAR) is used
// above.
DCHECK_EQ(implicit_cast<int>(event.ident), pipe_write);
if (event.flags & EV_EOF) {
// There are no readers attached to the write pipe. The client has
// closed its side of the pipe. There can be one last shot at
// receiving messages, in case the check-in message is delivered
// out of order, after the EOF notification.
blocking = false;
}
break;
default:
NOTREACHED();
break;
}
}
mach_port_t child_port = MACH_PORT_NULL;
std::swap(child_port_, child_port);
return child_port;
}
kern_return_t ChildPortHandshake::HandleChildPortCheckIn(
child_port_server_t server,
const child_port_token_t token,
mach_port_t port,
mach_msg_type_name_t right_type,
bool* destroy_complex_request) {
DCHECK_EQ(child_port_, kMachPortNull);
if (token != token_) {
// If the tokens not correct, someones attempting to spoof the legitimate
// client.
LOG(WARNING) << "ignoring incorrect token";
*destroy_complex_request = true;
} else {
checked_in_ = true;
if (right_type == MACH_MSG_TYPE_PORT_RECEIVE) {
// The message needs to carry a send right or a send-once right. This
// isnt a strict requirement of the protocol, but users of this class
// expect a send right or a send-once right, both of which can be managed
// by base::mac::ScopedMachSendRight. It is invalid to store a receive
// right in that scoper.
LOG(WARNING) << "ignoring MACH_MSG_TYPE_PORT_RECEIVE";
*destroy_complex_request = true;
} else {
// Communicate the child port back to the RunServer().
// *destroy_complex_request is left at false, because RunServer() needs
// the right to remain intact. It gives ownership of the right to its
// caller.
child_port_ = port;
}
}
// This is a MIG simpleroutine, there is no reply message.
return MIG_NO_REPLY;
}
// static
void ChildPortHandshake::RunClient(int pipe_read,
mach_port_t port,
mach_msg_type_name_t right_type) {
base::ScopedFD pipe_read_owner(pipe_read);
// Read the token and the service name from the read side of the pipe.
child_port_token_t token;
std::string service_name;
RunClientInternal_ReadPipe(pipe_read, &token, &service_name);
// Look up the server and check in with it by providing the token and port.
RunClientInternal_SendCheckIn(service_name, token, port, right_type);
}
// static
void ChildPortHandshake::RunClientInternal_ReadPipe(int pipe_read,
child_port_token_t* token,
std::string* service_name) {
// Read the token from the pipe.
CheckedReadFD(pipe_read, token, sizeof(*token));
// Read the service name from the pipe.
uint32_t service_name_length;
CheckedReadFD(pipe_read, &service_name_length, sizeof(service_name_length));
DCHECK_LT(service_name_length,
implicit_cast<uint32_t>(BOOTSTRAP_MAX_NAME_LEN));
if (service_name_length > 0) {
service_name->resize(service_name_length);
CheckedReadFD(pipe_read, &(*service_name)[0], service_name_length);
}
}
// static
void ChildPortHandshake::RunClientInternal_SendCheckIn(
const std::string& service_name,
child_port_token_t token,
mach_port_t port,
mach_msg_type_name_t right_type) {
// Get a send right to the server by looking up the service with the bootstrap
// server by name.
mach_port_t server_port;
kern_return_t kr =
bootstrap_look_up(bootstrap_port, service_name.c_str(), &server_port);
BOOTSTRAP_CHECK(kr == BOOTSTRAP_SUCCESS, kr) << "bootstrap_look_up";
base::mac::ScopedMachSendRight server_port_owner(server_port);
// Check in with the server.
kr = child_port_check_in(server_port, token, port, right_type);
MACH_CHECK(kr == KERN_SUCCESS, kr) << "child_port_check_in";
}
} // namespace crashpad

View File

@ -0,0 +1,226 @@
// 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.
#ifndef CRASHPAD_UTIL_MACH_CHILD_PORT_HANDSHAKE_H_
#define CRASHPAD_UTIL_MACH_CHILD_PORT_HANDSHAKE_H_
#include <mach/mach.h>
#include <string>
#include "base/basictypes.h"
#include "base/files/scoped_file.h"
#include "util/mach/child_port_server.h"
namespace crashpad {
namespace test {
namespace {
class ChildPortHandshakeTest;
} // namespace
} // namespace test
//! \brief Implements a handshake protocol that allows a parent process to
//! obtain a Mach port right from a child process.
//!
//! Ordinarily, there is no way for parent and child processes to exchange port
//! rights, outside of the rights that children inherit from their parents.
//! These include task-special ports and exception ports, but all of these have
//! system-defined uses, and cannot reliably be replaced: in a multi-threaded
//! parent, it is impossible to temporarily change one an inheritable port while
//! maintaining a guarantee that another thread will not attempt to use it, and
//! in children, it difficult to guarantee that nothing will attempt to use an
//! inheritable port before it can be replaced with the correct one. This latter
//! concern is becoming increasingly more pronounced as system libraries perform
//! more operations that rely on an inheritable port in module initializers.
//!
//! The protocol implemented by this class involves a server that runs in the
//! parent process. The server is published with the bootstrap server, which the
//! child has access to because the bootstrap port is one of the inherited
//! task-special ports. The parent and child also share a pipe, which the parent
//! can write to and the child can read from. After launching a child process,
//! the parent will write a random token to this pipe, along with the name under
//! which its server has been registered with the bootstrap server. The child
//! can then obtain a send right to this server with `bootstrap_look_up()`, and
//! send a check-in message containing the token value and the port right of its
//! choice by calling `child_port_check_in()`.
//!
//! The inclusion of the token authenticates the child to its parent. This is
//! necessary because the service is published with the bootstrap server, which
//! opens up access to it to more than the child process. Because the token is
//! passed to the child by a shared pipe, it constitutes a shared secret not
//! known by other processes that may have incidental access to the server. The
//! ChildPortHandshake server considers its randomly-generated token valid until
//! a client checks in with it. This mechanism is used instead of examining the
//! request messages audit trailer to verify the senders process ID because in
//! some process architectures, it may be impossible to verify the childs
//! process ID. This may happen when the child disassociates from the parent
//! with a double fork(), and the actual client is the parents grandchild. In
//! this case, the child would not check in, but the grandchild, in possession
//! of the token, would check in.
//!
//! The shared pipe serves another purpose: the server monitors it for an
//! end-of-file (no readers) condition. Once detected, it will stop its blocking
//! wait for a client to check in. This mechanism was chosen over monitoring a
//! child process directly for exit to account for the possibility that the
//! child might disassociate with a double fork().
//!
//! This class can be used to allow a child process to provide its parent with
//! a send right to its task port, in cases where it is desirable for the parent
//! to have such access. It can also be used to allow a child process to
//! establish its own server and provide its parent with a send right to that
//! server, for cases where a service is provided and it is undesirable or
//! impossible to provide it via the bootstrap or launchd interfaces.
class ChildPortHandshake : public ChildPortServer::Interface {
public:
//! \brief Initializes the server.
//!
//! This creates the pipe so that the “read” side can be obtained by calling
//! ReadPipeFD().
ChildPortHandshake();
~ChildPortHandshake();
//! \brief Obtains the “read” side of the pipe, to be used by the client.
//!
//! Callers must obtain this file descriptor and arrange for the caller to
//! have access to it before calling RunServer().
//!
//! \return The file descriptor that the client should read from.
int ReadPipeFD() const;
//! \brief Runs the server.
//!
//! This method performs these tasks:
//! - Closes the “read” side of the pipe in-process, so that the client
//! process holds the only file descriptor that can read from the pipe.
//! - Creates a random token and sends it via the pipe.
//! - Checks its service in with the bootstrap server, and sends the name
//! of its bootstrap service mapping via the pipe.
//! - Simultaneously receives messages on its Mach server and monitors the
//! pipe for end-of-file. This is a blocking operation.
//! - When a Mach message is received, calls HandleChildPortCheckIn() to
//! interpret and validate it, and if the message is valid, returns the
//! port right extracted from the message. If the message is not valid,
//! this method will continue waiting for a valid message. Valid messages
//! are properly formatted and have the correct token. If a valid message
//! carries a send or send-once right, it will be returned. If a valid
//! message contains a receive right, it will be destroyed and
//! `MACH_PORT_NULL` will be returned. If a message is not valid, this
//! method will continue waiting for pipe EOF or a valid message.
//! - When notified of pipe EOF, returns `MACH_PORT_NULL`.
//! - Regardless of return value, destroys the servers receive right and
//! closes the pipe.
//!
//! \return On success, the send or send-once right to the port provided by
//! the client. The caller takes ownership of this right. On failure,
//! `MACH_PORT_NULL`, indicating that the client did not check in properly
//! before terminating, where termination is detected by noticing that the
//! read side of the shared pipe has closed. On failure, a message
//! indiciating the nature of the failure will be logged.
mach_port_t RunServer();
// ChildPortServer::Interface:
kern_return_t HandleChildPortCheckIn(child_port_server_t server,
child_port_token_t token,
mach_port_t port,
mach_msg_type_name_t right_type,
bool* destroy_complex_request) override;
//! \brief Runs the client.
//!
//! This function performs these tasks:
//! - Reads the token from the pipe.
//! - Reads the bootstrap service name from the pipe.
//! - Obtains a send right to the server by calling `bootstrap_look_up()`.
//! - Sends a check-in message to the server by calling
//! `child_port_check_in()`, providing the token and the user-supplied port
//! right.
//! - Deallocates the send right to the server, and closes the pipe.
//!
//! There is no return value because `child_port_check_in()` is a MIG
//! `simpleroutine`, and the server does not send a reply. This allows
//! check-in to occur without blocking to wait for a reply.
//!
//! \param[in] pipe_read The “read” side of the pipe shared with the server
//! process.
//! \param[in] port The port that will be passed to the server by
//! `child_port_check_in()`.
//! \param[in] right_type The right type to furnish the parent with. If \a
//! port is a send right, this can be `MACH_MSG_TYPE_COPY_SEND` or
//! `MACH_MSG_TYPE_MOVE_SEND`. If \a port is a send-once right, this can
//! be `MACH_MSG_TYPE_MOVE_SEND_ONCE`. If \a port is a receive right,
//! this can be `MACH_MSG_TYPE_MAKE_SEND`. `MACH_MSG_TYPE_MOVE_RECEIVE`
//! is supported by the client interface but will be silently rejected by
//! server run by RunServer(), which expects to receive only send or
//! send-once rights.
static void RunClient(int pipe_read,
mach_port_t port,
mach_msg_type_name_t right_type);
private:
//! \brief Runs the read-from-pipe portion of the clients side of the
//! handshake. This is an implementation detail of RunClient and is only
//! exposed for testing purposes.
//!
//! \param[in] pipe_read The “read” side of the pipe shared with the server
//! process.
//! \param[out] token The token value read from \a pipe_read.
//! \param[out] service_name The service name as registered with the bootstrap
//! server, read from \a pipe_read.
static void RunClientInternal_ReadPipe(int pipe_read,
child_port_token_t* token,
std::string* service_name);
//! \brief Runs the check-in portion of the clients side of the handshake.
//! This is an implementation detail of RunClient and is only exposed for
//! testing purposes.
//!
//! \param[in] service_name The service name as registered with the bootstrap
//! server, to be looked up with `bootstrap_look_up()`.
//! \param[in] token The token value to provide during check-in.
//! \param[in] port The port that will be passed to the server by
//! `child_port_check_in()`.
//! \param[in] right_type The right type to furnish the parent with.
static void RunClientInternal_SendCheckIn(const std::string& service_name,
child_port_token_t token,
mach_port_t port,
mach_msg_type_name_t right_type);
// Communicates the token from RunServer(), where its generated, to
// HandleChildPortCheckIn(), where its validated.
child_port_token_t token_;
base::ScopedFD pipe_read_;
base::ScopedFD pipe_write_;
// Communicates the port received from the client from
// HandleChildPortCheckIn(), where its received, to RunServer(), where its
// returned. This is strongly-owned, but ownership is transferred to
// RunServer()s caller.
mach_port_t child_port_;
// Communicates that a check-in with a valid token was received by
// HandleChildPortCheckIn(), and that the value of child_port_ should be
// returned to RunServer()s caller.
bool checked_in_;
friend class test::ChildPortHandshakeTest;
DISALLOW_COPY_AND_ASSIGN(ChildPortHandshake);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_MACH_CHILD_PORT_HANDSHAKE_H_

View File

@ -0,0 +1,188 @@
// 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/mach/child_port_handshake.h"
#include "base/mac/scoped_mach_port.h"
#include "gtest/gtest.h"
#include "util/mach/child_port_types.h"
#include "util/mach/mach_extensions.h"
#include "util/test/multiprocess.h"
namespace crashpad {
namespace test {
namespace {
class ChildPortHandshakeTest : public Multiprocess {
public:
enum TestType {
kTestTypeChildChecksIn = 0,
kTestTypeChildDoesNotCheckIn_ReadsPipe,
kTestTypeChildDoesNotCheckIn,
kTestTypeTokenIncorrect,
kTestTypeTokenIncorrectThenCorrect,
};
explicit ChildPortHandshakeTest(TestType test_type)
: Multiprocess(), child_port_handshake_(), test_type_(test_type) {}
~ChildPortHandshakeTest() {}
private:
// Multiprocess:
void MultiprocessParent() override {
base::mac::ScopedMachSendRight child_port(
child_port_handshake_.RunServer());
switch (test_type_) {
case kTestTypeChildChecksIn:
case kTestTypeTokenIncorrectThenCorrect:
EXPECT_EQ(bootstrap_port, child_port);
break;
case kTestTypeChildDoesNotCheckIn_ReadsPipe:
case kTestTypeChildDoesNotCheckIn:
case kTestTypeTokenIncorrect:
EXPECT_EQ(kMachPortNull, child_port);
break;
}
}
void MultiprocessChild() override {
int read_pipe = child_port_handshake_.ReadPipeFD();
switch (test_type_) {
case kTestTypeChildChecksIn:
ChildPortHandshake::RunClient(
read_pipe, bootstrap_port, MACH_MSG_TYPE_COPY_SEND);
break;
case kTestTypeChildDoesNotCheckIn_ReadsPipe: {
// Dont run the standard client routine. Instead, drain the pipe, which
// will get the parent to the point that it begins waiting for a
// check-in message. Then, exit. The pipe is drained using the same
// implementation that the real client would use.
child_port_token_t token;
std::string service_name;
ChildPortHandshake::RunClientInternal_ReadPipe(
read_pipe, &token, &service_name);
break;
}
case kTestTypeChildDoesNotCheckIn:
break;
case kTestTypeTokenIncorrect: {
// Dont run the standard client routine. Instead, read the token and
// service name, mutate the token, and then check in with the bad token.
// The parent should reject the message.
child_port_token_t token;
std::string service_name;
ChildPortHandshake::RunClientInternal_ReadPipe(
read_pipe, &token, &service_name);
child_port_token_t bad_token = ~token;
ChildPortHandshake::RunClientInternal_SendCheckIn(
service_name, bad_token, mach_task_self(), MACH_MSG_TYPE_COPY_SEND);
break;
}
case kTestTypeTokenIncorrectThenCorrect: {
// Dont run the standard client routine. Instead, read the token and
// service name. Mutate the token, and check in with the bad token,
// expecting the parent to reject the message. Then, check in with the
// correct token, expecting the parent to accept it.
child_port_token_t token;
std::string service_name;
ChildPortHandshake::RunClientInternal_ReadPipe(
read_pipe, &token, &service_name);
child_port_token_t bad_token = ~token;
ChildPortHandshake::RunClientInternal_SendCheckIn(
service_name, bad_token, mach_task_self(), MACH_MSG_TYPE_COPY_SEND);
ChildPortHandshake::RunClientInternal_SendCheckIn(
service_name, token, bootstrap_port, MACH_MSG_TYPE_COPY_SEND);
break;
}
}
}
private:
ChildPortHandshake child_port_handshake_;
TestType test_type_;
DISALLOW_COPY_AND_ASSIGN(ChildPortHandshakeTest);
};
TEST(ChildPortHandshake, ChildChecksIn) {
// In this test, the client checks in with the server normally. It sends a
// copy of its bootstrap port to the server, because both parent and child
// should have the same bootstrap port, allowing for verification.
ChildPortHandshakeTest test(ChildPortHandshakeTest::kTestTypeChildChecksIn);
test.Run();
}
TEST(ChildPortHandshake, ChildDoesNotCheckIn) {
// In this test, the client exits without checking in. This tests that the
// server properly detects that it has lost a client. Whether or not the
// client closes the pipe before the server writes to it is a race, and the
// server needs to be able to detect client loss in both cases, so the
// ChildDoesNotCheckIn_ReadsPipe and NoChild tests also exist to test these
// individual cases more deterministically.
ChildPortHandshakeTest test(
ChildPortHandshakeTest::kTestTypeChildDoesNotCheckIn);
test.Run();
}
TEST(ChildPortHandshake, ChildDoesNotCheckIn_ReadsPipe) {
// In this test, the client reads from its pipe, and subsequently exits
// without checking in. This tests that the server properly detects that it
// has lost its client after sending instructions to it via the pipe, while
// waiting for a check-in message.
ChildPortHandshakeTest test(
ChildPortHandshakeTest::kTestTypeChildDoesNotCheckIn_ReadsPipe);
test.Run();
}
TEST(ChildPortHandshake, TokenIncorrect) {
// In this test, the client checks in with the server with an incorrect token
// value and a copy of its own task port. The server should reject the message
// because of the invalid token, and return MACH_PORT_NULL to its caller.
ChildPortHandshakeTest test(ChildPortHandshakeTest::kTestTypeTokenIncorrect);
test.Run();
}
TEST(ChildPortHandshake, TokenIncorrectThenCorrect) {
// In this test, the client checks in with the server with an incorrect token
// value and a copy of its own task port, and subsequently, the correct token
// value and a copy of its bootstrap port. The server should reject the first
// because of the invalid token, but it should continue waiting for a message
// with a valid token as long as the pipe remains open. It should wind wind up
// returning the bootstrap port, allowing for verification.
ChildPortHandshakeTest test(
ChildPortHandshakeTest::kTestTypeTokenIncorrectThenCorrect);
test.Run();
}
TEST(ChildPortHandshake, NoChild) {
// In this test, the client never checks in with the parent because the child
// never even runs. This tests that the server properly detects that it has
// no client at all, and does not terminate execution with an error such as
// “broken pipe” when attempting to send instructions to the client. This test
// is similar to ChildDoesNotCheckIn, but because theres no child at all, the
// server is guaranteed to see that its pipe partner is gone.
ChildPortHandshake child_port_handshake;
base::mac::ScopedMachSendRight child_port(child_port_handshake.RunServer());
EXPECT_EQ(kMachPortNull, child_port);
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -40,6 +40,8 @@
'mac/mac_util.h',
'mac/service_management.cc',
'mac/service_management.h',
'mach/child_port_handshake.cc',
'mach/child_port_handshake.h',
'mach/child_port_server.cc',
'mach/child_port_server.h',
'mach/child_port_types.h',
@ -236,6 +238,7 @@
'mac/launchd_test.mm',
'mac/mac_util_test.mm',
'mac/service_management_test.mm',
'mach/child_port_handshake_test.cc',
'mach/child_port_server_test.cc',
'mach/exc_client_variants_test.cc',
'mach/exc_server_variants_test.cc',