Use the CXX Standard Library interface
mainsrc/snmalloc/stl/cxx directory provides an interface that allows you to use the default C++ Standard Library (STL) with snmalloc.repository·main·Indexed 23 days ago
https://github.com/microsoft/snmallocA high-performance memory allocator designed for extreme scalability in multi-threaded environments, particularly for cross-thread deallocations. It supports C++, Linux/BSD (via LD_PRELOAD), and Rust (via the snmalloc-rs crate v0.7.4). Key features include thread-local and remote deallocation using a lock-free message passing scheme, a layered architecture for address space management, and a hardened version providing allocation randomization, metadata protection, and corruption detection.
src/snmalloc/stl/cxx directory provides an interface that allows you to use the default C++ Standard Library (STL) with snmalloc.A slab is a naturally aligned, power-of-two sized chunk that has been partitioned into a series of allocations, where every allocation in that slab is exactly the same size.
For example, a 16KiB slab can be split into 341 allocations of 48 bytes each, leaving 16 bytes unused at the end. This design allows snmalloc to support multiple slab sizes, ensuring that common allocation sizes can fit multiple times onto a single slab to minimize waste while reducing the frequency of the slower 'new slab' allocation path.
Strict Provenance refers to architectures (like CHERI/Arm Morello) that treat pointer authority (spatial bounds and virtual memory permissions) as a first-class citizen alongside the target address.
snmalloc uses this concept to impose strong constraints on how clients use managed memory. By using bounded pointers, snmalloc ensures that a pointer returned from malloc() cannot be used to access adjacent allocations, preventing spatial overflows.
Key concepts include:
snmalloc ensures no two live allocations have overlapping bounds. Pointers are bounded to no more than the slab entry used to back the allocation.realloc() changes the size class, it triggers an allocate-copy-deallocate sequence. Even if the object stays in place, the returned pointer is re-bounded as if it were a new allocation.snmalloc permits deallocating an object using a pointer to any part of that object. The allocator will find the object's start and end via metadata.snmalloc uses the lower bound address of the provided pointer to look up metadata, ensuring compatibility with architectures where bounds are monotonically non-increasing.To prevent attackers from easily predicting when a slab will be full (which could allow them to allocate objects adjacent to specific targets), snmalloc uses two mechanisms:
snmalloc is organized into a layered hierarchy. Each layer provides abstractions that the layers above it depend on. Understanding this hierarchy helps in knowing where to find specific functionality or where to inject custom logic:
ds_core/: Core data structures (language extension abstractions).aal/: Architecture Abstraction Layer (CPU intrinsics, virtual address-space size).ds_aal/: Data structures depending on the AAL.mitigations/: Security mitigation configuration (e.g., mitigations() function controlled by SNMALLOC_CHECK_CLIENT).pal/: Platform Abstraction Layer (OS/environment abstractions).ds/: Data structures depending on platform services or CPU features.mem/: Core allocator abstractions (templated over a back-end).backend_helpers/: Helpers for defining back ends (e.g., pagemap implementations, buddy allocators).backend/: Example implementations of global memory allocators. Users can replace this with a custom back end.global/: Front-end components assuming a global configuration.override/: Implementations for specific language APIs (C malloc, C++ operator new, Rust std::alloc, etc.).snmalloc implements a 'guarded' version of memcpy to prevent out-of-bounds (OOB) heap corruption. When memcpy(dst, src, len) is called, snmalloc can verify that the operation does not cross the boundary of the allocated memory blocks.
By default, for release builds, snmalloc performs a check to ensure the destination (dst) is large enough to hold len bytes. It can also be configured to check the source (src) to ensure it does not read beyond the end of the object.
To ensure safety, the following checks are conceptually performed:
if (src is managed by snmalloc)
check(remaining_bytes(src) >= len);
if (dst is managed by snmalloc)
check(remaining_bytes(dst) >= len);0 value in the chunk map to a virtual slab covering the entire address space.memcpy is called before snmalloc has been initialized.For security-sensitive applications, a hardened version of snmalloc is available. It provides the following protections:
memcpy: Provides a memcpy implementation that automatically checks bounds relative to the underlying malloc.snmalloc employs randomisation of allocation patterns to increase the difficulty of memory corruption exploits. While not a complete defense against spraying, it raises the bar for attackers by making relative addresses harder to predict.
Randomisation is applied to three specific areas:
To aid code auditing and enforce constraints, snmalloc uses the CapPtr<T, B> wrapper type. The template parameter B acts as a static annotation characterizing the pointer's role or supported operations.
Common roles for B include:
Note: CapPtr is a tool for developer guidance and static auditing; on non-CHERI architectures, it does not provide runtime security against reinterpret_cast or unsafe pointer operations.
snmalloc uses a novel technique to protect "in-band" meta-data (data stored within the unused memory of allocations) from corruption via use-after-free or out-of-bounds writes.
Instead of using a single linked list, snmalloc uses a doubly linked queue where both forward and backward pointers are encoded using cryptographic-like functions. This allows the allocator to verify the integrity of the list by checking the invariant: x.next.prev == x.
f(a) = a XOR k0, where k0 is a randomly chosen value.g(a, b) = (a XOR k1) * (b XOR k2), where k1 and k2 are randomly chosen 64-bit values.Because the order of construction and consumption must match to maintain these invariants, snmalloc uses queues rather than stacks for its free lists.
next or prev pointers during reallocation.prev pointer. The error is detected when the object is later reused.The Combining Lock is an optimization of the MCS queue lock designed to reduce contention. Instead of each thread only performing its own work, the thread that currently holds the lock (the 'head') can traverse the queue and execute the operations (lambdas) of other waiting threads.
WAITING: The thread is waiting for the lock to become available.HEAD: The thread is responsible for completing more work from the queue.DONE: Another thread has already completed the operation for this thread.LockStatus, and a function pointer (f_raw) representing the work to be done.F and uses manual dispatch (via reinterpret_cast) instead of virtual tables to avoid vtable overhead.enum class LockStatus
{
WAITING,
DONE,
HEAD
};
struct CombiningLockNode
{
std::atomic<CombiningLockNode*> next{nullptr};
std::atomic<LockStatus> status{WAITING};
void (*f_raw)(CombiningLockNode*);
void run()
{
f_raw(this);
}
};
template<typename F>
struct CombiningLockNodeTempl : CombiningLockNode
{
F f;
CombiningLockNodeTempl(F&& f_)
: CombiningLockNode([](CombiningLockNode* self) {
auto self_templ = reinterpret_cast<CombiningLockNodeTempl*>(self);
self_templ->f();
}),
f(f_) {}
};