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