blob: 51660fb610a0abeb438dc78063f0fcf4ef1f73a5 [file] [log] [blame]
// Copyright (c) 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/allocator/partition_allocator/starscan/write_protector.h"
#include <mutex>
#include <thread>
#include "base/allocator/partition_allocator/address_pool_manager.h"
#include "base/allocator/partition_allocator/partition_address_space.h"
#include "base/allocator/partition_allocator/partition_alloc_base/logging.h"
#include "base/allocator/partition_allocator/partition_alloc_base/posix/eintr_wrapper.h"
#include "base/allocator/partition_allocator/partition_alloc_base/threading/platform_thread.h"
#include "base/allocator/partition_allocator/partition_alloc_check.h"
#include "build/build_config.h"
#if defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
#include <fcntl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#endif // defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
namespace partition_alloc::internal {
PCScan::ClearType NoWriteProtector::SupportedClearType() const {
return PCScan::ClearType::kLazy;
}
#if defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
namespace {
void UserFaultFDThread(int uffd) {
PA_DCHECK(-1 != uffd);
static constexpr char kThreadName[] = "PCScanPFHandler";
internal::base::PlatformThread::SetName(kThreadName);
while (true) {
// Pool on the uffd descriptor for page fault events.
pollfd pollfd{.fd = uffd, .events = POLLIN};
const int nready = PA_HANDLE_EINTR(poll(&pollfd, 1, -1));
PA_CHECK(-1 != nready);
// Get page fault info.
uffd_msg msg;
const int nread = PA_HANDLE_EINTR(read(uffd, &msg, sizeof(msg)));
PA_CHECK(0 != nread);
// We only expect page faults.
PA_DCHECK(UFFD_EVENT_PAGEFAULT == msg.event);
// We have subscribed only to wp-fault events.
PA_DCHECK(UFFD_PAGEFAULT_FLAG_WP & msg.arg.pagefault.flags);
// Enter the safepoint. Concurrent faulted writes will wait until safepoint
// finishes.
PCScan::JoinScanIfNeeded();
}
}
} // namespace
UserFaultFDWriteProtector::UserFaultFDWriteProtector()
: uffd_(syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK)) {
if (uffd_ == -1) {
PA_LOG(WARNING) << "userfaultfd is not supported by the current kernel";
return;
}
PA_PCHECK(-1 != uffd_);
uffdio_api uffdio_api;
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
PA_CHECK(-1 != ioctl(uffd_, UFFDIO_API, &uffdio_api));
PA_CHECK(UFFD_API == uffdio_api.api);
// Register the giga-cage to listen uffd events.
struct uffdio_register uffdio_register;
uffdio_register.range.start = PartitionAddressSpace::RegularPoolBase();
uffdio_register.range.len = kPoolMaxSize;
uffdio_register.mode = UFFDIO_REGISTER_MODE_WP;
PA_CHECK(-1 != ioctl(uffd_, UFFDIO_REGISTER, &uffdio_register));
// Start uffd thread.
std::thread(UserFaultFDThread, uffd_).detach();
}
namespace {
enum class UserFaultFDWPMode {
kProtect,
kUnprotect,
};
void UserFaultFDWPSet(int uffd,
uintptr_t begin,
size_t length,
UserFaultFDWPMode mode) {
PA_DCHECK(0 == (begin % SystemPageSize()));
PA_DCHECK(0 == (length % SystemPageSize()));
uffdio_writeprotect wp;
wp.range.start = begin;
wp.range.len = length;
wp.mode =
(mode == UserFaultFDWPMode::kProtect) ? UFFDIO_WRITEPROTECT_MODE_WP : 0;
PA_PCHECK(-1 != ioctl(uffd, UFFDIO_WRITEPROTECT, &wp));
}
} // namespace
void UserFaultFDWriteProtector::ProtectPages(uintptr_t begin, size_t length) {
if (IsSupported())
UserFaultFDWPSet(uffd_, begin, length, UserFaultFDWPMode::kProtect);
}
void UserFaultFDWriteProtector::UnprotectPages(uintptr_t begin, size_t length) {
if (IsSupported())
UserFaultFDWPSet(uffd_, begin, length, UserFaultFDWPMode::kUnprotect);
}
PCScan::ClearType UserFaultFDWriteProtector::SupportedClearType() const {
return IsSupported() ? PCScan::ClearType::kEager : PCScan::ClearType::kLazy;
}
bool UserFaultFDWriteProtector::IsSupported() const {
return uffd_ != -1;
}
#endif // defined(PA_STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
} // namespace partition_alloc::internal