From 294887ad740dca67726369a8e4cbcfb4fa71a766 Mon Sep 17 00:00:00 2001 From: Nathaniel Filardo Date: Sat, 21 Nov 2020 03:18:48 +0000 Subject: [PATCH] SP: introduce CapPtr<> wrapper type & bounds taxonomy This commit just fills out ds/ptrwrap.h with the new types and adds utility methods to ds/address.h. --- src/ds/address.h | 80 +++++++++++++- src/ds/ptrwrap.h | 268 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 346 insertions(+), 2 deletions(-) diff --git a/src/ds/address.h b/src/ds/address.h index 399b1b0..56fe121 100644 --- a/src/ds/address.h +++ b/src/ds/address.h @@ -1,6 +1,7 @@ #pragma once #include "../pal/pal_consts.h" #include "bits.h" +#include "ptrwrap.h" #include @@ -24,6 +25,13 @@ namespace snmalloc return reinterpret_cast(reinterpret_cast(base) + diff); } + template + inline CapPtr + pointer_offset(CapPtr base, size_t diff) + { + return CapPtr(pointer_offset(base.unsafe_capptr, diff)); + } + /** * Perform pointer arithmetic and return the adjusted pointer. */ @@ -33,6 +41,14 @@ namespace snmalloc return reinterpret_cast(reinterpret_cast(base) + diff); } + template + inline CapPtr + pointer_offset_signed(CapPtr base, ptrdiff_t diff) + { + return CapPtr( + pointer_offset_signed(base.unsafe_capptr, diff)); + } + /** * Cast from a pointer type to an address. */ @@ -42,16 +58,36 @@ namespace snmalloc return reinterpret_cast(ptr); } + /* + * Provide address_cast methods for the provenance-hinting pointer wrapper + * types as well. While we'd prefer that these be methods on the wrapper + * type, they have to be defined later, because the AAL both define address_t, + * as per above, and uses the wrapper types in its own definition, e.g., of + * capptr_bound. + */ + + template + inline address_t address_cast(CapPtr a) + { + return address_cast(a.unsafe_capptr); + } + /** * Test if a pointer is aligned to a given size, which must be a power of * two. */ template - static inline bool is_aligned_block(void* p, size_t size) + static inline bool is_aligned_block(address_t p, size_t size) { static_assert(bits::is_pow2(alignment)); - return ((address_cast(p) | size) & (alignment - 1)) == 0; + return ((p | size) & (alignment - 1)) == 0; + } + + template + static inline bool is_aligned_block(void* p, size_t size) + { + return is_aligned_block(address_cast(p), size); } /** @@ -76,6 +112,12 @@ namespace snmalloc } } + template + inline CapPtr pointer_align_down(CapPtr p) + { + return CapPtr(pointer_align_down(p.unsafe_capptr)); + } + template inline address_t address_align_down(address_t p) { @@ -104,6 +146,12 @@ namespace snmalloc } } + template + inline CapPtr pointer_align_up(CapPtr p) + { + return CapPtr(pointer_align_up(p.unsafe_capptr)); + } + template inline address_t address_align_up(address_t p) { @@ -144,6 +192,13 @@ namespace snmalloc #endif } + template + inline CapPtr + pointer_align_up(CapPtr p, size_t alignment) + { + return CapPtr(pointer_align_up(p.unsafe_capptr, alignment)); + } + /** * Compute the difference in pointers in units of char. base is * expected to point to the base of some (sub)allocation into which cursor @@ -156,6 +211,16 @@ namespace snmalloc static_cast(cursor) - static_cast(base)); } + template< + typename T = void, + typename U = void, + enum capptr_bounds Tbounds, + enum capptr_bounds Ubounds> + inline size_t pointer_diff(CapPtr base, CapPtr cursor) + { + return pointer_diff(base.unsafe_capptr, cursor.unsafe_capptr); + } + /** * Compute the difference in pointers in units of char. This can be used * across allocations. @@ -166,4 +231,15 @@ namespace snmalloc static_cast(cursor) - static_cast(base)); } + template< + typename T = void, + typename U = void, + enum capptr_bounds Tbounds, + enum capptr_bounds Ubounds> + inline ptrdiff_t + pointer_diff_signed(CapPtr base, CapPtr cursor) + { + return pointer_diff_signed(base.unsafe_capptr, cursor.unsafe_capptr); + } + } // namespace snmalloc diff --git a/src/ds/ptrwrap.h b/src/ds/ptrwrap.h index a65d034..1de2ae6 100644 --- a/src/ds/ptrwrap.h +++ b/src/ds/ptrwrap.h @@ -15,4 +15,272 @@ namespace snmalloc template using AtomicPointer = std::atomic; + + /** + * Summaries of StrictProvenance metadata. We abstract away the particular + * size and any offset into the bounds. + * + * CBArena is as powerful as our pointers get: they're results from mmap(), + * and so confer as much authority as the kernel has given us. + * + * CBChunk is restricted to either a single chunk (SUPERSLAB_SIZE) or perhaps + * to several if we've requesed a large allocation (see capptr_chunk_is_alloc + * and its uses). + * + * CBChunkD is curious: we often use CBArena-bounded pointers to derive + * pointers to Allocslab metadata, and on most fast paths these pointers end + * up being ephemeral. As such, on NDEBUG builds, we elide the capptr_bounds + * that would bound these to chunks and instead just unsafely inherit the + * CBArena bounds. The use of CBChunkD thus helps to ensure that we + * eventually do invoke capptr_bounds when these pointers end up being longer + * lived! + * + * *E forms are "exported" and have had platform constraints applied. That + * means, for example, on CheriBSD, that they have had their VMMAP permission + * stripped. + * + * Yes, I wish the start-of-comment characters were aligned below as well. + * I blame clang format. + */ + enum capptr_bounds + { + /* Spatial Notes */ + CBArena, /* Arena */ + CBChunkD, /* Arena Chunk-bounded in debug; internal use only! */ + CBChunk, /* Chunk */ + CBChunkE, /* Chunk (+ platform constraints) */ + CBAlloc, /* Alloc */ + CBAllocE /* Alloc (+ platform constraints) */ + }; + + /** + * Compute the "exported" variant of a capptr_bounds annotation. This is + * used by the PAL's capptr_export function to compute its return value's + * annotation. + */ + template + constexpr capptr_bounds capptr_export_type() + { + static_assert( + (B == CBChunk) || (B == CBAlloc), "capptr_export_type of bad type"); + + switch (B) + { + case CBChunk: + return CBChunkE; + case CBAlloc: + return CBAllocE; + } + } + + template + constexpr bool capptr_is_bounds_refinement() + { + switch (BI) + { + case CBAllocE: + return BO == CBAllocE; + case CBAlloc: + return BO == CBAlloc; + case CBChunkE: + return BO == CBAllocE || BO == CBChunkE; + case CBChunk: + return BO == CBAlloc || BO == CBChunk || BO == CBChunkD; + case CBChunkD: + return BO == CBAlloc || BO == CBChunk || BO == CBChunkD; + case CBArena: + return BO == CBAlloc || BO == CBChunk || BO == CBChunkD || + BO == CBArena; + } + } + + /** + * A pointer annotated with a "phantom type parameter" carrying a static + * summary of its StrictProvenance metadata. + */ + template + struct CapPtr + { + T* unsafe_capptr; + + /** + * nullptr is implicitly constructable at any bounds type + */ + CapPtr(const std::nullptr_t n) : unsafe_capptr(n) {} + + CapPtr() : CapPtr(nullptr) {} + + /** + * all other constructions must be explicit + */ + explicit CapPtr(T* p) : unsafe_capptr(p) {} + + /** + * Allow static_cast<>-s that preserve bounds but vary the target type. + */ + template + SNMALLOC_FAST_PATH CapPtr as_static() + { + return CapPtr(static_cast(this->unsafe_capptr)); + } + + SNMALLOC_FAST_PATH CapPtr as_void() + { + return this->as_static(); + } + + /** + * A more aggressive bounds-preserving cast, using reinterpret_cast + */ + template + SNMALLOC_FAST_PATH CapPtr as_reinterpret() + { + return CapPtr(reinterpret_cast(this->unsafe_capptr)); + } + + SNMALLOC_FAST_PATH bool operator==(const CapPtr& rhs) const + { + return this->unsafe_capptr == rhs.unsafe_capptr; + } + + SNMALLOC_FAST_PATH bool operator!=(const CapPtr& rhs) const + { + return this->unsafe_capptr != rhs.unsafe_capptr; + } + + SNMALLOC_FAST_PATH bool operator<(const CapPtr& rhs) const + { + return this->unsafe_capptr < rhs.unsafe_capptr; + } + + SNMALLOC_FAST_PATH T* operator->() const + { + /* + * CBAllocE bounds are associated with objects coming from or going to the + * client; we should be doing nothing with them. + */ + static_assert(bounds != CBAllocE); + return this->unsafe_capptr; + } + }; + + static_assert(sizeof(CapPtr) == sizeof(void*)); + static_assert(alignof(CapPtr) == alignof(void*)); + + template + using CapPtrCBArena = CapPtr; + + template + using CapPtrCBChunk = CapPtr; + + template + using CapPtrCBChunkE = CapPtr; + + template + using CapPtrCBAlloc = CapPtr; + + /** + * Sometimes (with large allocations) we really mean the entire chunk (or even + * several chunks) to be the allocation. + */ + template + SNMALLOC_FAST_PATH CapPtr + capptr_chunk_is_alloc(CapPtr p) + { + return CapPtr(p.unsafe_capptr); + } + + /** + * With all the bounds and constraints in place, it's safe to extract a void + * pointer (to reveal to the client). + */ + SNMALLOC_FAST_PATH void* capptr_reveal(CapPtr p) + { + return p.unsafe_capptr; + } + + /** + * + * Wrap a std::atomic with bounds annotation and speak in terms of + * bounds-annotated pointers at the interface. + * + * Note the membranous sleight of hand being pulled here: this class puts + * annotations around an un-annotated std::atomic, to appease C++, yet + * will expose or consume only CapPtr with the same bounds annotation. + */ + template + struct AtomicCapPtr + { + std::atomic unsafe_capptr; + + /** + * nullptr is constructable at any bounds type + */ + AtomicCapPtr(const std::nullptr_t n) : unsafe_capptr(n) {} + + /** + * Interconversion with CapPtr + */ + AtomicCapPtr(CapPtr p) : unsafe_capptr(p.unsafe_capptr) {} + + operator CapPtr() const noexcept + { + return CapPtr(this->unsafe_capptr); + } + + // Our copy-assignment operator follows std::atomic and returns a copy of + // the RHS. Clang finds this surprising; we suppress the warning. + // NOLINTNEXTLINE(misc-unconventional-assign-operator) + CapPtr operator=(CapPtr p) noexcept + { + this->store(p); + return p; + } + + SNMALLOC_FAST_PATH CapPtr + load(std::memory_order order = std::memory_order_seq_cst) noexcept + { + return CapPtr(this->unsafe_capptr.load(order)); + } + + SNMALLOC_FAST_PATH void store( + CapPtr desired, + std::memory_order order = std::memory_order_seq_cst) noexcept + { + this->unsafe_capptr.store(desired.unsafe_capptr, order); + } + + SNMALLOC_FAST_PATH CapPtr exchange( + CapPtr desired, + std::memory_order order = std::memory_order_seq_cst) noexcept + { + return CapPtr( + this->unsafe_capptr.exchange(desired.unsafe_capptr, order)); + } + + SNMALLOC_FAST_PATH bool operator==(const AtomicCapPtr& rhs) const + { + return this->unsafe_capptr == rhs.unsafe_capptr; + } + + SNMALLOC_FAST_PATH bool operator!=(const AtomicCapPtr& rhs) const + { + return this->unsafe_capptr != rhs.unsafe_capptr; + } + + SNMALLOC_FAST_PATH bool operator<(const AtomicCapPtr& rhs) const + { + return this->unsafe_capptr < rhs.unsafe_capptr; + } + }; + + template + using AtomicCapPtrCBArena = AtomicCapPtr; + + template + using AtomicCapPtrCBChunk = AtomicCapPtr; + + template + using AtomicCapPtrCBAlloc = AtomicCapPtr; + } // namespace snmalloc