tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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