Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ was a bit less important and if a core goal was finding latent bugs.
against accessing freed memory
* guarantee distinct tags for adjacent memory allocations by incrementing
past matching values for deterministic detection of linear overflows
* [future] store previous random tag and increment it to get the next tag
* store the previous tag for each slot and increment it to get the next tag
for that slot to provide deterministic use-after-free detection through
multiple cycles of memory reuse

Expand Down Expand Up @@ -727,22 +727,29 @@ freeing as there would be if the kernel supported these features directly.

## Memory tagging

Random tags are set for all slab allocations when allocated, with 4 excluded values:
The first time a slab slot is used, a random tag is set for the allocation, with
4 excluded values:

1. the reserved `0` tag
2. the previous tag used for the slot
3. the current (or previous) tag used for the slot to the left
4. the current (or previous) tag used for the slot to the right

On each subsequent reuse of the slot, the tag is instead derived deterministically
by incrementing the previous tag for the slot, skipping past the same 4 excluded
values. This makes each slot cycle through all of the usable tags before any tag
is repeated.

When a slab allocation is freed, the reserved `0` tag is set for the slot.
Slab allocation slots are cleared before reuse when memory tagging is enabled.

This ensures the following properties:

- Linear overflows are deterministically detected.
- Use-after-free are deterministically detected until the freed slot goes through
both the random and FIFO quarantines, gets allocated again, goes through both
quarantines again and then finally gets allocated again for a 2nd time.
- Use-after-free accesses are deterministically detected until the slot has been
reused enough times for the incrementing tag to wrap around to the stale
pointer's tag, i.e. through a full cycle of the usable tags rather than being
probabilistically reused after a single reallocation.
- Since the default `0` tag is reserved, untagged pointers can't access slab
allocations and vice versa.

Expand Down
22 changes: 20 additions & 2 deletions h_malloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -609,11 +609,29 @@ static void *tag_and_clear_slab_slot(struct slab_metadata *metadata, void *slot_
// current or previous tag of left neighbor or 0 if there's no left neighbor or if it was never used
tem |= (1 << u4_arr_get(slot_tags, slot_idx));
// previous tag of this slot or 0 if it was never used
tem |= (1 << u4_arr_get(slot_tags, slot_idx + 1));
u8 prev_tag = u4_arr_get(slot_tags, slot_idx + 1);
tem |= (1 << prev_tag);
// current or previous tag of right neighbor or 0 if there's no right neighbor or if it was never used
tem |= (1 << u4_arr_get(slot_tags, slot_idx + 2));

void *tagged_ptr = arm_mte_create_random_tag(slot_ptr, tem);
void *tagged_ptr;
if (prev_tag == RESERVED_TAG) {
// A stored tag of 0 (RESERVED_TAG) means the slot was never used, since a used slot always
// stores a tag in [1, 15]. Pick a random tag as the baseline, excluding the reserved tag
// and the neighboring slots' tags.
tagged_ptr = arm_mte_create_random_tag(slot_ptr, tem);
} else {
// Derive the next tag by advancing past the previous one, skipping excluded tags and
// wrapping around within the tag space. This makes a slot deterministically cycle through
// all usable tags before repeating, extending use-after-free detection across multiple
// cycles of reuse.
const u8 tag_mask = (1 << TAG_WIDTH) - 1;
u8 tag = prev_tag;
do {
tag = (tag + 1) & tag_mask;
} while (tem & (1 << tag));
tagged_ptr = set_pointer_tag(slot_ptr, tag);
}
// slot addresses and sizes are always aligned by 16
arm_mte_tag_and_clear_mem(tagged_ptr, slot_size);

Expand Down