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
|
// 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 <aio.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory>
#include <string>
#include <utility>
#include "base/files/file.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/time/time.h"
namespace named_system_lock {
// A global preferences lock for Linux implemented with pthread mutexes in
// shared memory. Note that the shared memory region is leaked once per lock
// name for reasons described in the README.md. The size of the leaked region is
// ~40 bytes.
class ScopedLockImpl {
public:
ScopedLockImpl(const ScopedLockImpl&) = delete;
ScopedLockImpl& operator=(const ScopedLockImpl&) = delete;
~ScopedLockImpl();
static std::unique_ptr<ScopedLockImpl> TryCreate(
const std::string& shared_mem_name,
base::TimeDelta timeout);
private:
ScopedLockImpl(pthread_mutex_t* mutex, int shm_fd)
: mutex_(mutex), shm_fd_(shm_fd) {}
// RAW_PTR_EXCLUSION: Never allocated by PartitionAlloc (always mmap'ed), so
// there is no benefit to using a raw_ptr, only cost.
RAW_PTR_EXCLUSION pthread_mutex_t* mutex_;
int shm_fd_;
};
std::unique_ptr<ScopedLockImpl> ScopedLockImpl::TryCreate(
const std::string& shared_mem_name,
base::TimeDelta timeout) {
bool should_init_mutex = false;
int shm_fd = shm_open(shared_mem_name.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
if (shm_fd < 0 && errno == ENOENT) {
shm_fd =
shm_open(shared_mem_name.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
should_init_mutex = true;
}
if (shm_fd < 0) {
return nullptr;
}
base::stat_wrapper_t shm_stat;
if (base::File::Fstat(shm_fd, &shm_stat) < 0) {
VPLOG(1) << "Cannot stat shared memory " << shared_mem_name;
return nullptr;
}
if (shm_stat.st_uid != getuid() ||
(shm_stat.st_mode & 0777) != (S_IRUSR | S_IWUSR)) {
VLOG(1) << "Refusing to use shared memory region " << shared_mem_name
<< " with incorrect permissions";
return nullptr;
}
if (ftruncate(shm_fd, sizeof(pthread_mutex_t))) {
return nullptr;
}
void* addr = mmap(nullptr, sizeof(pthread_mutex_t), PROT_READ | PROT_WRITE,
MAP_SHARED, shm_fd, 0);
if (addr == MAP_FAILED) {
return nullptr;
}
pthread_mutex_t* mutex = static_cast<pthread_mutex_t*>(addr);
// Note that the mutex is configured with the "robust" attribute. This ensures
// that even if a process crashes while holding the mutex, the mutex is
// recoverable.
if (should_init_mutex) {
pthread_mutexattr_t attr = {};
if (pthread_mutexattr_init(&attr) ||
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) ||
pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST) ||
pthread_mutex_init(mutex, &attr)) {
munmap(addr, sizeof(pthread_mutex_t));
return nullptr;
}
}
const base::Time start = base::Time::NowFromSystemTime();
do {
switch (pthread_mutex_trylock(mutex)) {
case EOWNERDEAD:
// A process holding the mutex died, try to recover it.
if (pthread_mutex_consistent(mutex)) {
return nullptr;
}
// The mutex is restored. It is in the locked state.
[[fallthrough]];
case 0:
// The lock was acquired.
return base::WrapUnique<ScopedLockImpl>(
new ScopedLockImpl(mutex, shm_fd));
case EBUSY:
// The mutex is held by another process.
continue;
default:
// An error occurred.
return nullptr;
}
} while (base::Time::NowFromSystemTime() - start < timeout);
// The lock was not acquired before the timeout.
return nullptr;
}
ScopedLockImpl::~ScopedLockImpl() {
if (mutex_) {
pthread_mutex_unlock(mutex_);
munmap(mutex_, sizeof(pthread_mutex_t));
close(shm_fd_);
}
}
ScopedLock::ScopedLock(std::unique_ptr<ScopedLockImpl> impl)
: impl_(std::move(impl)) {}
ScopedLock::~ScopedLock() = default;
// static
std::unique_ptr<ScopedLock> ScopedLock::Create(const std::string& name,
base::TimeDelta timeout) {
std::unique_ptr<ScopedLockImpl> impl =
ScopedLockImpl::TryCreate(name, timeout);
return impl ? std::make_unique<ScopedLock>(std::move(impl)) : nullptr;
}
} // namespace named_system_lock
|