Commit Graph

90 Commits

Author SHA1 Message Date
Matthew Parkinson
cec015f296 Add FIFO behaviour for unchecked code.
Consuming available slabs in LIFO order makes predicting address reuse harder
but appears to have performance implications.  Condition this on CHECK_CLIENT
and instead use FIFO order on !CHECK_CLIENT builds.
2021-10-22 17:16:14 +01:00
Matthew Parkinson
4987a19fe9 Rename 2021-10-22 17:16:14 +01:00
Nathaniel Wesley Filardo
fe43f0bea8 Placate some compiler errors on CHERI 2021-10-20 12:02:08 +01:00
Nathaniel Wesley Filardo
3cdb7be478 MetaEntry: split get_metaslab by intent 2021-10-20 12:02:08 +01:00
Nathaniel Wesley Filardo
599ae0e632 Metaslab: add MetaCommon field
This preserves the chunk pointer through the use of a chunk as a slab.  It does
grow the structure by one pointer, but on non-CHERI it is still padded to 64
bytes, even with CHECK_CLIENT guards in place:

 0: MetaCommon chunk pointer
 8: next pointer
16: builder head[0]
24: builder head[1]
32: builder tail[0]
40: builder tail[1]
48: builder length[0] (uint16_t)
50: builder length[1] (uint16_t)
52: padding (4 bytes)
56: needed (uint16_t)
58: sleeping (bool)

(Sadly, on CHERI, even without CHECK_CLIENT guards and with no padding, there
are now four pointers in the structure -- chunk, next, head, tail -- plus five
extra bytes.  We will likely wish to explore encoding the head and tail offsets
relative to the chunk pointer.)

This lets us remove the "subversive amplification" in dealloc() in favor of just
preserving the chunk pointer.  Speaking of, be sure to assign that in all the
right places, and ASSERT that we've got it right.
2021-10-20 12:02:08 +01:00
Nathaniel Wesley Filardo
6c115eec18 NFC: doc tweaks 2021-10-20 12:02:08 +01:00
Nathaniel Wesley Filardo
f0b7dc4b04 NFC: Extract MetaCommon from ChunkRecord 2021-10-20 12:02:08 +01:00
Nathaniel Wesley Filardo
554db3997d localalloc::dealloc tweak size math for large objects
Avoid computing bits::next_pow2_bits(1 << n).  Even if the compiler can see
through enough of the algebra, it's surely more direct to just use n.

While here, slightly expand documentation about what's going on with the
"sizeclass" encoded into MetaEntry-s.
2021-10-20 12:02:08 +01:00
Nathaniel Wesley Filardo
3462c53983 NFC: Remove spurious forward declarations
There is no such thing as "struct Slab" any more.

We use alignof(RemoteAllocator) below, so we already require the complete type
definition at this point.
2021-10-20 12:02:08 +01:00
Nathaniel Wesley Filardo
c2ce9c118d NFC: Racing stripes on Metaslab accessors
Even on debug builds, these little things should be inlined
2021-10-20 12:02:08 +01:00
Nathaniel Wesley Filardo
96155db640 Collect freelist things in a namespace
Motivated by renaming `FreeObject::{Head,Queue,AtomicQueue}Ptr` to
`freelist::...Ptr`, in fact go further, moving `FreeObject` itself to
`freelist::Object` and `FreeListBuilder` to `freelist::Builder` and
`FreeListIter` to `freelist::Iter`
2021-10-13 16:30:41 +01:00
Nathaniel Wesley Filardo
bc365e0abb Default template args for FreeObject & friends
Now that explicit annotations have gotten us through the refactoring, it's time
for the scaffolding to disappear.  src/mem/freelist.h is left generic for any
future machinations, but `FreeObject::T<>`, the several `FreeObject::...Ptr<>`s,
`FreeListIter<>`, and `FreeListBuilder<>` are given default parameters and all
uses are shortened to use defaults where possible.
2021-10-13 16:30:41 +01:00
Nathaniel Wesley Filardo
d4c120dfe5 Free queues hold Wild pointers 2021-10-13 16:30:41 +01:00
Nathaniel Wesley Filardo
7750676598 NFC: FreeListIter domestication plumbing
Just an intermediate syntactic step to chase dependencies.  All these introduced
"domestication" callbacks are just the identity function, but they will let us
thread the LocalAlloc's handle to the Backend state down to where it's needed.
2021-10-13 16:30:41 +01:00
Nathaniel Wesley Filardo
7e53a2e82a CapPtr: shift free lists to Alloc bounds
This is incomplete, yet still more reflective of what's going on: we take the
exported pointers back from userspace and thread them directly into the free
lists.

