File: lock_mac.mm

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (165 lines) | stat: -rw-r--r-- 6,631 bytes parent folder | download | duplicates (9)
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
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/named_system_lock/lock.h"

#include <mach/mach.h>
#include <servers/bootstrap.h>
#include <sys/types.h>
#include <unistd.h>

#include <algorithm>
#include <memory>
#include <string>
#include <utility>

#include "base/apple/mach_logging.h"
#include "base/apple/scoped_mach_port.h"
#include "base/check.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"

namespace {

// Interval to poll for lock availability if it is not immediately available.
// Final interval is truncated to fit the available timeout.
constexpr base::TimeDelta kLockPollingInterval = base::Seconds(3);

//
// Attempts to acquire the receive right to a named Mach service.
// Single attempt, no retries. Logs errors other than "permission denied",
// since "permission denied" typically means the service receive rights have
// already been assigned.
//
// Returns the receive right if the right was successfully acquired. If the
// right cannot be acquired for any reason, returns an invalid right instead.
base::apple::ScopedMachReceiveRight TryAcquireReceive(
    const base::apple::ScopedMachSendRight& bootstrap_right,
    const std::string& service_name) {
  VLOG(2) << __func__;
  base::apple::ScopedMachReceiveRight target_right;
  kern_return_t check_in_result = bootstrap_check_in(
      bootstrap_right.get(), service_name.c_str(),
      base::apple::ScopedMachReceiveRight::Receiver(target_right).get());
  if (check_in_result != KERN_SUCCESS) {
    // Log error reports for all errors other than BOOTSTRAP_NOT_PRIVILEGED.
    // BOOTSTRAP_NOT_PRIVILEGED is not logged because it just means that another
    // process has acquired the receive rights for this service.
    if (check_in_result != BOOTSTRAP_NOT_PRIVILEGED) {
      BOOTSTRAP_LOG(ERROR, check_in_result)
          << " bootstrap_check_in to acquire lock: " << service_name;
    } else {
      BOOTSTRAP_VLOG(2, check_in_result)
          << " lock already held: " << service_name;
    }
    return base::apple::ScopedMachReceiveRight();
  }
  return target_right;
}

// Sleeps `wait_time` until the lock should be retried, but no more than
// `kLockPollingInterval`.
void WaitToRetryLock(base::TimeDelta wait_time) {
  base::PlatformThread::Sleep(std::min(wait_time, kLockPollingInterval));
}

}  // anonymous namespace

namespace named_system_lock {

class ScopedLockImpl {
 public:
  // Constructs a ScopedLockImpl from a receive right.
  explicit ScopedLockImpl(base::apple::ScopedMachReceiveRight receive_right);

  // Releases the receive right.
  ~ScopedLockImpl() = default;

  ScopedLockImpl(const ScopedLockImpl&) = delete;
  ScopedLockImpl& operator=(const ScopedLockImpl&) = delete;

 private:
  // The Mach port representing the held lock itself. We only care about
  // service ownership; no messages are transferred with this port.
  base::apple::ScopedMachReceiveRight receive_right_;
};

ScopedLockImpl::ScopedLockImpl(
    base::apple::ScopedMachReceiveRight receive_right)
    : receive_right_(std::move(receive_right)) {
  mach_port_type_t port_type = 0;
  kern_return_t port_check_result =
      mach_port_type(mach_task_self(), receive_right_.get(), &port_type);
  MACH_CHECK(port_check_result == KERN_SUCCESS, port_check_result)
      << "ScopedLockImpl could not verify lock port";
  CHECK(port_type & MACH_PORT_TYPE_RECEIVE)
      << "ScopedLockImpl given port without receive right";
}

ScopedLock::ScopedLock(std::unique_ptr<ScopedLockImpl> impl)
    : impl_(std::move(impl)) {}

ScopedLock::~ScopedLock() = default;

// static
std::unique_ptr<ScopedLock> ScopedLock::Create(const std::string& service_name,
                                               base::TimeDelta timeout) {
  // Find the right namespace for the lock. Non-privileged processes cannot
  // climb "up" out of their namespace, but user processes run with the right
  // namespace (the interactive user session) anyway. System processes need the
  // system namespace, which is the root of macOS' "tree" of Mach bootstrap
  // namespaces. Daemons (including LaunchDaemons) and system services naturally
  // run under this namespace, but `sudo` -- a POSIX utility that is not aware
  // of the Mach portions of the macOS kernel -- runs targets without changing
  // their bootstrap port (and therefore their namespace). The system namespace
  // is the only one guaranteed to be shared between users.
  //
  // mac/launcher_main.c uses an equivalent algorithm to find this namespace
  // when launching a privileged process. It's repeated here so processes
  // launched via sudo (such as the integration test helper) can reach the
  // system-scope locks.
  base::apple::ScopedMachSendRight bootstrap_right =
      base::apple::RetainMachSendRight(bootstrap_port);
  if (!geteuid()) {
    // Move the initial bootstrap right into `next_right` so the first loop is
    // not a special case. base::ScopedGeneric calls `abort()` if you `reset` it
    // to what it already holds, so this has to be a move, not a retain.
    base::apple::ScopedMachSendRight next_right(bootstrap_right.release());
    while (bootstrap_right.get() != next_right.get()) {
      bootstrap_right.reset(next_right.release());
      kern_return_t bootstrap_err = bootstrap_parent(
          bootstrap_right.get(),
          base::apple::ScopedMachSendRight::Receiver(next_right).get());
      if (bootstrap_err != KERN_SUCCESS) {
        BOOTSTRAP_LOG(ERROR, bootstrap_err)
            << "can't bootstrap_parent in ScopedLock::Create for euid 0";
        break;  // Use last known bootstrap_right.
      }
      CHECK(next_right.is_valid())
          << "bootstrap_parent yielded invalid port without error";
    }
  }

  // Make one try to acquire the lock, even if the timeout is zero or negative.
  base::apple::ScopedMachReceiveRight receive_right(
      TryAcquireReceive(bootstrap_right, service_name.c_str()));

  base::TimeTicks deadline = base::TimeTicks::Now() + timeout;
  for (base::TimeDelta remain = deadline - base::TimeTicks::Now();
       !receive_right.is_valid() && remain.is_positive();
       remain = deadline - base::TimeTicks::Now()) {
    WaitToRetryLock(remain);
    receive_right = TryAcquireReceive(bootstrap_right, service_name);
  }

  if (!receive_right.is_valid()) {
    return nullptr;
  }
  return std::make_unique<ScopedLock>(
      std::make_unique<ScopedLockImpl>(std::move(receive_right)));
}

}  // namespace named_system_lock