// Copyright 2016 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 "mojo/core/mach_port_relay.h" #include #include #include "base/logging.h" #include "base/mac/mach_port_util.h" #include "base/mac/scoped_mach_port.h" #include "base/metrics/histogram_macros.h" #include "base/process/process.h" namespace mojo { namespace core { namespace { // Errors that can occur in the broker (privileged parent) process. // These match tools/metrics/histograms.xml. // This enum is append-only. enum class BrokerUMAError : int { SUCCESS = 0, // Couldn't get a task port for the process with a given pid. ERROR_TASK_FOR_PID = 1, // Couldn't make a port with receive rights in the destination process. ERROR_MAKE_RECEIVE_PORT = 2, // Couldn't change the attributes of a Mach port. ERROR_SET_ATTRIBUTES = 3, // Couldn't extract a right from the destination. ERROR_EXTRACT_DEST_RIGHT = 4, // Couldn't send a Mach port in a call to mach_msg(). ERROR_SEND_MACH_PORT = 5, // Couldn't extract a right from the source. ERROR_EXTRACT_SOURCE_RIGHT = 6, ERROR_MAX }; // Errors that can occur in a child process. // These match tools/metrics/histograms.xml. // This enum is append-only. enum class ChildUMAError : int { SUCCESS = 0, // An error occurred while trying to receive a Mach port with mach_msg(). ERROR_RECEIVE_MACH_MESSAGE = 1, ERROR_MAX }; void ReportBrokerError(BrokerUMAError error) { UMA_HISTOGRAM_ENUMERATION("Mojo.MachPortRelay.BrokerError", static_cast(error), static_cast(BrokerUMAError::ERROR_MAX)); } void ReportChildError(ChildUMAError error) { UMA_HISTOGRAM_ENUMERATION("Mojo.MachPortRelay.ChildError", static_cast(error), static_cast(ChildUMAError::ERROR_MAX)); } } // namespace // static base::mac::ScopedMachSendRight MachPortRelay::ReceiveSendRight( base::mac::ScopedMachReceiveRight port) { // MACH_PORT_NULL doesn't need translation. if (!port.is_valid()) return base::mac::ScopedMachSendRight(); // Take ownership of the receive right. We only need it to read this single // send right, then it can be closed. base::mac::ScopedMachSendRight received_port( base::ReceiveMachPort(port.get())); if (!received_port.is_valid()) { ReportChildError(ChildUMAError::ERROR_RECEIVE_MACH_MESSAGE); DLOG(ERROR) << "Error receiving mach port"; return base::mac::ScopedMachSendRight(); } ReportChildError(ChildUMAError::SUCCESS); return received_port; } MachPortRelay::MachPortRelay(base::PortProvider* port_provider) : port_provider_(port_provider) { DCHECK(port_provider); port_provider_->AddObserver(this); } MachPortRelay::~MachPortRelay() { port_provider_->RemoveObserver(this); } void MachPortRelay::SendPortsToProcess(Channel::Message* message, base::ProcessHandle process) { DCHECK(message); mach_port_t task_port = port_provider_->TaskForPid(process); std::vector handles = message->TakeHandles(); // Message should have handles, otherwise there's no point in calling this // function. DCHECK(!handles.empty()); for (auto& handle : handles) { if (!handle.handle().is_valid_mach_port()) continue; if (task_port == MACH_PORT_NULL) { // Callers check the port provider for the task port before calling this // function, in order to queue pending messages. Therefore, if this fails, // it should be considered a genuine, bona fide, electrified, six-car // error. ReportBrokerError(BrokerUMAError::ERROR_TASK_FOR_PID); handle = PlatformHandleInTransit( PlatformHandle(base::mac::ScopedMachSendRight())); continue; } mach_port_name_t intermediate_port; base::MachCreateError error_code; intermediate_port = base::CreateIntermediateMachPort( task_port, handle.TakeHandle().TakeMachPort(), &error_code); if (intermediate_port == MACH_PORT_NULL) { BrokerUMAError uma_error; switch (error_code) { case base::MachCreateError::ERROR_MAKE_RECEIVE_PORT: uma_error = BrokerUMAError::ERROR_MAKE_RECEIVE_PORT; break; case base::MachCreateError::ERROR_SET_ATTRIBUTES: uma_error = BrokerUMAError::ERROR_SET_ATTRIBUTES; break; case base::MachCreateError::ERROR_EXTRACT_DEST_RIGHT: uma_error = BrokerUMAError::ERROR_EXTRACT_DEST_RIGHT; break; case base::MachCreateError::ERROR_SEND_MACH_PORT: uma_error = BrokerUMAError::ERROR_SEND_MACH_PORT; break; } ReportBrokerError(uma_error); handle = PlatformHandleInTransit( PlatformHandle(base::mac::ScopedMachSendRight())); continue; } handle = PlatformHandleInTransit::CreateForMachPortName(intermediate_port); ReportBrokerError(BrokerUMAError::SUCCESS); } message->SetHandles(std::move(handles)); } base::mac::ScopedMachSendRight MachPortRelay::ExtractPort( mach_port_t port_name, base::ProcessHandle process) { // No extraction necessary for MACH_PORT_NULL. if (port_name == MACH_PORT_NULL) return base::mac::ScopedMachSendRight(); mach_port_t task_port = port_provider_->TaskForPid(process); if (task_port == MACH_PORT_NULL) { ReportBrokerError(BrokerUMAError::ERROR_TASK_FOR_PID); return base::mac::ScopedMachSendRight(); } mach_port_t extracted_right = MACH_PORT_NULL; mach_msg_type_name_t extracted_right_type; kern_return_t kr = mach_port_extract_right(task_port, port_name, MACH_MSG_TYPE_MOVE_SEND, &extracted_right, &extracted_right_type); if (kr != KERN_SUCCESS) { ReportBrokerError(BrokerUMAError::ERROR_EXTRACT_SOURCE_RIGHT); return base::mac::ScopedMachSendRight(); } ReportBrokerError(BrokerUMAError::SUCCESS); DCHECK_EQ(static_cast(MACH_MSG_TYPE_PORT_SEND), extracted_right_type); return base::mac::ScopedMachSendRight(extracted_right); } void MachPortRelay::AddObserver(Observer* observer) { base::AutoLock locker(observers_lock_); bool inserted = observers_.insert(observer).second; DCHECK(inserted); } void MachPortRelay::RemoveObserver(Observer* observer) { base::AutoLock locker(observers_lock_); observers_.erase(observer); } void MachPortRelay::OnReceivedTaskPort(base::ProcessHandle process) { base::AutoLock locker(observers_lock_); for (auto* observer : observers_) observer->OnProcessReady(process); } } // namespace core } // namespace mojo