Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 1 | // Copyright 2024 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 | |
danakj | 51d26a4 | 2024-04-25 14:23:56 | [diff] [blame] | 5 | #ifdef UNSAFE_BUFFERS_BUILD |
| 6 | // TODO(crbug.com/40284755): Remove this and spanify to fix the errors. |
| 7 | #pragma allow_unsafe_buffers |
| 8 | #endif |
| 9 | |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 10 | // Protected memory is memory holding security-sensitive data intended to be |
| 11 | // left read-only for the majority of its lifetime to avoid being overwritten |
| 12 | // by attackers. ProtectedMemory is a simple wrapper around platform-specific |
| 13 | // APIs to set memory read-write and read-only when required. Protected memory |
| 14 | // should be set read-write for the minimum amount of time required. |
| 15 | // |
| 16 | // Normally mutable variables are held in read-write memory and constant data |
| 17 | // is held in read-only memory to ensure it is not accidentally overwritten. |
| 18 | // In some cases we want to hold mutable variables in read-only memory, except |
| 19 | // when they are being written to, to ensure that they are not tampered with. |
| 20 | // |
| 21 | // ProtectedMemory is a container class intended to hold a single variable in |
| 22 | // read-only memory, except when explicitly set read-write. The variable can be |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 23 | // set read-write by creating a scoped AutoWritableMemory object, the memory |
| 24 | // stays writable until the returned object goes out of scope and is destructed. |
| 25 | // The wrapped variable can be accessed using operator* and operator->. |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 26 | // |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 27 | // Instances of ProtectedMemory must be defined using DEFINE_PROTECTED_DATA |
| 28 | // and as global variables. Global definitions are required to avoid the linker |
| 29 | // placing statics in inlinable functions into a comdat section and setting the |
| 30 | // protected memory section read-write when they are merged. If a declaration of |
| 31 | // a protected variable is required DECLARE_PROTECTED_DATA should be used. |
| 32 | // |
| 33 | // Instances of `base::ProtectedMemory` use constant initialization. To allow |
| 34 | // protection of objects which do not provide constant initialization or would |
| 35 | // require a global constructor, `base::ProtectedMemory` provides lazy |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 36 | // initialization through `ProtectedMemoryInitializer`. Additionally, on |
| 37 | // platforms where it is not possible to have the protected memory section start |
| 38 | // as read-only, the very first call to ProtectedMemoryInitializer will |
| 39 | // initialize the memory section to read-only. Explicit initialization through |
| 40 | // `ProtectedMemoryInitializer` is mandatory, even for objects that provide |
| 41 | // constant initialization. This ensures that in the unlikely event that the |
| 42 | // value is modified before the memory is initialized to read-only, it will be |
| 43 | // forced back to a known, safe, initial state before it ever used. If data is |
| 44 | // accessed without initialization a CHECK triggers. This CHECK is not expected |
| 45 | // to provided security guarantees, but to help catch programming errors. |
| 46 | // |
| 47 | // TODO(crbug.com/356428974): Improve protection offered by Protected Memory. |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 48 | // |
| 49 | // `base::ProtectedMemory` requires T to be trivially destructible. T having |
| 50 | // a non-trivial constructor indicates that is holds data which can not be |
| 51 | // protected by `base::ProtectedMemory`. |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 52 | // |
| 53 | // EXAMPLE: |
| 54 | // |
| 55 | // struct Items { void* item1; }; |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 56 | // static DEFINE_PROTECTED_DATA base::ProtectedMemory<Items> items; |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 57 | // void InitializeItems() { |
| 58 | // // Explicitly set items read-write before writing to it. |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 59 | // auto writer = base::AutoWritableMemory(items); |
| 60 | // writer->item1 = /* ... */; |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 61 | // assert(items->item1 != nullptr); |
| 62 | // // items is set back to read-only on the destruction of writer |
| 63 | // } |
| 64 | // |
| 65 | // using FnPtr = void (*)(void); |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 66 | // DEFINE_PROTECTED_DATA base::ProtectedMemory<FnPtr> fnPtr; |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 67 | // FnPtr ResolveFnPtr(void) { |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 68 | // // `ProtectedMemoryInitializer` is a helper class for creating a static |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 69 | // // initializer for a ProtectedMemory variable. It implicitly sets the |
| 70 | // // variable read-write during initialization. |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 71 | // static base::ProtectedMemoryInitializer initializer(&fnPtr, |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 72 | // reinterpret_cast<FnPtr>(dlsym(/* ... */))); |
| 73 | // return *fnPtr; |
| 74 | // } |
| 75 | |
| 76 | #ifndef BASE_MEMORY_PROTECTED_MEMORY_H_ |
| 77 | #define BASE_MEMORY_PROTECTED_MEMORY_H_ |
| 78 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 79 | #include <stddef.h> |
| 80 | #include <stdint.h> |
| 81 | |
| 82 | #include <memory> |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 83 | #include <type_traits> |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 84 | |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 85 | #include "base/bits.h" |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 86 | #include "base/check.h" |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 87 | #include "base/check_op.h" |
Tom Sepez | 04e98bf | 2024-10-25 18:19:31 | [diff] [blame] | 88 | #include "base/compiler_specific.h" |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 89 | #include "base/gtest_prod_util.h" |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 90 | #include "base/memory/page_size.h" |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 91 | #include "base/memory/protected_memory_buildflags.h" |
| 92 | #include "base/memory/raw_ref.h" |
| 93 | #include "base/no_destructor.h" |
| 94 | #include "base/synchronization/lock.h" |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 95 | #include "base/thread_annotations.h" |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 96 | #include "build/build_config.h" |
| 97 | |
| 98 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 99 | #if BUILDFLAG(IS_WIN) |
| 100 | // Define a read-write prot section. The $a, $mem, and $z 'sub-sections' are |
| 101 | // merged alphabetically so $a and $z are used to define the start and end of |
| 102 | // the protected memory section, and $mem holds protected variables. |
| 103 | // (Note: Sections in Portable Executables are equivalent to segments in other |
| 104 | // executable formats, so this section is mapped into its own pages.) |
| 105 | #pragma section("prot$a", read, write) |
| 106 | #pragma section("prot$mem", read, write) |
| 107 | #pragma section("prot$z", read, write) |
| 108 | |
| 109 | // We want the protected memory section to be read-only, not read-write so we |
| 110 | // instruct the linker to set the section read-only at link time. We do this |
| 111 | // at link time instead of compile time, because defining the prot section |
| 112 | // read-only would cause mis-compiles due to optimizations assuming that the |
| 113 | // section contents are constant. |
| 114 | #pragma comment(linker, "/SECTION:prot,R") |
| 115 | |
| 116 | __declspec(allocate("prot$a")) |
| 117 | __declspec(selectany) char __start_protected_memory; |
| 118 | __declspec(allocate("prot$z")) |
| 119 | __declspec(selectany) char __stop_protected_memory; |
| 120 | |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 121 | #define DECLARE_PROTECTED_DATA constinit |
| 122 | #define DEFINE_PROTECTED_DATA constinit __declspec(allocate("prot$mem")) |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 123 | #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) |
| 124 | // This value is used to align the writers variable. That variable needs to be |
| 125 | // aligned to ensure that the protected memory section starts on a page |
| 126 | // boundary. |
| 127 | #if (PA_BUILDFLAG(IS_ANDROID) && PA_BUILDFLAG(PA_ARCH_CPU_64_BITS)) || \ |
| 128 | (PA_BUILDFLAG(IS_LINUX) && PA_BUILDFLAG(PA_ARCH_CPU_ARM64)) |
| 129 | // arm64 supports 4kb, 16kb, and 64kb pages. Set to the largest of 64kb as that |
| 130 | // will guarantee the section is page aligned regardless of the choice. |
| 131 | inline constexpr int kProtectedMemoryAlignment = 65536; |
| 132 | #elif PA_BUILDFLAG(PA_ARCH_CPU_PPC64) || defined(ARCH_CPU_PPC64) |
| 133 | // Modern ppc64 systems support 4kB (shift = 12) and 64kB (shift = 16) page |
| 134 | // sizes. Set to the largest of 64kb as that will guarantee the section is page |
| 135 | // aligned regardless of the choice. |
| 136 | inline constexpr int kProtectedMemoryAlignment = 65536; |
| 137 | #elif defined(_MIPS_ARCH_LOONGSON) || PA_BUILDFLAG(PA_ARCH_CPU_LOONGARCH64) || \ |
| 138 | defined(ARCH_CPU_LOONGARCH64) |
| 139 | // 16kb page size |
| 140 | inline constexpr int kProtectedMemoryAlignment = 16384; |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 141 | #else |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 142 | // 4kb page size |
| 143 | inline constexpr int kProtectedMemoryAlignment = 4096; |
| 144 | #endif |
| 145 | |
| 146 | __asm__(".section protected_memory, \"a\"\n\t"); |
| 147 | __asm__(".section protected_memory_buffer, \"a\"\n\t"); |
| 148 | |
| 149 | // Explicitly mark these variables hidden so the symbols are local to the |
| 150 | // currently built component. Otherwise they are created with global (external) |
| 151 | // linkage and component builds would break because a single pair of these |
| 152 | // symbols would override the rest. |
| 153 | __attribute__((visibility("hidden"))) extern char __start_protected_memory; |
| 154 | __attribute__((visibility("hidden"))) extern char __stop_protected_memory; |
| 155 | |
| 156 | #define DECLARE_PROTECTED_DATA constinit |
| 157 | #define DEFINE_PROTECTED_DATA \ |
| 158 | constinit __attribute__((section("protected_memory"))) |
| 159 | #elif BUILDFLAG(IS_MAC) |
| 160 | // The segment the section is in is defined with a linker flag in |
| 161 | // build/config/mac/BUILD.gn |
| 162 | #define DECLARE_PROTECTED_DATA constinit |
| 163 | #define DEFINE_PROTECTED_DATA \ |
| 164 | constinit __attribute__((section("PROTECTED_MEMORY, protected_memory"))) |
| 165 | |
| 166 | extern char __start_protected_memory __asm( |
| 167 | "section$start$PROTECTED_MEMORY$protected_memory"); |
| 168 | extern char __stop_protected_memory __asm( |
| 169 | "section$end$PROTECTED_MEMORY$protected_memory"); |
| 170 | #else |
| 171 | #error "Protected Memory is not supported on this platform." |
Jeffrey Gour | 6f946aad | 2024-07-11 16:07:54 | [diff] [blame] | 172 | #endif |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 173 | |
| 174 | #else |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 175 | #define DECLARE_PROTECTED_DATA constinit |
| 176 | #define DEFINE_PROTECTED_DATA DECLARE_PROTECTED_DATA |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 177 | #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 178 | |
| 179 | namespace base { |
| 180 | |
Jeffrey Gour | 6f946aad | 2024-07-11 16:07:54 | [diff] [blame] | 181 | template <typename T> |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 182 | class AutoWritableMemory; |
| 183 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 184 | FORWARD_DECLARE_TEST(ProtectedMemoryDeathTest, VerifyTerminationOnAccess); |
| 185 | |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 186 | namespace internal { |
Jeffrey Gour | 6f946aad | 2024-07-11 16:07:54 | [diff] [blame] | 187 | // Helper class which store the data and implement and initialization for |
| 188 | // constructing the underlying protected data lazily. The instance of T is only |
| 189 | // constructed when emplace is called. |
Will Harris | ca5caf82 | 2024-07-09 17:47:44 | [diff] [blame] | 190 | template <typename T> |
Jeffrey Gour | 6f946aad | 2024-07-11 16:07:54 | [diff] [blame] | 191 | class ProtectedDataHolder { |
Will Harris | ca5caf82 | 2024-07-09 17:47:44 | [diff] [blame] | 192 | public: |
| 193 | consteval ProtectedDataHolder() = default; |
| 194 | |
Tom Sepez | 04e98bf | 2024-10-25 18:19:31 | [diff] [blame] | 195 | T& GetReference() LIFETIME_BOUND { return *GetPointer(); } |
| 196 | const T& GetReference() const LIFETIME_BOUND { return *GetPointer(); } |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 197 | |
| 198 | T* GetPointer() { |
| 199 | CHECK(constructed_); |
| 200 | return reinterpret_cast<T*>(&data_); |
| 201 | } |
| 202 | const T* GetPointer() const { |
| 203 | CHECK(constructed_); |
| 204 | return reinterpret_cast<const T*>(&data_); |
| 205 | } |
| 206 | |
| 207 | template <typename... U> |
| 208 | void emplace(U&&... args) { |
| 209 | if (constructed_) { |
| 210 | std::destroy_at(reinterpret_cast<T*>(&data_)); |
| 211 | constructed_ = false; |
| 212 | } |
| 213 | |
| 214 | std::construct_at(reinterpret_cast<T*>(&data_), std::forward<U>(args)...); |
| 215 | constructed_ = true; |
| 216 | } |
| 217 | |
| 218 | private: |
| 219 | // Initializing with a constant/zero value ensures no global constructor is |
| 220 | // required when instantiating `ProtectedDataHolder` and `ProtectedMemory`. |
Arthur Sonzogni | 299864be1 | 2024-12-04 16:52:02 | [diff] [blame] | 221 | alignas(T) uint8_t data_[sizeof(T)] = {}; |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 222 | bool constructed_ = false; |
| 223 | }; |
| 224 | |
| 225 | } // namespace internal |
| 226 | |
| 227 | // The wrapper class for data of type `T` which is to be stored in protected |
| 228 | // memory. `ProtectedMemory` provides improved type safety in conjunction with |
| 229 | // the other classes, although the basic mechanisms like unlocking and |
| 230 | // re-locking of the memory would also work without it. |
| 231 | // |
| 232 | // To allow using `T`s which do not have constant initialization, the template |
| 233 | // parameter `ConstructLazily` enables a lazy initialization. In this case, an |
| 234 | // initialization before first access is mandatory (see |
| 235 | // `ProtectedMemoryInitializer`). |
Jeffrey Gour | 6f946aad | 2024-07-11 16:07:54 | [diff] [blame] | 236 | template <typename T> |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 237 | class ProtectedMemory { |
| 238 | public: |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 239 | // T must be trivially destructible. Otherwise it indicates that T holds data |
| 240 | // which would not be covered by this write protection, i.e. data allocated on |
André Kempe | 2e18b29 | 2024-04-08 13:56:02 | [diff] [blame] | 241 | // heap. This check complements the verification in the constructor since |
| 242 | // `ProtectedMemory` with `ConstructLazily` set to `true` is always trivially |
| 243 | // destructible. |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 244 | static_assert(std::is_trivially_destructible_v<T>); |
| 245 | |
| 246 | // For lazily constructed data we enable this constructor only if there are |
| 247 | // no arguments. For lazily constructed data no arguments are accepted as T is |
| 248 | // not initialized when `ProtectedMemory<T>` is created but through |
| 249 | // `ProtectedMemoryInitializer` instead. |
Jeffrey Gour | 6f946aad | 2024-07-11 16:07:54 | [diff] [blame] | 250 | consteval explicit ProtectedMemory() : data_() { |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 251 | static_assert(std::is_trivially_destructible_v<ProtectedMemory>); |
| 252 | } |
| 253 | |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 254 | ProtectedMemory(const ProtectedMemory&) = delete; |
| 255 | ProtectedMemory& operator=(const ProtectedMemory&) = delete; |
| 256 | |
| 257 | // Expose direct access to the encapsulated variable |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 258 | const T& operator*() const { return data_.GetReference(); } |
| 259 | const T* operator->() const { return data_.GetPointer(); } |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 260 | |
| 261 | private: |
Jeffrey Gour | 6f946aad | 2024-07-11 16:07:54 | [diff] [blame] | 262 | friend class AutoWritableMemory<T>; |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 263 | FRIEND_TEST_ALL_PREFIXES(ProtectedMemoryDeathTest, VerifyTerminationOnAccess); |
| 264 | |
Jeffrey Gour | 6f946aad | 2024-07-11 16:07:54 | [diff] [blame] | 265 | internal::ProtectedDataHolder<T> data_; |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 266 | }; |
| 267 | |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 268 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 269 | namespace internal { |
| 270 | // Checks that the byte at `ptr` is read-only. |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 271 | BASE_EXPORT void CheckMemoryReadOnly(const void* ptr); |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 272 | |
| 273 | // Abstract out platform-specific methods to get the beginning and end of the |
| 274 | // PROTECTED_MEMORY_SECTION. ProtectedMemoryEnd returns a pointer to the byte |
| 275 | // past the end of the PROTECTED_MEMORY_SECTION. |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 276 | inline constexpr void* kProtectedMemoryStart = &__start_protected_memory; |
| 277 | inline constexpr void* kProtectedMemoryEnd = &__stop_protected_memory; |
| 278 | } // namespace internal |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 279 | #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 280 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 281 | // Provide some common functionality for `AutoWritableMemory<T>`. |
| 282 | class BASE_EXPORT AutoWritableMemoryBase { |
| 283 | protected: |
| 284 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 285 | // Checks that `object` is located within the interval |
| 286 | // (internal::kProtectedMemoryStart, internal::kProtectedMemoryEnd). |
| 287 | template <typename T> |
| 288 | static bool IsObjectInProtectedSection(const T& object) { |
| 289 | const T* const ptr = std::addressof(object); |
| 290 | const T* const ptr_end = ptr + 1; |
Jeffrey Gour | 6f946aad | 2024-07-11 16:07:54 | [diff] [blame] | 291 | return (ptr >= internal::kProtectedMemoryStart) && |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 292 | (ptr_end <= internal::kProtectedMemoryEnd); |
| 293 | } |
| 294 | |
| 295 | template <typename T> |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 296 | static void CheckObjectReadOnly(const T& object) { |
| 297 | internal::CheckMemoryReadOnly(std::addressof(object)); |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 298 | } |
| 299 | |
| 300 | template <typename T> |
| 301 | static bool SetObjectReadWrite(T& object) { |
| 302 | T* const ptr = std::addressof(object); |
| 303 | T* const ptr_end = ptr + 1; |
| 304 | return SetMemoryReadWrite(ptr, ptr_end); |
| 305 | } |
| 306 | |
| 307 | static bool SetProtectedSectionReadOnly() { |
| 308 | return SetMemoryReadOnly(internal::kProtectedMemoryStart, |
| 309 | internal::kProtectedMemoryEnd); |
| 310 | } |
| 311 | |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 312 | static bool IsSectionStartPageAligned() { |
| 313 | const uintptr_t protected_memory_start = |
| 314 | reinterpret_cast<uintptr_t>(internal::kProtectedMemoryStart); |
| 315 | const uintptr_t page_start = |
| 316 | bits::AlignDown(protected_memory_start, GetPageSize()); |
| 317 | return page_start == protected_memory_start; |
| 318 | } |
| 319 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 320 | // When linking, each DSO will have its own protected section. We can't keep |
| 321 | // track of each section, yet we have to ensure to always unlock and re-lock |
| 322 | // the correct section. |
| 323 | // |
| 324 | // We solve this by defining a separate global writers variable (explained |
| 325 | // below) in every dynamic shared object (DSO) that includes this header. To |
| 326 | // do that we use this structure to define global writer data without |
| 327 | // duplicate symbol errors. |
| 328 | // |
| 329 | // Storing the data in a substructure is required to store `writers` within |
| 330 | // the protected subsection. If `writers` and `writers_lock()` are located |
| 331 | // directly in `AutoWritableMemoryBase`, for unknown reasons `writers` is not |
| 332 | // placed into the protected section. |
| 333 | struct WriterData { |
| 334 | // `writers` is a global holding the number of ProtectedMemory instances set |
| 335 | // writable, used to avoid races setting protected memory readable/writable. |
| 336 | // When this reaches zero the protected memory region is set read only. |
| 337 | // Access is controlled by writers_lock. |
| 338 | // |
| 339 | // Declare writers in the protected memory section to avoid the scenario |
| 340 | // where an attacker could overwrite it with a large value and invoke code |
| 341 | // that constructs and destructs an AutoWritableMemory. After such a call |
| 342 | // protected memory would still be set writable because writers > 0. |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 343 | #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) |
| 344 | // On Linux, the protected memory section is not automatically page aligned. |
| 345 | // This means that attempts to reset the protected memory region to readonly |
| 346 | // will set some of the preceding section that is on the same page readonly |
| 347 | // as well. By forcing the writers to be aligned on a multiple of the page |
| 348 | // size, we can ensure the protected memory section starts on a page |
| 349 | // boundary, preventing this issue. |
| 350 | constinit __attribute__((section("protected_memory"), |
| 351 | aligned(kProtectedMemoryAlignment))) |
| 352 | #else |
André Kempe | 0f7da88a | 2024-03-22 19:12:47 | [diff] [blame] | 353 | DEFINE_PROTECTED_DATA |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 354 | #endif |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 355 | static inline size_t writers GUARDED_BY(writers_lock()) = 0; |
| 356 | |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 357 | #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) |
| 358 | // On Linux, there is no guarantee the section following the protected |
| 359 | // memory section is page aligned. This can result in attempts to change |
| 360 | // the access permissions of the end of the protected memory section |
| 361 | // overflowing to the next section. To ensure this doesn't happen, a buffer |
| 362 | // section called protected_memory_buffer is created. Since the very first |
| 363 | // variable declared after writers is put in this section, it will be |
| 364 | // created as the next section after the protected memory section (since |
| 365 | // sections are created in the order they are declared in the source file). |
| 366 | // By explicitly setting the alignment of the variable to a multiple of the |
| 367 | // page size, we can ensure this buffer section starts on a page boundary. |
| 368 | // This guarantees that altering the access permissions of the end of the |
| 369 | // protected memory section will not affect the next section. The variable |
| 370 | // protected_memory_section_buffer serves no purpose other than to ensure |
| 371 | // protected_memory_buffer section is created. |
| 372 | constinit |
| 373 | __attribute__((section("protected_memory_buffer"), |
| 374 | aligned(kProtectedMemoryAlignment))) static inline bool |
| 375 | protected_memory_section_buffer = false; |
| 376 | #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) |
| 377 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 378 | // Synchronizes access to the writers variable and the simultaneous actions |
| 379 | // that need to happen alongside writers changes, e.g. setting the protected |
| 380 | // memory region readable when writers is decremented to 0. |
| 381 | static Lock& writers_lock() { |
| 382 | static NoDestructor<Lock> writers_lock; |
| 383 | return *writers_lock; |
| 384 | } |
| 385 | }; |
| 386 | |
| 387 | private: |
| 388 | // Abstract out platform-specific memory APIs. |end| points to the byte |
| 389 | // past the end of the region of memory having its memory protections |
| 390 | // changed. |
| 391 | static bool SetMemoryReadWrite(void* start, void* end); |
| 392 | static bool SetMemoryReadOnly(void* start, void* end); |
| 393 | #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 394 | }; |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 395 | |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 396 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 397 | // This class acts as a static initializer that initializes the protected memory |
| 398 | // region to read only. It will be engaged the first time a protected memory |
| 399 | // object is statically initialized. |
| 400 | class BASE_EXPORT AutoWritableMemoryInitializer |
| 401 | : public AutoWritableMemoryBase { |
| 402 | public: |
| 403 | #if BUILDFLAG(IS_WIN) |
| 404 | AutoWritableMemoryInitializer() { CHECK(IsSectionStartPageAligned()); } |
| 405 | #else |
| 406 | AutoWritableMemoryInitializer() LOCKS_EXCLUDED(WriterData::writers_lock()) { |
| 407 | CHECK(IsSectionStartPageAligned()); |
| 408 | // This doesn't need to be run on Windows, because the linker can pre-set |
| 409 | // the memory to read-only. |
| 410 | AutoLock auto_lock(WriterData::writers_lock()); |
| 411 | // Reset the writers variable to 0 to ensure that the attacker didn't set |
| 412 | // the variable to something large before the section was read-only. |
| 413 | WriterData::writers = 0; |
| 414 | CHECK(SetProtectedSectionReadOnly()); |
| 415 | #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) |
| 416 | // Set the protected_memory_section_buffer to true to ensure the buffer |
| 417 | // section is created. If a variable is declared but not used the memory |
| 418 | // section won't be created. |
| 419 | WriterData::protected_memory_section_buffer = true; |
| 420 | #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) |
| 421 | } |
| 422 | #endif // BUILDFLAG(IS_WIN) |
| 423 | }; |
| 424 | #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 425 | |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 426 | // A class that sets a given ProtectedMemory variable writable while the |
| 427 | // AutoWritableMemory is in scope. This class implements the logic for setting |
| 428 | // the protected memory region read-only/read-write in a thread-safe manner. |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 429 | // |
| 430 | // |AutoWritableMemory| affects the write-permissions of _all_ protected data |
| 431 | // for a DSO, not just of the instance that it's being passed! All protected |
| 432 | // data is stored within the same binary section. At the same time, the OS-level |
| 433 | // support enforcing write protection can only be changed at page level. To |
| 434 | // allow a more fine grained control a dedicated page per instance of protected |
| 435 | // data would be required. |
Jeffrey Gour | 6f946aad | 2024-07-11 16:07:54 | [diff] [blame] | 436 | template <typename T> |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 437 | class AutoWritableMemory : public AutoWritableMemoryBase { |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 438 | public: |
Jeffrey Gour | 6f946aad | 2024-07-11 16:07:54 | [diff] [blame] | 439 | explicit AutoWritableMemory(ProtectedMemory<T>& protected_memory) |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 440 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 441 | LOCKS_EXCLUDED(WriterData::writers_lock()) |
| 442 | #endif |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 443 | : protected_memory_(protected_memory) { |
| 444 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 445 | |
| 446 | // Check that the data is located in the protected section to |
| 447 | // ensure consistency of data. |
| 448 | CHECK(IsObjectInProtectedSection(protected_memory_->data_)); |
| 449 | CHECK(IsObjectInProtectedSection(WriterData::writers)); |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 450 | |
| 451 | { |
Jeffrey Gour | e63ec7f | 2024-08-28 01:12:53 | [diff] [blame] | 452 | AutoLock auto_lock(WriterData::writers_lock()); |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame] | 453 | |
| 454 | if (WriterData::writers == 0) { |
Jeffrey Gour |
|