ipc_channel_mach.cc (27036B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ipc_channel_mach.h" 8 9 #include <mach/mach.h> 10 #include <sys/fileport.h> 11 12 #ifdef XP_MACOSX 13 # include <bsm/libbsm.h> 14 #endif 15 16 #include "base/process_util.h" 17 #include "chrome/common/ipc_channel_utils.h" 18 #include "mozilla/ipc/ProtocolUtils.h" 19 20 using namespace mozilla::ipc; 21 22 namespace IPC { 23 24 static constexpr mach_msg_id_t kIPDLMessageId = 'IPDL'; 25 26 const Channel::ChannelKind ChannelMach::sKind{ 27 .create_raw_pipe = &ChannelMach::CreateRawPipe, 28 .num_relayed_attachments = &ChannelMach::NumRelayedAttachments, 29 .is_valid_handle = &ChannelMach::IsValidHandle, 30 }; 31 32 ChannelMach::ChannelMach(mozilla::UniqueMachReceiveRight receive, 33 mozilla::UniqueMachSendRight send, Mode mode, 34 base::ProcessId other_pid) 35 : receive_port_(std::move(receive)), 36 send_port_(std::move(send)), 37 send_buffer_(mozilla::MakeUnique<char[]>(Channel::kReadBufferSize)), 38 receive_buffer_(mozilla::MakeUnique<char[]>(Channel::kReadBufferSize)), 39 other_pid_(other_pid) { 40 EnqueueHelloMessage(); 41 } 42 43 bool ChannelMach::EnqueueHelloMessage() { 44 chan_cap_.NoteExclusiveAccess(); 45 46 mozilla::UniquePtr<Message> msg( 47 new Message(MSG_ROUTING_NONE, HELLO_MESSAGE_TYPE)); 48 if (!msg->WriteInt(base::GetCurrentProcId())) { 49 CloseLocked(); 50 return false; 51 } 52 53 // If we don't have a receive_port_ when we're queueing the "hello" message, 54 // build one, and send the corresponding send right in the hello message. 55 mozilla::UniqueMachSendRight peer_send; 56 if (!receive_port_ && !CreateRawPipe(&receive_port_, &peer_send)) { 57 CloseLocked(); 58 return false; 59 } 60 if (!msg->WriteMachSendRight(std::move(peer_send))) { 61 CloseLocked(); 62 return false; 63 } 64 65 OutputQueuePush(std::move(msg)); 66 return true; 67 } 68 69 bool ChannelMach::Connect(Listener* listener) { 70 IOThread().AssertOnCurrentThread(); 71 mozilla::MutexAutoLock lock(SendMutex()); 72 chan_cap_.NoteExclusiveAccess(); 73 74 if (!receive_port_) { 75 return false; 76 } 77 78 listener_ = listener; 79 80 // Mark this port as receiving IPC from our peer process. This allows the 81 // kernel to boost the QoS of the receiver based on the QoS of the sender. 82 // (ignore failures to set this, as it's non-fatal). 83 kern_return_t kr = 84 mach_port_set_attributes(mach_task_self(), receive_port_.get(), 85 MACH_PORT_IMPORTANCE_RECEIVER, nullptr, 0); 86 if (kr != KERN_SUCCESS) { 87 CHROMIUM_LOG(ERROR) << "mach_port_set_attributes failed: " 88 << mach_error_string(kr); 89 } 90 91 // Register to receive a notification when all send rights for this port have 92 // been destroyed. 93 // NOTE: MACH_NOTIFY_NO_SENDERS does not consider send-once rights to be send 94 // rights for the purposes of there being "no senders", so the send-once right 95 // used for the notification will not prevent it from being sent. 96 mozilla::UniqueMachSendRight previous; 97 kr = mach_port_request_notification( 98 mach_task_self(), receive_port_.get(), MACH_NOTIFY_NO_SENDERS, 0, 99 receive_port_.get(), MACH_MSG_TYPE_MAKE_SEND_ONCE, 100 mozilla::getter_Transfers(previous)); 101 if (kr != KERN_SUCCESS) { 102 CHROMIUM_LOG(ERROR) << "mach_port_request_notification: " 103 << mach_error_string(kr); 104 return false; 105 } 106 107 // Begin listening for messages on our receive port. 108 MessageLoopForIO::current()->WatchMachReceivePort(receive_port_.get(), 109 &watch_controller_, this); 110 111 return ContinueConnect(nullptr); 112 } 113 114 bool ChannelMach::ContinueConnect(mozilla::UniqueMachSendRight send_port) { 115 chan_cap_.NoteExclusiveAccess(); 116 MOZ_ASSERT(receive_port_); 117 118 // If we're still waiting for a mach send right from our peer, don't clear 119 // waiting_connect_ yet. 120 if (!send_port_) { 121 if (!send_port) { 122 MOZ_ASSERT(waiting_connect_); 123 return true; 124 } 125 send_port_ = std::move(send_port); 126 } 127 128 waiting_connect_ = false; 129 130 return ProcessOutgoingMessages(); 131 } 132 133 void ChannelMach::SetOtherPid(base::ProcessId other_pid) { 134 IOThread().AssertOnCurrentThread(); 135 mozilla::MutexAutoLock lock(SendMutex()); 136 chan_cap_.NoteExclusiveAccess(); 137 MOZ_RELEASE_ASSERT( 138 other_pid_ == base::kInvalidProcessId || other_pid_ == other_pid, 139 "Multiple sources of SetOtherPid disagree!"); 140 other_pid_ = other_pid; 141 } 142 143 namespace { 144 145 // Small helper type for safely working with Mach message buffers, which consist 146 // of a sequence of C structs. 147 struct MsgBufferHelper { 148 public: 149 MsgBufferHelper(char* buf, size_t size) 150 : start_(buf), current_(start_), end_(start_ + size) {} 151 152 template <typename T> 153 T* Next() { 154 MOZ_RELEASE_ASSERT(Remaining().size() >= sizeof(T)); 155 T* obj = reinterpret_cast<T*>(current_); 156 current_ += sizeof(T); 157 return obj; 158 } 159 160 template <typename T, typename U> 161 T* CastLast(U* previous) { 162 static_assert(sizeof(T) >= sizeof(U), "casting to a smaller type?"); 163 MOZ_RELEASE_ASSERT(reinterpret_cast<char*>(previous + 1) == current_); 164 current_ = reinterpret_cast<char*>(previous); 165 return Next<T>(); 166 } 167 168 template <typename T> 169 T* SetTrailerOffset(size_t offset) { 170 MOZ_RELEASE_ASSERT(offset < size_t(end_ - start_) - sizeof(T)); 171 // Limit any future reads to the region before the trailer. 172 end_ = start_ + offset; 173 return reinterpret_cast<T*>(end_); 174 } 175 176 template <typename T> 177 void WriteDescriptor(mach_msg_base_t* base, T descriptor) { 178 MOZ_ASSERT(base->header.msgh_size == Offset(), "unexpected size?"); 179 *Next<T>() = descriptor; 180 base->header.msgh_size = Offset(); 181 base->header.msgh_bits |= MACH_MSGH_BITS_COMPLEX; 182 base->body.msgh_descriptor_count++; 183 } 184 185 char* WriteBytes(mach_msg_base_t* base, size_t size) { 186 MOZ_ASSERT(base->header.msgh_size == Offset(), "unexpected size?"); 187 MOZ_RELEASE_ASSERT(Remaining().size() >= round_msg(size)); 188 char* bytes = current_; 189 current_ += round_msg(size); 190 base->header.msgh_size = Offset(); 191 return bytes; 192 } 193 194 mozilla::Span<char> Remaining() { return {current_, end_}; } 195 196 private: 197 size_t Offset() { return current_ - start_; } 198 199 char* start_; 200 char* current_; 201 char* end_; 202 }; 203 204 } // namespace 205 206 static bool SenderIs(mach_msg_audit_trailer_t* trailer, 207 const audit_token_t& expected) { 208 return memcmp(&trailer->msgh_audit, &expected, sizeof(audit_token_t)) == 0; 209 } 210 211 bool ChannelMach::ProcessIncomingMessage() { 212 chan_cap_.NoteOnTarget(); 213 214 MsgBufferHelper buf_helper(receive_buffer_.get(), Channel::kReadBufferSize); 215 216 auto* header = buf_helper.Next<mach_msg_header_t>(); 217 *header = mach_msg_header_t{.msgh_size = Channel::kReadBufferSize, 218 .msgh_local_port = receive_port_.get()}; 219 auto destroy_msg = 220 mozilla::MakeScopeExit([header] { mach_msg_destroy(header); }); 221 222 kern_return_t kr = mach_msg( 223 header, 224 MACH_RCV_MSG | MACH_RCV_TIMEOUT | 225 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | 226 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) | MACH_RCV_VOUCHER, 227 0, header->msgh_size, receive_port_.get(), /* timeout */ 0, 228 MACH_PORT_NULL); 229 if (kr != KERN_SUCCESS) { 230 if (kr == MACH_RCV_TIMED_OUT) { 231 return true; 232 } 233 CHROMIUM_LOG(ERROR) << "mach_msg receive failed: " << mach_error_string(kr); 234 return false; 235 } 236 237 // Get a pointer to the message audit trailer. This contains information 238 // about which entitiy sent the particular notification. 239 auto* trailer = 240 buf_helper.SetTrailerOffset<mach_msg_audit_trailer_t>(header->msgh_size); 241 if (!trailer) { 242 CHROMIUM_LOG(ERROR) << "buffer doesn't have space for audit trailer"; 243 return false; 244 } 245 246 // Respond to notifications from the kernel. 247 if (SenderIs(trailer, KERNEL_AUDIT_TOKEN_VALUE)) { 248 // If we've received MACH_NOTIFY_NO_SENDERS, the other side has gone away, 249 // so we return `false` to close the channel. Otherwise the notification is 250 // ignored, and we return `true`. 251 return header->msgh_id != MACH_NOTIFY_NO_SENDERS; 252 } 253 254 if (header->msgh_id != kIPDLMessageId) { 255 CHROMIUM_LOG(ERROR) << "unknown mach message type from peer: " 256 << header->msgh_id; 257 return false; 258 } 259 260 // If we have an audit token for our peer, ensure it matches the one we 261 // recorded from our HELLO message. 262 if (peer_audit_token_ && !SenderIs(trailer, *peer_audit_token_)) { 263 CHROMIUM_LOG(ERROR) << "message not sent by expected peer"; 264 return false; 265 } 266 267 if (buf_helper.Remaining().Length() < sizeof(mach_msg_body_t)) { 268 CHROMIUM_LOG(ERROR) << "message is too small"; 269 return false; 270 } 271 272 // Read out descriptors from the sent message. 273 auto* msg_body = buf_helper.Next<mach_msg_body_t>(); 274 if ((msg_body->msgh_descriptor_count > 0) != 275 MACH_MSGH_BITS_IS_COMPLEX(header->msgh_bits)) { 276 CHROMIUM_LOG(ERROR) 277 << "expected msgh_descriptor_count to match MACH_MSGH_BITS_COMPLEX"; 278 return false; 279 } 280 281 mach_msg_ool_descriptor_t* ool_descr = nullptr; 282 nsTArray<mozilla::UniqueMachSendRight> send_rights; 283 nsTArray<mozilla::UniqueMachReceiveRight> receive_rights; 284 for (size_t i = 0; i < msg_body->msgh_descriptor_count; ++i) { 285 auto* descr = buf_helper.Next<mach_msg_type_descriptor_t>(); 286 switch (descr->type) { 287 case MACH_MSG_OOL_DESCRIPTOR: { 288 if (ool_descr) { 289 CHROMIUM_LOG(ERROR) << "unexpected duplicate MACH_MSG_OOL_DESCRIPTOR"; 290 return false; 291 } 292 ool_descr = buf_helper.CastLast<mach_msg_ool_descriptor_t>(descr); 293 break; 294 } 295 case MACH_MSG_PORT_DESCRIPTOR: { 296 auto* port_descr = 297 buf_helper.CastLast<mach_msg_port_descriptor_t>(descr); 298 switch (port_descr->disposition) { 299 case MACH_MSG_TYPE_MOVE_SEND: 300 send_rights.EmplaceBack( 301 std::exchange(port_descr->name, MACH_PORT_NULL)); 302 break; 303 case MACH_MSG_TYPE_MOVE_RECEIVE: 304 receive_rights.EmplaceBack( 305 std::exchange(port_descr->name, MACH_PORT_NULL)); 306 break; 307 default: 308 CHROMIUM_LOG(ERROR) << "unexpected port descriptor disposition"; 309 return false; 310 } 311 break; 312 } 313 case MACH_MSG_OOL_PORTS_DESCRIPTOR: { 314 auto* ool_ports_descr = 315 buf_helper.CastLast<mach_msg_ool_ports_descriptor_t>(descr); 316 mozilla::Span<mach_port_t> names( 317 reinterpret_cast<mach_port_t*>(ool_ports_descr->address), 318 ool_ports_descr->count); 319 switch (ool_ports_descr->disposition) { 320 case MACH_MSG_TYPE_MOVE_SEND: 321 send_rights.SetCapacity(names.Length()); 322 for (mach_port_t& name : names) { 323 send_rights.EmplaceBack(std::exchange(name, MACH_PORT_NULL)); 324 } 325 break; 326 case MACH_MSG_TYPE_MOVE_RECEIVE: 327 receive_rights.SetCapacity(names.Length()); 328 for (mach_port_t& name : names) { 329 receive_rights.EmplaceBack(std::exchange(name, MACH_PORT_NULL)); 330 } 331 break; 332 default: 333 CHROMIUM_LOG(ERROR) << "unexpected port descriptor disposition"; 334 return false; 335 } 336 break; 337 } 338 default: 339 CHROMIUM_LOG(ERROR) << "unexpected descriptor type"; 340 return false; 341 } 342 } 343 344 // If we have an OOL descriptor, the payload is in that buffer, otherwise, it 345 // is the remainder of the message buffer. 346 mozilla::Span<const char> payload = 347 ool_descr 348 ? mozilla::Span{reinterpret_cast<const char*>(ool_descr->address), 349 static_cast<size_t>(ool_descr->size)} 350 : buf_helper.Remaining(); 351 352 // Check that the payload contains a complete message of the expected size 353 // before constructing it. 354 uint32_t hdr_size = 355 Message::MessageSize(payload.data(), payload.data() + payload.size()); 356 if (!hdr_size || round_msg(hdr_size) != payload.size()) { 357 CHROMIUM_LOG(ERROR) << "Message size does not match transferred payload"; 358 return false; 359 } 360 361 mozilla::UniquePtr<Message> message = 362 mozilla::MakeUnique<Message>(payload.data(), payload.size()); 363 364 // Transfer ownership of the voucher port into the IPC::Message. 365 if (MACH_MSGH_BITS_VOUCHER(header->msgh_bits) == MACH_MSG_TYPE_MOVE_SEND) { 366 message->mach_voucher_.reset(header->msgh_voucher_port); 367 header->msgh_voucher_port = MACH_PORT_NULL; 368 header->msgh_bits &= ~MACH_MSGH_BITS_VOUCHER_MASK; 369 } 370 371 // Unwrap any fileports attached to this message into FDs. FDs are always 372 // added after other mach port rights, so we can assume the last 373 // |num_handles| rights are fileports. 374 if (message->header()->num_handles > send_rights.Length()) { 375 CHROMIUM_LOG(ERROR) << "Missing send rights in message"; 376 return false; 377 } 378 for (auto& wrapped_fd : 379 mozilla::Span{send_rights}.Last(message->header()->num_handles)) { 380 message->attached_handles_.AppendElement( 381 mozilla::UniqueFileHandle(fileport_makefd(wrapped_fd.get()))); 382 } 383 send_rights.TruncateLength(send_rights.Length() - 384 message->header()->num_handles); 385 message->attached_send_rights_ = std::move(send_rights); 386 message->attached_receive_rights_ = std::move(receive_rights); 387 388 // Note: We set other_pid_ below when we receive a Hello message (which has no 389 // routing ID), but we only emit a profiler marker for messages with a routing 390 // ID, so there's no conflict here. 391 AddIPCProfilerMarker(*message, other_pid_, MessageDirection::eReceiving, 392 MessagePhase::TransferEnd); 393 394 #ifdef IPC_MESSAGE_DEBUG_EXTRA 395 DLOG(INFO) << "received message on channel @" << this << " with type " 396 << m.type(); 397 #endif 398 399 if (message->routing_id() == MSG_ROUTING_NONE && 400 message->type() == HELLO_MESSAGE_TYPE) { 401 // The hello message contains the process ID, as well as an optional 402 // send_port if the channel was initialized with a receive port. 403 if (peer_audit_token_) { 404 CHROMIUM_LOG(ERROR) << "Unexpected duplicate HELLO message"; 405 return false; 406 } 407 peer_audit_token_.emplace(trailer->msgh_audit); 408 409 // Read the hello message. 410 IPC::MessageReader reader(*message); 411 int32_t other_pid = -1; 412 mozilla::UniqueMachSendRight send_port; 413 if (!reader.ReadInt(&other_pid) || 414 !reader.ConsumeMachSendRight(&send_port)) { 415 return false; 416 } 417 if (send_port_ && send_port) { 418 CHROMIUM_LOG(ERROR) << "Unexpected send_port in HELLO message"; 419 return false; 420 } 421 if (!send_port_ && !send_port) { 422 CHROMIUM_LOG(ERROR) << "Expected send_port in HELLO message"; 423 return false; 424 } 425 #ifdef XP_MACOSX 426 if (XRE_IsParentProcess() && 427 audit_token_to_pid(trailer->msgh_audit) != other_pid) { 428 CHROMIUM_LOG(ERROR) << "audit token does not correspond to given pid"; 429 return false; 430 } 431 #endif 432 433 SetOtherPid(other_pid); 434 if (!send_port_) { 435 mozilla::MutexAutoLock lock(SendMutex()); 436 if (!ContinueConnect(std::move(send_port))) { 437 CHROMIUM_LOG(ERROR) << "ContinueConnect failed"; 438 return false; 439 } 440 } 441 442 listener_->OnChannelConnected(other_pid); 443 } else { 444 if (!peer_audit_token_) { 445 CHROMIUM_LOG(ERROR) << "Unexpected message before HELLO message"; 446 return false; 447 } 448 449 mozilla::LogIPCMessage::Run run(message.get()); 450 listener_->OnMessageReceived(std::move(message)); 451 } 452 return true; 453 } 454 455 template <class T> 456 static T* VmAllocateBuffer(size_t count) { 457 size_t bytes = count * sizeof(T); 458 vm_address_t address; 459 kern_return_t kr = 460 vm_allocate(mach_task_self(), &address, bytes, 461 VM_MAKE_TAG(VM_MEMORY_MACH_MSG) | VM_FLAGS_ANYWHERE); 462 if (kr != KERN_SUCCESS) { 463 NS_ABORT_OOM(bytes); 464 } 465 return reinterpret_cast<T*>(address); 466 } 467 468 template <class UniquePort> 469 static void WritePorts(MsgBufferHelper& buf_helper, mach_msg_base_t* base, 470 mach_msg_type_name_t disposition, 471 nsTArray<UniquePort>& attachments, bool send_inline) { 472 if (send_inline) { 473 for (auto& port : attachments) { 474 buf_helper.WriteDescriptor<mach_msg_port_descriptor_t>( 475 base, { 476 .name = port.release(), 477 .disposition = disposition, 478 .type = MACH_MSG_PORT_DESCRIPTOR, 479 }); 480 } 481 } else if (!attachments.IsEmpty()) { 482 mach_port_t* ports = VmAllocateBuffer<mach_port_t>(attachments.Length()); 483 for (size_t i = 0; i < attachments.Length(); ++i) { 484 ports[i] = attachments[i].release(); 485 } 486 buf_helper.WriteDescriptor<mach_msg_ool_ports_descriptor_t>( 487 base, { 488 .address = ports, 489 .deallocate = true, 490 .copy = MACH_MSG_VIRTUAL_COPY, 491 .disposition = disposition, 492 .type = MACH_MSG_OOL_PORTS_DESCRIPTOR, 493 .count = (mach_msg_size_t)attachments.Length(), 494 }); 495 } 496 } 497 498 bool ChannelMach::ProcessOutgoingMessages() { 499 chan_cap_.NoteLockHeld(); 500 501 DCHECK(!waiting_connect_); // Why are we trying to send messages if there's 502 // no connection? 503 504 while (!output_queue_.IsEmpty()) { 505 if (!send_port_) { 506 return false; 507 } 508 509 Message* msg = output_queue_.FirstElement().get(); 510 511 if (!send_buffer_has_message_) { 512 AddIPCProfilerMarker(*msg, other_pid_, MessageDirection::eSending, 513 MessagePhase::TransferStart); 514 515 // Reserve |sizeof(mach_msg_audit_trailer_t)| bytes at the end of the 516 // buffer, as the receiving side will need enough space for the trailer. 517 mach_msg_size_t max_size = 518 Channel::kReadBufferSize - sizeof(mach_msg_audit_trailer_t); 519 MsgBufferHelper buf_helper(send_buffer_.get(), max_size); 520 521 // Clear out the message header to an initial state so we can build it. 522 auto* base = buf_helper.Next<mach_msg_base_t>(); 523 *base = {{ 524 .msgh_bits = MACH_MSGH_BITS(/* remote */ MACH_MSG_TYPE_COPY_SEND, 525 /* local */ 0), 526 .msgh_size = sizeof(mach_msg_base_t), 527 .msgh_remote_port = send_port_.get(), 528 .msgh_id = kIPDLMessageId, 529 }}; 530 send_buffer_has_message_ = true; 531 532 // Convert FDs to send rights using `fileport_makeport`. 533 // The number of handles is recorded in the header so that they can be 534 // split out on the other side. 535 msg->header()->num_handles = msg->attached_handles_.Length(); 536 msg->attached_send_rights_.SetCapacity( 537 msg->attached_send_rights_.Length() + 538 msg->attached_handles_.Length()); 539 for (auto& fd : msg->attached_handles_) { 540 mach_port_t fileport; 541 kern_return_t kr = fileport_makeport(fd.get(), &fileport); 542 if (kr != KERN_SUCCESS) { 543 CHROMIUM_LOG(ERROR) 544 << "fileport_makeport failed: " << mach_error_string(kr); 545 return false; 546 } 547 msg->attached_send_rights_.AppendElement( 548 mozilla::UniqueMachSendRight(fileport)); 549 } 550 551 // Check if there's enough space in the buffer to fit a port descriptor 552 // for every attached handle + a mach_msg_ool_descriptor_t for the 553 // payload. If there isn't, send them out-of-line. 554 size_t inline_descr_size = sizeof(mach_msg_port_descriptor_t) * 555 (msg->attached_send_rights_.Length() + 556 msg->attached_receive_rights_.Length()) + 557 sizeof(mach_msg_ool_descriptor_t); 558 bool send_inline = buf_helper.Remaining().size() > inline_descr_size; 559 WritePorts(buf_helper, base, MACH_MSG_TYPE_MOVE_SEND, 560 msg->attached_send_rights_, send_inline); 561 WritePorts(buf_helper, base, MACH_MSG_TYPE_MOVE_RECEIVE, 562 msg->attached_receive_rights_, send_inline); 563 564 // Determine where to write the message payload. We'll write it inline if 565 // there's space, otherwise it'll be sent out-of-line. 566 char* payload = nullptr; 567 if (buf_helper.Remaining().size() > msg->size()) { 568 payload = buf_helper.WriteBytes(base, msg->size()); 569 } else { 570 // NOTE: If `msg` holds the message in a single buffer, we could pass it 571 // down without copying by passing a pointer in an ool descriptor with 572 // `deallocate = false`. 573 payload = VmAllocateBuffer<char>(round_msg(msg->size())); 574 buf_helper.WriteDescriptor<mach_msg_ool_descriptor_t>( 575 base, { 576 .address = payload, 577 .deallocate = true, 578 .copy = MACH_MSG_VIRTUAL_COPY, 579 .type = MACH_MSG_OOL_DESCRIPTOR, 580 .size = (mach_msg_size_t)round_msg(msg->size()), 581 }); 582 } 583 584 // Write the full message payload into the payload buffer. 585 auto iter = msg->Buffers().Iter(); 586 MOZ_ALWAYS_TRUE(msg->Buffers().ReadBytes(iter, payload, msg->size())); 587 } 588 589 MOZ_ASSERT(send_buffer_has_message_, "Failed to build a message?"); 590 auto* header = reinterpret_cast<mach_msg_header_t*>(send_buffer_.get()); 591 kern_return_t kr = mach_msg(header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, 592 header->msgh_size, 0, MACH_PORT_NULL, 593 /* timeout */ 0, MACH_PORT_NULL); 594 if (kr == KERN_SUCCESS) { 595 // Don't clean up the message anymore. 596 send_buffer_has_message_ = false; 597 598 AddIPCProfilerMarker(*msg, other_pid_, MessageDirection::eSending, 599 MessagePhase::TransferEnd); 600 601 #ifdef IPC_MESSAGE_DEBUG_EXTRA 602 DLOG(INFO) << "sent message @" << msg << " on channel @" << this 603 << " with type " << msg->type(); 604 #endif 605 606 OutputQueuePop(); 607 } else { 608 if (kr == MACH_SEND_TIMED_OUT) { 609 // The message timed out, set up a runnable to re-try the send on the 610 // IPC I/O thread. 611 // 612 // NOTE: It'd be nice to use MACH_NOTIFY_SEND_POSSIBLE here, but using 613 // it naively can lead to port leaks when the port becomes a DEAD_NAME 614 // due to issues in the port subsystem. 615 XRE_GetAsyncIOEventTarget()->Dispatch(NS_NewRunnableFunction( 616 "ChannelMach::Retry", [self = RefPtr{this}]() { 617 mozilla::MutexAutoLock lock(self->SendMutex()); 618 self->chan_cap_.NoteLockHeld(); 619 if (self->receive_port_) { 620 self->ProcessOutgoingMessages(); 621 } 622 })); 623 return true; 624 } 625 626 if (kr != MACH_SEND_INVALID_DEST) { 627 CHROMIUM_LOG(ERROR) 628 << "mach_msg send failed: " << mach_error_string(kr); 629 } 630 return false; 631 } 632 } 633 return true; 634 } 635 636 bool ChannelMach::Send(mozilla::UniquePtr<Message> message) { 637 // NOTE: This method may be called on threads other than `IOThread()`. 638 mozilla::MutexAutoLock lock(SendMutex()); 639 chan_cap_.NoteLockHeld(); 640 641 #ifdef IPC_MESSAGE_DEBUG_EXTRA 642 DLOG(INFO) << "sending message @" << message.get() << " on channel @" << this 643 << " with type " << message->type() << " (" 644 << output_queue_.Count() << " in queue)"; 645 #endif 646 647 // If the channel has been closed, ProcessOutgoingMessages() is never going 648 // to pop anything off output_queue; output_queue will only get emptied when 649 // the channel is destructed. We might as well delete message now, instead 650 // of waiting for the channel to be destructed. 651 if (!receive_port_) { 652 if (mozilla::ipc::LoggingEnabled()) { 653 printf_stderr("Can't send message %s, because this channel is closed.\n", 654 message->name()); 655 } 656 return false; 657 } 658 659 OutputQueuePush(std::move(message)); 660 if (!waiting_connect_ && !send_buffer_has_message_) { 661 return ProcessOutgoingMessages(); 662 } 663 664 return true; 665 } 666 667 void ChannelMach::OnMachMessageReceived(mach_port_t port) { 668 IOThread().AssertOnCurrentThread(); 669 chan_cap_.NoteOnTarget(); 670 671 if (receive_port_) { 672 if (!ProcessIncomingMessage()) { 673 Close(); 674 listener_->OnChannelError(); 675 // The OnChannelError() call may delete this, so we need to exit now. 676 return; 677 } 678 } 679 } 680 681 void ChannelMach::OutputQueuePush(mozilla::UniquePtr<Message> msg) { 682 chan_cap_.NoteLockHeld(); 683 684 mozilla::LogIPCMessage::LogDispatchWithPid(msg.get(), other_pid_); 685 686 MOZ_DIAGNOSTIC_ASSERT(receive_port_); 687 msg->AssertAsLargeAsHeader(); 688 output_queue_.Push(std::move(msg)); 689 } 690 691 void ChannelMach::OutputQueuePop() { 692 if (send_buffer_has_message_) { 693 mach_msg_destroy(reinterpret_cast<mach_msg_header_t*>(send_buffer_.get())); 694 send_buffer_has_message_ = false; 695 } 696 697 mozilla::UniquePtr<Message> message = output_queue_.Pop(); 698 } 699 700 void ChannelMach::Close() { 701 IOThread().AssertOnCurrentThread(); 702 mozilla::MutexAutoLock lock(SendMutex()); 703 CloseLocked(); 704 } 705 706 void ChannelMach::CloseLocked() { 707 chan_cap_.NoteExclusiveAccess(); 708 709 // Close can be called multiple times, so we need to make sure we're 710 // idempotent. 711 712 watch_controller_.StopWatchingMachPort(); 713 receive_port_ = nullptr; 714 send_port_ = nullptr; 715 716 while (!output_queue_.IsEmpty()) { 717 OutputQueuePop(); 718 } 719 } 720 721 // static 722 bool ChannelMach::CreateRawPipe(ChannelHandle* server, ChannelHandle* client) { 723 return CreateRawPipe(&server->emplace<mozilla::UniqueMachReceiveRight>(), 724 &client->emplace<mozilla::UniqueMachSendRight>()); 725 } 726 727 // static 728 bool ChannelMach::CreateRawPipe(mozilla::UniqueMachReceiveRight* server, 729 mozilla::UniqueMachSendRight* client) { 730 mach_port_options_t options{}; 731 options.flags = MPO_INSERT_SEND_RIGHT | MPO_QLIMIT; 732 options.mpl.mpl_qlimit = MACH_PORT_QLIMIT_LARGE; 733 734 mach_port_t port = MACH_PORT_NULL; 735 kern_return_t kr = mach_port_construct(mach_task_self(), &options, 0, &port); 736 if (kr != KERN_SUCCESS) { 737 CHROMIUM_LOG(ERROR) << "mach_port_construct failed: " 738 << mach_error_string(kr); 739 return false; 740 } 741 742 // Within a single task, all references to the same port have the same name. 743 // Thanks to `MPO_INSERT_SEND_RIGHT`, both a send and receive right were 744 // inserted for this name. 745 server->reset(port); 746 client->reset(port); 747 return true; 748 } 749 750 // static 751 uint32_t ChannelMach::NumRelayedAttachments(const Message& message) { 752 return 0; 753 } 754 755 // static 756 bool ChannelMach::IsValidHandle(const ChannelHandle& handle) { 757 if (const auto* server = 758 std::get_if<mozilla::UniqueMachReceiveRight>(&handle)) { 759 return !!*server; 760 } 761 if (const auto* client = 762 std::get_if<mozilla::UniqueMachReceiveRight>(&handle)) { 763 return !!*client; 764 } 765 return false; 766 } 767 768 } // namespace IPC