// 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 #include #include #include #include #include #include #include #include #include #include "base/check_op.h" #include "base/cxx17_backports.h" #include "base/logging.h" #include "base/mac/mach_logging.h" #include "base/mac/scoped_mach_port.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(); 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_; DISALLOW_COPY_AND_ASSIGN(ChildPortHandshakeServer); }; 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::mac::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::mac::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 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.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, base::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: { // There’s 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 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(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(); break; } } 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 token’s not correct, someone’s 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::mac::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