Tcache Keys
A primitive double-free protection
Last updated
Was this helpful?
A primitive double-free protection
Last updated
Was this helpful?
Starting from glibc 2.29, the tcache was hardened by the addition of a second field in the tcache_entry
struct, the :
It's a pointer to a tcache_perthread_struct
. In the function, we can see what key
is set to:
The chunk being freed is variable e
. We can see here that before tcache_put()
is called on it, there is a check being done:
The check determines whether the key
field of the chunk e
is set to the address of the tcache_perthread_struct
already. Remember that this happens when it is put into the tcache with tcache_put()
! If the pointer is already there, there is a very high chance that it's because the chunk has already been freed, in which case it's a double-free!
It's not a 100% guaranteed double-free though - as the comment above it says:
This test succeeds on double free. However, we don't 100% trust it (it also matches random payload data at a 1 in 2^<size_t> chance), so verify it's not an unlikely coincidence before aborting.
There is a 1/2^<size_t>
chance that the key
being tcache_perthread_struct
already is a coincidence. To verify, it simply iterates through the tcache bin and compares the chunks to the one being freed:
Iterates through each entry, calls it tmp
and compares it to e
. If equal, it detected a double-free.
You can think of the key
as an effectively random value (due to ASLR) that gets checked against, and if it's the correct value then something is suspicious.
In fact, the key
can even be helpful for us - the fd
pointer of the tcache chunk is mangled, so a UAF does not guarantee a heap leak. The key
field is not mangled, so if we can leak the location of tcache_perthread_struct
instead, this gives us a heap leak as it is always located at heap_base + 0x10
.
The value of tcache_key does not really have to be a cryptographically secure random number. It only needs to be arbitrary enough so that it does not collide with values present in applications. [...]
This isn't a huge change - it's still only straight double-frees that are affected. We can no longer leak the heap via the key
, however.
When a chunk is freed and tcache_put()
is called on it, the key
field is set to the location of the tcache_perthread_struct
. Why is this relevant? Let's check :
So, what can we do against this? Well, this protection doesn't affect us that much - it stops a simple double-free, but if we have any kind of UAF primitive we can easily overwrite e->key
. Even with a single byte, we still have a 255/256 chance of overwriting it to something that doesn't match key
. Creating fake tcache chunks doesn't matter either, as even in the latest glibc version there is , meaning tcache poisoning is still doable.
In glibc 2.34, the key
field was . Instead of tcache_put()
setting key
to the location of the tcache_perthread_struct
, it sets it to :
Note the as well!
What is tcache_key
? It's defined and set directly below, in the function:
It attempts to call __getrandom()
, which is defined as a stub and for Linux ; it just uses a syscall to read n
random bytes. If that fails for some reason, it calls the function instead, which generates a pseudo-random number seeded by the time. Long story short: tcache_key
is random. The , and the operation is the same, just it's completely random rather than based on ASLR. As the comment above it says