1// Copyright (c) 2011 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 "content/browser/mach_broker_mac.h" 6 7#include <bsm/libbsm.h> 8#include <servers/bootstrap.h> 9 10#include "base/bind.h" 11#include "base/bind_helpers.h" 12#include "base/command_line.h" 13#include "base/logging.h" 14#include "base/mac/foundation_util.h" 15#include "base/mac/scoped_mach_port.h" 16#include "base/strings/string_util.h" 17#include "base/strings/stringprintf.h" 18#include "base/strings/sys_string_conversions.h" 19#include "base/threading/platform_thread.h" 20#include "content/browser/renderer_host/render_process_host_impl.h" 21#include "content/public/browser/browser_thread.h" 22#include "content/public/browser/child_process_data.h" 23#include "content/public/browser/notification_service.h" 24#include "content/public/browser/notification_types.h" 25#include "content/public/common/content_switches.h" 26 27namespace content { 28 29namespace { 30 31// Prints a string representation of a Mach error code. 32std::string MachErrorCode(kern_return_t err) { 33 return base::StringPrintf("0x%x %s", err, mach_error_string(err)); 34} 35 36// Mach message structure used in the child as a sending message. 37struct MachBroker_ChildSendMsg { 38 mach_msg_header_t header; 39 mach_msg_body_t body; 40 mach_msg_port_descriptor_t child_task_port; 41}; 42 43// Complement to the ChildSendMsg, this is used in the parent for receiving 44// a message. Contains a message trailer with audit information. 45struct MachBroker_ParentRecvMsg : public MachBroker_ChildSendMsg { 46 mach_msg_audit_trailer_t trailer; 47}; 48 49} // namespace 50 51class MachListenerThreadDelegate : public base::PlatformThread::Delegate { 52 public: 53 explicit MachListenerThreadDelegate(MachBroker* broker) 54 : broker_(broker), 55 server_port_(MACH_PORT_NULL) { 56 DCHECK(broker_); 57 } 58 59 bool Init() { 60 DCHECK(server_port_ == MACH_PORT_NULL); 61 62 mach_port_t port; 63 kern_return_t kr = mach_port_allocate(mach_task_self(), 64 MACH_PORT_RIGHT_RECEIVE, 65 &port); 66 if (kr != KERN_SUCCESS) { 67 LOG(ERROR) << "Failed to allocate MachBroker server port: " 68 << MachErrorCode(kr); 69 return false; 70 } 71 72 // Allocate a send right for the server port. 73 kr = mach_port_insert_right( 74 mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); 75 if (kr != KERN_SUCCESS) { 76 LOG(ERROR) << "Failed to insert send right for MachBroker server port: " 77 << MachErrorCode(kr); 78 return false; 79 } 80 81 server_port_.reset(port); 82 83 // Register the port with the bootstrap server. Because bootstrap_register 84 // is deprecated, this has to be wraped in an ObjC interface. 85 NSPort* ns_port = [NSMachPort portWithMachPort:port 86 options:NSMachPortDeallocateNone]; 87 NSString* name = base::SysUTF8ToNSString(broker_->GetMachPortName()); 88 return [[NSMachBootstrapServer sharedInstance] registerPort:ns_port 89 name:name]; 90 } 91 92 // Implement |PlatformThread::Delegate|. 93 virtual void ThreadMain() OVERRIDE { 94 MachBroker_ParentRecvMsg msg; 95 bzero(&msg, sizeof(msg)); 96 msg.header.msgh_size = sizeof(msg); 97 msg.header.msgh_local_port = server_port_.get(); 98 99 kern_return_t kr; 100 do { 101 // Use the kernel audit information to make sure this message is from 102 // a task that this process spawned. The kernel audit token contains the 103 // unspoofable pid of the task that sent the message. 104 mach_msg_option_t options = MACH_RCV_MSG | 105 MACH_RCV_TRAILER_TYPE(MACH_RCV_TRAILER_AUDIT) | 106 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); 107 108 kr = mach_msg(&msg.header, options, 0, sizeof(msg), server_port_, 109 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); 110 if (kr == KERN_SUCCESS) { 111 // TODO(rsesek): In the 10.7 SDK, there's audit_token_to_pid(). 112 pid_t child_pid; 113 audit_token_to_au32(msg.trailer.msgh_audit, 114 NULL, NULL, NULL, NULL, NULL, &child_pid, NULL, NULL); 115 116 mach_port_t child_task_port = msg.child_task_port.name; 117 118 // Take the lock and update the broker information. 119 base::AutoLock lock(broker_->GetLock()); 120 broker_->FinalizePid(child_pid, child_task_port); 121 } 122 } while (kr == KERN_SUCCESS); 123 124 LOG(ERROR) << "MachBroker thread exiting; mach_msg() likely failed: " 125 << MachErrorCode(kr); 126 } 127 128 private: 129 // The MachBroker to use when new child task rights are received. Can be 130 // NULL. 131 MachBroker* broker_; // weak 132 133 base::mac::ScopedMachPort server_port_; 134 135 DISALLOW_COPY_AND_ASSIGN(MachListenerThreadDelegate); 136}; 137 138bool MachBroker::ChildSendTaskPortToParent() { 139 // Look up the named MachBroker port that's been registered with the 140 // bootstrap server. 141 mach_port_t bootstrap_port; 142 kern_return_t kr = task_get_bootstrap_port(mach_task_self(), &bootstrap_port); 143 if (kr != KERN_SUCCESS) { 144 LOG(ERROR) << "Failed to look up bootstrap port: " << MachErrorCode(kr); 145 return false; 146 } 147 148 mach_port_t parent_port; 149 kr = bootstrap_look_up(bootstrap_port, 150 const_cast<char*>(GetMachPortName().c_str()), &parent_port); 151 if (kr != KERN_SUCCESS) { 152 LOG(ERROR) << "Failed to look up named parent port: " << MachErrorCode(kr); 153 return false; 154 } 155 156 // Create the check in message. This will copy a send right on this process' 157 // (the child's) task port and send it to the parent. 158 MachBroker_ChildSendMsg msg; 159 bzero(&msg, sizeof(msg)); 160 msg.header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_COPY_SEND) | 161 MACH_MSGH_BITS_COMPLEX; 162 msg.header.msgh_remote_port = parent_port; 163 msg.header.msgh_size = sizeof(msg); 164 msg.body.msgh_descriptor_count = 1; 165 msg.child_task_port.name = mach_task_self(); 166 msg.child_task_port.disposition = MACH_MSG_TYPE_PORT_SEND; 167 msg.child_task_port.type = MACH_MSG_PORT_DESCRIPTOR; 168 169 kr = mach_msg(&msg.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, sizeof(msg), 170 0, MACH_PORT_NULL, 100 /*milliseconds*/, MACH_PORT_NULL); 171 if (kr != KERN_SUCCESS) { 172 LOG(ERROR) << "Failed to send task port to parent: " << MachErrorCode(kr); 173 return false; 174 } 175 176 return true; 177} 178 179MachBroker* MachBroker::GetInstance() { 180 return Singleton<MachBroker, LeakySingletonTraits<MachBroker> >::get(); 181} 182 183base::Lock& MachBroker::GetLock() { 184 return lock_; 185} 186 187void MachBroker::EnsureRunning() { 188 lock_.AssertAcquired(); 189 190 if (!listener_thread_started_) { 191 listener_thread_started_ = true; 192 193 BrowserThread::PostTask( 194 BrowserThread::UI, FROM_HERE, 195 base::Bind(&MachBroker::RegisterNotifications, base::Unretained(this))); 196 197 // Intentional leak. This thread is never joined or reaped. 198 MachListenerThreadDelegate* thread = new MachListenerThreadDelegate(this); 199 if (thread->Init()) { 200 base::PlatformThread::CreateNonJoinable(0, thread); 201 } else { 202 LOG(ERROR) << "Failed to initialize the MachListenerThreadDelegate"; 203 } 204 } 205} 206 207void MachBroker::AddPlaceholderForPid(base::ProcessHandle pid) { 208 lock_.AssertAcquired(); 209 210 DCHECK_EQ(0u, mach_map_.count(pid)); 211 mach_map_[pid] = MACH_PORT_NULL; 212} 213 214mach_port_t MachBroker::TaskForPid(base::ProcessHandle pid) const { 215 base::AutoLock lock(lock_); 216 MachBroker::MachMap::const_iterator it = mach_map_.find(pid); 217 if (it == mach_map_.end()) 218 return MACH_PORT_NULL; 219 return it->second; 220} 221 222void MachBroker::BrowserChildProcessHostDisconnected( 223 const ChildProcessData& data) { 224 InvalidatePid(data.handle); 225} 226 227void MachBroker::BrowserChildProcessCrashed(const ChildProcessData& data) { 228 InvalidatePid(data.handle); 229} 230 231void MachBroker::Observe(int type, 232 const NotificationSource& source, 233 const NotificationDetails& details) { 234 // TODO(rohitrao): These notifications do not always carry the proper PIDs, 235 // especially when the renderer is already gone or has crashed. Find a better 236 // way to listen for child process deaths. http://crbug.com/55734 237 base::ProcessHandle handle = 0; 238 switch (type) { 239 case NOTIFICATION_RENDERER_PROCESS_CLOSED: 240 handle = Details<RenderProcessHost::RendererClosedDetails>( 241 details)->handle; 242 break; 243 case NOTIFICATION_RENDERER_PROCESS_TERMINATED: 244 handle = Source<RenderProcessHost>(source)->GetHandle(); 245 break; 246 default: 247 NOTREACHED() << "Unexpected notification"; 248 break; 249 } 250 InvalidatePid(handle); 251} 252 253MachBroker::MachBroker() : listener_thread_started_(false) { 254} 255 256MachBroker::~MachBroker() {} 257 258void MachBroker::FinalizePid(base::ProcessHandle pid, 259 mach_port_t task_port) { 260 lock_.AssertAcquired(); 261 262 MachMap::iterator it = mach_map_.find(pid); 263 if (it == mach_map_.end()) { 264 // Do nothing for unknown pids. 265 LOG(ERROR) << "Unknown process " << pid << " is sending Mach IPC messages!"; 266 return; 267 } 268 269 DCHECK(it->second == MACH_PORT_NULL); 270 if (it->second == MACH_PORT_NULL) 271 it->second = task_port; 272} 273 274void MachBroker::InvalidatePid(base::ProcessHandle pid) { 275 base::AutoLock lock(lock_); 276 MachBroker::MachMap::iterator it = mach_map_.find(pid); 277 if (it == mach_map_.end()) 278 return; 279 280 kern_return_t kr = mach_port_deallocate(mach_task_self(), 281 it->second); 282 LOG_IF(WARNING, kr != KERN_SUCCESS) 283 << "Failed to mach_port_deallocate mach task " << it->second 284 << ", error " << MachErrorCode(kr); 285 mach_map_.erase(it); 286} 287 288// static 289std::string MachBroker::GetMachPortName() { 290 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 291 const bool is_child = command_line->HasSwitch(switches::kProcessType); 292 293 // In non-browser (child) processes, use the parent's pid. 294 const pid_t pid = is_child ? getppid() : getpid(); 295 return base::StringPrintf("%s.rohitfork.%d", base::mac::BaseBundleID(), pid); 296} 297 298void MachBroker::RegisterNotifications() { 299 registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CLOSED, 300 NotificationService::AllBrowserContextsAndSources()); 301 registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_TERMINATED, 302 NotificationService::AllBrowserContextsAndSources()); 303 304 // No corresponding StopObservingBrowserChildProcesses, 305 // we leak this singleton. 306 BrowserChildProcessObserver::Add(this); 307} 308 309} // namespace content 310