| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // TODO(crbug.com/40284755): Remove this and spanify to fix the errors. |
| #pragma allow_unsafe_buffers |
| #endif |
| |
| // Protected memory is memory holding security-sensitive data intended to be |
| // left read-only for the majority of its lifetime to avoid being overwritten |
| // by attackers. ProtectedMemory is a simple wrapper around platform-specific |
| // APIs to set memory read-write and read-only when required. Protected memory |
| // should be set read-write for the minimum amount of time required. |
| // |
| // Normally mutable variables are held in read-write memory and constant data |
| // is held in read-only memory to ensure it is not accidentally overwritten. |
| // In some cases we want to hold mutable variables in read-only memory, except |
| // when they are being written to, to ensure that they are not tampered with. |
| // |
| // ProtectedMemory is a container class intended to hold a single variable in |
| // read-only memory, except when explicitly set read-write. The variable can be |
| // set read-write by creating a scoped AutoWritableMemory object, the memory |
| // stays writable until the returned object goes out of scope and is destructed. |
| // The wrapped variable can be accessed using operator* and operator->. |
| // |
| // Instances of ProtectedMemory must be defined using DEFINE_PROTECTED_DATA |
| // and as global variables. Global definitions are required to avoid the linker |
| // placing statics in inlinable functions into a comdat section and setting the |
| // protected memory section read-write when they are merged. If a declaration of |
| // a protected variable is required DECLARE_PROTECTED_DATA should be used. |
| // |
| // Instances of `base::ProtectedMemory` use constant initialization. To allow |
| // protection of objects which do not provide constant initialization or would |
| // require a global constructor, `base::ProtectedMemory` provides lazy |
| // initialization through `ProtectedMemoryInitializer`. Additionally, on |
| // platforms where it is not possible to have the protected memory section start |
| // as read-only, the very first call to ProtectedMemoryInitializer will |
| // initialize the memory section to read-only. Explicit initialization through |
| // `ProtectedMemoryInitializer` is mandatory, even for objects that provide |
| // constant initialization. This ensures that in the unlikely event that the |
| // value is modified before the memory is initialized to read-only, it will be |
| // forced back to a known, safe, initial state before it ever used. If data is |
| // accessed without initialization a CHECK triggers. This CHECK is not expected |
| // to provided security guarantees, but to help catch programming errors. |
| // |
| // TODO(crbug.com/356428974): Improve protection offered by Protected Memory. |
| // |
| // `base::ProtectedMemory` requires T to be trivially destructible. T having |
| // a non-trivial constructor indicates that is holds data which can not be |
| // protected by `base::ProtectedMemory`. |
| // |
| // EXAMPLE: |
| // |
| // struct Items { void* item1; }; |
| // static DEFINE_PROTECTED_DATA base::ProtectedMemory<Items> items; |
| // void InitializeItems() { |
| // // Explicitly set items read-write before writing to it. |
| // auto writer = base::AutoWritableMemory(items); |
| // writer->item1 = /* ... */; |
| // assert(items->item1 != nullptr); |
| // // items is set back to read-only on the destruction of writer |
| // } |
| // |
| // using FnPtr = void (*)(void); |
| // DEFINE_PROTECTED_DATA base::ProtectedMemory<FnPtr> fnPtr; |
| // FnPtr ResolveFnPtr(void) { |
| // // `ProtectedMemoryInitializer` is a helper class for creating a static |
| // // initializer for a ProtectedMemory variable. It implicitly sets the |
| // // variable read-write during initialization. |
| // static base::ProtectedMemoryInitializer initializer(&fnPtr, |
| // reinterpret_cast<FnPtr>(dlsym(/* ... */))); |
| // return *fnPtr; |
| // } |
| |
| #ifndef BASE_MEMORY_PROTECTED_MEMORY_H_ |
| #define BASE_MEMORY_PROTECTED_MEMORY_H_ |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <type_traits> |
| |
| #include "base/bits.h" |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/compiler_specific.h" |
| #include "base/gtest_prod_util.h" |
| #include "base/memory/page_size.h" |
| #include "base/memory/protected_memory_buildflags.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/no_destructor.h" |
| #include "base/synchronization/lock.h" |
| #include "base/thread_annotations.h" |
| #include "build/build_config.h" |
| |
| #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| #if BUILDFLAG(IS_WIN) |
| // Define a read-write prot section. The $a, $mem, and $z 'sub-sections' are |
| // merged alphabetically so $a and $z are used to define the start and end of |
| // the protected memory section, and $mem holds protected variables. |
| // (Note: Sections in Portable Executables are equivalent to segments in other |
| // executable formats, so this section is mapped into its own pages.) |
| #pragma section("prot$a", read, write) |
| #pragma section("prot$mem", read, write) |
| #pragma section("prot$z", read, write) |
| |
| // We want the protected memory section to be read-only, not read-write so we |
| // instruct the linker to set the section read-only at link time. We do this |
| // at link time instead of compile time, because defining the prot section |
| // read-only would cause mis-compiles due to optimizations assuming that the |
| // section contents are constant. |
| #pragma comment(linker, "/SECTION:prot,R") |
| |
| __declspec(allocate("prot$a")) |
| __declspec(selectany) char __start_protected_memory; |
| __declspec(allocate("prot$z")) |
| __declspec(selectany) char __stop_protected_memory; |
| |
| #define DECLARE_PROTECTED_DATA constinit |
| #define DEFINE_PROTECTED_DATA constinit __declspec(allocate("prot$mem")) |
| #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) |
| // This value is used to align the writers variable. That variable needs to be |
| // aligned to ensure that the protected memory section starts on a page |
| // boundary. |
| #if (PA_BUILDFLAG(IS_ANDROID) && PA_BUILDFLAG(PA_ARCH_CPU_64_BITS)) || \ |
| (PA_BUILDFLAG(IS_LINUX) && PA_BUILDFLAG(PA_ARCH_CPU_ARM64)) |
| // arm64 supports 4kb, 16kb, and 64kb pages. Set to the largest of 64kb as that |
| // will guarantee the section is page aligned regardless of the choice. |
| inline constexpr int kProtectedMemoryAlignment = 65536; |
| #elif PA_BUILDFLAG(PA_ARCH_CPU_PPC64) || defined(ARCH_CPU_PPC64) |
| // Modern ppc64 systems support 4kB (shift = 12) and 64kB (shift = 16) page |
| // sizes. Set to the largest of 64kb as that will guarantee the section is page |
| // aligned regardless of the choice. |
| inline constexpr int kProtectedMemoryAlignment = 65536; |
| #elif defined(_MIPS_ARCH_LOONGSON) || PA_BUILDFLAG(PA_ARCH_CPU_LOONGARCH64) || \ |
| defined(ARCH_CPU_LOONGARCH64) |
| // 16kb page size |
| inline constexpr int kProtectedMemoryAlignment = 16384; |
| #else |
| // 4kb page size |
| inline constexpr int kProtectedMemoryAlignment = 4096; |
| #endif |
| |
| __asm__(".section protected_memory, \"a\"\n\t"); |
| __asm__(".section protected_memory_buffer, \"a\"\n\t"); |
| |
| // Explicitly mark these variables hidden so the symbols are local to the |
| // currently built component. Otherwise they are created with global (external) |
| // linkage and component builds would break because a single pair of these |
| // symbols would override the rest. |
| __attribute__((visibility("hidden"))) extern char __start_protected_memory; |
| __attribute__((visibility("hidden"))) extern char __stop_protected_memory; |
| |
| #define DECLARE_PROTECTED_DATA constinit |
| #define DEFINE_PROTECTED_DATA \ |
| constinit __attribute__((section("protected_memory"))) |
| #elif BUILDFLAG(IS_MAC) |
| // The segment the section is in is defined with a linker flag in |
| // build/config/mac/BUILD.gn |
| #define DECLARE_PROTECTED_DATA constinit |
| #define DEFINE_PROTECTED_DATA \ |
| constinit __attribute__((section("PROTECTED_MEMORY, protected_memory"))) |
| |
| extern char __start_protected_memory __asm( |
| "section$start$PROTECTED_MEMORY$protected_memory"); |
| extern char __stop_protected_memory __asm( |
| "section$end$PROTECTED_MEMORY$protected_memory"); |
| #else |
| #error "Protected Memory is not supported on this platform." |
| #endif |
| |
| #else |
| #define DECLARE_PROTECTED_DATA constinit |
| #define DEFINE_PROTECTED_DATA DECLARE_PROTECTED_DATA |
| #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| |
| namespace base { |
| |
| template <typename T> |
| class AutoWritableMemory; |
| |
| FORWARD_DECLARE_TEST(ProtectedMemoryDeathTest, VerifyTerminationOnAccess); |
| |
| namespace internal { |
| // Helper class which store the data and implement and initialization for |
| // constructing the underlying protected data lazily. The instance of T is only |
| // constructed when emplace is called. |
| template <typename T> |
| class ProtectedDataHolder { |
| public: |
| consteval ProtectedDataHolder() = default; |
| |
| T& GetReference() LIFETIME_BOUND { return *GetPointer(); } |
| const T& GetReference() const LIFETIME_BOUND { return *GetPointer(); } |
| |
| T* GetPointer() { |
| CHECK(constructed_); |
| return reinterpret_cast<T*>(&data_); |
| } |
| const T* GetPointer() const { |
| CHECK(constructed_); |
| return reinterpret_cast<const T*>(&data_); |
| } |
| |
| template <typename... U> |
| void emplace(U&&... args) { |
| if (constructed_) { |
| std::destroy_at(reinterpret_cast<T*>(&data_)); |
| constructed_ = false; |
| } |
| |
| std::construct_at(reinterpret_cast<T*>(&data_), std::forward<U>(args)...); |
| constructed_ = true; |
| } |
| |
| private: |
| // Initializing with a constant/zero value ensures no global constructor is |
| // required when instantiating `ProtectedDataHolder` and `ProtectedMemory`. |
| alignas(T) uint8_t data_[sizeof(T)] = {}; |
| bool constructed_ = false; |
| }; |
| |
| } // namespace internal |
| |
| // The wrapper class for data of type `T` which is to be stored in protected |
| // memory. `ProtectedMemory` provides improved type safety in conjunction with |
| // the other classes, although the basic mechanisms like unlocking and |
| // re-locking of the memory would also work without it. |
| // |
| // To allow using `T`s which do not have constant initialization, the template |
| // parameter `ConstructLazily` enables a lazy initialization. In this case, an |
| // initialization before first access is mandatory (see |
| // `ProtectedMemoryInitializer`). |
| template <typename T> |
| class ProtectedMemory { |
| public: |
| // T must be trivially destructible. Otherwise it indicates that T holds data |
| // which would not be covered by this write protection, i.e. data allocated on |
| // heap. This check complements the verification in the constructor since |
| // `ProtectedMemory` with `ConstructLazily` set to `true` is always trivially |
| // destructible. |
| static_assert(std::is_trivially_destructible_v<T>); |
| |
| // For lazily constructed data we enable this constructor only if there are |
| // no arguments. For lazily constructed data no arguments are accepted as T is |
| // not initialized when `ProtectedMemory<T>` is created but through |
| // `ProtectedMemoryInitializer` instead. |
| consteval explicit ProtectedMemory() : data_() { |
| static_assert(std::is_trivially_destructible_v<ProtectedMemory>); |
| } |
| |
| ProtectedMemory(const ProtectedMemory&) = delete; |
| ProtectedMemory& operator=(const ProtectedMemory&) = delete; |
| |
| // Expose direct access to the encapsulated variable |
| const T& operator*() const { return data_.GetReference(); } |
| const T* operator->() const { return data_.GetPointer(); } |
| |
| private: |
| friend class AutoWritableMemory<T>; |
| FRIEND_TEST_ALL_PREFIXES(ProtectedMemoryDeathTest, VerifyTerminationOnAccess); |
| |
| internal::ProtectedDataHolder<T> data_; |
| }; |
| |
| #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| namespace internal { |
| // Checks that the byte at `ptr` is read-only. |
| BASE_EXPORT void CheckMemoryReadOnly(const void* ptr); |
| |
| // Abstract out platform-specific methods to get the beginning and end of the |
| // PROTECTED_MEMORY_SECTION. ProtectedMemoryEnd returns a pointer to the byte |
| // past the end of the PROTECTED_MEMORY_SECTION. |
| inline constexpr void* kProtectedMemoryStart = &__start_protected_memory; |
| inline constexpr void* kProtectedMemoryEnd = &__stop_protected_memory; |
| } // namespace internal |
| #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| |
| // Provide some common functionality for `AutoWritableMemory<T>`. |
| class BASE_EXPORT AutoWritableMemoryBase { |
| protected: |
| #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| // Checks that `object` is located within the interval |
| // (internal::kProtectedMemoryStart, internal::kProtectedMemoryEnd). |
| template <typename T> |
| static bool IsObjectInProtectedSection(const T& object) { |
| const T* const ptr = std::addressof(object); |
| const T* const ptr_end = ptr + 1; |
| return (ptr >= internal::kProtectedMemoryStart) && |
| (ptr_end <= internal::kProtectedMemoryEnd); |
| } |
| |
| template <typename T> |
| static void CheckObjectReadOnly(const T& object) { |
| internal::CheckMemoryReadOnly(std::addressof(object)); |
| } |
| |
| template <typename T> |
| static bool SetObjectReadWrite(T& object) { |
| T* const ptr = std::addressof(object); |
| T* const ptr_end = ptr + 1; |
| return SetMemoryReadWrite(ptr, ptr_end); |
| } |
| |
| static bool SetProtectedSectionReadOnly() { |
| return SetMemoryReadOnly(internal::kProtectedMemoryStart, |
| internal::kProtectedMemoryEnd); |
| } |
| |
| static bool IsSectionStartPageAligned() { |
| const uintptr_t protected_memory_start = |
| reinterpret_cast<uintptr_t>(internal::kProtectedMemoryStart); |
| const uintptr_t page_start = |
| bits::AlignDown(protected_memory_start, GetPageSize()); |
| return page_start == protected_memory_start; |
| } |
| |
| // When linking, each DSO will have its own protected section. We can't keep |
| // track of each section, yet we have to ensure to always unlock and re-lock |
| // the correct section. |
| // |
| // We solve this by defining a separate global writers variable (explained |
| // below) in every dynamic shared object (DSO) that includes this header. To |
| // do that we use this structure to define global writer data without |
| // duplicate symbol errors. |
| // |
| // Storing the data in a substructure is required to store `writers` within |
| // the protected subsection. If `writers` and `writers_lock()` are located |
| // directly in `AutoWritableMemoryBase`, for unknown reasons `writers` is not |
| // placed into the protected section. |
| struct WriterData { |
| // `writers` is a global holding the number of ProtectedMemory instances set |
| // writable, used to avoid races setting protected memory readable/writable. |
| // When this reaches zero the protected memory region is set read only. |
| // Access is controlled by writers_lock. |
| // |
| // Declare writers in the protected memory section to avoid the scenario |
| // where an attacker could overwrite it with a large value and invoke code |
| // that constructs and destructs an AutoWritableMemory. After such a call |
| // protected memory would still be set writable because writers > 0. |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) |
| // On Linux, the protected memory section is not automatically page aligned. |
| // This means that attempts to reset the protected memory region to readonly |
| // will set some of the preceding section that is on the same page readonly |
| // as well. By forcing the writers to be aligned on a multiple of the page |
| // size, we can ensure the protected memory section starts on a page |
| // boundary, preventing this issue. |
| constinit __attribute__((section("protected_memory"), |
| aligned(kProtectedMemoryAlignment))) |
| #else |
| DEFINE_PROTECTED_DATA |
| #endif |
| static inline size_t writers GUARDED_BY(writers_lock()) = 0; |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) |
| // On Linux, there is no guarantee the section following the protected |
| // memory section is page aligned. This can result in attempts to change |
| // the access permissions of the end of the protected memory section |
| // overflowing to the next section. To ensure this doesn't happen, a buffer |
| // section called protected_memory_buffer is created. Since the very first |
| // variable declared after writers is put in this section, it will be |
| // created as the next section after the protected memory section (since |
| // sections are created in the order they are declared in the source file). |
| // By explicitly setting the alignment of the variable to a multiple of the |
| // page size, we can ensure this buffer section starts on a page boundary. |
| // This guarantees that altering the access permissions of the end of the |
| // protected memory section will not affect the next section. The variable |
| // protected_memory_section_buffer serves no purpose other than to ensure |
| // protected_memory_buffer section is created. |
| constinit |
| __attribute__((section("protected_memory_buffer"), |
| aligned(kProtectedMemoryAlignment))) static inline bool |
| protected_memory_section_buffer = false; |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) |
| |
| // Synchronizes access to the writers variable and the simultaneous actions |
| // that need to happen alongside writers changes, e.g. setting the protected |
| // memory region readable when writers is decremented to 0. |
| static Lock& writers_lock() { |
| static NoDestructor<Lock> writers_lock; |
| return *writers_lock; |
| } |
| }; |
| |
| private: |
| // Abstract out platform-specific memory APIs. |end| points to the byte |
| // past the end of the region of memory having its memory protections |
| // changed. |
| static bool SetMemoryReadWrite(void* start, void* end); |
| static bool SetMemoryReadOnly(void* start, void* end); |
| #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| }; |
| |
| #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| // This class acts as a static initializer that initializes the protected memory |
| // region to read only. It will be engaged the first time a protected memory |
| // object is statically initialized. |
| class BASE_EXPORT AutoWritableMemoryInitializer |
| : public AutoWritableMemoryBase { |
| public: |
| #if BUILDFLAG(IS_WIN) |
| AutoWritableMemoryInitializer() { CHECK(IsSectionStartPageAligned()); } |
| #else |
| AutoWritableMemoryInitializer() LOCKS_EXCLUDED(WriterData::writers_lock()) { |
| CHECK(IsSectionStartPageAligned()); |
| // This doesn't need to be run on Windows, because the linker can pre-set |
| // the memory to read-only. |
| AutoLock auto_lock(WriterData::writers_lock()); |
| // Reset the writers variable to 0 to ensure that the attacker didn't set |
| // the variable to something large before the section was read-only. |
| WriterData::writers = 0; |
| CHECK(SetProtectedSectionReadOnly()); |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) |
| // Set the protected_memory_section_buffer to true to ensure the buffer |
| // section is created. If a variable is declared but not used the memory |
| // section won't be created. |
| WriterData::protected_memory_section_buffer = true; |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID) |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| }; |
| #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| |
| // A class that sets a given ProtectedMemory variable writable while the |
| // AutoWritableMemory is in scope. This class implements the logic for setting |
| // the protected memory region read-only/read-write in a thread-safe manner. |
| // |
| // |AutoWritableMemory| affects the write-permissions of _all_ protected data |
| // for a DSO, not just of the instance that it's being passed! All protected |
| // data is stored within the same binary section. At the same time, the OS-level |
| // support enforcing write protection can only be changed at page level. To |
| // allow a more fine grained control a dedicated page per instance of protected |
| // data would be required. |
| template <typename T> |
| class AutoWritableMemory : public AutoWritableMemoryBase { |
| public: |
| explicit AutoWritableMemory(ProtectedMemory<T>& protected_memory) |
| #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| LOCKS_EXCLUDED(WriterData::writers_lock()) |
| #endif |
| : protected_memory_(protected_memory) { |
| #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| |
| // Check that the data is located in the protected section to |
| // ensure consistency of data. |
| CHECK(IsObjectInProtectedSection(protected_memory_->data_)); |
| CHECK(IsObjectInProtectedSection(WriterData::writers)); |
| |
| { |
| AutoLock auto_lock(WriterData::writers_lock()); |
| |
| if (WriterData::writers == 0) { |
| CheckObjectReadOnly(protected_memory_->data_); |
| CheckObjectReadOnly(WriterData::writers); |
| CHECK(SetObjectReadWrite(WriterData::writers)); |
| } |
| |
| ++WriterData::writers; |
| } |
| |
| CHECK(SetObjectReadWrite(protected_memory_->data_)); |
| #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| } |
| |
| ~AutoWritableMemory() |
| #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| LOCKS_EXCLUDED(WriterData::writers_lock()) |
| #endif |
| { |
| #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| AutoLock auto_lock(WriterData::writers_lock()); |
| CHECK_GT(WriterData::writers, 0u); |
| --WriterData::writers; |
| |
| if (WriterData::writers == 0) { |
| // Lock the whole section of protected memory and set _all_ instances of |
| // ProtectedMemory to non-writeable. |
| CHECK(SetProtectedSectionReadOnly()); |
| CheckObjectReadOnly( |
| *static_cast<const char*>(internal::kProtectedMemoryStart)); |
| CheckObjectReadOnly(WriterData::writers); |
| } |
| #endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| } |
| |
| AutoWritableMemory(AutoWritableMemory& original) = delete; |
| AutoWritableMemory& operator=(AutoWritableMemory& original) = delete; |
| AutoWritableMemory(AutoWritableMemory&& original) = delete; |
| AutoWritableMemory& operator=(AutoWritableMemory&& original) = delete; |
| |
| T& GetProtectedData() { return protected_memory_->data_.GetReference(); } |
| T* GetProtectedDataPtr() { return protected_memory_->data_.GetPointer(); } |
| |
| template <typename... U> |
| void emplace(U&&... args) { |
| protected_memory_->data_.emplace(std::forward<U>(args)...); |
| } |
| |
| private: |
| const raw_ref<ProtectedMemory<T>> protected_memory_; |
| }; |
| |
| // Helper class for creating simple ProtectedMemory static initializers. |
| class ProtectedMemoryInitializer { |
| public: |
| template <typename T, typename... U> |
| explicit ProtectedMemoryInitializer(ProtectedMemory<T>& protected_memory, |
| U&&... args) { |
| InitializeAutoWritableMemory(); |
| AutoWritableMemory writer(protected_memory); |
| writer.emplace(std::forward<U>(args)...); |
| } |
| |
| ProtectedMemoryInitializer() = delete; |
| ProtectedMemoryInitializer(const ProtectedMemoryInitializer&) = delete; |
| ProtectedMemoryInitializer& operator=(const ProtectedMemoryInitializer&) = |
| delete; |
| |
| private: |
| void InitializeAutoWritableMemory() { |
| #if BUILDFLAG(PROTECTED_MEMORY_ENABLED) |
| static AutoWritableMemoryInitializer memory_initializer; |
| #else |
| // No-op if protected memory is not enabled. |
| #endif |
| } |
| }; |
| |
| } // namespace base |
| |
| #endif // BASE_MEMORY_PROTECTED_MEMORY_H_ |