blob: 710f6c8179e4e231862d98f352ad8846e0fd0230 [file]
// 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. With template parameter `ConstructLazily` set to `true`, the
// value is constructed lazily when initialized through
// `ProtectedMemoryInitializer`. In this case, explicit initialization through
// `ProtectedMemoryInitializer` is mandatory to prevent accessing uninitialized
// memory. If data is accessed without initialization a CHECK triggers.
//
// `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, false> 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, true> 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/check.h"
#include "base/check_op.h"
#include "base/gtest_prod_util.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"))
#else
#error "Protected Memory is currently only supported on Windows."
#endif // BUILDFLAG(IS_WIN)
#else
#define DECLARE_PROTECTED_DATA constinit
#define DEFINE_PROTECTED_DATA DECLARE_PROTECTED_DATA
#endif // BUILDFLAG(PROTECTED_MEMORY_ENABLED)
namespace base {
template <typename T, bool ConstructLazily>
class AutoWritableMemory;
FORWARD_DECLARE_TEST(ProtectedMemoryDeathTest, VerifyTerminationOnAccess);
namespace internal {
// Helper classes which store the data and implement and initialization
// according to `ConstructLazily`. With `ConstructLazily` set to false, the
// instance of T is created upon construction time, whereas with
// `ConstructLazily` set to true, the instance of T is only constructed when
// emplace is called.
template <typename T, bool ConstructLazily>
class ProtectedDataHolder {
public:
consteval ProtectedDataHolder() = default;
template <typename... U>
consteval explicit ProtectedDataHolder(U&&... args)
: data_(std::forward<U>(args)...) {}
T& GetReference() { return data_; }
const T& GetReference() const { return data_; }
T* GetPointer() { return &data_; }
const T* GetPointer() const { return &data_; }
template <typename... U>
void emplace(U&&... data) {
data_ = T(std::forward<U>(data)...);
}
private:
T data_ = T();
};
template <typename T>
class ProtectedDataHolder<T, true /*ConstructLazily*/> {
public:
consteval ProtectedDataHolder() = default;
T& GetReference() { return *GetPointer(); }
const T& GetReference() const { 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)] = {0};
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, bool ConstructLazily = false>
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.
template <
typename... U,
bool ConstructLazilyP = ConstructLazily,
std::enable_if_t<!ConstructLazilyP || sizeof...(U) == 0, bool> = true>
consteval explicit ProtectedMemory(U&&... args)
: data_(std::forward<U>(args)...) {
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, ConstructLazily>;
FRIEND_TEST_ALL_PREFIXES(ProtectedMemoryDeathTest, VerifyTerminationOnAccess);
internal::ProtectedDataHolder<T, ConstructLazily> data_;
};
#if BUILDFLAG(PROTECTED_MEMORY_ENABLED)
namespace internal {
// Checks that the byte at `ptr` is read-only.
BASE_EXPORT bool IsMemoryReadOnly(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 bool IsObjectReadOnly(const T& object) {
return internal::IsMemoryReadOnly(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);
}
// 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.
DEFINE_PROTECTED_DATA
static inline size_t writers GUARDED_BY(writers_lock()) = 0;
// 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)
};
// 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, bool ConstructLazily>
class AutoWritableMemory : public AutoWritableMemoryBase {
public:
explicit AutoWritableMemory(
ProtectedMemory<T, ConstructLazily>& 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));
{
base::AutoLock auto_lock(WriterData::writers_lock());
if (WriterData::writers == 0) {
CHECK(IsObjectReadOnly(protected_memory_->data_));
CHECK(IsObjectReadOnly(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)
base::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
// base::ProtectedMemory to non-writeable.
CHECK(SetProtectedSectionReadOnly());
CHECK(IsObjectReadOnly(
*static_cast<const char*>(internal::kProtectedMemoryStart)));
CHECK(IsObjectReadOnly(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, ConstructLazily>> protected_memory_;
};
// Helper class for creating simple ProtectedMemory static initializers.
class ProtectedMemoryInitializer {
public:
template <typename T, bool ConstructLazily, typename... U>
explicit ProtectedMemoryInitializer(
ProtectedMemory<T, ConstructLazily>& protected_memory,
U&&... args) {
AutoWritableMemory writer(protected_memory);
writer.emplace(std::forward<U>(args)...);
}
ProtectedMemoryInitializer() = delete;
ProtectedMemoryInitializer(const ProtectedMemoryInitializer&) = delete;
ProtectedMemoryInitializer& operator=(const ProtectedMemoryInitializer&) =
delete;
};
} // namespace base
#endif // BASE_MEMORY_PROTECTED_MEMORY_H_