mach_ipc_mac.cc (15658B)
1 // Copyright (c) 2006-2008 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 "chrome/common/mach_ipc_mac.h" 6 7 #include "base/logging.h" 8 #include "base/message_loop.h" 9 #include "base/string_util.h" 10 #include "mozilla/GeckoArgs.h" 11 #include "mozilla/ipc/IOThread.h" 12 #include "mozilla/Result.h" 13 #include "mozilla/ResultVariant.h" 14 #include "mozilla/ScopeExit.h" 15 #include "mozilla/UniquePtrExtensions.h" 16 #include "nsDebug.h" 17 #include "nsXULAppAPI.h" 18 19 #ifdef XP_MACOSX 20 # include <bsm/libbsm.h> 21 # include <servers/bootstrap.h> 22 #endif 23 24 namespace { 25 // Struct for sending a Mach message with a single port. 26 struct MachSinglePortMessage { 27 mach_msg_header_t header; 28 mach_msg_body_t body; 29 mach_msg_port_descriptor_t data; 30 }; 31 32 // Struct for receiving a Mach message with a single port. 33 struct MachSinglePortMessageTrailer : MachSinglePortMessage { 34 mach_msg_audit_trailer_t trailer; 35 }; 36 } // namespace 37 38 //============================================================================== 39 kern_return_t MachSendPortSendRight( 40 mach_port_t endpoint, mach_port_t attachment, 41 mozilla::Maybe<mach_msg_timeout_t> opt_timeout, 42 mach_msg_type_name_t endpoint_disposition) { 43 mach_msg_option_t opts = MACH_SEND_MSG; 44 mach_msg_timeout_t timeout = MACH_MSG_TIMEOUT_NONE; 45 if (opt_timeout) { 46 opts |= MACH_SEND_TIMEOUT; 47 timeout = *opt_timeout; 48 } 49 50 MachSinglePortMessage send_msg{}; 51 send_msg.header.msgh_bits = 52 MACH_MSGH_BITS(endpoint_disposition, 0) | MACH_MSGH_BITS_COMPLEX; 53 send_msg.header.msgh_size = sizeof(send_msg); 54 send_msg.header.msgh_remote_port = endpoint; 55 send_msg.header.msgh_local_port = MACH_PORT_NULL; 56 send_msg.header.msgh_reserved = 0; 57 send_msg.header.msgh_id = 0; 58 send_msg.body.msgh_descriptor_count = 1; 59 send_msg.data.name = attachment; 60 send_msg.data.disposition = MACH_MSG_TYPE_COPY_SEND; 61 send_msg.data.type = MACH_MSG_PORT_DESCRIPTOR; 62 63 return mach_msg(&send_msg.header, opts, send_msg.header.msgh_size, 0, 64 MACH_PORT_NULL, timeout, MACH_PORT_NULL); 65 } 66 67 //============================================================================== 68 kern_return_t MachReceivePortSendRight( 69 const mozilla::UniqueMachReceiveRight& endpoint, 70 mozilla::Maybe<mach_msg_timeout_t> opt_timeout, 71 mozilla::UniqueMachSendRight* attachment, audit_token_t* audit_token) { 72 mach_msg_option_t opts = MACH_RCV_MSG | 73 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | 74 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); 75 mach_msg_timeout_t timeout = MACH_MSG_TIMEOUT_NONE; 76 if (opt_timeout) { 77 opts |= MACH_RCV_TIMEOUT; 78 timeout = *opt_timeout; 79 } 80 81 MachSinglePortMessageTrailer recv_msg{}; 82 recv_msg.header.msgh_local_port = endpoint.get(); 83 recv_msg.header.msgh_size = sizeof(recv_msg); 84 85 kern_return_t kr = 86 mach_msg(&recv_msg.header, opts, 0, recv_msg.header.msgh_size, 87 endpoint.get(), timeout, MACH_PORT_NULL); 88 if (kr != KERN_SUCCESS) { 89 return kr; 90 } 91 92 if (NS_WARN_IF(!(recv_msg.header.msgh_bits & MACH_MSGH_BITS_COMPLEX)) || 93 NS_WARN_IF(recv_msg.body.msgh_descriptor_count != 1) || 94 NS_WARN_IF(recv_msg.data.type != MACH_MSG_PORT_DESCRIPTOR) || 95 NS_WARN_IF(recv_msg.data.disposition != MACH_MSG_TYPE_MOVE_SEND) || 96 NS_WARN_IF(recv_msg.header.msgh_size != sizeof(MachSinglePortMessage))) { 97 mach_msg_destroy(&recv_msg.header); 98 return KERN_FAILURE; // Invalid message format 99 } 100 101 attachment->reset(recv_msg.data.name); 102 if (audit_token) { 103 *audit_token = recv_msg.trailer.msgh_audit; 104 } 105 return KERN_SUCCESS; 106 } 107 108 #ifdef XP_MACOSX 109 static std::string FormatMachError(kern_return_t kr) { 110 return StringPrintf("0x%x %s", kr, mach_error_string(kr)); 111 } 112 113 //============================================================================== 114 bool MachChildProcessCheckIn( 115 const char* bootstrap_service_name, mach_msg_timeout_t timeout, 116 std::vector<mozilla::UniqueMachSendRight>& send_rights) { 117 mozilla::UniqueMachSendRight task_sender; 118 kern_return_t kr = bootstrap_look_up(bootstrap_port, bootstrap_service_name, 119 mozilla::getter_Transfers(task_sender)); 120 if (kr != KERN_SUCCESS) { 121 CHROMIUM_LOG(ERROR) << "child bootstrap_look_up failed: " 122 << FormatMachError(kr); 123 return false; 124 } 125 126 mozilla::UniqueMachReceiveRight reply_port; 127 kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, 128 mozilla::getter_Transfers(reply_port)); 129 if (kr != KERN_SUCCESS) { 130 CHROMIUM_LOG(ERROR) << "child mach_port_allocate failed: " 131 << FormatMachError(kr); 132 return false; 133 } 134 135 // The buffer must be big enough to store a full reply including 136 // kMaxPassedMachSendRights port descriptors. 137 size_t buffer_size = sizeof(mach_msg_base_t) + 138 sizeof(mach_msg_port_descriptor_t) * 139 mozilla::geckoargs::kMaxPassedMachSendRights + 140 sizeof(mach_msg_trailer_t); 141 mozilla::UniquePtr<uint8_t[]> buffer = 142 mozilla::MakeUnique<uint8_t[]>(buffer_size); 143 144 // Send a single descriptor - the process mach_task_self() port. 145 MachSinglePortMessage* request = 146 reinterpret_cast<MachSinglePortMessage*>(buffer.get()); 147 request->header.msgh_bits = 148 MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE) | 149 MACH_MSGH_BITS_COMPLEX; 150 request->header.msgh_size = sizeof(MachSinglePortMessage); 151 request->header.msgh_remote_port = task_sender.get(); 152 request->header.msgh_local_port = reply_port.get(); 153 request->body.msgh_descriptor_count = 1; 154 request->data.type = MACH_MSG_PORT_DESCRIPTOR; 155 request->data.disposition = MACH_MSG_TYPE_COPY_SEND; 156 request->data.name = mach_task_self(); 157 kr = mach_msg(&request->header, 158 MACH_SEND_MSG | MACH_RCV_MSG | MACH_SEND_TIMEOUT, 159 request->header.msgh_size, buffer_size, 160 request->header.msgh_local_port, timeout, MACH_PORT_NULL); 161 if (kr != KERN_SUCCESS) { 162 // NOTE: The request owns no ports, so we don't need to call 163 // mach_msg_destroy on error here. 164 CHROMIUM_LOG(ERROR) << "child mach_msg failed: " << FormatMachError(kr); 165 return false; 166 } 167 168 mach_msg_base_t* reply = reinterpret_cast<mach_msg_base_t*>(buffer.get()); 169 MOZ_RELEASE_ASSERT(reply->header.msgh_bits & MACH_MSGH_BITS_COMPLEX); 170 MOZ_RELEASE_ASSERT(reply->body.msgh_descriptor_count <= 171 mozilla::geckoargs::kMaxPassedMachSendRights); 172 173 mach_msg_port_descriptor_t* descrs = 174 reinterpret_cast<mach_msg_port_descriptor_t*>(reply + 1); 175 for (size_t i = 0; i < reply->body.msgh_descriptor_count; ++i) { 176 MOZ_RELEASE_ASSERT(descrs[i].type == MACH_MSG_PORT_DESCRIPTOR); 177 MOZ_RELEASE_ASSERT(descrs[i].disposition == MACH_MSG_TYPE_MOVE_SEND); 178 send_rights.emplace_back(descrs[i].name); 179 } 180 181 return true; 182 } 183 184 //============================================================================== 185 namespace { 186 187 mozilla::Result<mozilla::Ok, mozilla::ipc::LaunchError> 188 MachHandleProcessCheckInSync( 189 mach_port_t endpoint, pid_t child_pid, mach_msg_timeout_t timeout, 190 const std::vector<mozilla::UniqueMachSendRight>& send_rights, 191 task_t* child_task) { 192 using mozilla::Err; 193 using mozilla::Ok; 194 using mozilla::Result; 195 using mozilla::ipc::LaunchError; 196 197 MOZ_ASSERT(send_rights.size() <= mozilla::geckoargs::kMaxPassedMachSendRights, 198 "Child process cannot receive more than kMaxPassedMachSendRights " 199 "during check-in!"); 200 201 // Receive the check-in message from content. This will contain its 'task_t' 202 // data, and a reply port which can be used to send the reply message. 203 MachSinglePortMessageTrailer request{}; 204 request.header.msgh_size = sizeof(request); 205 request.header.msgh_local_port = endpoint; 206 207 // Ensure that the request from the new child process is cleaned up if we fail 208 // in some way, such as to not leak any resources. 209 auto destroyRequestMessage = 210 mozilla::MakeScopeExit([&] { mach_msg_destroy(&request.header); }); 211 212 kern_return_t kr = 213 mach_msg(&request.header, 214 MACH_RCV_MSG | MACH_RCV_TIMEOUT | 215 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | 216 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT), 217 0, request.header.msgh_size, endpoint, timeout, MACH_PORT_NULL); 218 if (kr != KERN_SUCCESS) { 219 CHROMIUM_LOG(ERROR) << "parent mach_msg(MACH_RECV_MSG) failed: " 220 << FormatMachError(kr); 221 return Err(LaunchError("mach_msg(MACH_RECV_MSG)", kr)); 222 } 223 224 if (NS_WARN_IF(!(request.header.msgh_bits & MACH_MSGH_BITS_COMPLEX)) || 225 NS_WARN_IF(request.body.msgh_descriptor_count != 1) || 226 NS_WARN_IF(request.data.type != MACH_MSG_PORT_DESCRIPTOR) || 227 NS_WARN_IF(request.data.disposition != MACH_MSG_TYPE_MOVE_SEND) || 228 NS_WARN_IF(request.header.msgh_size != sizeof(MachSinglePortMessage))) { 229 CHROMIUM_LOG(ERROR) << "invalid child process check-in message format"; 230 return Err(LaunchError("invalid child process check-in message format")); 231 } 232 233 // Ensure the message was sent by the newly spawned child process. 234 if (audit_token_to_pid(request.trailer.msgh_audit) != child_pid) { 235 CHROMIUM_LOG(ERROR) << "task_t was not sent by child process"; 236 return Err(LaunchError("audit_token_to_pid")); 237 } 238 239 // Ensure the task_t corresponds to the newly spawned child process. 240 pid_t task_pid = -1; 241 kr = pid_for_task(request.data.name, &task_pid); 242 if (kr != KERN_SUCCESS) { 243 CHROMIUM_LOG(ERROR) << "pid_for_task failed: " << FormatMachError(kr); 244 return Err(LaunchError("pid_for_task", kr)); 245 } 246 if (task_pid != child_pid) { 247 CHROMIUM_LOG(ERROR) << "task_t is not for child process"; 248 return Err(LaunchError("task_pid")); 249 } 250 251 // We've received the task_t for the correct process, reply to the message 252 // with any send rights over to that child process which they should have on 253 // startup. 254 size_t reply_size = sizeof(mach_msg_base_t) + 255 sizeof(mach_msg_port_descriptor_t) * send_rights.size(); 256 mozilla::UniquePtr<uint8_t[]> buffer = 257 mozilla::MakeUnique<uint8_t[]>(reply_size); 258 mach_msg_base_t* reply = reinterpret_cast<mach_msg_base_t*>(buffer.get()); 259 reply->header.msgh_bits = 260 MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0) | MACH_MSGH_BITS_COMPLEX; 261 reply->header.msgh_size = reply_size; 262 reply->header.msgh_remote_port = request.header.msgh_remote_port; 263 reply->body.msgh_descriptor_count = send_rights.size(); 264 265 // Fill the descriptors from our mChildArgs. 266 mach_msg_port_descriptor_t* descrs = 267 reinterpret_cast<mach_msg_port_descriptor_t*>(reply + 1); 268 for (size_t i = 0; i < send_rights.size(); ++i) { 269 descrs[i].type = MACH_MSG_PORT_DESCRIPTOR; 270 descrs[i].disposition = MACH_MSG_TYPE_COPY_SEND; 271 descrs[i].name = send_rights[i].get(); 272 } 273 274 // Send the reply. 275 kr = mach_msg(&reply->header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, 276 reply->header.msgh_size, 0, MACH_PORT_NULL, /* timeout */ 0, 277 MACH_PORT_NULL); 278 if (kr != KERN_SUCCESS) { 279 // NOTE: The only port which `mach_msg_destroy` would destroy is 280 // `header.msgh_remote_port`, which is actually owned by `request`, so we 281 // don't want to call `mach_msg_destroy` on the reply here. 282 // 283 // If we ever support passing receive rights, we'll need to make sure to 284 // clean them up here, as their ownership must be moved into the message. 285 CHROMIUM_LOG(ERROR) << "parent mach_msg(MACH_SEND_MSG) failed: " 286 << FormatMachError(kr); 287 return Err(LaunchError("mach_msg(MACH_SEND_MSG)", kr)); 288 } 289 290 // At this point, we've transferred the reply port, and are now taking the 291 // mach task port from the request message (to pass to our caller), no longer 292 // destroy the request message on error. 293 *child_task = request.data.name; 294 destroyRequestMessage.release(); 295 296 return Ok(); 297 } 298 299 class MachCheckInListener : public MessageLoopForIO::MachPortWatcher { 300 public: 301 MachCheckInListener(MachHandleProcessCheckInPromise::Private* promise, 302 mozilla::UniqueMachReceiveRight endpoint, pid_t child_pid, 303 std::vector<mozilla::UniqueMachSendRight> send_rights) 304 : promise_(promise), 305 child_pid_(child_pid), 306 endpoint_(std::move(endpoint)), 307 send_rights_(std::move(send_rights)) {} 308 309 // Start listening for a check-in - can |delete this|. 310 void Start(mozilla::TimeDuration timeout); 311 312 private: 313 void OnMachMessageReceived(mach_port_t port); 314 void OnMachSendPossible(mach_port_t, bool) { MOZ_ASSERT_UNREACHABLE(); } 315 316 // Complete the promise, and |delete this|. 317 void CompleteAndDelete( 318 const mozilla::Result<task_t, mozilla::ipc::LaunchError>& result); 319 void FailAndDelete(mozilla::StaticString aFunction) { 320 CompleteAndDelete(mozilla::Err(mozilla::ipc::LaunchError(aFunction))); 321 } 322 323 RefPtr<MachHandleProcessCheckInPromise::Private> promise_; 324 pid_t child_pid_ = -1; 325 mozilla::UniqueMachReceiveRight endpoint_; 326 MessageLoopForIO::MachPortWatchController watch_controller_; 327 nsCOMPtr<nsITimer> timeout_timer_; 328 std::vector<mozilla::UniqueMachSendRight> send_rights_; 329 }; 330 331 void MachCheckInListener::Start(mozilla::TimeDuration timeout) { 332 mozilla::ipc::AssertIOThread(); 333 334 // If a timeout was provided, start a timer. 335 if (timeout != mozilla::TimeDuration::Forever()) { 336 nsresult rv = NS_NewTimerWithCallback( 337 getter_AddRefs(timeout_timer_), 338 [this](nsITimer*) { FailAndDelete("MachCheckInListener TimedOut"); }, 339 timeout, nsITimer::TYPE_ONE_SHOT, "MachCheckInListener"_ns, 340 XRE_GetAsyncIOEventTarget()); 341 if (NS_FAILED(rv)) { 342 FailAndDelete("MachCheckIn: NewTimer"); 343 return; 344 } 345 } 346 347 if (!MessageLoopForIO::current()->WatchMachReceivePort( 348 endpoint_.get(), &watch_controller_, this)) { 349 FailAndDelete("MachCheckIn: WatchMachPort"); 350 return; 351 } 352 } 353 354 void MachCheckInListener::OnMachMessageReceived(mach_port_t port) { 355 mozilla::ipc::AssertIOThread(); 356 MOZ_ASSERT(endpoint_.get() == port); 357 358 task_t task = MACH_PORT_NULL; 359 auto result = 360 MachHandleProcessCheckInSync(endpoint_.get(), child_pid_, 361 /* timeout */ 0, send_rights_, &task); 362 CompleteAndDelete(result.map([&](const mozilla::Ok&) { return task; })); 363 } 364 365 void MachCheckInListener::CompleteAndDelete( 366 const mozilla::Result<task_t, mozilla::ipc::LaunchError>& result) { 367 mozilla::ipc::AssertIOThread(); 368 369 if (result.isOk()) { 370 promise_->Resolve(result.inspect(), __func__); 371 } else { 372 promise_->Reject(result.inspectErr(), __func__); 373 } 374 375 watch_controller_.StopWatchingMachPort(); 376 if (timeout_timer_) { 377 timeout_timer_->Cancel(); 378 } 379 380 // SAFETY: MachCheckInListener can only be called by MessageLoopForIO or by a 381 // nsITimer callback. By cancelling both callbacks above, on the IPC I/O 382 // thread, we will never be called again. 383 delete this; 384 } 385 386 } // namespace 387 388 RefPtr<MachHandleProcessCheckInPromise> MachHandleProcessCheckIn( 389 mozilla::UniqueMachReceiveRight endpoint, pid_t child_pid, 390 mozilla::TimeDuration timeout, 391 std::vector<mozilla::UniqueMachSendRight> send_rights) { 392 mozilla::ipc::AssertIOThread(); 393 394 auto promise = 395 mozilla::MakeRefPtr<MachHandleProcessCheckInPromise::Private>(__func__); 396 (new MachCheckInListener(promise, std::move(endpoint), child_pid, 397 std::move(send_rights))) 398 ->Start(timeout); 399 return promise; 400 } 401 402 #endif