diff --git a/src/mem/address_space.h b/src/mem/address_space.h index 1ef4e40..81e740e 100644 --- a/src/mem/address_space.h +++ b/src/mem/address_space.h @@ -1,6 +1,7 @@ #include "../ds/address.h" #include "../ds/flaglock.h" #include "../pal/pal.h" +#include "arenamap.h" #include namespace snmalloc @@ -13,7 +14,7 @@ namespace snmalloc * It cannot unreserve memory, so this does not require the * usual complexity of a buddy allocator. */ - template + template class AddressSpaceManager { /** @@ -173,14 +174,24 @@ namespace snmalloc * The returned block is guaranteed to be aligened to the size. * * Only request 2^n sizes, and not less than a pointer. + * + * On StrictProvenance architectures, any underlying allocations made as + * part of satisfying the request will be registered with the provided + * arena_map for use in subsequent amplification. */ template - CapPtr reserve(size_t size) + CapPtr reserve(size_t size, ArenaMap& arena_map) { SNMALLOC_ASSERT(bits::is_pow2(size)); SNMALLOC_ASSERT(size >= sizeof(void*)); - if constexpr (pal_supports) + /* + * For sufficiently large allocations with platforms that support aligned + * allocations and architectures that don't require StrictProvenance, + * try asking the platform first. + */ + if constexpr ( + pal_supports && !aal_supports) { if (size >= PAL::minimum_alloc_size) return CapPtr( @@ -198,9 +209,43 @@ namespace snmalloc size_t block_size = 0; if constexpr (pal_supports) { - block_size = PAL::minimum_alloc_size; - block = CapPtr( - PAL::template reserve_aligned(block_size)); + /* + * aal_supports ends up here, too, and we ensure + * that we always allocate whole ArenaMap granules. + */ + if constexpr (aal_supports) + { + static_assert( + !aal_supports || + (ArenaMap::alloc_size >= PAL::minimum_alloc_size), + "Provenance root granule must be at least PAL's " + "minimum_alloc_size"); + block_size = bits::align_up(size, ArenaMap::alloc_size); + } + else + { + /* + * We will have handled the case where size >= minimum_alloc_size + * above, so we are left to handle only small things here. + */ + block_size = PAL::minimum_alloc_size; + } + + void* block_raw = PAL::template reserve_aligned(block_size); + + block = CapPtr(block_raw); + + if constexpr (aal_supports) + { + auto root_block = CapPtr(block_raw); + auto root_size = block_size; + do + { + arena_map.register_root(root_block); + root_block = pointer_offset(root_block, ArenaMap::alloc_size); + root_size -= ArenaMap::alloc_size; + } while (root_size > 0); + } } else if constexpr (!pal_supports) { @@ -249,7 +294,8 @@ namespace snmalloc * used, by smaller objects. */ template - CapPtr reserve_with_left_over(size_t size) + CapPtr + reserve_with_left_over(size_t size, ArenaMap& arena_map) { SNMALLOC_ASSERT(size >= sizeof(void*)); @@ -257,7 +303,7 @@ namespace snmalloc size_t rsize = bits::next_pow2(size); - auto res = reserve(rsize); + auto res = reserve(rsize, arena_map); if (res != nullptr) { diff --git a/src/mem/largealloc.h b/src/mem/largealloc.h index edf44ed..daa7ea3 100644 --- a/src/mem/largealloc.h +++ b/src/mem/largealloc.h @@ -14,7 +14,7 @@ namespace snmalloc { - template + template class MemoryProviderStateMixin; class Largeslab : public Baseslab @@ -30,7 +30,7 @@ namespace snmalloc template typename AP> friend class MPMCStack; - template + template friend class MemoryProviderStateMixin; AtomicCapPtr next = nullptr; @@ -62,7 +62,7 @@ namespace snmalloc // This represents the state that the large allcoator needs to add to the // global state of the allocator. This is currently stored in the memory // provider, so we add this in. - template + template class MemoryProviderStateMixin { /** @@ -71,10 +71,23 @@ namespace snmalloc */ std::atomic_flag lazy_decommit_guard = {}; + /** + * Instantiate the ArenaMap here. + * + * In most cases, this will be a purely static object (a DefaultArenaMap + * using a GlobalPagemapTemplate or ExternalGlobalPagemapTemplate). For + * sandboxes, this may have per-instance state (e.g., the sandbox root); + * presently, that's handled by the MemoryProviderStateMixin constructor + * that takes a pointer to address space it owns. There is some + * non-orthogonality of concerns here. + */ + ArenaMap arena_map = {}; + + using ASM = AddressSpaceManager; /** * Manages address space for this memory provider. */ - AddressSpaceManager address_space = {}; + ASM address_space = {}; /** * High-water mark of used memory. @@ -143,24 +156,26 @@ namespace snmalloc /** * Make a new memory provide for this PAL. */ - static MemoryProviderStateMixin* make() noexcept + static MemoryProviderStateMixin* make() noexcept { // Temporary stack-based storage to start the allocator in. - AddressSpaceManager local{}; + ASM local_asm{}; + ArenaMap local_am{}; // Allocate permanent storage for the allocator usung temporary allocator - MemoryProviderStateMixin* allocated = - local + MemoryProviderStateMixin* allocated = + local_asm .template reserve_with_left_over( - sizeof(MemoryProviderStateMixin)) - .template as_static>() + sizeof(MemoryProviderStateMixin), local_am) + .template as_static() .unsafe_capptr; if (allocated == nullptr) error("Failed to initialise system!"); // Move address range inside itself - allocated->address_space = std::move(local); + allocated->address_space = std::move(local_asm); + allocated->arena_map = std::move(local_am); // Register this allocator for low-memory call-backs if constexpr (pal_supports) @@ -224,7 +239,7 @@ namespace snmalloc class LowMemoryNotificationObject : public PalNotificationObject { - MemoryProviderStateMixin* memory_provider; + MemoryProviderStateMixin* memory_provider; /*** * Method for callback object to perform lazy decommit. @@ -237,8 +252,7 @@ namespace snmalloc } public: - LowMemoryNotificationObject( - MemoryProviderStateMixin* memory_provider) + LowMemoryNotificationObject(MemoryProviderStateMixin* memory_provider) : PalNotificationObject(&process), memory_provider(memory_provider) {} }; @@ -254,7 +268,8 @@ namespace snmalloc // Cache line align size_t size = bits::align_up(sizeof(T), 64); size = bits::max(size, alignment); - auto p = address_space.template reserve_with_left_over(size); + auto p = + address_space.template reserve_with_left_over(size, arena_map); if (p == nullptr) return nullptr; @@ -268,7 +283,7 @@ namespace snmalloc { size_t size = bits::one_at_bit(SUPERSLAB_BITS) << large_class; peak_memory_used_bytes += size; - return address_space.template reserve(size) + return address_space.template reserve(size, arena_map) .template as_static(); } @@ -282,6 +297,17 @@ namespace snmalloc size_t peak = peak_memory_used_bytes; return {peak - avail, peak}; } + + template + SNMALLOC_FAST_PATH CapPtr capptr_amplify(CapPtr r) + { + return arena_map.template capptr_amplify(r); + } + + ArenaMap& arenamap() + { + return arena_map; + } }; using Stats = AllocStats; @@ -379,10 +405,19 @@ namespace snmalloc stats.superslab_push(); memory_provider.push_large_stack(p, large_class); } + + template + SNMALLOC_FAST_PATH CapPtr capptr_amplify(CapPtr r) + { + return memory_provider.template capptr_amplify(r); + } }; + struct DefaultPrimAlloc; + #ifndef SNMALLOC_DEFAULT_MEMORY_PROVIDER -# define SNMALLOC_DEFAULT_MEMORY_PROVIDER MemoryProviderStateMixin +# define SNMALLOC_DEFAULT_MEMORY_PROVIDER \ + MemoryProviderStateMixin> #endif /** diff --git a/src/mem/pool.h b/src/mem/pool.h index 713cbfa..e9e4c99 100644 --- a/src/mem/pool.h +++ b/src/mem/pool.h @@ -22,7 +22,7 @@ namespace snmalloc { private: friend Pooled; - template + template friend class MemoryProviderStateMixin; friend SNMALLOC_DEFAULT_MEMORY_PROVIDER; diff --git a/src/test/func/fixed_region/fixed_region.cc b/src/test/func/fixed_region/fixed_region.cc index 06d138a..2059fc9 100644 --- a/src/test/func/fixed_region/fixed_region.cc +++ b/src/test/func/fixed_region/fixed_region.cc @@ -33,7 +33,9 @@ using namespace snmalloc; int main() { #ifndef SNMALLOC_PASS_THROUGH // Depends on snmalloc specific features - auto& mp = *MemoryProviderStateMixin::make(); + auto& mp = *MemoryProviderStateMixin< + DefaultPal, + DefaultArenaMap>::make(); // 28 is large enough to produce a nested allocator. // It is also large enough for the example to run in. diff --git a/src/test/func/sandbox/sandbox.cc b/src/test/func/sandbox/sandbox.cc index 97ca1a1..1531133 100644 --- a/src/test/func/sandbox/sandbox.cc +++ b/src/test/func/sandbox/sandbox.cc @@ -43,14 +43,48 @@ namespace struct Sandbox { using NoOpPal = PALNoAlloc; + + struct ArenaMap + { + /** + * A pointer with authority to the entire sandbox region + */ + CapPtr arena_root; + + /** + * Amplify using arena_root; that is, exclusively within the sandbox. + */ + template + SNMALLOC_FAST_PATH CapPtr capptr_amplify(CapPtr r) + { + return Aal::capptr_rebound(arena_root, r); + } + + /* + * This class does not implement register_root; there should be no + * attempts to call that function. + */ + }; + + /** + * The MemoryProvider for sandbox-memory-backed Allocs, both inside and + * outside the sandbox proper: no memory allocation operations and + * amplification confined to sandbox memory. + */ + using NoOpMemoryProvider = MemoryProviderStateMixin; + /** * Type for the allocator that lives outside of the sandbox and allocates * sandbox-owned memory. + * This Allocator, by virtue of having its amplification confined to + * the sandbox, can be used to free only allocations made from sandbox + * memory. It (insecurely) routes messages to in-sandbox snmallocs, + * though, so it can free any sandbox-backed snmalloc allocation. */ using ExternalAlloc = Allocator< never_init, no_op_init, - MemoryProviderStateMixin, + NoOpMemoryProvider, SNMALLOC_DEFAULT_CHUNKMAP, false>; /** @@ -71,7 +105,7 @@ namespace * likely be only one of these inside any given sandbox and so this would * not have to be per-instance state. */ - MemoryProviderStateMixin* real_state; + NoOpMemoryProvider* real_state; /** * Pop an element from the large stack for the specified size class, @@ -106,6 +140,16 @@ namespace { return real_state->template reserve(large_class); } + + /** + * Amplify by appealing to the real_state, which has our sandbox + * ArenaMap implementation. + */ + template + SNMALLOC_FAST_PATH CapPtr capptr_amplify(CapPtr r) + { + return real_state->template capptr_amplify(r); + } }; /** @@ -143,7 +187,7 @@ namespace /** * The memory provider for this sandbox. */ - MemoryProviderStateMixin state; + NoOpMemoryProvider state; /** * The allocator for callers outside the sandbox to allocate memory inside. @@ -167,6 +211,9 @@ namespace sb_size - sizeof(SharedState)), alloc(state, SNMALLOC_DEFAULT_CHUNKMAP(), &shared_state->queue) { + // Register the sandbox memory with the sandbox arenamap + state.arenamap().arena_root = CapPtr(start); + auto* state_proxy = static_cast( alloc.alloc(sizeof(MemoryProviderProxy))); state_proxy->real_state = &state; diff --git a/src/test/func/two_alloc_types/main.cc b/src/test/func/two_alloc_types/main.cc index 946fcd2..2daf440 100644 --- a/src/test/func/two_alloc_types/main.cc +++ b/src/test/func/two_alloc_types/main.cc @@ -41,7 +41,10 @@ int main() { setup(); - MemoryProviderStateMixin mp; + MemoryProviderStateMixin< + DefaultPal, + DefaultArenaMap> + mp; // 26 is large enough to produce a nested allocator. // It is also large enough for the example to run in.