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 #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 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 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->. 26 // 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 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. 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`. 52 // 53 // EXAMPLE: 54 // 55 // struct Items { void* item1; }; 56 // static DEFINE_PROTECTED_DATA base::ProtectedMemory<Items> items; 57 // void InitializeItems() { 58 // // Explicitly set items read-write before writing to it. 59 // auto writer = base::AutoWritableMemory(items); 60 // writer->item1 = /* ... */; 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); 66 // DEFINE_PROTECTED_DATA base::ProtectedMemory<FnPtr> fnPtr; 67 // FnPtr ResolveFnPtr(void) { 68 // // `ProtectedMemoryInitializer` is a helper class for creating a static 69 // // initializer for a ProtectedMemory variable. It implicitly sets the 70 // // variable read-write during initialization. 71 // static base::ProtectedMemoryInitializer initializer(&fnPtr, 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 79 #include <stddef.h> 80 #include <stdint.h> 81 82 #include <memory> 83 #include <type_traits> 84 85 #include "base/bits.h" 86 #include "base/check.h" 87 #include "base/check_op.h" 88 #include "base/compiler_specific.h" 89 #include "base/gtest_prod_util.h" 90 #include "base/memory/page_size.h" 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" 95 #include "base/thread_annotations.h" 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 121 #define DECLARE_PROTECTED_DATA constinit 122 #define DEFINE_PROTECTED_DATA constinit __declspec(allocate("prot$mem")) 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; 141 #else 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." 172 #endif 173 174 #else 175 #define DECLARE_PROTECTED_DATA constinit 176 #define DEFINE_PROTECTED_DATA DECLARE_PROTECTED_DATA 177 #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) 178 179 namespace base { 180 181 template <typename T> 182 class AutoWritableMemory; 183 184 FORWARD_DECLARE_TEST(ProtectedMemoryDeathTest, VerifyTerminationOnAccess); 185 186 namespace internal { 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. 190 template <typename T> 191 class ProtectedDataHolder { 192 public: 193 consteval ProtectedDataHolder() = default; 194 GetReference()195 T& GetReference() LIFETIME_BOUND { return *GetPointer(); } GetReference()196 const T& GetReference() const LIFETIME_BOUND { return *GetPointer(); } 197 GetPointer()198 T* GetPointer() { 199 CHECK(constructed_); 200 return reinterpret_cast<T*>(&data_); 201 } GetPointer()202 const T* GetPointer() const { 203 CHECK(constructed_); 204 return reinterpret_cast<const T*>(&data_); 205 } 206 207 template <typename... U> emplace(U &&...args)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`. 221 alignas(T) uint8_t data_[sizeof(T)] = {0}; 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`). 236 template <typename T> 237 class ProtectedMemory { 238 public: 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 241 // heap. This check complements the verification in the constructor since 242 // `ProtectedMemory` with `ConstructLazily` set to `true` is always trivially 243 // destructible. 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. ProtectedMemory()250 consteval explicit ProtectedMemory() : data_() { 251 static_assert(std::is_trivially_destructible_v<ProtectedMemory>); 252 } 253 254 ProtectedMemory(const ProtectedMemory&) = delete; 255 ProtectedMemory& operator=(const ProtectedMemory&) = delete; 256 257 // Expose direct access to the encapsulated variable 258 const T& operator*() const { return data_.GetReference(); } 259 const T* operator->() const { return data_.GetPointer(); } 260 261 private: 262 friend class AutoWritableMemory<T>; 263 FRIEND_TEST_ALL_PREFIXES(ProtectedMemoryDeathTest, VerifyTerminationOnAccess); 264 265 internal::ProtectedDataHolder<T> data_; 266 }; 267 268 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) 269 namespace internal { 270 // Checks that the byte at `ptr` is read-only. 271 BASE_EXPORT void CheckMemoryReadOnly(const void* ptr); 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. 276 inline constexpr void* kProtectedMemoryStart = &__start_protected_memory; 277 inline constexpr void* kProtectedMemoryEnd = &__stop_protected_memory; 278 } // namespace internal 279 #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) 280 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> IsObjectInProtectedSection(const T & object)288 static bool IsObjectInProtectedSection(const T& object) { 289 const T* const ptr = std::addressof(object); 290 const T* const ptr_end = ptr + 1; 291 return (ptr >= internal::kProtectedMemoryStart) && 292 (ptr_end <= internal::kProtectedMemoryEnd); 293 } 294 295 template <typename T> CheckObjectReadOnly(const T & object)296 static void CheckObjectReadOnly(const T& object) { 297 internal::CheckMemoryReadOnly(std::addressof(object)); 298 } 299 300 template <typename T> SetObjectReadWrite(T & object)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 SetProtectedSectionReadOnly()307 static bool SetProtectedSectionReadOnly() { 308 return SetMemoryReadOnly(internal::kProtectedMemoryStart, 309 internal::kProtectedMemoryEnd); 310 } 311 IsSectionStartPageAligned()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 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. 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 353 DEFINE_PROTECTED_DATA 354 #endif 355 static inline size_t writers GUARDED_BY(writers_lock()) = 0; 356 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 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. writers_lockWriterData381 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) 394 }; 395 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) AutoWritableMemoryInitializer()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 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. 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. 436 template <typename T> 437 class AutoWritableMemory : public AutoWritableMemoryBase { 438 public: AutoWritableMemory(ProtectedMemory<T> & protected_memory)439 explicit AutoWritableMemory(ProtectedMemory<T>& protected_memory) 440 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) 441 LOCKS_EXCLUDED(WriterData::writers_lock()) 442 #endif 443 : protected_memory_(protected_memory) { 444 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) 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)); 450 451 { 452 AutoLock auto_lock(WriterData::writers_lock()); 453 454 if (WriterData::writers == 0) { 455 CheckObjectReadOnly(protected_memory_->data_); 456 CheckObjectReadOnly(WriterData::writers); 457 CHECK(SetObjectReadWrite(WriterData::writers)); 458 } 459 460 ++WriterData::writers; 461 } 462 463 CHECK(SetObjectReadWrite(protected_memory_->data_)); 464 #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) 465 } 466 467 ~AutoWritableMemory() 468 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) LOCKS_EXCLUDED(WriterData::writers_lock ())469 LOCKS_EXCLUDED(WriterData::writers_lock()) 470 #endif 471 { 472 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) 473 AutoLock auto_lock(WriterData::writers_lock()); 474 CHECK_GT(WriterData::writers, 0u); 475 --WriterData::writers; 476 477 if (WriterData::writers == 0) { 478 // Lock the whole section of protected memory and set _all_ instances of 479 // ProtectedMemory to non-writeable. 480 CHECK(SetProtectedSectionReadOnly()); 481 CheckObjectReadOnly( 482 *static_cast<const char*>(internal::kProtectedMemoryStart)); 483 CheckObjectReadOnly(WriterData::writers); 484 } 485 #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) 486 } 487 488 AutoWritableMemory(AutoWritableMemory& original) = delete; 489 AutoWritableMemory& operator=(AutoWritableMemory& original) = delete; 490 AutoWritableMemory(AutoWritableMemory&& original) = delete; 491 AutoWritableMemory& operator=(AutoWritableMemory&& original) = delete; 492 GetProtectedData()493 T& GetProtectedData() { return protected_memory_->data_.GetReference(); } GetProtectedDataPtr()494 T* GetProtectedDataPtr() { return protected_memory_->data_.GetPointer(); } 495 496 template <typename... U> emplace(U &&...args)497 void emplace(U&&... args) { 498 protected_memory_->data_.emplace(std::forward<U>(args)...); 499 } 500 501 private: 502 const raw_ref<ProtectedMemory<T>> protected_memory_; 503 }; 504 505 // Helper class for creating simple ProtectedMemory static initializers. 506 class ProtectedMemoryInitializer { 507 public: 508 template <typename T, typename... U> ProtectedMemoryInitializer(ProtectedMemory<T> & protected_memory,U &&...args)509 explicit ProtectedMemoryInitializer(ProtectedMemory<T>& protected_memory, 510 U&&... args) { 511 InitializeAutoWritableMemory(); 512 AutoWritableMemory writer(protected_memory); 513 writer.emplace(std::forward<U>(args)...); 514 } 515 516 ProtectedMemoryInitializer() = delete; 517 ProtectedMemoryInitializer(const ProtectedMemoryInitializer&) = delete; 518 ProtectedMemoryInitializer& operator=(const ProtectedMemoryInitializer&) = 519 delete; 520 521 private: InitializeAutoWritableMemory()522 void InitializeAutoWritableMemory() { 523 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) 524 static AutoWritableMemoryInitializer memory_initializer; 525 #else 526 // No-op if protected memory is not enabled. 527 #endif 528 } 529 }; 530 531 } // namespace base 532 533 #endif // BASE_MEMORY_PROTECTED_MEMORY_H_ 534