1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173

content / browser / child_process_task_port_provider_mac.cc [blame]

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/child_process_task_port_provider_mac.h"

#include <map>

#include "base/apple/foundation_util.h"
#include "base/apple/mach_logging.h"
#include "base/debug/crash_logging.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "content/common/child_process.mojom.h"
#include "mojo/public/cpp/system/platform_handle.h"

#if BUILDFLAG(IS_MAC)
#include "content/common/mac/task_port_policy.h"
#endif

namespace content {

ChildProcessTaskPortProvider* ChildProcessTaskPortProvider::GetInstance() {
  static base::NoDestructor<ChildProcessTaskPortProvider> provider;
  return provider.get();
}

void ChildProcessTaskPortProvider::OnChildProcessLaunched(
    base::ProcessHandle process_handle,
    mojom::ChildProcess* child_process) {
  if (!ShouldRequestTaskPorts())
    return;

  child_process->GetTaskPort(
      base::BindOnce(&ChildProcessTaskPortProvider::OnTaskPortReceived,
                     base::Unretained(this), process_handle));
}

mach_port_t ChildProcessTaskPortProvider::TaskForHandle(
    base::ProcessHandle process_handle) const {
  base::AutoLock lock(lock_);
  auto it = handle_to_task_port_.find(process_handle);
  if (it == handle_to_task_port_.end()) {
    return MACH_PORT_NULL;
  }
  return it->second.get();
}

ChildProcessTaskPortProvider::ChildProcessTaskPortProvider() {
  if (!ShouldRequestTaskPorts()) {
    LOG(WARNING) << "AppleMobileFileIntegrity is disabled. The browser will "
                    "not collect child process task ports.";
    return;
  }

  CHECK(base::apple::CreateMachPort(¬ification_port_, nullptr));

  const std::string dispatch_name = base::StringPrintf(
      "%s.ChildProcessTaskPortProvider.%p", base::apple::BaseBundleID(), this);
  notification_source_ = std::make_unique<base::apple::DispatchSourceMach>(
      dispatch_name.c_str(), notification_port_.get(), ^{
        OnTaskPortDied();
      });
  notification_source_->Resume();
}

ChildProcessTaskPortProvider::~ChildProcessTaskPortProvider() {}

bool ChildProcessTaskPortProvider::ShouldRequestTaskPorts() const {
#if BUILDFLAG(IS_MAC)
  // Set a crash key for the lifetime of the browser process to help debug
  // other failures.
  static auto* crash_key = base::debug::AllocateCrashKeyString(
      "amfi-status", base::debug::CrashKeySize::Size64);
  static const bool should_request_task_ports =
      [](base::debug::CrashKeyString* crash_key) -> bool {
    const MachTaskPortPolicy task_port_policy(GetMachTaskPortPolicy());
    bool allow_everything = task_port_policy.AmfiIsAllowEverything();
    base::debug::SetCrashKeyString(
        crash_key,
        base::StringPrintf("rv=%d status=0x%llx allow_everything=%d",
                           task_port_policy.amfi_status_retval,
                           task_port_policy.amfi_status, allow_everything));
    return !allow_everything;
  }(crash_key);
  return should_request_task_ports;
#else
  return true;
#endif
}

void ChildProcessTaskPortProvider::OnTaskPortReceived(
    base::ProcessHandle process_handle,
    mojo::PlatformHandle task_port) {
  DCHECK(ShouldRequestTaskPorts());
  if (!task_port.is_mach_send()) {
    DLOG(ERROR) << "Invalid handle received as task port for pid "
                << base::GetProcId(process_handle);
    return;
  }
  base::apple::ScopedMachSendRight port = task_port.TakeMachSendRight();

  // Request a notification from the kernel for when the port becomes a dead
  // name, indicating that the process has died.
  base::apple::ScopedMachSendRight previous;
  kern_return_t kr = mach_port_request_notification(
      mach_task_self(), port.get(), MACH_NOTIFY_DEAD_NAME, 0,
      notification_port_.get(), MACH_MSG_TYPE_MAKE_SEND_ONCE,
      base::apple::ScopedMachSendRight::Receiver(previous).get());
  if (kr != KERN_SUCCESS) {
    // If the argument was invalid, the process is likely already dead.
    MACH_DVLOG(1, kr) << "mach_port_request_notification";
    return;
  }

  DVLOG(1) << "Received task port for PID=" << base::GetProcId(process_handle)
           << ", port name=" << port.get();

  {
    base::AutoLock lock(lock_);
    auto it = handle_to_task_port_.find(process_handle);
    if (it == handle_to_task_port_.end()) {
      handle_to_task_port_.emplace(process_handle, std::move(port));
    } else {
      // If a task port already exists for the PID, then reset it if the port
      // is of a different name. The port name may be the same when running in
      // single-process mode, tests, or if the PID is reused and this races the
      // DEAD_NAME notification. Self-reseting is not allowed on ScopedGeneric,
      // so test for that first.
      if (it->second.get() != port.get())
        it->second = std::move(port);
    }
  }

  NotifyObservers(process_handle);
}

void ChildProcessTaskPortProvider::OnTaskPortDied() {
  DCHECK(ShouldRequestTaskPorts());

  mach_dead_name_notification_t notification{};
  kern_return_t kr =
      mach_msg(¬ification.not_header, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0,
               sizeof(notification), notification_port_.get(), /*timeout=*/0,
               MACH_PORT_NULL);
  if (kr != KERN_SUCCESS) {
    MACH_LOG(ERROR, kr) << "mach_msg";
    return;
  }

  // A NOTIFY_SEND_ONCE might be delivered from the send-once right allocated
  // via mach_port_request_notification().
  if (notification.not_header.msgh_id != MACH_NOTIFY_DEAD_NAME)
    return;

  // Release the DEAD_NAME right.
  base::apple::ScopedMachSendRight dead_port(notification.not_port);

  base::AutoLock lock(lock_);
  std::erase_if(handle_to_task_port_, [&dead_port](const auto& pair) {
    if (pair.second.get() == dead_port.get()) {
      DVLOG(1) << "Task died, PID=" << base::GetProcId(pair.first)
               << ", task port name=" << dead_port.get();
      return true;
    }
    return false;
  });
}

}  // namespace content