xref: /aosp_15_r20/external/cronet/base/memory/protected_memory.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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, the memory
19 // stays writable until the returned object goes out of scope and is destructed.
20 // The wrapped variable can be accessed using operator* and operator->.
21 //
22 // Instances of ProtectedMemory must be defined using DEFINE_PROTECTED_DATA
23 // and as global variables. Global definitions are required to avoid the linker
24 // placing statics in inlinable functions into a comdat section and setting the
25 // protected memory section read-write when they are merged. If a declaration of
26 // a protected variable is required DECLARE_PROTECTED_DATA should be used.
27 //
28 // Instances of `base::ProtectedMemory` use constant initialization. To allow
29 // protection of objects which do not provide constant initialization or would
30 // require a global constructor, `base::ProtectedMemory` provides lazy
31 // initialization. With template parameter `ConstructLazily` set to `true`, the
32 // value is constructed lazily when initialized through
33 // `ProtectedMemoryInitializer`. In this case, explicit initialization through
34 // `ProtectedMemoryInitializer` is mandatory to prevent accessing uninitialized
35 // memory. If data is accessed without initialization a CHECK triggers.
36 //
37 // `base::ProtectedMemory` requires T to be trivially destructible. T having
38 // a non-trivial constructor indicates that is holds data which can not be
39 // protected by `base::ProtectedMemory`.
40 //
41 // EXAMPLE:
42 //
43 //  struct Items { void* item1; };
44 //  static DEFINE_PROTECTED_DATA base::ProtectedMemory<Items, false> items;
45 //  void InitializeItems() {
46 //    // Explicitly set items read-write before writing to it.
47 //    auto writer = base::AutoWritableMemory(items);
48 //    writer->item1 = /* ... */;
49 //    assert(items->item1 != nullptr);
50 //    // items is set back to read-only on the destruction of writer
51 //  }
52 //
53 //  using FnPtr = void (*)(void);
54 //  DEFINE_PROTECTED_DATA base::ProtectedMemory<FnPtr, true> fnPtr;
55 //  FnPtr ResolveFnPtr(void) {
56 //    // `ProtectedMemoryInitializer` is a helper class for creating a static
57 //    // initializer for a ProtectedMemory variable. It implicitly sets the
58 //    // variable read-write during initialization.
59 //    static base::ProtectedMemoryInitializer initializer(&fnPtr,
60 //      reinterpret_cast<FnPtr>(dlsym(/* ... */)));
61 //    return *fnPtr;
62 //  }
63 
64 #ifndef BASE_MEMORY_PROTECTED_MEMORY_H_
65 #define BASE_MEMORY_PROTECTED_MEMORY_H_
66 
67 #include <stddef.h>
68 #include <stdint.h>
69 
70 #include <memory>
71 #include <type_traits>
72 
73 #include "base/check.h"
74 #include "base/check_op.h"
75 #include "base/gtest_prod_util.h"
76 #include "base/memory/protected_memory_buildflags.h"
77 #include "base/memory/raw_ref.h"
78 #include "base/no_destructor.h"
79 #include "base/synchronization/lock.h"
80 #include "base/thread_annotations.h"
81 #include "build/build_config.h"
82 
83 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
84 #if BUILDFLAG(IS_WIN)
85 // Define a read-write prot section. The $a, $mem, and $z 'sub-sections' are
86 // merged alphabetically so $a and $z are used to define the start and end of
87 // the protected memory section, and $mem holds protected variables.
88 // (Note: Sections in Portable Executables are equivalent to segments in other
89 // executable formats, so this section is mapped into its own pages.)
90 #pragma section("prot$a", read, write)
91 #pragma section("prot$mem", read, write)
92 #pragma section("prot$z", read, write)
93 
94 // We want the protected memory section to be read-only, not read-write so we
95 // instruct the linker to set the section read-only at link time. We do this
96 // at link time instead of compile time, because defining the prot section
97 // read-only would cause mis-compiles due to optimizations assuming that the
98 // section contents are constant.
99 #pragma comment(linker, "/SECTION:prot,R")
100 
101 __declspec(allocate("prot$a"))
102 __declspec(selectany) char __start_protected_memory;
103 __declspec(allocate("prot$z"))
104 __declspec(selectany) char __stop_protected_memory;
105 
106 #define DECLARE_PROTECTED_DATA constinit
107 #define DEFINE_PROTECTED_DATA constinit __declspec(allocate("prot$mem"))
108 #else
109 #error "Protected Memory is currently only supported on Windows."
110 #endif  // BUILDFLAG(IS_WIN)
111 
112 #else
113 #define DECLARE_PROTECTED_DATA constinit
114 #define DEFINE_PROTECTED_DATA DECLARE_PROTECTED_DATA
115 #endif  // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
116 
117 namespace base {
118 
119 template <typename T, bool ConstructLazily>
120 class AutoWritableMemory;
121 
122 FORWARD_DECLARE_TEST(ProtectedMemoryDeathTest, VerifyTerminationOnAccess);
123 
124 namespace internal {
125 // Helper classes which store the data and implement and initialization
126 // according to `ConstructLazily`. With `ConstructLazily` set to false, the
127 // instance of T is created upon construction time, whereas with
128 // `ConstructLazily` set to true, the instance of T is only constructed when
129 // emplace is called.
130 template <typename T, bool ConstructLazily>
131 class ProtectedDataHolder {
132  public:
133   consteval ProtectedDataHolder() = default;
134 
135   template <typename... U>
ProtectedDataHolder(U &&...args)136   consteval explicit ProtectedDataHolder(U&&... args)
137       : data_(std::forward<U>(args)...) {}
138 
GetReference()139   T& GetReference() { return data_; }
GetReference()140   const T& GetReference() const { return data_; }
141 
GetPointer()142   T* GetPointer() { return &data_; }
GetPointer()143   const T* GetPointer() const { return &data_; }
144 
145   template <typename... U>
emplace(U &&...data)146   void emplace(U&&... data) {
147     data_ = T(std::forward<U>(data)...);
148   }
149 
150  private:
151   T data_ = T();
152 };
153 
154 template <typename T>
155 class ProtectedDataHolder<T, true /*ConstructLazily*/> {
156  public:
157   consteval ProtectedDataHolder() = default;
158 
GetReference()159   T& GetReference() { return *GetPointer(); }
GetReference()160   const T& GetReference() const { return *GetPointer(); }
161 
GetPointer()162   T* GetPointer() {
163     CHECK(constructed_);
164     return reinterpret_cast<T*>(&data_);
165   }
GetPointer()166   const T* GetPointer() const {
167     CHECK(constructed_);
168     return reinterpret_cast<const T*>(&data_);
169   }
170 
171   template <typename... U>
emplace(U &&...args)172   void emplace(U&&... args) {
173     if (constructed_) {
174       std::destroy_at(reinterpret_cast<T*>(&data_));
175       constructed_ = false;
176     }
177 
178     std::construct_at(reinterpret_cast<T*>(&data_), std::forward<U>(args)...);
179     constructed_ = true;
180   }
181 
182  private:
183   // Initializing with a constant/zero value ensures no global constructor is
184   // required when instantiating `ProtectedDataHolder` and `ProtectedMemory`.
185   alignas(T) uint8_t data_[sizeof(T)] = {0};
186   bool constructed_ = false;
187 };
188 
189 }  // namespace internal
190 
191 // The wrapper class for data of type `T` which is to be stored in protected
192 // memory. `ProtectedMemory` provides improved type safety in conjunction with
193 // the other classes, although the basic mechanisms like unlocking and
194 // re-locking of the memory would also work without it.
195 //
196 // To allow using `T`s which do not have constant initialization, the template
197 // parameter `ConstructLazily` enables a lazy initialization. In this case, an
198 // initialization before first access is mandatory (see
199 // `ProtectedMemoryInitializer`).
200 template <typename T, bool ConstructLazily = false>
201 class ProtectedMemory {
202  public:
203   // T must be trivially destructible. Otherwise it indicates that T holds data
204   // which would not be covered by this write protection, i.e. data allocated on
205   // heap. This check complements the verification in the constructor since
206   // `ProtectedMemory` with `ConstructLazily` set to `true` is always trivially
207   // destructible.
208   static_assert(std::is_trivially_destructible_v<T>);
209 
210   // For lazily constructed data we enable this constructor only if there are
211   // no arguments. For lazily constructed data no arguments are accepted as T is
212   // not initialized when `ProtectedMemory<T>` is created but through
213   // `ProtectedMemoryInitializer` instead.
214   template <
215       typename... U,
216       bool ConstructLazilyP = ConstructLazily,
217       std::enable_if_t<!ConstructLazilyP || sizeof...(U) == 0, bool> = true>
ProtectedMemory(U &&...args)218   consteval explicit ProtectedMemory(U&&... args)
219       : data_(std::forward<U>(args)...) {
220     static_assert(std::is_trivially_destructible_v<ProtectedMemory>);
221   }
222 
223   ProtectedMemory(const ProtectedMemory&) = delete;
224   ProtectedMemory& operator=(const ProtectedMemory&) = delete;
225 
226   // Expose direct access to the encapsulated variable
227   const T& operator*() const { return data_.GetReference(); }
228   const T* operator->() const { return data_.GetPointer(); }
229 
230  private:
231   friend class AutoWritableMemory<T, ConstructLazily>;
232   FRIEND_TEST_ALL_PREFIXES(ProtectedMemoryDeathTest, VerifyTerminationOnAccess);
233 
234   internal::ProtectedDataHolder<T, ConstructLazily> data_;
235 };
236 
237 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
238 namespace internal {
239 // Checks that the byte at `ptr` is read-only.
240 BASE_EXPORT bool IsMemoryReadOnly(const void* ptr);
241 
242 // Abstract out platform-specific methods to get the beginning and end of the
243 // PROTECTED_MEMORY_SECTION. ProtectedMemoryEnd returns a pointer to the byte
244 // past the end of the PROTECTED_MEMORY_SECTION.
245 inline constexpr void* kProtectedMemoryStart = &__start_protected_memory;
246 inline constexpr void* kProtectedMemoryEnd = &__stop_protected_memory;
247 }  // namespace internal
248 #endif  // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
249 
250 // Provide some common functionality for `AutoWritableMemory<T>`.
251 class BASE_EXPORT AutoWritableMemoryBase {
252  protected:
253 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
254   // Checks that `object` is located within the interval
255   // (internal::kProtectedMemoryStart, internal::kProtectedMemoryEnd).
256   template <typename T>
IsObjectInProtectedSection(const T & object)257   static bool IsObjectInProtectedSection(const T& object) {
258     const T* const ptr = std::addressof(object);
259     const T* const ptr_end = ptr + 1;
260     return (ptr > internal::kProtectedMemoryStart) &&
261            (ptr_end <= internal::kProtectedMemoryEnd);
262   }
263 
264   template <typename T>
IsObjectReadOnly(const T & object)265   static bool IsObjectReadOnly(const T& object) {
266     return internal::IsMemoryReadOnly(std::addressof(object));
267   }
268 
269   template <typename T>
SetObjectReadWrite(T & object)270   static bool SetObjectReadWrite(T& object) {
271     T* const ptr = std::addressof(object);
272     T* const ptr_end = ptr + 1;
273     return SetMemoryReadWrite(ptr, ptr_end);
274   }
275 
SetProtectedSectionReadOnly()276   static bool SetProtectedSectionReadOnly() {
277     return SetMemoryReadOnly(internal::kProtectedMemoryStart,
278                              internal::kProtectedMemoryEnd);
279   }
280 
281   // When linking, each DSO will have its own protected section. We can't keep
282   // track of each section, yet we have to ensure to always unlock and re-lock
283   // the correct section.
284   //
285   // We solve this by defining a separate global writers variable (explained
286   // below) in every dynamic shared object (DSO) that includes this header. To
287   // do that we use this structure to define global writer data without
288   // duplicate symbol errors.
289   //
290   // Storing the data in a substructure is required to store `writers` within
291   // the protected subsection. If `writers` and `writers_lock()` are located
292   // directly in `AutoWritableMemoryBase`, for unknown reasons `writers` is not
293   // placed into the protected section.
294   struct WriterData {
295     // `writers` is a global holding the number of ProtectedMemory instances set
296     // writable, used to avoid races setting protected memory readable/writable.
297     // When this reaches zero the protected memory region is set read only.
298     // Access is controlled by writers_lock.
299     //
300     // Declare writers in the protected memory section to avoid the scenario
301     // where an attacker could overwrite it with a large value and invoke code
302     // that constructs and destructs an AutoWritableMemory. After such a call
303     // protected memory would still be set writable because writers > 0.
304     DEFINE_PROTECTED_DATA
305     static inline size_t writers GUARDED_BY(writers_lock()) = 0;
306 
307     // Synchronizes access to the writers variable and the simultaneous actions
308     // that need to happen alongside writers changes, e.g. setting the protected
309     // memory region readable when writers is decremented to 0.
writers_lockWriterData310     static Lock& writers_lock() {
311       static NoDestructor<Lock> writers_lock;
312       return *writers_lock;
313     }
314   };
315 
316  private:
317   // Abstract out platform-specific memory APIs. |end| points to the byte
318   // past the end of the region of memory having its memory protections
319   // changed.
320   static bool SetMemoryReadWrite(void* start, void* end);
321   static bool SetMemoryReadOnly(void* start, void* end);
322 #endif  // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
323 };
324 
325 // A class that sets a given ProtectedMemory variable writable while the
326 // AutoWritableMemory is in scope. This class implements the logic for setting
327 // the protected memory region read-only/read-write in a thread-safe manner.
328 //
329 // |AutoWritableMemory| affects the write-permissions of _all_ protected data
330 // for a DSO, not just of the instance that it's being passed! All protected
331 // data is stored within the same binary section. At the same time, the OS-level
332 // support enforcing write protection can only be changed at page level. To
333 // allow a more fine grained control a dedicated page per instance of protected
334 // data would be required.
335 template <typename T, bool ConstructLazily>
336 class AutoWritableMemory : public AutoWritableMemoryBase {
337  public:
AutoWritableMemory(ProtectedMemory<T,ConstructLazily> & protected_memory)338   explicit AutoWritableMemory(
339       ProtectedMemory<T, ConstructLazily>& protected_memory)
340 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
341       LOCKS_EXCLUDED(WriterData::writers_lock())
342 #endif
343       : protected_memory_(protected_memory) {
344 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
345 
346     // Check that the data is located in the protected section to
347     // ensure consistency of data.
348     CHECK(IsObjectInProtectedSection(protected_memory_->data_));
349     CHECK(IsObjectInProtectedSection(WriterData::writers));
350 
351     {
352       base::AutoLock auto_lock(WriterData::writers_lock());
353 
354       if (WriterData::writers == 0) {
355         CHECK(IsObjectReadOnly(protected_memory_->data_));
356         CHECK(IsObjectReadOnly(WriterData::writers));
357         CHECK(SetObjectReadWrite(WriterData::writers));
358       }
359 
360       ++WriterData::writers;
361     }
362 
363     CHECK(SetObjectReadWrite(protected_memory_->data_));
364 #endif  // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
365   }
366 
367   ~AutoWritableMemory()
368 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
LOCKS_EXCLUDED(WriterData::writers_lock ())369       LOCKS_EXCLUDED(WriterData::writers_lock())
370 #endif
371   {
372 #if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
373     base::AutoLock auto_lock(WriterData::writers_lock());
374     CHECK_GT(WriterData::writers, 0u);
375     --WriterData::writers;
376 
377     if (WriterData::writers == 0) {
378       // Lock the whole section of protected memory and set _all_ instances of
379       // base::ProtectedMemory to non-writeable.
380       CHECK(SetProtectedSectionReadOnly());
381       CHECK(IsObjectReadOnly(
382           *static_cast<const char*>(internal::kProtectedMemoryStart)));
383       CHECK(IsObjectReadOnly(WriterData::writers));
384     }
385 #endif  // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
386   }
387 
388   AutoWritableMemory(AutoWritableMemory& original) = delete;
389   AutoWritableMemory& operator=(AutoWritableMemory& original) = delete;
390   AutoWritableMemory(AutoWritableMemory&& original) = delete;
391   AutoWritableMemory& operator=(AutoWritableMemory&& original) = delete;
392 
GetProtectedData()393   T& GetProtectedData() { return protected_memory_->data_.GetReference(); }
GetProtectedDataPtr()394   T* GetProtectedDataPtr() { return protected_memory_->data_.GetPointer(); }
395 
396   template <typename... U>
emplace(U &&...args)397   void emplace(U&&... args) {
398     protected_memory_->data_.emplace(std::forward<U>(args)...);
399   }
400 
401  private:
402   const raw_ref<ProtectedMemory<T, ConstructLazily>> protected_memory_;
403 };
404 
405 // Helper class for creating simple ProtectedMemory static initializers.
406 class ProtectedMemoryInitializer {
407  public:
408   template <typename T, bool ConstructLazily, typename... U>
ProtectedMemoryInitializer(ProtectedMemory<T,ConstructLazily> & protected_memory,U &&...args)409   explicit ProtectedMemoryInitializer(
410       ProtectedMemory<T, ConstructLazily>& protected_memory,
411       U&&... args) {
412     AutoWritableMemory writer(protected_memory);
413     writer.emplace(std::forward<U>(args)...);
414   }
415 
416   ProtectedMemoryInitializer() = delete;
417   ProtectedMemoryInitializer(const ProtectedMemoryInitializer&) = delete;
418   ProtectedMemoryInitializer& operator=(const ProtectedMemoryInitializer&) =
419       delete;
420 };
421 
422 }  // namespace base
423 
424 #endif  // BASE_MEMORY_PROTECTED_MEMORY_H_
425