| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef BASE_MEMORY_SAFETY_CHECKS_H_ |
| #define BASE_MEMORY_SAFETY_CHECKS_H_ |
| |
| #include <stdint.h> |
| |
| #include <new> |
| #include <type_traits> |
| |
| #include "base/base_export.h" |
| #include "base/compiler_specific.h" |
| #include "base/dcheck_is_on.h" |
| #include "base/memory/stack_allocated.h" |
| #include "partition_alloc/buildflags.h" |
| |
| #if PA_BUILDFLAG(USE_PARTITION_ALLOC) |
| #include "base/allocator/partition_alloc_support.h" |
| #include "partition_alloc/partition_alloc_constants.h" // nogncheck |
| #endif |
| |
| #if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| #include "partition_alloc/shim/allocator_shim_default_dispatch_to_partition_alloc.h" |
| #endif // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| |
| // This header defines `ADVANCED_MEMORY_SAFETY_CHECKS()` macro. |
| // They can be used to specify a class/struct that is targeted to perform |
| // additional CHECKS across variety of memory safety mechanisms such as |
| // PartitionAllocator. |
| // ``` |
| // class Foo { |
| // ADVANCED_MEMORY_SAFETY_CHECKS(); |
| // } |
| // ``` |
| // Checks here are disabled by default because of their performance cost. |
| // Currently, the macro is managed by the memory safety team internally and |
| // you should not add / remove it manually. |
| // |
| // Additional checks here are categorized into either one of enum |
| // `MemorySafetyCheck`. Some of them are too costly and disabled even for |
| // `ADVANCED_MEMORY_SAFETY_CHECKS()` annotated types. These checks can be |
| // enabled by passing optional arguments to the macro. |
| // ``` |
| // class Foo { |
| // ADVANCED_MEMORY_SAFETY_CHECKS( |
| // /*enable=*/ kFoo | kBar); |
| // } |
| // ``` |
| // It is also possible to disable default checks for annotated types. |
| // ``` |
| // class Foo { |
| // ADVANCED_MEMORY_SAFETY_CHECKS( |
| // /*enable=*/ kFoo, |
| // /*disable=*/ kBaz); |
| // } |
| // ``` |
| |
| namespace base { |
| |
| // We cannot hide things behind anonymous namespace because they are referenced |
| // via macro, which can be defined anywhere. |
| // To avoid tainting ::base namespace, define things inside this namespace. |
| namespace internal { |
| |
| enum class MemorySafetyCheck : uint32_t { |
| kNone = 0, |
| kForcePartitionAlloc = (1u << 0), |
| // Enables |FreeFlags::kSchedulerLoopQuarantineForAdvancedMemorySafetyChecks|. |
| // Requires PA-E. |
| kSchedulerLoopQuarantine = (1u << 1), |
| }; |
| |
| constexpr MemorySafetyCheck operator|(MemorySafetyCheck a, |
| MemorySafetyCheck b) { |
| return static_cast<MemorySafetyCheck>(static_cast<uint32_t>(a) | |
| static_cast<uint32_t>(b)); |
| } |
| |
| constexpr MemorySafetyCheck operator&(MemorySafetyCheck a, |
| MemorySafetyCheck b) { |
| return static_cast<MemorySafetyCheck>(static_cast<uint32_t>(a) & |
| static_cast<uint32_t>(b)); |
| } |
| |
| constexpr MemorySafetyCheck operator~(MemorySafetyCheck a) { |
| return static_cast<MemorySafetyCheck>(~static_cast<uint32_t>(a)); |
| } |
| |
| // Set of checks for ADVANCED_MEMORY_SAFETY_CHECKS() annotated objects. |
| constexpr auto kAdvancedMemorySafetyChecks = |
| MemorySafetyCheck::kForcePartitionAlloc | |
| MemorySafetyCheck::kSchedulerLoopQuarantine; |
| |
| // Define type traits to determine type |T|'s memory safety check status. |
| namespace { |
| |
| // Allocator type traits. |
| constexpr bool ShouldUsePartitionAlloc(MemorySafetyCheck checks) { |
| #if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| return static_cast<bool>(checks & |
| (MemorySafetyCheck::kForcePartitionAlloc | |
| MemorySafetyCheck::kSchedulerLoopQuarantine)); |
| #else |
| return false; |
| #endif // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| } |
| |
| #if PA_BUILDFLAG(USE_PARTITION_ALLOC) |
| |
| // Returns |partition_alloc::AllocFlags| corresponding to |checks|. |
| constexpr partition_alloc::AllocFlags GetAllocFlags(MemorySafetyCheck checks) { |
| return partition_alloc::AllocFlags::kReturnNull | |
| partition_alloc::AllocFlags::kNoHooks; |
| } |
| |
| // Returns |partition_alloc::FreeFlags| corresponding to |checks|. |
| constexpr partition_alloc::FreeFlags GetFreeFlags(MemorySafetyCheck checks) { |
| auto flags = partition_alloc::FreeFlags::kNone; |
| if (static_cast<bool>(checks & MemorySafetyCheck::kSchedulerLoopQuarantine)) { |
| flags |= partition_alloc::FreeFlags:: |
| kSchedulerLoopQuarantineForAdvancedMemorySafetyChecks; |
| } |
| return flags; |
| } |
| |
| #endif // PA_BUILDFLAG(USE_PARTITION_ALLOC) |
| |
| } // namespace |
| |
| // Public utility type traits. |
| template <typename T> |
| inline constexpr MemorySafetyCheck get_memory_safety_checks = [] { |
| if constexpr (requires { T::kMemorySafetyChecks; }) { |
| return T::kMemorySafetyChecks; |
| } else { |
| return static_cast<MemorySafetyCheck>(0); |
| } |
| }(); |
| |
| template <typename T, MemorySafetyCheck c> |
| inline constexpr bool is_memory_safety_checked = |
| (get_memory_safety_checks<T> & c) == c; |
| |
| // Allocator functions. |
| #if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| ALWAYS_INLINE partition_alloc::PartitionRoot* |
| GetPartitionRootForMemorySafetyCheckedAllocation() { |
| return allocator_shim::internal::PartitionAllocMalloc::Allocator(); |
| } |
| #endif // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| |
| template <MemorySafetyCheck checks> |
| NOINLINE void* HandleMemorySafetyCheckedOperatorNew(std::size_t count) { |
| #if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| if constexpr (ShouldUsePartitionAlloc(checks)) { |
| return GetPartitionRootForMemorySafetyCheckedAllocation() |
| ->AllocInline<GetAllocFlags(checks)>(count); |
| } |
| #endif // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| return ::operator new(count); |
| } |
| |
| template <MemorySafetyCheck checks> |
| NOINLINE void* HandleMemorySafetyCheckedOperatorNew( |
| std::size_t count, |
| std::align_val_t alignment) { |
| #if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| if constexpr (ShouldUsePartitionAlloc(checks)) { |
| return GetPartitionRootForMemorySafetyCheckedAllocation() |
| ->AlignedAlloc<GetAllocFlags(checks)>(static_cast<size_t>(alignment), |
| count); |
| } |
| #endif // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| return ::operator new(count, alignment); |
| } |
| |
| template <MemorySafetyCheck checks> |
| NOINLINE void HandleMemorySafetyCheckedOperatorDelete(void* ptr) { |
| #if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| if constexpr (ShouldUsePartitionAlloc(checks)) { |
| GetPartitionRootForMemorySafetyCheckedAllocation() |
| ->Free<GetFreeFlags(checks)>(ptr); |
| return; |
| } |
| #endif // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| ::operator delete(ptr); |
| } |
| |
| template <MemorySafetyCheck checks> |
| NOINLINE void HandleMemorySafetyCheckedOperatorDelete( |
| void* ptr, |
| std::align_val_t alignment) { |
| #if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| if constexpr (ShouldUsePartitionAlloc(checks)) { |
| GetPartitionRootForMemorySafetyCheckedAllocation() |
| ->Free<GetFreeFlags(checks)>(ptr); |
| return; |
| } |
| #endif // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| ::operator delete(ptr, alignment); |
| } |
| |
| } // namespace internal |
| |
| // Macros to annotate class/struct's default memory safety check. |
| // ADVANCED_MEMORY_SAFETY_CHECKS(): Enable Check |kAdvancedChecks| for this |
| // object. |
| // |
| // Note that if you use this macro at the top of struct declaration, the |
| // declaration context would be left as |private|. Please switch it back to |
| // |public| manually if needed. |
| // |
| // struct ObjectWithAdvancedChecks { |
| // ADVANCED_MEMORY_SAFETY_CHECKS(); |
| // public: |
| // int public_field; |
| // }; |
| #define MEMORY_SAFETY_CHECKS_INTERNAL(SPECIFIER, DEFAULT_CHECKS, \ |
| ENABLED_CHECKS, DISABLED_CHECKS, ...) \ |
| public: \ |
| static constexpr auto kMemorySafetyChecks = [] { \ |
| using enum base::internal::MemorySafetyCheck; \ |
| return (DEFAULT_CHECKS | ENABLED_CHECKS) & ~(DISABLED_CHECKS); \ |
| }(); \ |
| SPECIFIER static void* operator new(std::size_t count) { \ |
| return base::internal::HandleMemorySafetyCheckedOperatorNew< \ |
| kMemorySafetyChecks>(count); \ |
| } \ |
| SPECIFIER static void* operator new(std::size_t count, \ |
| std::align_val_t alignment) { \ |
| return base::internal::HandleMemorySafetyCheckedOperatorNew< \ |
| kMemorySafetyChecks>(count, alignment); \ |
| } \ |
| /* Though we do not hook placement new, we need to define this */ \ |
| /* explicitly to allow it. */ \ |
| ALWAYS_INLINE static void* operator new(std::size_t, void* ptr) { \ |
| return ptr; \ |
| } \ |
| SPECIFIER static void operator delete(void* ptr) noexcept { \ |
| base::internal::HandleMemorySafetyCheckedOperatorDelete< \ |
| kMemorySafetyChecks>(ptr); \ |
| } \ |
| SPECIFIER static void operator delete(void* ptr, \ |
| std::align_val_t alignment) noexcept { \ |
| base::internal::HandleMemorySafetyCheckedOperatorDelete< \ |
| kMemorySafetyChecks>(ptr, alignment); \ |
| } \ |
| \ |
| private: \ |
| static_assert(true) /* semicolon here */ |
| |
| #if DCHECK_IS_ON() |
| // Specify NOINLINE to display the operator on a stack trace. |
| // When 2 args provided, these two are passed to `ENABLED_CHECKS` and |
| // `DISABLED_CHECKS`. A couple of `MemorySafetyCheck::kNone` is ignored. |
| // When 1 arg provided, the one is passed to `ENABLED_CHECKS` and the first |
| // `MemorySafetyCheck::kNone` serves a default value for `DISABLED_CHECKS`. |
| // When 0 arg provided, both of `MemorySafetyCheck::kNone`s serve as default |
| // values for `ENABLED_CHECKS` and `DISABLED_CHECKS` accordingly. |
| #define ADVANCED_MEMORY_SAFETY_CHECKS(...) \ |
| MEMORY_SAFETY_CHECKS_INTERNAL( \ |
| NOINLINE NOT_TAIL_CALLED, \ |
| base::internal::kAdvancedMemorySafetyChecks __VA_OPT__(, ) __VA_ARGS__, \ |
| kNone, kNone) |
| #else |
| #define ADVANCED_MEMORY_SAFETY_CHECKS(...) \ |
| MEMORY_SAFETY_CHECKS_INTERNAL( \ |
| ALWAYS_INLINE, \ |
| base::internal::kAdvancedMemorySafetyChecks __VA_OPT__(, ) __VA_ARGS__, \ |
| kNone, kNone) |
| #endif // DCHECK_IS_ON() |
| |
| // When a struct/class with `ADVANCED_MEMORY_SAFETY_CHECKS()` is inherited, a |
| // derived struct/class operator will use customized `operator new()` and |
| // `operator delete()` too. If a class has multiple base classes with the macro, |
| // a compiler may complain ambiguity between multiple `operator new()`s. On the |
| // other hand, if a class uses private inheritance, a compiler may report |
| // private `operator new()` that is making impossible to `new` that class. We |
| // have two utility macros to resolve these issues: |
| // - `INHERIT_MEMORY_SAFETY_CHECKS(BaseClass)` |
| // Explicitly exports operators from given `BaseClass` to re-apply |
| // checks specified in the parent class. This is the recommended option as |
| // a derived class is likely to have the same characteristics to its baes |
| // class. This macro accepts additional arguments to overwrite |
| // `BaseClass`'s opted-in checks. |
| // ``` |
| // INHERIT_MEMORY_SAFETY_CHECKS(BaseClass, |
| // /*enable=*/ kFoo | kBar, |
| // /*disable=*/ kBaz); |
| // ``` |
| // - `DEFAULT_MEMORY_SAFETY_CHECKS()` |
| // Re-define default `operator new()` and `operator delete()` using |
| // global operators that comes with default checks. This macro accepts |
| // additional arguments to enable some checks manually. |
| // ``` |
| // DEFAULT_MEMORY_SAFETY_CHECKS(BaseClass, |
| // /*enable=*/ kFoo | kBar); |
| // ``` |
| // |
| // Note that if you use these macros at the top of struct declaration, the |
| // declaration context would be left as |private|. Please switch it back to |
| // |public| manually if needed. |
| #define INHERIT_MEMORY_SAFETY_CHECKS(BASE_CLASS, ...) \ |
| MEMORY_SAFETY_CHECKS_INTERNAL(ALWAYS_INLINE, \ |
| BASE_CLASS::kMemorySafetyChecks __VA_OPT__(, ) \ |
| __VA_ARGS__, \ |
| kNone, kNone) |
| |
| #define DEFAULT_MEMORY_SAFETY_CHECKS(...) \ |
| MEMORY_SAFETY_CHECKS_INTERNAL( \ |
| ALWAYS_INLINE, kNone __VA_OPT__(, ) __VA_ARGS__, kNone, kNone) |
| |
| // Utility function to detect Double-Free or Out-of-Bounds writes. |
| // This function can be called to memory assumed to be valid. |
| // If not, this may crash (not guaranteed). |
| // This is useful if you want to investigate crashes at `free()`, |
| // to know which point at execution it goes wrong. |
| BASE_EXPORT void CheckHeapIntegrity(const void* ptr); |
| |
| // The function here is called right before crashing with |
| // `DoubleFreeOrCorruptionDetected()`. We provide an address for the slot start |
| // to the function, and it may use that for debugging purpose. |
| void SetDoubleFreeOrCorruptionDetectedFn(void (*fn)(uintptr_t)); |
| |
| // Utility class to exclude deallocation from optional safety checks when an |
| // instance is on the stack. Can be applied to performance critical functions. |
| class BASE_EXPORT ScopedSafetyChecksExclusion { |
| STACK_ALLOCATED(); |
| |
| public: |
| // Make this non-trivially-destructible to suppress unused variable warning. |
| ~ScopedSafetyChecksExclusion() {} // NOLINT(modernize-use-equals-default) |
| |
| private: |
| #if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| base::allocator::ScopedSchedulerLoopQuarantineExclusion |
| opt_out_scheduler_loop_quarantine_; |
| #endif // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| }; |
| |
| #if PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| using base::allocator::SchedulerLoopQuarantineScanPolicyUpdater; |
| #else |
| class SchedulerLoopQuarantineScanPolicyUpdater { |
| public: |
| ALWAYS_INLINE void DisallowScanlessPurge() {} |
| ALWAYS_INLINE void AllowScanlessPurge() {} |
| }; |
| #endif // PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) |
| |
| } // namespace base |
| |
| #endif // BASE_MEMORY_SAFETY_CHECKS_H_ |