Tcache Stashing Unlink Attack
Introduction
The “Tcache Stashing Unlink” is a sophisticated heap exploitation technique that targets the way glibc (specifically from 2.27 onwards) refills the tcache from the Small Bins. When a malloc request is satisfied by a Small Bin chunk and the corresponding tcache bin has empty slots, glibc “stashes” the remaining chunks from that Small Bin into the tcache.
By corrupting the bk pointer of a chunk in the Small Bin, an attacker can trick this stashing logic into linking an arbitrary memory location (like the stack or a global variable) into the tcache.
Prerequisites
- Small Bin Control: Ability to place chunks into the Small Bin (requires bypassing/filling tcache and triggering consolidation/sorting).
- BK Overwrite: A vulnerability (like a heap overflow) to overwrite the
bkpointer of a chunk in the Small Bin. - Writable Address: A known writable address to satisfy the
bck->fd = bincheck in glibc’sunlinkmacro (or rather the stashing logic’s equivalent). - Calloc/Malloc Pair: Often
callocis used to trigger the Small Bin allocation because it bypasses the tcache for its initial allocation.
Example from tcache_stashing_unlink.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(){
unsigned long stack_var[0x10] = {0};
unsigned long *chunk_lis[0x10] = {0};
unsigned long *target;
setbuf(stdout, NULL);
// Set up fake chunk bk to bypass checks
stack_var[3] = (unsigned long)(&stack_var[2]);
for(int i = 0;i < 9;i++){
chunk_lis[i] = (unsigned long*)malloc(0x90);
}
// Fill tcache
for(int i = 3;i < 9;i++){
free(chunk_lis[i]);
}
free(chunk_lis[1]);
// Put chunks into unsorted bin
free(chunk_lis[0]);
free(chunk_lis[2]);
// Move to small bin
malloc(0xa0);
// Leave space in tcache
malloc(0x90);
malloc(0x90);
// VULNERABILITY: Corrupt small bin bk
chunk_lis[2][1] = (unsigned long)stack_var;
// Trigger stashing unlink
calloc(1,0x90);
target = malloc(0x90);
assert(target == &stack_var[2]);
return 0;
}Attack Flow Explained
1. Tcache and Small Bin Preparation
To use Small Bins, the tcache for the target size must first be partially filled. In the example, we free 7 chunks to fill the tcache, then free more chunks which go into the Unsorted Bin. By allocating a larger size, these chunks are sorted into the Small Bin.
We then allocate some chunks from the tcache to leave at least two slots open.
2. Corrupting the Small Bin BK
The vulnerability is used to overwrite the bk pointer of a chunk currently in the Small Bin. We point it to our fake chunk location (the stack).
chunk_lis[2][1] = (unsigned long)stack_var;3. Triggering the Stashing Refill
We call calloc(1, 0x90). calloc does not use chunks directly from the tcache. It looks in the Small Bin, finds a chunk, and returns it. Crucially, it then sees that the tcache for size 0x90 has empty slots and begins “stashing” the rest of the chunks from that Small Bin into the tcache.
The stashing logic follows the corrupted bk chain:
- It takes the next chunk (
chunk_lis[0]). - It follows its
bkto ourstack_var. - It “stashes” our
stack_varinto the tcache.
4. Arbitrary Allocation
The next malloc(0x90) request will be served directly from the tcache, returning the pointer to our stack variable.
Security Implications
This attack is particularly dangerous because it bypasses many tcache protections and can be used to link non-heap memory into the heap management structures. It also results in a pointer to the arena being written to the fake chunk’s bk->fd location, which can sometimes be used as an information leak.