1// Copyright 2016 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "base/mac/mach_port_broker.h" 6 7#include <bsm/libbsm.h> 8#include <servers/bootstrap.h> 9 10#include "base/logging.h" 11#include "base/mac/foundation_util.h" 12#include "base/mac/mach_logging.h" 13#include "base/strings/string_util.h" 14#include "base/strings/stringprintf.h" 15 16namespace base { 17 18namespace { 19 20// Mach message structure used in the child as a sending message. 21struct MachPortBroker_ChildSendMsg { 22 mach_msg_header_t header; 23 mach_msg_body_t body; 24 mach_msg_port_descriptor_t child_task_port; 25}; 26 27// Complement to the ChildSendMsg, this is used in the parent for receiving 28// a message. Contains a message trailer with audit information. 29struct MachPortBroker_ParentRecvMsg : public MachPortBroker_ChildSendMsg { 30 mach_msg_audit_trailer_t trailer; 31}; 32 33} // namespace 34 35// static 36bool MachPortBroker::ChildSendTaskPortToParent(const std::string& name) { 37 // Look up the named MachPortBroker port that's been registered with the 38 // bootstrap server. 39 mach_port_t parent_port; 40 kern_return_t kr = bootstrap_look_up(bootstrap_port, 41 const_cast<char*>(GetMachPortName(name, true).c_str()), &parent_port); 42 if (kr != KERN_SUCCESS) { 43 BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_look_up"; 44 return false; 45 } 46 base::mac::ScopedMachSendRight scoped_right(parent_port); 47 48 // Create the check in message. This will copy a send right on this process' 49 // (the child's) task port and send it to the parent. 50 MachPortBroker_ChildSendMsg msg; 51 bzero(&msg, sizeof(msg)); 52 msg.header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_COPY_SEND) | 53 MACH_MSGH_BITS_COMPLEX; 54 msg.header.msgh_remote_port = parent_port; 55 msg.header.msgh_size = sizeof(msg); 56 msg.body.msgh_descriptor_count = 1; 57 msg.child_task_port.name = mach_task_self(); 58 msg.child_task_port.disposition = MACH_MSG_TYPE_PORT_SEND; 59 msg.child_task_port.type = MACH_MSG_PORT_DESCRIPTOR; 60 61 kr = mach_msg(&msg.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, sizeof(msg), 62 0, MACH_PORT_NULL, 100 /*milliseconds*/, MACH_PORT_NULL); 63 if (kr != KERN_SUCCESS) { 64 MACH_LOG(ERROR, kr) << "mach_msg"; 65 return false; 66 } 67 68 return true; 69} 70 71// static 72std::string MachPortBroker::GetMachPortName(const std::string& name, 73 bool is_child) { 74 // In child processes, use the parent's pid. 75 const pid_t pid = is_child ? getppid() : getpid(); 76 return base::StringPrintf( 77 "%s.%s.%d", base::mac::BaseBundleID(), name.c_str(), pid); 78} 79 80mach_port_t MachPortBroker::TaskForPid(base::ProcessHandle pid) const { 81 base::AutoLock lock(lock_); 82 MachPortBroker::MachMap::const_iterator it = mach_map_.find(pid); 83 if (it == mach_map_.end()) 84 return MACH_PORT_NULL; 85 return it->second; 86} 87 88MachPortBroker::MachPortBroker(const std::string& name) : name_(name) {} 89 90MachPortBroker::~MachPortBroker() {} 91 92bool MachPortBroker::Init() { 93 DCHECK(server_port_.get() == MACH_PORT_NULL); 94 95 // Check in with launchd and publish the service name. 96 mach_port_t port; 97 kern_return_t kr = bootstrap_check_in( 98 bootstrap_port, GetMachPortName(name_, false).c_str(), &port); 99 if (kr != KERN_SUCCESS) { 100 BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_check_in"; 101 return false; 102 } 103 server_port_.reset(port); 104 105 // Start the dispatch source. 106 std::string queue_name = 107 base::StringPrintf("%s.MachPortBroker", base::mac::BaseBundleID()); 108 dispatch_source_.reset(new base::DispatchSourceMach( 109 queue_name.c_str(), server_port_.get(), ^{ HandleRequest(); })); 110 dispatch_source_->Resume(); 111 112 return true; 113} 114 115void MachPortBroker::AddPlaceholderForPid(base::ProcessHandle pid) { 116 lock_.AssertAcquired(); 117 DCHECK_EQ(0u, mach_map_.count(pid)); 118 mach_map_[pid] = MACH_PORT_NULL; 119} 120 121void MachPortBroker::InvalidatePid(base::ProcessHandle pid) { 122 lock_.AssertAcquired(); 123 124 MachMap::iterator mach_it = mach_map_.find(pid); 125 if (mach_it != mach_map_.end()) { 126 kern_return_t kr = mach_port_deallocate(mach_task_self(), mach_it->second); 127 MACH_LOG_IF(WARNING, kr != KERN_SUCCESS, kr) << "mach_port_deallocate"; 128 mach_map_.erase(mach_it); 129 } 130} 131 132void MachPortBroker::HandleRequest() { 133 MachPortBroker_ParentRecvMsg msg; 134 bzero(&msg, sizeof(msg)); 135 msg.header.msgh_size = sizeof(msg); 136 msg.header.msgh_local_port = server_port_.get(); 137 138 const mach_msg_option_t options = MACH_RCV_MSG | 139 MACH_RCV_TRAILER_TYPE(MACH_RCV_TRAILER_AUDIT) | 140 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); 141 142 kern_return_t kr = mach_msg(&msg.header, 143 options, 144 0, 145 sizeof(msg), 146 server_port_.get(), 147 MACH_MSG_TIMEOUT_NONE, 148 MACH_PORT_NULL); 149 if (kr != KERN_SUCCESS) { 150 MACH_LOG(ERROR, kr) << "mach_msg"; 151 return; 152 } 153 154 // Use the kernel audit information to make sure this message is from 155 // a task that this process spawned. The kernel audit token contains the 156 // unspoofable pid of the task that sent the message. 157 // 158 // TODO(rsesek): In the 10.7 SDK, there's audit_token_to_pid(). 159 pid_t child_pid; 160 audit_token_to_au32(msg.trailer.msgh_audit, 161 NULL, NULL, NULL, NULL, NULL, &child_pid, NULL, NULL); 162 163 mach_port_t child_task_port = msg.child_task_port.name; 164 165 // Take the lock and update the broker information. 166 { 167 base::AutoLock lock(lock_); 168 FinalizePid(child_pid, child_task_port); 169 } 170 NotifyObservers(child_pid); 171} 172 173void MachPortBroker::FinalizePid(base::ProcessHandle pid, 174 mach_port_t task_port) { 175 lock_.AssertAcquired(); 176 177 MachMap::iterator it = mach_map_.find(pid); 178 if (it == mach_map_.end()) { 179 // Do nothing for unknown pids. 180 LOG(ERROR) << "Unknown process " << pid << " is sending Mach IPC messages!"; 181 return; 182 } 183 184 DCHECK(it->second == MACH_PORT_NULL); 185 if (it->second == MACH_PORT_NULL) 186 it->second = task_port; 187} 188 189} // namespace base 190