Skip to content

Tcache Metadata Hijacking

tcache_perthread_struct kontrol yapısını — counts[] ve entries[] dizilerini ya da işaret ettikleri chunk-içi e->next head'lerini — corrupt ederek, allocator'ın kendi metadata'sının sonraki malloc()'u attacker'ın seçtiği bir adrese yönlendirmesini sağlamak.

Mechanism

Suistimal edilen invariant: allocator per-thread defter-tutma struct'ına koşulsuz güvenir

glibc, her thread'in tcache state'ini, thread'in arena'sındaki ilk chunk olarak yaşayan tek bir heap-yerleşik kontrol yapısında tutar:

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

İki alan her fast-path allocation'ı kapılar: counts[i]'nin bin i'deki hazır chunk'ların sayısı olduğuna inanılır ve entries[i]'nin o bin'in singly linked free list'inin head'i olduğuna inanılır. tcache_get(), entries[i]'yi pop edip counts[i]'yi azaltmadan önce yalnızca minimal bir tc_idx aralık kontrolü yapar; bu değerleri herhangi bir güvenilir kaynaktan yeniden türetmez.

Metadata hijacking, tek bir free edilmiş chunk yerine bu YAPIYI hedefler. Sade tcache-poisoning, bir free edilmiş chunk'ın next link'ini forge eder ki ikinci allocation yönlendirilsin. Metadata hijacking ise bunun yerine kontrol struct'ının entries[] head'lerini ve counts[]'unu doğrudan yeniden yazar — list head'leri ham olarak saklanır (yalnızca chunk-içi next link'leri Safe-Linking'in PROTECT_PTR'ı ile mangle edilir), dolayısıyla entries[i]'yi overwrite etmek, o size class'ın sonraki pop'unu herhangi bir adrese, attacker'ın seçtiği kadar çok size class için aynı anda yöneltir. counts[i]'yi artırmak, allocator'ı bir bin'in boş olmadığına ikna eder ki daha yavaş, daha iyi kontrol edilen koda düşmek yerine tcache yolunu alsın.

İlişki: tcache-metadata-poisoning ile aynı struct, farklı çerçeve

Bu not ile tcache-metadata-poisoning, aynı kök tekniği — tcache_perthread_struct'ın entries[]/counts[] alanlarının struct-seviyesi corruption'ı — kapsar; ayrı bir zafiyet sınıfı değildir. Poisoning notu tek bir hedeflenmiş entries[] head overwrite'ına (bir sonraki malloc'u tek bir adrese yöneltmek) odaklanır; bu hijacking notu ise aynı primitive'in birden fazla bin'i aynı anda ele geçirip malloc'u genel bir arbitrary-address allocator'a dönüştüren genelleştirilmiş biçimini vurgular. İkisini birlikte oku.

Walkthrough

Üst düzey yeniden üretim; herkese açık CTF-Wiki tcache materyalini takip eder. tcache_perthread_struct heap'teki ilk chunk'tır — struct'ın kendisi 64*2 + 64*8 = 0x280 byte'tır, onu tutan chunk ise SIZE_SZ header + 16-byte alignment (request2size) ile 64-bit'te 0x290'a yuvarlanır — dolayısıyla herhangi bir erken UAF, lineer overflow ya da struct'a bir pointer döndüren bir poisoning, metadata üzerinde bir write handle verir.

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

// 2. Forge a bin: claim one ready chunk and set its head.
counts[idx]  = 1;              // pretend bin `idx` is non-empty
entries[idx] = (void*)target;  // head -> arbitrary address (UNMANGLED)

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

Yönlendirilen pointer, o ilk pop için hiçbir heap leak gerektirmeden döndürülür; çünkü entries[]'deki list head'i PROTECT_PTR'dan geçirilmez.

Neden global bir primitive'e genelleşir

Birkaç i için counts[i]/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 — malloc'u tek seferlik bir yönlendirme yerine etkili biçimde bir arbitrary-address allocator'a dönüştürür. Bu, sade poisoning'e göre kavramsal üstünlüktür ve devam eden bir arbitrary-write primitive ile doğal olarak eşleşir.

Kalıntı kontroller hâlâ geçerli

tcache_get(), pop edilen head üzerinde aligned_OK'i zorlar; hizasız bir forge edilmiş adres malloc(): unaligned tcache chunk detected çıkarır. Forge edilmiş head'ler 16-byte aligned olmalı ve sonraki glibc'de eklenen tcache key/counts tutarlılık kontrolleri beceriksiz corruption'ın maliyetini yükseltir.

Detection

  • Bilinen herhangi bir eşlenmiş heap aralığı dışında pointer döndüren heap allocator'lar ya da aynı adresi alias'layan iki canlı allocation, ASan/heap-debug araçlarında (AddressSanitizer, glibc MALLOC_CHECK_, electric-fence) ortaya çıkar.
  • Process log'larında/core'larında abort string'leri: malloc(): unaligned tcache chunk detected, free(): invalid pointer ya da tcache-key abort mesajları runtime'da metadata kurcalamasına işaret eder.
  • EDR/telemetri: .bss, GOT, stack ya da __free_hook/__malloc_hook region'ına düşen ve hemen ardından üzerinden bir write gelen bir malloc() türevli pointer güçlü bir anomalidir. Toplanan backtrace'lerdeki tcache_get / _int_malloc içindeki çökmeleri ilişkilendir.
  • Fuzzing/CI: hedefleri ASan altında çalıştır; ilk heap chunk'ına ulaşan UAF ve overflow, bir primitive haline gelmeden önce güvenilir biçimde yakalanır.

Mitigation

  • Safe-Linking (glibc ≥ 2.32) chunk-içi next pointer'larını mangle eder ama kontrol struct'ındaki entries[] head'lerini korumaz — savunmacılar onun bu yolu durdurduğunu varsaymamalıdır.
  • aligned_OK ve tcache key alanı (glibc ≥ 2.29) bir sınıf forge-edilmiş/double-free edilmiş entry'yi yakalar; glibc'yi güncel tut.
  • -D_FORTIFY_SOURCE=2 ile derle, testte ASan/MTE'yi etkinleştir ve kök neden UAF/overflow'u düzelt — metadata struct'ına yalnızca onun üzerinden ulaşılır.
  • Allocator metadata'sını izole eden heap hardening (guard page'ler, hardened allocator'lardaki gibi out-of-line metadata) maliyeti yükseltir; upstream glibc perthread struct'ı izole etmez.

References