update memory tagging documentation
parent
365ee6900d
commit
abe54dba27
100
README.md
100
README.md
|
@ -724,80 +724,38 @@ freeing as there would be if the kernel supported these features directly.
|
|||
|
||||
## Memory tagging
|
||||
|
||||
**Memory tagging has been implemented and this section is currently
|
||||
out-of-date.**
|
||||
Random tags are set for all slab allocations when allocated. 5 possible values
|
||||
are excluded: the default 0 tag, a statically reserved free tag, the previous
|
||||
tag used for the slot, the current (or previous) tag used for the slot to the
|
||||
left and the current (or previous) tag used for the slot to the right. 3 of
|
||||
these are dynamic random values. When a slab allocation is freed, the reserved
|
||||
free tag is set for the slot. Linear overflows are deterministically detected.
|
||||
Use-after-free has deterministic detection 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. Since
|
||||
the default 0 tag isn't used, untagged memory can't access malloc allocations
|
||||
and vice versa, although it may make sense to reuse the default tag for free
|
||||
data to avoid reducing the possible random tags from 15 to 14, since freed
|
||||
data is always zeroed anyway.
|
||||
|
||||
Integrating extensive support for ARMv8.5 memory tagging is planned and this
|
||||
section will be expanded to cover the details on the chosen design. The approach
|
||||
for slab allocations is currently covered, but it can also be used for the
|
||||
allocator metadata region and large allocations.
|
||||
Slab allocations are done in a statically reserved region for each size class
|
||||
and all metadata is in a statically reserved region, so interactions between
|
||||
different uses of the same address space is not applicable.
|
||||
|
||||
Memory allocations are already always multiples of naturally aligned 16 byte
|
||||
units, so memory tags are a natural fit into a malloc implementation due to the
|
||||
16 byte alignment requirement. The only extra memory consumption will come from
|
||||
the hardware supported storage for the tag values (4 bits per 16 bytes).
|
||||
Large allocations beyond the largest slab allocation size class (128k by
|
||||
default) are guaranteed to have randomly sized guard regions to the left and
|
||||
right. Random and FIFO address space quarantines provide use-after-free
|
||||
detection. Random tags would still be useful for probabilistic detection of
|
||||
overflows, probabilistic detection of use-after-free once the address space is
|
||||
out of the quarantine and reused for another allocation and deterministic
|
||||
detection of use-after-free for reuse by another allocator. We need to test
|
||||
whether the cost is acceptable for enabling this by default.
|
||||
|
||||
The baseline policy will be to generate random tags for each slab allocation
|
||||
slot on first use. The highest value will be reserved for marking freed memory
|
||||
allocations to detect any accesses to freed memory so it won't be part of the
|
||||
generated range. Adjacent slots will be guaranteed to have distinct memory tags
|
||||
in order to guarantee that linear overflows are detected. There are a few ways
|
||||
of implementing this and it will end up depending on the performance costs of
|
||||
different approaches. If there's an efficient way to fetch the adjacent tag
|
||||
values without wasting extra memory, it will be possible to check for them and
|
||||
skip them either by generating a new random value in a loop or incrementing
|
||||
past them since the tiny bit of bias wouldn't matter. Another approach would be
|
||||
alternating odd and even tag values but that would substantially reduce the
|
||||
overall randomness of the tags and there's very little entropy from the start.
|
||||
|
||||
Once a slab allocation has been freed, the tag will be set to the reserved
|
||||
value for free memory and the previous tag value will be stored inside the
|
||||
allocation itself. The next time the slot is allocated, the chosen tag value
|
||||
will be the previous value incremented by one to provide use-after-free
|
||||
detection between generations of allocations. The stored tag will be wiped
|
||||
before retagging the memory, to avoid leaking it and as part of preserving the
|
||||
security property of newly allocated memory being zeroed due to zero-on-free.
|
||||
It will eventually wrap all the way around, but this ends up providing a strong
|
||||
guarantee for many allocation cycles due to the combination of 4 bit tags with
|
||||
the FIFO quarantine feature providing delayed free. It also benefits from
|
||||
random slot allocation and the randomized portion of delayed free, which result
|
||||
in a further delay along with preventing a deterministic bypass by forcing a
|
||||
reuse after a certain number of allocation cycles. Similarly to the initial tag
|
||||
generation, tag values for adjacent allocations will be skipped by incrementing
|
||||
past them.
|
||||
|
||||
For example, consider this slab of allocations that are not yet used with 15
|
||||
representing the tag for free memory. For the sake of simplicity, there will be
|
||||
no quarantine or other slabs for this example:
|
||||
|
||||
| 15 | 15 | 15 | 15 | 15 | 15 |
|
||||
|
||||
Three slots are randomly chosen for allocations, with random tags assigned (2,
|
||||
7, 14) since these slots haven't ever been used and don't have saved values:
|
||||
|
||||
| 15 | 2 | 15 | 7 | 14 | 15 |
|
||||
|
||||
The 2nd allocation slot is freed, and is set back to the tag for free memory
|
||||
(15), but with the previous tag value stored in the freed space:
|
||||
|
||||
| 15 | 15 | 15 | 7 | 14 | 15 |
|
||||
|
||||
The first slot is allocated for the first time, receiving the random value 3:
|
||||
|
||||
| 3 | 15 | 15 | 7 | 14 | 15 |
|
||||
|
||||
The 2nd slot is randomly chosen again, so the previous tag (2) is retrieved and
|
||||
incremented to 3 as part of the use-after-free mitigation. An adjacent
|
||||
allocation already uses the tag 3, so the tag is further incremented to 4 (it
|
||||
would be incremented to 5 if one of the adjacent tags was 4):
|
||||
|
||||
| 3 | 4 | 15 | 7 | 14 | 15 |
|
||||
|
||||
The last slot is randomly chosen for the next allocation, and is assigned the
|
||||
random value 14. However, it's placed next to an allocation with the tag 14 so
|
||||
the tag is incremented and wraps around to 0:
|
||||
|
||||
| 3 | 4 | 15 | 7 | 14 | 0 |
|
||||
When memory tagging is enabled, checking for write-after-free at allocation
|
||||
time and checking canaries are both disabled. Canaries will be more thoroughly
|
||||
disabled when using memory tagging in the future, but Android currently has
|
||||
very dynamic memory tagging support where it can be enabled/disabled at any
|
||||
time which creates a barrier to optimizing by disabling redundant features.
|
||||
|
||||
## API extensions
|
||||
|
||||
|
|
Loading…
Reference in New Issue