|  | // 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 <mach/mach.h> | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #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<int>(error), | 
|  | static_cast<int>(BrokerUMAError::ERROR_MAX)); | 
|  | } | 
|  |  | 
|  | void ReportChildError(ChildUMAError error) { | 
|  | UMA_HISTOGRAM_ENUMERATION("Mojo.MachPortRelay.ChildError", | 
|  | static_cast<int>(error), | 
|  | static_cast<int>(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<PlatformHandleInTransit> 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_name_t>(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 |