blob: aac29a0c9e8bc10272996273665fc30e17a58b87 [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
André Kempe0f7da88a2024-03-22 19:12:4718// 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->.
Jeff Goura3e4ebd2024-01-23 21:04:4921//
André Kempe0f7da88a2024-03-22 19:12:4722// 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`.
Jeff Goura3e4ebd2024-01-23 21:04:4940//
41// EXAMPLE:
42//
43// struct Items { void* item1; };
André Kempe0f7da88a2024-03-22 19:12:4744// static DEFINE_PROTECTED_DATA base::ProtectedMemory<Items, false> items;
Jeff Goura3e4ebd2024-01-23 21:04:4945// void InitializeItems() {
46// // Explicitly set items read-write before writing to it.
André Kempe0f7da88a2024-03-22 19:12:4747// auto writer = base::AutoWritableMemory(items);
48// writer->item1 = /* ... */;
Jeff Goura3e4ebd2024-01-23 21:04:4949// assert(items->item1 != nullptr);
50// // items is set back to read-only on the destruction of writer
51// }
52//
53// using FnPtr = void (*)(void);
André Kempe0f7da88a2024-03-22 19:12:4754// DEFINE_PROTECTED_DATA base::ProtectedMemory<FnPtr, true> fnPtr;
Jeff Goura3e4ebd2024-01-23 21:04:4955// FnPtr ResolveFnPtr(void) {
André Kempe0f7da88a2024-03-22 19:12:4756// // `ProtectedMemoryInitializer` is a helper class for creating a static
Jeff Goura3e4ebd2024-01-23 21:04:4957// // initializer for a ProtectedMemory variable. It implicitly sets the
58// // variable read-write during initialization.
André Kempe0f7da88a2024-03-22 19:12:4759// static base::ProtectedMemoryInitializer initializer(&fnPtr,
Jeff Goura3e4ebd2024-01-23 21:04:4960// reinterpret_cast<FnPtr>(dlsym(/* ... */)));
61// return *fnPtr;
62// }
63
64#ifndef BASE_MEMORY_PROTECTED_MEMORY_H_
65#define BASE_MEMORY_PROTECTED_MEMORY_H_
66
André Kempe5adc4d5a2024-03-15 12:23:3367#include <stddef.h>
68#include <stdint.h>
69
70#include <memory>
André Kempe0f7da88a2024-03-22 19:12:4771#include <type_traits>
André Kempe5adc4d5a2024-03-15 12:23:3372
73#include "base/check.h"
Jeff Goura3e4ebd2024-01-23 21:04:4974#include "base/check_op.h"
André Kempe5adc4d5a2024-03-15 12:23:3375#include "base/gtest_prod_util.h"
Jeff Goura3e4ebd2024-01-23 21:04:4976#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"
André Kempe5adc4d5a2024-03-15 12:23:3380#include "base/thread_annotations.h"
Jeff Goura3e4ebd2024-01-23 21:04:4981#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
André Kempe0f7da88a2024-03-22 19:12:47106#define DECLARE_PROTECTED_DATA constinit
107#define DEFINE_PROTECTED_DATA constinit __declspec(allocate("prot$mem"))
Jeff Goura3e4ebd2024-01-23 21:04:49108#else
109#error "Protected Memory is currently only supported on Windows."
110#endif // BUILDFLAG(IS_WIN)
111
112#else
André Kempe0f7da88a2024-03-22 19:12:47113#define DECLARE_PROTECTED_DATA constinit
114#define DEFINE_PROTECTED_DATA DECLARE_PROTECTED_DATA
Jeff Goura3e4ebd2024-01-23 21:04:49115#endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
116
117namespace base {
118
André Kempe0f7da88a2024-03-22 19:12:47119template <typename T, bool ConstructLazily>
Jeff Goura3e4ebd2024-01-23 21:04:49120class AutoWritableMemory;
121
André Kempe5adc4d5a2024-03-15 12:23:33122FORWARD_DECLARE_TEST(ProtectedMemoryDeathTest, VerifyTerminationOnAccess);
123
André Kempe0f7da88a2024-03-22 19:12:47124namespace 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.
130template <typename T, bool ConstructLazily>
131class ProtectedDataHolder {
132 public:
133 consteval ProtectedDataHolder() = default;
134
135 template <typename... U>
136 consteval explicit ProtectedDataHolder(U&&... args)
137 : data_(std::forward<U>(args)...) {}
138
139 T& GetReference() { return data_; }
140 const T& GetReference() const { return data_; }
141
142 T* GetPointer() { return &data_; }
143 const T* GetPointer() const { return &data_; }
144
145 template <typename... U>
146 void emplace(U&&... data) {
147 data_ = T(std::forward<U>(data)...);
148 }
149
150 private:
151 T data_ = T();
152};
153
Jeff Goura3e4ebd2024-01-23 21:04:49154template <typename T>
André Kempe0f7da88a2024-03-22 19:12:47155class ProtectedDataHolder<T, true /*ConstructLazily*/> {
156 public:
157 consteval ProtectedDataHolder() = default;
158
159 T& GetReference() { return *GetPointer(); }
160 const T& GetReference() const { return *GetPointer(); }
161
162 T* GetPointer() {
163 CHECK(constructed_);
164 return reinterpret_cast<T*>(&data_);
165 }
166 const T* GetPointer() const {
167 CHECK(constructed_);
168 return reinterpret_cast<const T*>(&data_);
169 }
170
171 template <typename... U>
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`).
200template <typename T, bool ConstructLazily = false>
Jeff Goura3e4ebd2024-01-23 21:04:49201class ProtectedMemory {
202 public:
André Kempe0f7da88a2024-03-22 19:12:47203 // 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.
206 static_assert(std::is_trivially_destructible_v<T>);
207
208 // For lazily constructed data we enable this constructor only if there are
209 // no arguments. For lazily constructed data no arguments are accepted as T is
210 // not initialized when `ProtectedMemory<T>` is created but through
211 // `ProtectedMemoryInitializer` instead.
212 template <
213 typename... U,
214 bool ConstructLazilyP = ConstructLazily,
215 std::enable_if_t<!ConstructLazilyP || sizeof...(U) == 0, bool> = true>
216 consteval explicit ProtectedMemory(U&&... args)
217 : data_(std::forward<U>(args)...) {
218 static_assert(std::is_trivially_destructible_v<ProtectedMemory>);
219 }
220
Jeff Goura3e4ebd2024-01-23 21:04:49221 ProtectedMemory(const ProtectedMemory&) = delete;
222 ProtectedMemory& operator=(const ProtectedMemory&) = delete;
223
224 // Expose direct access to the encapsulated variable
André Kempe0f7da88a2024-03-22 19:12:47225 const T& operator*() const { return data_.GetReference(); }
226 const T* operator->() const { return data_.GetPointer(); }
Jeff Goura3e4ebd2024-01-23 21:04:49227
228 private:
André Kempe0f7da88a2024-03-22 19:12:47229 friend class AutoWritableMemory<T, ConstructLazily>;
André Kempe5adc4d5a2024-03-15 12:23:33230 FRIEND_TEST_ALL_PREFIXES(ProtectedMemoryDeathTest, VerifyTerminationOnAccess);
231
André Kempe0f7da88a2024-03-22 19:12:47232 internal::ProtectedDataHolder<T, ConstructLazily> data_;
Jeff Goura3e4ebd2024-01-23 21:04:49233};
234
Jeff Goura3e4ebd2024-01-23 21:04:49235#if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
André Kempe5adc4d5a2024-03-15 12:23:33236namespace internal {
237// Checks that the byte at `ptr` is read-only.
238BASE_EXPORT bool IsMemoryReadOnly(const void* ptr);
Jeff Goura3e4ebd2024-01-23 21:04:49239
240// Abstract out platform-specific methods to get the beginning and end of the
241// PROTECTED_MEMORY_SECTION. ProtectedMemoryEnd returns a pointer to the byte
242// past the end of the PROTECTED_MEMORY_SECTION.
André Kempe5adc4d5a2024-03-15 12:23:33243inline constexpr void* kProtectedMemoryStart = &__start_protected_memory;
244inline constexpr void* kProtectedMemoryEnd = &__stop_protected_memory;
245} // namespace internal
Jeff Goura3e4ebd2024-01-23 21:04:49246#endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
247
André Kempe5adc4d5a2024-03-15 12:23:33248// Provide some common functionality for `AutoWritableMemory<T>`.
249class BASE_EXPORT AutoWritableMemoryBase {
250 protected:
251#if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
252 // Checks that `object` is located within the interval
253 // (internal::kProtectedMemoryStart, internal::kProtectedMemoryEnd).
254 template <typename T>
255 static bool IsObjectInProtectedSection(const T& object) {
256 const T* const ptr = std::addressof(object);
257 const T* const ptr_end = ptr + 1;
258 return (ptr > internal::kProtectedMemoryStart) &&
259 (ptr_end <= internal::kProtectedMemoryEnd);
260 }
261
262 template <typename T>
263 static bool IsObjectReadOnly(const T& object) {
264 return internal::IsMemoryReadOnly(std::addressof(object));
265 }
266
267 template <typename T>
268 static bool SetObjectReadWrite(T& object) {
269 T* const ptr = std::addressof(object);
270 T* const ptr_end = ptr + 1;
271 return SetMemoryReadWrite(ptr, ptr_end);
272 }
273
274 static bool SetProtectedSectionReadOnly() {
275 return SetMemoryReadOnly(internal::kProtectedMemoryStart,
276 internal::kProtectedMemoryEnd);
277 }
278
279 // When linking, each DSO will have its own protected section. We can't keep
280 // track of each section, yet we have to ensure to always unlock and re-lock
281 // the correct section.
282 //
283 // We solve this by defining a separate global writers variable (explained
284 // below) in every dynamic shared object (DSO) that includes this header. To
285 // do that we use this structure to define global writer data without
286 // duplicate symbol errors.
287 //
288 // Storing the data in a substructure is required to store `writers` within
289 // the protected subsection. If `writers` and `writers_lock()` are located
290 // directly in `AutoWritableMemoryBase`, for unknown reasons `writers` is not
291 // placed into the protected section.
292 struct WriterData {
293 // `writers` is a global holding the number of ProtectedMemory instances set
294 // writable, used to avoid races setting protected memory readable/writable.
295 // When this reaches zero the protected memory region is set read only.
296 // Access is controlled by writers_lock.
297 //
298 // Declare writers in the protected memory section to avoid the scenario
299 // where an attacker could overwrite it with a large value and invoke code
300 // that constructs and destructs an AutoWritableMemory. After such a call
301 // protected memory would still be set writable because writers > 0.
André Kempe0f7da88a2024-03-22 19:12:47302 DEFINE_PROTECTED_DATA
André Kempe5adc4d5a2024-03-15 12:23:33303 static inline size_t writers GUARDED_BY(writers_lock()) = 0;
304
305 // Synchronizes access to the writers variable and the simultaneous actions
306 // that need to happen alongside writers changes, e.g. setting the protected
307 // memory region readable when writers is decremented to 0.
308 static Lock& writers_lock() {
309 static NoDestructor<Lock> writers_lock;
310 return *writers_lock;
311 }
312 };
313
314 private:
315 // Abstract out platform-specific memory APIs. |end| points to the byte
316 // past the end of the region of memory having its memory protections
317 // changed.
318 static bool SetMemoryReadWrite(void* start, void* end);
319 static bool SetMemoryReadOnly(void* start, void* end);
320#endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
Jeff Goura3e4ebd2024-01-23 21:04:49321};
Jeff Goura3e4ebd2024-01-23 21:04:49322
323// A class that sets a given ProtectedMemory variable writable while the
324// AutoWritableMemory is in scope. This class implements the logic for setting
325// the protected memory region read-only/read-write in a thread-safe manner.
André Kempe5adc4d5a2024-03-15 12:23:33326//
327// |AutoWritableMemory| affects the write-permissions of _all_ protected data
328// for a DSO, not just of the instance that it's being passed! All protected
329// data is stored within the same binary section. At the same time, the OS-level
330// support enforcing write protection can only be changed at page level. To
331// allow a more fine grained control a dedicated page per instance of protected
332// data would be required.
André Kempe0f7da88a2024-03-22 19:12:47333template <typename T, bool ConstructLazily>
André Kempe5adc4d5a2024-03-15 12:23:33334class AutoWritableMemory : public AutoWritableMemoryBase {
Jeff Goura3e4ebd2024-01-23 21:04:49335 public:
André Kempe0f7da88a2024-03-22 19:12:47336 explicit AutoWritableMemory(
337 ProtectedMemory<T, ConstructLazily>& protected_memory)
André Kempe5adc4d5a2024-03-15 12:23:33338#if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
339 LOCKS_EXCLUDED(WriterData::writers_lock())
340#endif
Jeff Goura3e4ebd2024-01-23 21:04:49341 : protected_memory_(protected_memory) {
342#if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
André Kempe5adc4d5a2024-03-15 12:23:33343
344 // Check that the data is located in the protected section to
345 // ensure consistency of data.
346 CHECK(IsObjectInProtectedSection(protected_memory_->data_));
347 CHECK(IsObjectInProtectedSection(WriterData::writers));
Jeff Goura3e4ebd2024-01-23 21:04:49348
349 {
André Kempe5adc4d5a2024-03-15 12:23:33350 base::AutoLock auto_lock(WriterData::writers_lock());
351
352 if (WriterData::writers == 0) {
353 CHECK(IsObjectReadOnly(protected_memory_->data_));
354 CHECK(IsObjectReadOnly(WriterData::writers));
355 CHECK(SetObjectReadWrite(WriterData::writers));
Jeff Goura3e4ebd2024-01-23 21:04:49356 }
357
André Kempe5adc4d5a2024-03-15 12:23:33358 ++WriterData::writers;
Jeff Goura3e4ebd2024-01-23 21:04:49359 }
360
André Kempe5adc4d5a2024-03-15 12:23:33361 CHECK(SetObjectReadWrite(protected_memory_->data_));
Jeff Goura3e4ebd2024-01-23 21:04:49362#endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
363 }
364
André Kempe5adc4d5a2024-03-15 12:23:33365 ~AutoWritableMemory()
Jeff Goura3e4ebd2024-01-23 21:04:49366#if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
André Kempe5adc4d5a2024-03-15 12:23:33367 LOCKS_EXCLUDED(WriterData::writers_lock())
368#endif
369 {
370#if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
371 base::AutoLock auto_lock(WriterData::writers_lock());
372 CHECK_GT(WriterData::writers, 0u);
373 --WriterData::writers;
Jeff Goura3e4ebd2024-01-23 21:04:49374
André Kempe5adc4d5a2024-03-15 12:23:33375 if (WriterData::writers == 0) {
376 // Lock the whole section of protected memory and set _all_ instances of
377 // base::ProtectedMemory to non-writeable.
378 CHECK(SetProtectedSectionReadOnly());
379 CHECK(IsObjectReadOnly(
380 *static_cast<const char*>(internal::kProtectedMemoryStart)));
381 CHECK(IsObjectReadOnly(WriterData::writers));
Jeff Goura3e4ebd2024-01-23 21:04:49382 }
383#endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
384 }
385
386 AutoWritableMemory(AutoWritableMemory& original) = delete;
387 AutoWritableMemory& operator=(AutoWritableMemory& original) = delete;
388 AutoWritableMemory(AutoWritableMemory&& original) = delete;
389 AutoWritableMemory& operator=(AutoWritableMemory&& original) = delete;
390
André Kempe0f7da88a2024-03-22 19:12:47391 T& GetProtectedData() { return protected_memory_->data_.GetReference(); }
392 T* GetProtectedDataPtr() { return protected_memory_->data_.GetPointer(); }
393
394 template <typename... U>
395 void emplace(U&&... args) {
396 protected_memory_->data_.emplace(std::forward<U>(args)...);
397 }
Jeff Goura3e4ebd2024-01-23 21:04:49398
399 private:
André Kempe0f7da88a2024-03-22 19:12:47400 const raw_ref<ProtectedMemory<T, ConstructLazily>> protected_memory_;
Jeff Goura3e4ebd2024-01-23 21:04:49401};
402
André Kempe0f7da88a2024-03-22 19:12:47403// Helper class for creating simple ProtectedMemory static initializers.
404class ProtectedMemoryInitializer {
405 public:
406 template <typename T, bool ConstructLazily, typename... U>
407 explicit ProtectedMemoryInitializer(
408 ProtectedMemory<T, ConstructLazily>& protected_memory,
409 U&&... args) {
410 AutoWritableMemory writer(protected_memory);
411 writer.emplace(std::forward<U>(args)...);
412 }
413
414 ProtectedMemoryInitializer() = delete;
415 ProtectedMemoryInitializer(const ProtectedMemoryInitializer&) = delete;
416 ProtectedMemoryInitializer& operator=(const ProtectedMemoryInitializer&) =
417 delete;
418};
Jeff Goura3e4ebd2024-01-23 21:04:49419
420} // namespace base
421
422#endif // BASE_MEMORY_PROTECTED_MEMORY_H_