So: move capptr_to_user_address_control to list construction time rather than
list consumption time.
2021-10-13 16:30:41 +01:00
Nathaniel Wesley Filardo
e25db7b832 Move to FreeObject::T<capptr::bound<>>
FreeObject itself is now just a namespace (but `friend`-ly); the actual free
list nodes are FreeObject::T-s that are templatized on the (perceived)
`capptr::bound<>` of the pointer they contain.  (These may differ across an
instantiated snmalloc; for example, in the sandboxing design, the in-sandbox
allocators may perceive all remotes to be full of `AllocUser` while the
privileged allocator of sandbox memory should perceive its remote queue as
holding `AllocUserWild` pointers in need of domestication.)

The interfaces to `FreeObject::T`-s now let us distinguish between the base and
inductive cases of the queues:

* in the inductive case, the pointer we hold to a `FreeObject::T` and its
  next_object have the same bounds

* in the base case, the pointer we hold has different bounds (typically,
  domesticated by contrast to the wild pointers in the queues).

To keep the clutter down a bit, we occasionally use raw pointers when we can be
reasonably certain that domestication is assured.  Moreover, we define some type
aliases, `FreeObject::{HeadPtr, QueuePtr, AtomicQueuePtr}`, that are slightly
more convenient labels than, e.g., `CapPtr<FreeObject::T<BQueue>, BView>`.
Because we are using template parameters for the `capptr::bound<>`s themselves,
we cannot use the aliases for `CapPtr<>s` provided within `capptr::`.

The two primary interfaces around free objects (`FreeListIter` AND
`FreeListBuilder`) are adjusted appropriately and their `BView` and `BQueue`
template paramters are plumbed explicitly around the tree.  This makes for quite
a bit of noise at the moment, but means that we'll be able to evolve parts of
the tree separately and can consider putting defaults in once that's done.
2021-10-13 16:30:41 +01:00
Nathaniel Wesley Filardo
9065893181 Overhaul CapPtr
* Switch to a multidimensional taxonomy.

  Rather than encoding the abstract bound states in a single enum, move to a
  more algebraic treatment.  The dimensions themselves are within the
  snmalloc::capptr_bounds namespace so that their fairly generic names do not
  conflict with consumer code.  Aliases for many points in the space are
  established outside that namespace for ease of use elsewhere.

* Introduce several new namespaces:

    * snmalloc::capptr::dimension holds each of the dimension enums

    * snmalloc::capptr holds the bound<> type itself and a ConceptBound

    * snmalloc::capptr::bounds gives convenient specializations of bound<>

    * snmalloc::capptr also has aliases for CapPtr<> itself

  All told, rather than `CapPtr<T, CBChunk>`, we now expect client code to read
  `capptr::Chunk<T>` in almost all cases (and this is just an alias for the
  appropriate `CapPtr<T, bounds<...>>` type).  When the bound<>s themselves are
  necessary, as when calling capptr_bound, we expect that they will almost
  always be pronounced using an alias (e.g., `capptr::bounds::Alloc`).

* Chase consequences.

