This commit adds a simple XOR encoding to the next_object pointer in
FreeObjects. This removes the trivial way of getting hold of a physical
address from the system by observing the free list pointers in
deallocated objects.
Take the maximum of...
* CACHELINE_SIZE (for performance)
* next_pow2(NUM_SIZECLASSES + 1) so that, when the pagemap points to a Remote,
the (small) size class stuffed in the bottom bits can be removed by alignment
* next_pow2(NUM_LARGE_CLASSES + 1) so that, when the pagemap isn't pointing to
a Remote, when the associated chunk is (part of) a large allocation, aligning
the Remote* results in 0.
The last of these conditions will almost never be the deciding factor, as there
are generally many more small size classes than large ones, but it shouldn't
hurt to be safe.
This extends the freelist protection to the remote message queues. They
effectively perform doubly linked list entries for the message queue
with the enqueue operation first linking in the previous pointer, and
then then atomically setting the next. This ensures that the visible
states always satisfy the invariant that the forward and backward
pointers are correct for any visisble object.
There is a key_global that is used for all remote deallocations. The
remote cache uses the same protection to build the temporary lists
before forwarding to the next allocator.
The mpscq is integrated into the remoteallocator as it is no longer
a reusable datastructure, but a special purpose implementation.
# Pagemap
The Pagemap now stores all the meta-data for the object allocation. The meta-data in the pagemap is effectively a triple of the sizeclass, the remote allocator, and a pointer to a 64 byte block of meta-data for this chunk of memory. By storing the pointer to a block, it allows the pagemap to handle multiple slab sizes without branching on the fast path. There is one entry in the pagemap per 16KiB of address space, but by using the same entry in the pagemap for 4 adjacent entries, then we can treat a 64KiB range can be treated as a single slab of allocations.
This change also means there is almost no capability amplification required by the implementation on CHERI for finding meta-data. The only amplification is required, when we change the way a chunk is used to a size of object allocation.
# Backend
There is a second major aspect of the refactor that there is now a narrow API that abstracts the Pagemap, PAL and address space management. This should better enable the compartmentalisation and makes it easier to produce alternative backends for various research directions. This is a template parameter that can be used to specialised by the front-end in different ways.
# Thread local state
The thread local state has been refactored into two components, one (called 'localalloc') that is stored directly in the TLS and is constant initialised, and one that is allocated in the address space (called 'coreallloc') which is lazily created and pooled.
# Difference
This removes Superslabs/Medium slabs as there meta-data is now part of the pagemap.
Storing a pointer to an allocator id in an unused object could be a
gadget for escallating priviledge of an attacker, by enabling
use-after-free to corrupt the allocator structure, and then create more
damage.
This commit adds an alternative implementation that does not cache the
allocator id.
Continue tightening the screws on pointer bounds.
Notably, pointers in remote queues are bounded to the free objects. While we
believe that something like MTE is required to make in-band metadata safe, this
is a kind of defense in depth for StrictProvenance architectures: UAF for small
and medium objects expose mostly other (free) small or medium objects and not
allocator metadata (modulo some potential aliasing when Superslabs and
Mediumslabs interconvert). This might shift the burdon on an attacker from
simply holding a UAF pointer to having had to farm several heap pointers.
The policy of bounding remote queue pointers may make the allocator's behavior
for small objects unexpected: while initial object construction during
allocation (that is, when the free list is empty) continues to cleave out
exportable pointers from elevated pointers to internal slabs, reuse pulls from
free lists of *already-bounded* objects. These objects are queued by the
deallocation side, of course, but these paths now include "parallel
reconstruction" of a pointer to the free object from the amplified view of the
returned pointer, rather than queueing amplified pointers and leaving
reconstruction to the allocation side.
Medium objects are possibly similarly mysterious with the added twist that
medium slabs do not store pointers but rather always cleave from their
self-reference (but their interface has always operated using pointers).
Nevertheless, pointers to medium objects end up in remote queues, so we continue
to engage in "parallel reconstruction" in the deallocation paths.
Like all the annotations so far, leave these as CBArena-bounded.
However, we can start to do a little better than we were doing before: for
not-large deallocations, we know that the internal superslab pointer is
sufficiently authoritative that we can use it to get to data structures rather
than also passing an amplified pointer to the allocation in question. This,
then, partially reverts some of the earlier changes made while plumbing
CapPtr<>s through the system.
While here, avoid quite as many void*-s in favor of Remote* or SlabNext*.
Squeeze some bits out of allocator IDs so that we can land the sizeclass in
each Remote object. The intent is that, on StrictProvenance architectures like
CHERI, we will be able to route Remote messages through RemoteCache-s without
needing to amplify back to read the sizeclass metadata field out of the slab
headers.
Fixes a few places where Clang complains about Windows specific code,
and also uses macros supported by Clang on Windows. A few places
separating platform and compiler specific code, as MSVC and WIN32 were
used interchangably previously.
The encoding in the top bits for the size class did not respect kernel
pointers. Using an intptr_t means, we can use a signed shift to
maintain the kernel pointers.
The design of Remote used the top bits of the allocator id to encode
the sizeclass of the deallocation. On 32bit, or on a platform that uses all the bits
we cannot use these bits for a sizeclass.
This commit uses the bottom bit of the allocator id (which is
guaranteed to be 0), to indicate if the object is the minimum
allocation size. If it is not the minimum allocation size the
subsequent byte is used to encode the sizeclass.
The code uses constexpr to decide which strategy to use.