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 | |
| 5 | // Protected memory is memory holding security-sensitive data intended to be |
| 6 | // left read-only for the majority of its lifetime to avoid being overwritten |
| 7 | // by attackers. ProtectedMemory is a simple wrapper around platform-specific |
| 8 | // APIs to set memory read-write and read-only when required. Protected memory |
| 9 | // should be set read-write for the minimum amount of time required. |
| 10 | // |
| 11 | // Normally mutable variables are held in read-write memory and constant data |
| 12 | // is held in read-only memory to ensure it is not accidentally overwritten. |
| 13 | // In some cases we want to hold mutable variables in read-only memory, except |
| 14 | // when they are being written to, to ensure that they are not tampered with. |
| 15 | // |
| 16 | // ProtectedMemory is a container class intended to hold a single variable in |
| 17 | // read-only memory, except when explicitly set read-write. The variable can be |
| 18 | // set read-write by creating a scoped AutoWritableMemory object by calling |
| 19 | // AutoWritableMemory::Create(), the memory stays writable until the returned |
| 20 | // object goes out of scope and is destructed. The wrapped variable can be |
| 21 | // accessed using operator* and operator->. |
| 22 | // |
| 23 | // Instances of ProtectedMemory must be declared in the PROTECTED_MEMORY_SECTION |
| 24 | // and as global variables. Because protected memory variables are globals, the |
| 25 | // the same rules apply disallowing non-trivial constructors and destructors. |
| 26 | // Global definitions are required to avoid the linker placing statics in |
| 27 | // inlinable functions into a comdat section and setting the protected memory |
| 28 | // section read-write when they are merged. |
| 29 | // |
| 30 | // EXAMPLE: |
| 31 | // |
| 32 | // struct Items { void* item1; }; |
| 33 | // static PROTECTED_MEMORY_SECTION base::ProtectedMemory<Items> items; |
| 34 | // void InitializeItems() { |
| 35 | // // Explicitly set items read-write before writing to it. |
| 36 | // auto writer = base::AutoWritableMemory::Create(items); |
| 37 | // items->item1 = /* ... */; |
| 38 | // assert(items->item1 != nullptr); |
| 39 | // // items is set back to read-only on the destruction of writer |
| 40 | // } |
| 41 | // |
| 42 | // using FnPtr = void (*)(void); |
| 43 | // PROTECTED_MEMORY_SECTION base::ProtectedMemory<FnPtr> fnPtr; |
| 44 | // FnPtr ResolveFnPtr(void) { |
| 45 | // // The Initializer nested class is a helper class for creating a static |
| 46 | // // initializer for a ProtectedMemory variable. It implicitly sets the |
| 47 | // // variable read-write during initialization. |
| 48 | // static base::ProtectedMemory<FnPtr>::Initializer I(&fnPtr, |
| 49 | // reinterpret_cast<FnPtr>(dlsym(/* ... */))); |
| 50 | // return *fnPtr; |
| 51 | // } |
| 52 | |
| 53 | #ifndef BASE_MEMORY_PROTECTED_MEMORY_H_ |
| 54 | #define BASE_MEMORY_PROTECTED_MEMORY_H_ |
| 55 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 56 | #include <stddef.h> |
| 57 | #include <stdint.h> |
| 58 | |
| 59 | #include <memory> |
| 60 | |
| 61 | #include "base/check.h" |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 62 | #include "base/check_op.h" |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 63 | #include "base/gtest_prod_util.h" |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 64 | #include "base/memory/protected_memory_buildflags.h" |
| 65 | #include "base/memory/raw_ref.h" |
| 66 | #include "base/no_destructor.h" |
| 67 | #include "base/synchronization/lock.h" |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 68 | #include "base/thread_annotations.h" |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 69 | #include "build/build_config.h" |
| 70 | |
| 71 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 72 | #if BUILDFLAG(IS_WIN) |
| 73 | // Define a read-write prot section. The $a, $mem, and $z 'sub-sections' are |
| 74 | // merged alphabetically so $a and $z are used to define the start and end of |
| 75 | // the protected memory section, and $mem holds protected variables. |
| 76 | // (Note: Sections in Portable Executables are equivalent to segments in other |
| 77 | // executable formats, so this section is mapped into its own pages.) |
| 78 | #pragma section("prot$a", read, write) |
| 79 | #pragma section("prot$mem", read, write) |
| 80 | #pragma section("prot$z", read, write) |
| 81 | |
| 82 | // We want the protected memory section to be read-only, not read-write so we |
| 83 | // instruct the linker to set the section read-only at link time. We do this |
| 84 | // at link time instead of compile time, because defining the prot section |
| 85 | // read-only would cause mis-compiles due to optimizations assuming that the |
| 86 | // section contents are constant. |
| 87 | #pragma comment(linker, "/SECTION:prot,R") |
| 88 | |
| 89 | __declspec(allocate("prot$a")) |
| 90 | __declspec(selectany) char __start_protected_memory; |
| 91 | __declspec(allocate("prot$z")) |
| 92 | __declspec(selectany) char __stop_protected_memory; |
| 93 | |
| 94 | #define PROTECTED_MEMORY_SECTION __declspec(allocate("prot$mem")) |
| 95 | #else |
| 96 | #error "Protected Memory is currently only supported on Windows." |
| 97 | #endif // BUILDFLAG(IS_WIN) |
| 98 | |
| 99 | #else |
| 100 | #define PROTECTED_MEMORY_SECTION |
| 101 | #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 102 | |
| 103 | namespace base { |
| 104 | |
| 105 | template <typename T> |
| 106 | class AutoWritableMemory; |
| 107 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 108 | FORWARD_DECLARE_TEST(ProtectedMemoryDeathTest, VerifyTerminationOnAccess); |
| 109 | |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 110 | template <typename T> |
| 111 | class ProtectedMemory { |
| 112 | public: |
| 113 | ProtectedMemory() = default; |
| 114 | ProtectedMemory(const ProtectedMemory&) = delete; |
| 115 | ProtectedMemory& operator=(const ProtectedMemory&) = delete; |
| 116 | |
| 117 | // Expose direct access to the encapsulated variable |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 118 | const T& operator*() const { return data_; } |
| 119 | const T* operator->() const { return &data_; } |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 120 | |
| 121 | // Helper class for creating simple ProtectedMemory static initializers. |
| 122 | class Initializer { |
| 123 | public: |
| 124 | // Defined out-of-line below to break circular definition dependency between |
| 125 | // ProtectedMemory and AutoWritableMemory. |
| 126 | Initializer(ProtectedMemory<T>* PM, T&& Init); |
| 127 | |
| 128 | Initializer() = delete; |
| 129 | Initializer(const Initializer&) = delete; |
| 130 | Initializer& operator=(const Initializer&) = delete; |
| 131 | }; |
| 132 | |
| 133 | private: |
| 134 | friend class AutoWritableMemory<T>; |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 135 | FRIEND_TEST_ALL_PREFIXES(ProtectedMemoryDeathTest, VerifyTerminationOnAccess); |
| 136 | |
| 137 | T data_; |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 138 | }; |
| 139 | |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 140 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 141 | namespace internal { |
| 142 | // Checks that the byte at `ptr` is read-only. |
| 143 | BASE_EXPORT bool IsMemoryReadOnly(const void* ptr); |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 144 | |
| 145 | // Abstract out platform-specific methods to get the beginning and end of the |
| 146 | // PROTECTED_MEMORY_SECTION. ProtectedMemoryEnd returns a pointer to the byte |
| 147 | // past the end of the PROTECTED_MEMORY_SECTION. |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 148 | inline constexpr void* kProtectedMemoryStart = &__start_protected_memory; |
| 149 | inline constexpr void* kProtectedMemoryEnd = &__stop_protected_memory; |
| 150 | } // namespace internal |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 151 | #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 152 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 153 | // Provide some common functionality for `AutoWritableMemory<T>`. |
| 154 | class BASE_EXPORT AutoWritableMemoryBase { |
| 155 | protected: |
| 156 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 157 | // Checks that `object` is located within the interval |
| 158 | // (internal::kProtectedMemoryStart, internal::kProtectedMemoryEnd). |
| 159 | template <typename T> |
| 160 | static bool IsObjectInProtectedSection(const T& object) { |
| 161 | const T* const ptr = std::addressof(object); |
| 162 | const T* const ptr_end = ptr + 1; |
| 163 | return (ptr > internal::kProtectedMemoryStart) && |
| 164 | (ptr_end <= internal::kProtectedMemoryEnd); |
| 165 | } |
| 166 | |
| 167 | template <typename T> |
| 168 | static bool IsObjectReadOnly(const T& object) { |
| 169 | return internal::IsMemoryReadOnly(std::addressof(object)); |
| 170 | } |
| 171 | |
| 172 | template <typename T> |
| 173 | static bool SetObjectReadWrite(T& object) { |
| 174 | T* const ptr = std::addressof(object); |
| 175 | T* const ptr_end = ptr + 1; |
| 176 | return SetMemoryReadWrite(ptr, ptr_end); |
| 177 | } |
| 178 | |
| 179 | static bool SetProtectedSectionReadOnly() { |
| 180 | return SetMemoryReadOnly(internal::kProtectedMemoryStart, |
| 181 | internal::kProtectedMemoryEnd); |
| 182 | } |
| 183 | |
| 184 | // When linking, each DSO will have its own protected section. We can't keep |
| 185 | // track of each section, yet we have to ensure to always unlock and re-lock |
| 186 | // the correct section. |
| 187 | // |
| 188 | // We solve this by defining a separate global writers variable (explained |
| 189 | // below) in every dynamic shared object (DSO) that includes this header. To |
| 190 | // do that we use this structure to define global writer data without |
| 191 | // duplicate symbol errors. |
| 192 | // |
| 193 | // Storing the data in a substructure is required to store `writers` within |
| 194 | // the protected subsection. If `writers` and `writers_lock()` are located |
| 195 | // directly in `AutoWritableMemoryBase`, for unknown reasons `writers` is not |
| 196 | // placed into the protected section. |
| 197 | struct WriterData { |
| 198 | // `writers` is a global holding the number of ProtectedMemory instances set |
| 199 | // writable, used to avoid races setting protected memory readable/writable. |
| 200 | // When this reaches zero the protected memory region is set read only. |
| 201 | // Access is controlled by writers_lock. |
| 202 | // |
| 203 | // Declare writers in the protected memory section to avoid the scenario |
| 204 | // where an attacker could overwrite it with a large value and invoke code |
| 205 | // that constructs and destructs an AutoWritableMemory. After such a call |
| 206 | // protected memory would still be set writable because writers > 0. |
| 207 | PROTECTED_MEMORY_SECTION |
| 208 | static inline size_t writers GUARDED_BY(writers_lock()) = 0; |
| 209 | |
| 210 | // Synchronizes access to the writers variable and the simultaneous actions |
| 211 | // that need to happen alongside writers changes, e.g. setting the protected |
| 212 | // memory region readable when writers is decremented to 0. |
| 213 | static Lock& writers_lock() { |
| 214 | static NoDestructor<Lock> writers_lock; |
| 215 | return *writers_lock; |
| 216 | } |
| 217 | }; |
| 218 | |
| 219 | private: |
| 220 | // Abstract out platform-specific memory APIs. |end| points to the byte |
| 221 | // past the end of the region of memory having its memory protections |
| 222 | // changed. |
| 223 | static bool SetMemoryReadWrite(void* start, void* end); |
| 224 | static bool SetMemoryReadOnly(void* start, void* end); |
| 225 | #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 226 | }; |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 227 | |
| 228 | // A class that sets a given ProtectedMemory variable writable while the |
| 229 | // AutoWritableMemory is in scope. This class implements the logic for setting |
| 230 | // the protected memory region read-only/read-write in a thread-safe manner. |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 231 | // |
| 232 | // |AutoWritableMemory| affects the write-permissions of _all_ protected data |
| 233 | // for a DSO, not just of the instance that it's being passed! All protected |
| 234 | // data is stored within the same binary section. At the same time, the OS-level |
| 235 | // support enforcing write protection can only be changed at page level. To |
| 236 | // allow a more fine grained control a dedicated page per instance of protected |
| 237 | // data would be required. |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 238 | template <typename T> |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 239 | class AutoWritableMemory : public AutoWritableMemoryBase { |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 240 | public: |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 241 | explicit AutoWritableMemory(ProtectedMemory<T>& protected_memory) |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 242 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 243 | LOCKS_EXCLUDED(WriterData::writers_lock()) |
| 244 | #endif |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 245 | : protected_memory_(protected_memory) { |
| 246 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 247 | |
| 248 | // Check that the data is located in the protected section to |
| 249 | // ensure consistency of data. |
| 250 | CHECK(IsObjectInProtectedSection(protected_memory_->data_)); |
| 251 | CHECK(IsObjectInProtectedSection(WriterData::writers)); |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 252 | |
| 253 | { |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 254 | base::AutoLock auto_lock(WriterData::writers_lock()); |
| 255 | |
| 256 | if (WriterData::writers == 0) { |
| 257 | CHECK(IsObjectReadOnly(protected_memory_->data_)); |
| 258 | CHECK(IsObjectReadOnly(WriterData::writers)); |
| 259 | CHECK(SetObjectReadWrite(WriterData::writers)); |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 260 | } |
| 261 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 262 | ++WriterData::writers; |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 263 | } |
| 264 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 265 | CHECK(SetObjectReadWrite(protected_memory_->data_)); |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 266 | #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 267 | } |
| 268 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 269 | ~AutoWritableMemory() |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 270 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 271 | LOCKS_EXCLUDED(WriterData::writers_lock()) |
| 272 | #endif |
| 273 | { |
| 274 | #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 275 | base::AutoLock auto_lock(WriterData::writers_lock()); |
| 276 | CHECK_GT(WriterData::writers, 0u); |
| 277 | --WriterData::writers; |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 278 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 279 | if (WriterData::writers == 0) { |
| 280 | // Lock the whole section of protected memory and set _all_ instances of |
| 281 | // base::ProtectedMemory to non-writeable. |
| 282 | CHECK(SetProtectedSectionReadOnly()); |
| 283 | CHECK(IsObjectReadOnly( |
| 284 | *static_cast<const char*>(internal::kProtectedMemoryStart))); |
| 285 | CHECK(IsObjectReadOnly(WriterData::writers)); |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 286 | } |
| 287 | #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| 288 | } |
| 289 | |
| 290 | AutoWritableMemory(AutoWritableMemory& original) = delete; |
| 291 | AutoWritableMemory& operator=(AutoWritableMemory& original) = delete; |
| 292 | AutoWritableMemory(AutoWritableMemory&& original) = delete; |
| 293 | AutoWritableMemory& operator=(AutoWritableMemory&& original) = delete; |
| 294 | |
André Kempe | 5adc4d5a | 2024-03-15 12:23:33 | [diff] [blame^] | 295 | T& GetProtectedData() { return protected_memory_->data_; } |
| 296 | T* GetProtectedDataPtr() { return &(protected_memory_->data_); } |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 297 | |
| 298 | private: |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 299 | |
| 300 | const raw_ref<ProtectedMemory<T>> protected_memory_; |
| 301 | }; |
| 302 | |
Jeff Gour | a3e4ebd | 2024-01-23 21:04:49 | [diff] [blame] | 303 | template <typename T> |
| 304 | ProtectedMemory<T>::Initializer::Initializer( |
| 305 | ProtectedMemory<T>* protected_memory, |
| 306 | T&& initial_value) { |
| 307 | AutoWritableMemory<T> writer(*protected_memory); |
| 308 | writer.GetProtectedData() = std::forward<T>(initial_value); |
| 309 | } |
| 310 | |
| 311 | } // namespace base |
| 312 | |
| 313 | #endif // BASE_MEMORY_PROTECTED_MEMORY_H_ |