crashpad/util/mach/child_port_handshake.cc
Peter Boström 6265674c98 Migrate to NOTREACHED()
This was tested locally by adding "-Wunreachable-code-aggressive" after
making NOTREACHED() [[noreturn]] in mini_chromium and then getting that
to compile.

Bug: chromium:40580068
Change-Id: I7ec1c72be1d73436d128660a621e9060eaebaee8
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/5780891
Reviewed-by: Mark Mentovai <mark@chromium.org>
2024-08-14 03:11:45 +00:00

462 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2014 The Crashpad Authors
//
// 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 <pthread.h>
#include <stdint.h>
#include <sys/event.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <iterator>
#include <utility>
#include "base/apple/mach_logging.h"
#include "base/apple/scoped_mach_port.h"
#include "base/check_op.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/posix/eintr_wrapper.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "util/file/file_io.h"
#include "util/mach/bootstrap.h"
#include "util/mach/child_port.h"
#include "util/mach/child_port_server.h"
#include "util/mach/mach_extensions.h"
#include "util/mach/mach_message.h"
#include "util/mach/mach_message_server.h"
#include "util/misc/implicit_cast.h"
#include "util/misc/random_string.h"
namespace crashpad {
namespace {
class ChildPortHandshakeServer final : public ChildPortServer::Interface {
public:
ChildPortHandshakeServer();
ChildPortHandshakeServer(const ChildPortHandshakeServer&) = delete;
ChildPortHandshakeServer& operator=(const ChildPortHandshakeServer&) = delete;
~ChildPortHandshakeServer();
mach_port_t RunServer(base::ScopedFD server_write_fd,
ChildPortHandshake::PortRightType port_right_type);
private:
// 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,
const mach_msg_trailer_t* trailer,
bool* destroy_request) override;
child_port_token_t token_;
mach_port_t port_;
mach_msg_type_name_t right_type_;
bool checked_in_;
};
ChildPortHandshakeServer::ChildPortHandshakeServer()
: token_(0),
port_(MACH_PORT_NULL),
right_type_(MACH_MSG_TYPE_PORT_NONE),
checked_in_(false) {
}
ChildPortHandshakeServer::~ChildPortHandshakeServer() {
}
mach_port_t ChildPortHandshakeServer::RunServer(
base::ScopedFD server_write_fd,
ChildPortHandshake::PortRightType port_right_type) {
DCHECK_EQ(port_, kMachPortNull);
DCHECK(!checked_in_);
DCHECK(server_write_fd.is_valid());
// Initialize the token and share it with the client via the pipe.
token_ = base::RandUint64();
if (!LoggingWriteFile(server_write_fd.get(), &token_, sizeof(token_))) {
LOG(WARNING) << "no client check-in";
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(
"org.chromium.crashpad.child_port_handshake.%d.%llu.%s",
getpid(),
thread_id,
RandomString().c_str());
// Check the new service in with the bootstrap server, obtaining a receive
// right for it.
base::apple::ScopedMachReceiveRight server_port(
BootstrapCheckIn(service_name));
CHECK(server_port.is_valid());
// Share the service name with the client via the pipe.
uint32_t service_name_length = service_name.size();
if (!LoggingWriteFile(server_write_fd.get(),
&service_name_length,
sizeof(service_name_length))) {
LOG(WARNING) << "no client check-in";
return MACH_PORT_NULL;
}
if (!LoggingWriteFile(
server_write_fd.get(), service_name.c_str(), service_name_length)) {
LOG(WARNING) << "no client check-in";
return MACH_PORT_NULL;
}
// Prior to macOS 10.12, a kqueue cannot monitor a raw Mach receive right with
// EVFILT_MACHPORT. It requires a port set. Compare 10.11.6
// xnu-3248.60.10/osfmk/ipc/ipc_pset.c filt_machportattach(), which requires
// MACH_PORT_RIGHT_PORT_SET, to 10.12.0 xnu-3789.1.32/osfmk/ipc/ipc_pset.c
// filt_machportattach(), which also handles MACH_PORT_TYPE_RECEIVE. Create a
// new port set and add the receive right to it.
base::apple::ScopedMachPortSet server_port_set(
NewMachPort(MACH_PORT_RIGHT_PORT_SET));
CHECK(server_port_set.is_valid());
kern_return_t kr = mach_port_insert_member(
mach_task_self(), server_port.get(), server_port_set.get());
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.get(),
EVFILT_MACHPORT,
EV_ADD | EV_CLEAR,
0,
0,
nullptr);
EV_SET(&changelist[1],
server_write_fd.get(),
EVFILT_WRITE,
EV_ADD | EV_CLEAR,
0,
0,
nullptr);
int rv = HANDLE_EINTR(
kevent(kq.get(), changelist, std::size(changelist), nullptr, 0, nullptr));
PCHECK(rv != -1) << "kevent";
ChildPortServer child_port_server(this);
bool blocking = true;
DCHECK(!checked_in_);
while (!checked_in_) {
DCHECK_EQ(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;
constexpr 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.get());
// 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.get(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kReceiveLargeIgnore,
kMachMessageTimeoutNonblocking);
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), server_write_fd.get());
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();
}
}
if (port_ == MACH_PORT_NULL) {
return MACH_PORT_NULL;
}
bool mismatch = false;
switch (port_right_type) {
case ChildPortHandshake::PortRightType::kReceiveRight:
if (right_type_ != MACH_MSG_TYPE_PORT_RECEIVE) {
LOG(ERROR) << "expected receive right, observed " << right_type_;
mismatch = true;
}
break;
case ChildPortHandshake::PortRightType::kSendRight:
if (right_type_ != MACH_MSG_TYPE_PORT_SEND &&
right_type_ != MACH_MSG_TYPE_PORT_SEND_ONCE) {
LOG(ERROR) << "expected send or send-once right, observed "
<< right_type_;
mismatch = true;
}
break;
}
if (mismatch) {
MachMessageDestroyReceivedPort(port_, right_type_);
port_ = MACH_PORT_NULL;
return MACH_PORT_NULL;
}
mach_port_t port = MACH_PORT_NULL;
std::swap(port_, port);
return port;
}
kern_return_t ChildPortHandshakeServer::HandleChildPortCheckIn(
child_port_server_t server,
const child_port_token_t token,
mach_port_t port,
mach_msg_type_name_t right_type,
const mach_msg_trailer_t* trailer,
bool* destroy_request) {
DCHECK_EQ(port_, kMachPortNull);
DCHECK(!checked_in_);
if (token != token_) {
// If the tokens not correct, someones attempting to spoof the legitimate
// client.
LOG(WARNING) << "ignoring incorrect token";
*destroy_request = true;
} else {
checked_in_ = true;
if (right_type != MACH_MSG_TYPE_PORT_RECEIVE &&
right_type != MACH_MSG_TYPE_PORT_SEND &&
right_type != MACH_MSG_TYPE_PORT_SEND_ONCE) {
// The message needs to carry a receive, send, or send-once right.
LOG(ERROR) << "invalid right type " << right_type;
*destroy_request = true;
} else {
// Communicate the child port and right type back to the RunServer().
// *destroy_request is left at false, because RunServer() needs the right
// to remain intact. It gives ownership of the right to its caller.
port_ = port;
right_type_ = right_type;
}
}
// This is a MIG simpleroutine, there is no reply message.
return MIG_NO_REPLY;
}
} // namespace
ChildPortHandshake::ChildPortHandshake()
: client_read_fd_(),
server_write_fd_() {
// Use socketpair() instead of pipe(). There is no way to suppress SIGPIPE on
// pipes in Mac OS X 10.6, because the F_SETNOSIGPIPE fcntl() command was not
// introduced until 10.7.
int pipe_fds[2];
PCHECK(socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_fds) == 0)
<< "socketpair";
client_read_fd_.reset(pipe_fds[0]);
server_write_fd_.reset(pipe_fds[1]);
// Simulate pipe() semantics by shutting down the “wrong” sides of the socket.
PCHECK(shutdown(server_write_fd_.get(), SHUT_RD) == 0) << "shutdown SHUT_RD";
PCHECK(shutdown(client_read_fd_.get(), SHUT_WR) == 0) << "shutdown SHUT_WR";
// SIGPIPE is undesirable when writing to this pipe. Allow broken-pipe writes
// to fail with EPIPE instead.
constexpr int value = 1;
PCHECK(setsockopt(server_write_fd_.get(),
SOL_SOCKET,
SO_NOSIGPIPE,
&value,
sizeof(value)) == 0) << "setsockopt";
}
ChildPortHandshake::~ChildPortHandshake() {
}
base::ScopedFD ChildPortHandshake::ClientReadFD() {
DCHECK(client_read_fd_.is_valid());
return std::move(client_read_fd_);
}
base::ScopedFD ChildPortHandshake::ServerWriteFD() {
DCHECK(server_write_fd_.is_valid());
return std::move(server_write_fd_);
}
mach_port_t ChildPortHandshake::RunServer(PortRightType port_right_type) {
client_read_fd_.reset();
return RunServerForFD(std::move(server_write_fd_), port_right_type);
}
bool ChildPortHandshake::RunClient(mach_port_t port,
mach_msg_type_name_t right_type) {
server_write_fd_.reset();
return RunClientForFD(std::move(client_read_fd_), port, right_type);
}
// static
mach_port_t ChildPortHandshake::RunServerForFD(base::ScopedFD server_write_fd,
PortRightType port_right_type) {
ChildPortHandshakeServer server;
return server.RunServer(std::move(server_write_fd), port_right_type);
}
// static
bool ChildPortHandshake::RunClientForFD(base::ScopedFD client_read_fd,
mach_port_t port,
mach_msg_type_name_t right_type) {
DCHECK(client_read_fd.is_valid());
// Read the token and the service name from the read side of the pipe.
child_port_token_t token;
std::string service_name;
if (!RunClientInternal_ReadPipe(
client_read_fd.get(), &token, &service_name)) {
return false;
}
// Look up the server and check in with it by providing the token and port.
return RunClientInternal_SendCheckIn(service_name, token, port, right_type);
}
// static
bool ChildPortHandshake::RunClientInternal_ReadPipe(int client_read_fd,
child_port_token_t* token,
std::string* service_name) {
// Read the token from the pipe.
if (!LoggingReadFileExactly(client_read_fd, token, sizeof(*token))) {
return false;
}
// Read the service name from the pipe.
uint32_t service_name_length;
if (!LoggingReadFileExactly(
client_read_fd, &service_name_length, sizeof(service_name_length))) {
return false;
}
service_name->resize(service_name_length);
if (!service_name->empty() &&
!LoggingReadFileExactly(
client_read_fd, &(*service_name)[0], service_name_length)) {
return false;
}
return true;
}
// static
bool 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.
base::apple::ScopedMachSendRight server_port(BootstrapLookUp(service_name));
if (server_port == kMachPortNull) {
return false;
}
// Check in with the server.
kern_return_t kr = child_port_check_in(
server_port.get(), token, port, right_type);
if (kr != KERN_SUCCESS) {
MACH_LOG(ERROR, kr) << "child_port_check_in";
return false;
}
return true;
}
} // namespace crashpad