* Prune old taxa and aliases that are no longer in use in snmalloc2.
2021-10-13 16:30:41 +01:00
Matthew Parkinson
bba66e4f7e Randomise slab filling (#397)
# Free List builder track length

This commit makes the free list builder track the length of the lists in
the Random case.

# Refactor free list creation.

Minor refactoring to share code between the new free list and existing
path.

# Randomise slab filling

Knowing when a slab is going to become full makes it easier to by pass
the free list entries as protection for OOB writes.  This commit
randomises when a slab will become full.

This commit changes two things

* the free list builder can return some fraction of the deallocations
  on a slab.
* when there is a single free slab, we can with some probability
  allocate an additional slab.

These two combine to make it difficult to predict when a slab will be
free.

# Apply suggestions from code review

Co-authored-by: Nathaniel Wesley Filardo <nfilardo@microsoft.com>
2021-10-07 15:51:18 +01:00
Matthew Parkinson
8ac2adc4e5 Added a sequential queue
This changes the slab lists to use a sequential queue.
They were previously stored in a stack.

This commit also tidies up some incomplete refactoring from the
initial snmalloc2 work.
2021-09-29 14:26:09 +01:00
Matthew Parkinson
dbb7965507 Minor. 2021-09-29 14:26:09 +01:00
David Chisnall
fd18528278 Add missing inline from header. (#388) 2021-09-07 08:44:47 +01:00
Nathaniel Wesley Filardo
dac1ba6a6c Metaslab: SlabLink field rather than parent class
Introduce Metaslab::from_link(SlabLink*) to encapsulate the "container of"
dance.  Note that Metaslab was not a standard layout type prior to this change
(since both SlabLink and Metaslab defined non-static data members), and so the
reinterpret_cast<>s replaced here with ::from_link() were UB, but everyone lays
out classes as one expects so it was fine in practice.

Most of the uses of ::from_link() are already guarded by checks that the link
pointer is not nullptr, but in src/mem/corealloc.h:/debug_is_empty_impl we shift
to testing the link pointer explicitly before converting to the metaslab.

Despite that Metaslab is now standard layout, we still don't fall back to the
inter-convertibility of a standard layout class and its first[*] data member
since we're going to want to put a common initial sequence across Metaslab and
ChunkRecord and the SlabLink isn't likely to be in it.
2021-09-03 15:35:15 +01:00
Nathaniel Wesley Filardo
7a02ae949f Remove stale static_assert
With snmalloc2, slabs are linked through the Metaslab structure directly rather
than in-band in a free allocation, so we no longer need to store a SlabLink in
even the smallest allocation classes.
2021-08-13 16:46:00 +01:00
David Chisnall
e8374479f4 Snmalloc2 API cleanups for sandbox use. (#359)
This is the set of changes required for snmalloc2 to be usable by the
process sandboxing code and incorporates some API changes that reduce
the amount of code required to embed snmalloc.  Highlights:

 - Merge the config and back-end classes.
 - Everything in config is now global (all methods are static)
 - The GlobalState class is gone (all global state is managed by global
   methods on the config class)
 - LocalState is now a member of the config class, all methods are
   instance methods.
 - Not every configuration needs to use the lazy initialisation hooks.
   They now need to be provided only if they are used.  If the
   configuration does not provide an `ensure_init` method, it is not
   called.  If it does not provide an `is_initialised` method then the
   global initialisation state is not checked.
 - There is now an `snmalloc::Options` class that default initialises
   itself to the default behaviour.  Every configuration must provide a
   `constexpr` instance of this class.  Each flag can be separately
   overridden and new flags can be added without breaking any existing
   API consumers.

The config classes are moved into the backend directory.
2021-08-05 15:08:12 +01:00
Matthew Parkinson
81bf341732 XOR encoded next_object
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.
2021-07-26 15:32:32 +01:00
Matthew Parkinson
529b90b01b Default some statistics 2021-07-23 07:23:35 +01:00
Matthew Parkinson
9df0101dfd Enable guard pages in CHECK_CLIENT
Change the behaviour to use PROT_NONE for reservations in CHECK_CLIENT
mode.  This means that we only provide access once data is actually
being used.
2021-07-21 09:36:06 +01:00
Nathaniel Wesley Filardo
2ba0de76b3 mem/metaslab: use uintptr_t alignment functions
It is UB to offset from `nullptr` (except perhaps with a 0 offset).  Apparently
clang is able to use this to reason, given `void* p`, that comparing
`__builtin_align_down(p, x)` against `handle.fake_large_remote` (i.e., a `static
inline constexpr` `nullptr`) must be the same as comparing `p` itself against
`nullptr`.

In `MetaEntry`'s constructor, converting the provided `RemoteAllocator*` to
`uintptr_t` before offsetting avoids the UB.  (While here, don't use
`address_cast()`, as `address_t` will, on CHERI, be `ptraddr_t` and not
`uintptr_t`.)

Doing the alignment in `get_remote` at `uintptr_t` before casting to
`RemoteAllocator*`, rather than converting and then aligning, prevents the
reasoning above from eliminating the alignment.
2021-07-20 14:42:53 +01:00
Matthew Parkinson
b501da69db Implements protection on remote messages queues
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.
2021-07-19 12:57:03 +01:00
Matthew Parkinson
f0e2ab702a Major refactor of snmalloc (#343)
# 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.
2021-07-12 15:53:36 +01:00
Matthew Parkinson
e77a5d9c58 Remove cache-friendly offset. 2021-05-18 14:58:15 +01:00
Matthew Parkinson
f0ebbebf74 Refactor is_start_of_object 2021-05-18 14:58:15 +01:00
Nathaniel Filardo
95871ff8a1 SP: free lists and remote queues are CBAlloc
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.
2021-04-09 12:39:29 +01:00
Nathaniel Filardo
f7821e11eb SP: LargeAlloc return CBChunk & chase consequences
Even if we opt not to bound these pointers internally (if they aren't headed out
to the user program or we later derive bounded pointers), they should still be
annotated as something other than CBArena, ensuring that we do not attempt to
use them for general amplification.
2021-04-09 12:39:29 +01:00
Nathaniel Filardo
54fec3821f SP: alloc paths: begin using CBAllocE
Begin turning the screws on bounds: pointers the allocator is about to reveal
must be annotated as CBAllocE.  Use the PAL's capptr_export and the AAL's
capptr_bound<> to get them there.
2021-04-09 12:39:29 +01:00
Nathaniel Filardo
c5aff8ed74 SP: plumb CapPtr through cache_friendly functions
Change these functions from being void* to void* to either consuming or
producing FreeObject*-s as appropriate.
2021-04-09 12:39:29 +01:00
Nathaniel Filardo
97f508e2b3 SP: further CapPtr plumbing 2021-04-09 12:39:29 +01:00
Nathaniel Filardo
005f5787ef SP: start plumbing CapPtr<>s 2021-04-09 12:39:29 +01:00
Matthew Parkinson
9ca73b8153 Implementing full permutation of slab
Allocate slab is randomly in all possible permutations. This increases
the entropy of the order considerably.  This uses an algorithm to build
a random cycle in a slab, and then use this to build the free list.

We disable the per-slab randomisation in the non-CHECK_CLIENT builds.
2021-04-07 11:43:53 +01:00
Nathaniel Filardo
7f841ff081 NFC: Introduce and switch to pal_zero
This wrapper will allow us to pass `AuthPtr<T,B> p` to zero() without needing to
write `p.unsafe_auth_ptr` to get to a `T*` inside.  Moreover, it will give us a
convenient point to assert that `B` is such that the pointer can be used to
manipulate the memory map (i.e. is not exported).
2021-04-06 16:25:57 +01:00
Nathaniel Filardo
d1b2522a10 Remove stale get_slab
Everything seems to be using the `Metaslab::get_slab` form instead
2021-04-06 16:25:57 +01:00
Matthew Parkinson
f15dc6ee2e Expose Entropy
Define various parts of random that can be used to make the layout of
memory more random.  Thread this through the allocator.

Expose the concept as part of the Pal. Subsequent commits will expose
that on different platforms.
2021-04-06 14:09:18 +01:00
Matthew Parkinson
afc6283e01 Threshold freelist wakeup
When a slab has been fully allocated, then we no longer
check it has entries until something returns an allocation to this slab.
However, it is possible that only a single allocation is available, and
then we can end up frequently on the slow path.

This change only considers free lists that cover at least 1/8 of a slab.
This means that we will hit the slow path less frequently.  This also
means that the randomisation changes will have more entropy: with a
single element free list there is only one order.

For large small sizes it can still be a single element, as 1/8 is of the
slab capacity is below 1. We max out the trigger at 31 elements to
reduce unneeded wasted space.
2021-03-25 12:04:36 +00:00
Matthew Parkinson
578abd8db4 Randomise slab allocation pattern (#304)
The slab allocation pattern is randomised based on the deallocation
pattern.  This achieved by using two queues to enqueue free elements
onto.  We pick "randomly", which queue to add to, and then when we take
the free_queue to use, we splice the two queues together.
2021-03-24 16:12:22 +00:00
Matthew Parkinson
04a185e634 Remove allocated field from Metaslab
The metaslab contains a field specifying how many elements have been
allocated.  As the code has evolved this field has now always become
the maximum capacity of the slab for the sizeclass.

This commit looks up this value based on the sizeclass, and removes the
field from the slab's metadata.
2021-03-22 13:07:57 +00:00
Matthew Parkinson
8824622136 Better reciprocal division and modulus.
The previous reciprocal division branch on the prime that the sizeclass
was constructed from. All sizeclasses can be represented as
  2^n * {1,3,5,7}
This lead to a very small table, but some work to calculate the
appropriate shifts and multiplications to implement reciprocal division.

This commit uses a completely uniform representation for every
sizeclass using a lookup table.  Due to the precise ranges that we query
the modulus and rounding on, we can do this much more efficiently.

The func-release-rounding exhaustively tests all the queries we are
interested in.
2021-03-19 20:11:15 +00:00
Matthew Parkinson
50f412157f Protect free list pointers stored in object space
Free list pointers can be exploited by attackers. This commit implements
a simple encoding scheme to detect corruption of the pointers.  This can
be used to detect UAF and double free.

This does not currently address anything for Medium or Large
allocations.  It also does not address cross thread deallocations.

Co-authored-by: Nathaniel Wesley Filardo <nfilardo@microsoft.com>
2021-03-19 11:28:39 +00:00
Nathaniel Filardo
1d1b013d85 NFC: cdllist: prepare for pointer wrappers 2021-03-16 09:29:19 +00:00
Nathaniel Filardo
4f6cf8cb40 NFC: make Slab, Mediumslab interfaces static
Going forward, this gives us explicit pointers with which to carry bounds
annotations.  Otherwise, assuming AuthPtr overloads operator->, a OOP-style call
like

    AuthPtr<Slab, Bounds> slab;
    slab->foo()

will create a `Slab* this` within the body of `Slab::foo`, leaving it unable to
see or propagate the Bounds annotation.  If it invokes callees that expect
`AuthPtr` arguments, it will therefore have to fabricate new `Bounds` unsafely.
2021-03-16 09:29:19 +00:00
Nathaniel Filardo
49fefc3f83 NFC: introduce SlabNext type for intra-small-slab free pointers
Mostly cosmetic, but eliminates some void*-s and makes intention clearer.
2021-03-16 09:29:19 +00:00