tor-browser

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

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