// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/mac/mach_port_rendezvous.h" #include #include #include #include #include #include "base/containers/buffer_iterator.h" #include "base/logging.h" #include "base/mac/foundation_util.h" #include "base/mac/mach_logging.h" #include "base/mac/scoped_mach_msg_destroy.h" #include "base/notreached.h" #include "base/strings/stringprintf.h" namespace base { namespace { // The name to use in the bootstrap server, formatted with the BaseBundleID and // PID of the server. constexpr char kBootstrapNameFormat[] = "%s.MachPortRendezvousServer.%d"; // This limit is arbitrary and can be safely increased in the future. constexpr size_t kMaximumRendezvousPorts = 5; enum MachRendezvousMsgId : mach_msg_id_t { kMachRendezvousMsgIdRequest = 'mrzv', kMachRendezvousMsgIdResponse = 'MRZV', }; size_t CalculateResponseSize(size_t num_ports) { return sizeof(mach_msg_base_t) + (num_ports * sizeof(mach_msg_port_descriptor_t)) + (num_ports * sizeof(MachPortsForRendezvous::key_type)); } } // namespace MachRendezvousPort::MachRendezvousPort(mach_port_t name, mach_msg_type_name_t disposition) : name_(name), disposition_(disposition) { DCHECK(disposition == MACH_MSG_TYPE_MOVE_RECEIVE || disposition == MACH_MSG_TYPE_MOVE_SEND || disposition == MACH_MSG_TYPE_MOVE_SEND_ONCE || disposition == MACH_MSG_TYPE_COPY_SEND || disposition == MACH_MSG_TYPE_MAKE_SEND || disposition == MACH_MSG_TYPE_MAKE_SEND_ONCE); } MachRendezvousPort::MachRendezvousPort(mac::ScopedMachSendRight send_right) : name_(send_right.release()), disposition_(MACH_MSG_TYPE_MOVE_SEND) {} MachRendezvousPort::MachRendezvousPort( mac::ScopedMachReceiveRight receive_right) : name_(receive_right.release()), disposition_(MACH_MSG_TYPE_MOVE_RECEIVE) {} MachRendezvousPort::~MachRendezvousPort() = default; void MachRendezvousPort::Destroy() { // Map the disposition to the type of right to deallocate. mach_port_right_t right = 0; switch (disposition_) { case 0: DCHECK(name_ == MACH_PORT_NULL); return; case MACH_MSG_TYPE_COPY_SEND: case MACH_MSG_TYPE_MAKE_SEND: case MACH_MSG_TYPE_MAKE_SEND_ONCE: // Right is not owned, would be created by transit. return; case MACH_MSG_TYPE_MOVE_RECEIVE: right = MACH_PORT_RIGHT_RECEIVE; break; case MACH_MSG_TYPE_MOVE_SEND: right = MACH_PORT_RIGHT_SEND; break; case MACH_MSG_TYPE_MOVE_SEND_ONCE: right = MACH_PORT_RIGHT_SEND_ONCE; break; default: NOTREACHED() << "Leaking port name " << name_ << " with disposition " << disposition_; return; } kern_return_t kr = mach_port_mod_refs(mach_task_self(), name_, right, -1); MACH_DCHECK(kr == KERN_SUCCESS, kr) << "Failed to drop ref on port name " << name_; name_ = MACH_PORT_NULL; disposition_ = 0; } // static MachPortRendezvousServer* MachPortRendezvousServer::GetInstance() { static auto* instance = new MachPortRendezvousServer(); return instance; } void MachPortRendezvousServer::RegisterPortsForPid( pid_t pid, const MachPortsForRendezvous& ports) { lock_.AssertAcquired(); DCHECK_LT(ports.size(), kMaximumRendezvousPorts); DCHECK(!ports.empty()); ScopedDispatchObject exit_watcher(dispatch_source_create( DISPATCH_SOURCE_TYPE_PROC, static_cast(pid), DISPATCH_PROC_EXIT, dispatch_source_->queue())); dispatch_source_set_event_handler(exit_watcher, ^{ OnClientExited(pid); }); dispatch_resume(exit_watcher); auto it = client_data_.emplace(pid, ClientData{std::move(exit_watcher), ports}); DCHECK(it.second); } MachPortRendezvousServer::ClientData::ClientData( ScopedDispatchObject exit_watcher, MachPortsForRendezvous ports) : exit_watcher(exit_watcher), ports(ports) {} MachPortRendezvousServer::ClientData::ClientData(ClientData&&) = default; MachPortRendezvousServer::ClientData::~ClientData() = default; MachPortRendezvousServer::MachPortRendezvousServer() { std::string bootstrap_name = StringPrintf(kBootstrapNameFormat, mac::BaseBundleID(), getpid()); kern_return_t kr = bootstrap_check_in( bootstrap_port, bootstrap_name.c_str(), mac::ScopedMachReceiveRight::Receiver(server_port_).get()); BOOTSTRAP_CHECK(kr == KERN_SUCCESS, kr) << "bootstrap_check_in " << bootstrap_name; dispatch_source_ = std::make_unique( bootstrap_name.c_str(), server_port_.get(), ^{ HandleRequest(); }); dispatch_source_->Resume(); } MachPortRendezvousServer::~MachPortRendezvousServer() {} void MachPortRendezvousServer::HandleRequest() { // Receive the request message, using the kernel audit token to ascertain the // PID of the sender. struct : mach_msg_header_t { mach_msg_audit_trailer_t trailer; } request{}; request.msgh_size = sizeof(request); request.msgh_local_port = server_port_.get(); const mach_msg_option_t options = MACH_RCV_MSG | MACH_RCV_TIMEOUT | MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); mach_msg_return_t mr = mach_msg(&request, options, 0, sizeof(request), server_port_.get(), 0, MACH_PORT_NULL); if (mr != KERN_SUCCESS) { MACH_LOG(ERROR, mr) << "mach_msg receive"; return; } // Destroy the message in case of an early return, which will release // any rights from a bad message. In the case of a disallowed sender, // the destruction of the reply port will break them out of a mach_msg. ScopedMachMsgDestroy scoped_message(&request); if (request.msgh_id != kMachRendezvousMsgIdRequest || request.msgh_size != sizeof(mach_msg_header_t)) { // Do not reply to messages that are unexpected. return; } pid_t sender_pid = audit_token_to_pid(request.trailer.msgh_audit); MachPortsForRendezvous ports_to_send = PortsForPid(sender_pid); if (ports_to_send.empty()) { return; } std::unique_ptr response = CreateReplyMessage(request.msgh_remote_port, ports_to_send); auto* header = reinterpret_cast(response.get()); mr = mach_msg(header, MACH_SEND_MSG, header->msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if (mr == KERN_SUCCESS) { scoped_message.Disarm(); } else { MACH_LOG(ERROR, mr) << "mach_msg send"; } } MachPortsForRendezvous MachPortRendezvousServer::PortsForPid(pid_t pid) { MachPortsForRendezvous ports_to_send; AutoLock lock(lock_); auto it = client_data_.find(pid); if (it != client_data_.end()) { ports_to_send = std::move(it->second.ports); client_data_.erase(it); } return ports_to_send; } std::unique_ptr MachPortRendezvousServer::CreateReplyMessage( mach_port_t reply_port, const MachPortsForRendezvous& ports) { const size_t port_count = ports.size(); const size_t buffer_size = CalculateResponseSize(port_count); auto buffer = std::make_unique(buffer_size); BufferIterator iterator(buffer.get(), buffer_size); auto* message = iterator.MutableObject(); message->header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MOVE_SEND_ONCE) | MACH_MSGH_BITS_COMPLEX; message->header.msgh_size = checked_cast(buffer_size); message->header.msgh_remote_port = reply_port; message->header.msgh_id = kMachRendezvousMsgIdResponse; message->body.msgh_descriptor_count = checked_cast(port_count); auto descriptors = iterator.MutableSpan(port_count); auto port_identifiers = iterator.MutableSpan(port_count); auto port_it = ports.begin(); for (size_t i = 0; i < port_count; ++i, ++port_it) { const MachRendezvousPort& port_for_rendezvous = port_it->second; mach_msg_port_descriptor_t* descriptor = &descriptors[i]; descriptor->name = port_for_rendezvous.name(); descriptor->disposition = port_for_rendezvous.disposition(); descriptor->type = MACH_MSG_PORT_DESCRIPTOR; port_identifiers[i] = port_it->first; } return buffer; } void MachPortRendezvousServer::OnClientExited(pid_t pid) { MachPortsForRendezvous ports = PortsForPid(pid); for (auto& pair : ports) { pair.second.Destroy(); } } // static MachPortRendezvousClient* MachPortRendezvousClient::GetInstance() { static MachPortRendezvousClient* client = []() -> auto* { auto* client = new MachPortRendezvousClient(); if (!client->AcquirePorts()) { delete client; client = nullptr; } return client; } (); return client; } mac::ScopedMachSendRight MachPortRendezvousClient::TakeSendRight( MachPortsForRendezvous::key_type key) { MachRendezvousPort port = PortForKey(key); DCHECK(port.disposition() == 0 || port.disposition() == MACH_MSG_TYPE_PORT_SEND || port.disposition() == MACH_MSG_TYPE_PORT_SEND_ONCE); return mac::ScopedMachSendRight(port.name()); } mac::ScopedMachReceiveRight MachPortRendezvousClient::TakeReceiveRight( MachPortsForRendezvous::key_type key) { MachRendezvousPort port = PortForKey(key); DCHECK(port.disposition() == 0 || port.disposition() == MACH_MSG_TYPE_PORT_RECEIVE); return mac::ScopedMachReceiveRight(port.name()); } size_t MachPortRendezvousClient::GetPortCount() { AutoLock lock(lock_); return ports_.size(); } // static std::string MachPortRendezvousClient::GetBootstrapName() { return StringPrintf(kBootstrapNameFormat, mac::BaseBundleID(), getppid()); } bool MachPortRendezvousClient::AcquirePorts() { AutoLock lock(lock_); mac::ScopedMachSendRight server_port; std::string bootstrap_name = GetBootstrapName(); kern_return_t kr = bootstrap_look_up( bootstrap_port, const_cast(bootstrap_name.c_str()), mac::ScopedMachSendRight::Receiver(server_port).get()); if (kr != KERN_SUCCESS) { BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_look_up " << bootstrap_name; return false; } return SendRequest(std::move(server_port)); } bool MachPortRendezvousClient::SendRequest( mac::ScopedMachSendRight server_port) { const size_t buffer_size = CalculateResponseSize(kMaximumRendezvousPorts) + sizeof(mach_msg_trailer_t); auto buffer = std::make_unique(buffer_size); BufferIterator iterator(buffer.get(), buffer_size); // Perform a send and receive mach_msg. auto* message = iterator.MutableObject(); message->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE); // The |buffer_size| is used for receiving, since it includes space for the // the entire reply and receiving trailer. But for the request being sent, // the size is just an empty message. message->header.msgh_size = sizeof(mach_msg_header_t); message->header.msgh_remote_port = server_port.release(); message->header.msgh_local_port = mig_get_reply_port(); message->header.msgh_id = kMachRendezvousMsgIdRequest; kern_return_t mr = mach_msg( &message->header, MACH_SEND_MSG | MACH_RCV_MSG, message->header.msgh_size, checked_cast(buffer_size), message->header.msgh_local_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if (mr != KERN_SUCCESS) { MACH_LOG(ERROR, mr) << "mach_msg"; return false; } if (message->header.msgh_id != kMachRendezvousMsgIdResponse) { // Check if the response contains a rendezvous reply. If there were no // ports for this client, then the send right would have been destroyed. if (message->header.msgh_id == MACH_NOTIFY_SEND_ONCE) { return true; } return false; } const size_t port_count = message->body.msgh_descriptor_count; auto descriptors = iterator.Span(port_count); auto port_identifiers = iterator.Span(port_count); if (descriptors.size() != port_identifiers.size()) { // Ensure that the descriptors and keys are of the same size. return false; } for (size_t i = 0; i < port_count; ++i) { MachRendezvousPort rendezvous_port(descriptors[i].name, descriptors[i].disposition); ports_.emplace(port_identifiers[i], rendezvous_port); } return true; } MachRendezvousPort MachPortRendezvousClient::PortForKey( MachPortsForRendezvous::key_type key) { AutoLock lock(lock_); auto it = ports_.find(key); MachRendezvousPort port; if (it != ports_.end()) { port = it->second; ports_.erase(it); } return port; } MachPortRendezvousClient::MachPortRendezvousClient() = default; MachPortRendezvousClient::~MachPortRendezvousClient() = default; } // namespace base