| Index: util/mach/child_port_handshake.cc
|
| diff --git a/util/mach/child_port_handshake.cc b/util/mach/child_port_handshake.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c5344bbcdcbbed097ee12346ed1f88fdfd38bed6
|
| --- /dev/null
|
| +++ b/util/mach/child_port_handshake.cc
|
| @@ -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 method’s 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 server’s 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: {
|
| + // There’s 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 it’s 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 token’s not correct, someone’s 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
|
| + // isn’t 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
|
|
|