1 // Copyright 2021 The Chromium Authors
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 "partition_alloc/starscan/write_protector.h"
6
7 #include <mutex>
8 #include <thread>
9
10 #include "build/build_config.h"
11 #include "partition_alloc/address_pool_manager.h"
12 #include "partition_alloc/partition_address_space.h"
13 #include "partition_alloc/partition_alloc_base/logging.h"
14 #include "partition_alloc/partition_alloc_base/posix/eintr_wrapper.h"
15 #include "partition_alloc/partition_alloc_base/threading/platform_thread.h"
16 #include "partition_alloc/partition_alloc_check.h"
17
18 #if PA_CONFIG(STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
19 #include <fcntl.h>
20 #include <linux/userfaultfd.h>
21 #include <poll.h>
22 #include <sys/ioctl.h>
23 #include <sys/stat.h>
24 #include <sys/syscall.h>
25 #include <sys/types.h>
26 #endif // PA_CONFIG(STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
27
28 namespace partition_alloc::internal {
29
SupportedClearType() const30 PCScan::ClearType NoWriteProtector::SupportedClearType() const {
31 return PCScan::ClearType::kLazy;
32 }
33
34 #if PA_CONFIG(STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
35
36 namespace {
UserFaultFDThread(int uffd)37 void UserFaultFDThread(int uffd) {
38 PA_DCHECK(-1 != uffd);
39
40 static constexpr char kThreadName[] = "PCScanPFHandler";
41 internal::base::PlatformThread::SetName(kThreadName);
42
43 while (true) {
44 // Pool on the uffd descriptor for page fault events.
45 pollfd pollfd{
46 .fd = uffd,
47 .events = POLLIN,
48 .revents = 0, // Unused output param of `pool` call.
49 };
50 const int nready = WrapEINTR(poll)(&pollfd, 1, -1);
51 PA_CHECK(-1 != nready);
52
53 // Get page fault info.
54 uffd_msg msg;
55 const int nread = WrapEINTR(read)(uffd, &msg, sizeof(msg));
56 PA_CHECK(0 != nread);
57
58 // We only expect page faults.
59 PA_DCHECK(UFFD_EVENT_PAGEFAULT == msg.event);
60 // We have subscribed only to wp-fault events.
61 PA_DCHECK(UFFD_PAGEFAULT_FLAG_WP & msg.arg.pagefault.flags);
62
63 // Enter the safepoint. Concurrent faulted writes will wait until safepoint
64 // finishes.
65 PCScan::JoinScanIfNeeded();
66 }
67 }
68 } // namespace
69
UserFaultFDWriteProtector()70 UserFaultFDWriteProtector::UserFaultFDWriteProtector()
71 : uffd_(syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK)) {
72 if (uffd_ == -1) {
73 PA_LOG(WARNING) << "userfaultfd is not supported by the current kernel";
74 return;
75 }
76
77 PA_PCHECK(-1 != uffd_);
78
79 uffdio_api uffdio_api;
80 uffdio_api.api = UFFD_API;
81 uffdio_api.features = 0;
82 PA_CHECK(-1 != ioctl(uffd_, UFFDIO_API, &uffdio_api));
83 PA_CHECK(UFFD_API == uffdio_api.api);
84
85 // Register the regular pool to listen uffd events.
86 struct uffdio_register uffdio_register;
87 uffdio_register.range.start = PartitionAddressSpace::RegularPoolBase();
88 uffdio_register.range.len = kPoolMaxSize;
89 uffdio_register.mode = UFFDIO_REGISTER_MODE_WP;
90 PA_CHECK(-1 != ioctl(uffd_, UFFDIO_REGISTER, &uffdio_register));
91
92 // Start uffd thread.
93 std::thread(UserFaultFDThread, uffd_).detach();
94 }
95
96 namespace {
97 enum class UserFaultFDWPMode {
98 kProtect,
99 kUnprotect,
100 };
101
UserFaultFDWPSet(int uffd,uintptr_t begin,size_t length,UserFaultFDWPMode mode)102 void UserFaultFDWPSet(int uffd,
103 uintptr_t begin,
104 size_t length,
105 UserFaultFDWPMode mode) {
106 PA_DCHECK(0 == (begin % SystemPageSize()));
107 PA_DCHECK(0 == (length % SystemPageSize()));
108
109 uffdio_writeprotect wp;
110 wp.range.start = begin;
111 wp.range.len = length;
112 wp.mode =
113 (mode == UserFaultFDWPMode::kProtect) ? UFFDIO_WRITEPROTECT_MODE_WP : 0;
114 PA_PCHECK(-1 != ioctl(uffd, UFFDIO_WRITEPROTECT, &wp));
115 }
116 } // namespace
117
ProtectPages(uintptr_t begin,size_t length)118 void UserFaultFDWriteProtector::ProtectPages(uintptr_t begin, size_t length) {
119 if (IsSupported()) {
120 UserFaultFDWPSet(uffd_, begin, length, UserFaultFDWPMode::kProtect);
121 }
122 }
123
UnprotectPages(uintptr_t begin,size_t length)124 void UserFaultFDWriteProtector::UnprotectPages(uintptr_t begin, size_t length) {
125 if (IsSupported()) {
126 UserFaultFDWPSet(uffd_, begin, length, UserFaultFDWPMode::kUnprotect);
127 }
128 }
129
SupportedClearType() const130 PCScan::ClearType UserFaultFDWriteProtector::SupportedClearType() const {
131 return IsSupported() ? PCScan::ClearType::kEager : PCScan::ClearType::kLazy;
132 }
133
IsSupported() const134 bool UserFaultFDWriteProtector::IsSupported() const {
135 return uffd_ != -1;
136 }
137
138 #endif // PA_CONFIG(STARSCAN_UFFD_WRITE_PROTECTOR_SUPPORTED)
139
140 } // namespace partition_alloc::internal
141