blob: 61b057cf9865522221245c64d7cfe19fc293fc7c [file] [log] [blame]
Jeff Goura3e4ebd2024-01-23 21:04:491// 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é Kempe5adc4d5a2024-03-15 12:23:3356#include <stddef.h>
57#include <stdint.h>
58
59#include <memory>
60
61#include "base/check.h"
Jeff Goura3e4ebd2024-01-23 21:04:4962#include "base/check_op.h"
André Kempe5adc4d5a2024-03-15 12:23:3363#include "base/gtest_prod_util.h"
Jeff Goura3e4ebd2024-01-23 21:04:4964#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é Kempe5adc4d5a2024-03-15 12:23:3368#include "base/thread_annotations.h"
Jeff Goura3e4ebd2024-01-23 21:04:4969#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
103namespace base {
104
105template <typename T>
106class AutoWritableMemory;
107
André Kempe5adc4d5a2024-03-15 12:23:33108FORWARD_DECLARE_TEST(ProtectedMemoryDeathTest, VerifyTerminationOnAccess);
109
Jeff Goura3e4ebd2024-01-23 21:04:49110template <typename T>
111class 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é Kempe5adc4d5a2024-03-15 12:23:33118 const T& operator*() const { return data_; }
119 const T* operator->() const { return &data_; }
Jeff Goura3e4ebd2024-01-23 21:04:49120
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é Kempe5adc4d5a2024-03-15 12:23:33135 FRIEND_TEST_ALL_PREFIXES(ProtectedMemoryDeathTest, VerifyTerminationOnAccess);
136
137 T data_;
Jeff Goura3e4ebd2024-01-23 21:04:49138};
139
Jeff Goura3e4ebd2024-01-23 21:04:49140#if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
André Kempe5adc4d5a2024-03-15 12:23:33141namespace internal {
142// Checks that the byte at `ptr` is read-only.
143BASE_EXPORT bool IsMemoryReadOnly(const void* ptr);
Jeff Goura3e4ebd2024-01-23 21:04:49144
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é Kempe5adc4d5a2024-03-15 12:23:33148inline constexpr void* kProtectedMemoryStart = &__start_protected_memory;
149inline constexpr void* kProtectedMemoryEnd = &__stop_protected_memory;
150} // namespace internal
Jeff Goura3e4ebd2024-01-23 21:04:49151#endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
152
André Kempe5adc4d5a2024-03-15 12:23:33153// Provide some common functionality for `AutoWritableMemory<T>`.
154class 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 Goura3e4ebd2024-01-23 21:04:49226};
Jeff Goura3e4ebd2024-01-23 21:04:49227
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é Kempe5adc4d5a2024-03-15 12:23:33231//
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 Goura3e4ebd2024-01-23 21:04:49238template <typename T>
André Kempe5adc4d5a2024-03-15 12:23:33239class AutoWritableMemory : public AutoWritableMemoryBase {
Jeff Goura3e4ebd2024-01-23 21:04:49240 public:
Jeff Goura3e4ebd2024-01-23 21:04:49241 explicit AutoWritableMemory(ProtectedMemory<T>& protected_memory)
André Kempe5adc4d5a2024-03-15 12:23:33242#if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
243 LOCKS_EXCLUDED(WriterData::writers_lock())
244#endif
Jeff Goura3e4ebd2024-01-23 21:04:49245 : protected_memory_(protected_memory) {
246#if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
André Kempe5adc4d5a2024-03-15 12:23:33247
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 Goura3e4ebd2024-01-23 21:04:49252
253 {
André Kempe5adc4d5a2024-03-15 12:23:33254 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 Goura3e4ebd2024-01-23 21:04:49260 }
261
André Kempe5adc4d5a2024-03-15 12:23:33262 ++WriterData::writers;
Jeff Goura3e4ebd2024-01-23 21:04:49263 }
264
André Kempe5adc4d5a2024-03-15 12:23:33265 CHECK(SetObjectReadWrite(protected_memory_->data_));
Jeff Goura3e4ebd2024-01-23 21:04:49266#endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
267 }
268
André Kempe5adc4d5a2024-03-15 12:23:33269 ~AutoWritableMemory()
Jeff Goura3e4ebd2024-01-23 21:04:49270#if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
André Kempe5adc4d5a2024-03-15 12:23:33271 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 Goura3e4ebd2024-01-23 21:04:49278
André Kempe5adc4d5a2024-03-15 12:23:33279 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 Goura3e4ebd2024-01-23 21:04:49286 }
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é Kempe5adc4d5a2024-03-15 12:23:33295 T& GetProtectedData() { return protected_memory_->data_; }
296 T* GetProtectedDataPtr() { return &(protected_memory_->data_); }
Jeff Goura3e4ebd2024-01-23 21:04:49297
298 private:
Jeff Goura3e4ebd2024-01-23 21:04:49299
300 const raw_ref<ProtectedMemory<T>> protected_memory_;
301};
302
Jeff Goura3e4ebd2024-01-23 21:04:49303template <typename T>
304ProtectedMemory<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_