Skip to content

tcache metadata poisoning

tcache_perthread_struct'ın kendisini (counts/entries dizilerini) corrupt ederek, allocator'ın her size class için attacker'ın seçtiği pointer'ları aynı anda dağıtmasını sağlamak.

Mechanism

Suistimal edilen invariant: per-thread tcache_perthread_struct'ın kendisi bir heap chunk'tır ve tamamen güvenilirdir

tcache defter-tutması, thread'in heap'indeki ilk chunk olan tek bir heap-allocated kontrol yapısında yaşar:

typedef struct tcache_perthread_struct
{
  uint16_t counts[TCACHE_MAX_BINS];      /* TCACHE_MAX_BINS == 64 */
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

static __thread tcache_perthread_struct *tcache = NULL;

tcache_get/tcache_put bu dizileri doğrudan okur ve yazar:

e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);

Sade tcache-poisoning bir free edilmiş chunk'ın next'ini forge ederken, metadata poisoning kontrol struct'ının entries head pointer'larını ve counts'unu forge eder. entries[tc_idx] ham, mangle edilmemiş list head'i olduğundan (yalnızca chunk'ların içindeki next link'leri PROTECT_PTR ile mangle edilir), entries[tc_idx]'i yazmak, o size'ın sonraki malloc'unun nerede döndürüleceğini doğrudan ayarlar — o ilk pop için hiçbir safe-linking dansı gerekmez. Bir counts[tc_idx]'i artırmak, allocator'ı bir bin'in boş olmadığına inandırır ki tcache fast path'ini alsın. Tüm struct'ı kontrol etmek, malloc'u her size class için eşzamanlı olarak bir arbitrary-address allocator'a dönüştürür.

Walkthrough

tcache_perthread_struct heap'teki ilk chunk'tır (64-bit'te size 0x290), dolayısıyla ona ulaşan erken bir UAF/overflow ya da ona bir pointer döndüren bir tcache-poisoning, alanlarını overwrite etmene olanak tanır.

// 1. Obtain a write handle to the tcache control struct.
//    e.g. poison a freed 0x290 chunk's next to point at the heap base,
//    or free the very first allocation and reclaim it.
uint16_t *counts   = (uint16_t*)tcache_struct;          // counts[64]
void    **entries  = (void**)(tcache_struct + 0x80);    // entries[64]

// 2. Forge a bin: make the size-0x40 bin (tc_idx) claim one ready chunk
size_t tc_idx = 2;                  // (0x40 user size class)
counts[tc_idx]  = 1;                // pretend the bin is non-empty
entries[tc_idx] = (void*)target;    // head -> arbitrary address (unmangled!)

// 3. Allocate: tcache_get pops `target` directly.
void *p = malloc(0x40);             // returns `target`

Beklenen çıktı: p == target. entries[] list head'i olduğundan (chunk-içi bir next değil), bu ilk pop'a hiçbir PROTECT_PTR mangling'i uygulanmaz, dolayısıyla ilk arbitrary allocation için bir heap leak gereksizdir.

Neden genelleşir

Birkaç i için counts[i] ve entries[i]'yi ayarlamak, kontrol struct'ının tek bir corruption'ının her biri seçilmiş bir adreste olmak üzere birçok farklı malloc(size) isteğini servis etmesine olanak tanır — tek seferlik bir poison yerine etkili biçimde global bir allocation primitive'i.

tcache_get hâlâ alignment'ı kontrol eder

Pop edilen head aligned_OK'i sağlamalı, yoksa malloc(): unaligned tcache chunk detected tetiklenir. Forge edilmiş entries[i] 16-byte aligned olmalıdır.

Detection

  • Herhangi bir bilinen eşlenmiş heap aralığının dışına düşen bir pointer döndüren malloc(), ya da aynı adresi alias'layan iki canlı allocation, heap-debug araçlarında (AddressSanitizer, glibc MALLOC_CHECK_, electric-fence) yakalanır.
  • Process log'larında/core'larında abort string'leri: malloc(): unaligned tcache chunk detected (forge edilmiş hizasız head) ve tcache-key / counts tutarlılık abort'ları, runtime'da kontrol struct'ının kurcalandığına işaret eder.
  • EDR/telemetri: .bss, GOT, stack ya da hook region'larına düşen ve hemen ardından üzerinden bir write gelen bir malloc() türevli pointer güçlü bir anomalidir; toplanan backtrace'lerde tcache_get / _int_malloc içindeki çökmeleri ilişkilendir.
  • Fuzzing/CI: hedefleri ASan altında çalıştır; ilk heap chunk'ına (perthread struct) ulaşan UAF ve overflow, bir primitive haline gelmeden önce güvenilir biçimde yakalanır.

Mitigation

  • Safe-Linking chunk-içi next link'lerini korur ama kontrol struct'ındaki entries[] head'lerini korumaz, dolayısıyla bu yol pointer mangling'den etkilenmez.
  • tcache_get'teki aligned_OK hizasız forge edilmiş head'leri reddeder.
  • Kontrol struct'ını öngörülemez biçimde yerleştiren bir heap layout'u ya da guard page'ler ona ulaşmanın maliyetini yükseltir; glibc'nin kendisi onu izole etmez.

References