• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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