Skip to content

House of IO

tcache_perthread_struct'ın kendisini — tcache bin head'lerinin heap-resident array'ini — corrupt et; böylece entries[] pointer'ları arbitrary, unmangled address'lerle overwrite edilir ve temiz bir arbitrary-allocation primitive elde edilir.

Mechanism

Suistimal edilen invariant

Her thread'in tcache metadata'sı tek bir heap chunk'ta yaşar; şöyle tanımlanır:

typedef struct tcache_perthread_struct {
  uint16_t      counts[TCACHE_MAX_BINS];   /* per-bin chunk count   */
  tcache_entry *entries[TCACHE_MAX_BINS];  /* per-bin freelist head */
} tcache_perthread_struct;

entries[] head'leri raw olarak saklanır — chunk içindeki fd pointer'larının aksine, safe-linking pointer mangling'inden geçirilmezler. Yani bu struct'a yazabilen biri herhangi bir bin'in head'ini herhangi bir address'e set edebilir; o boyutta sonraki malloc() address'i doğrudan geri verir. Allocator'ın dayattığı tek püf nokta counts[i]: tcache fast path'leri bir bin'den ancak count'u non-zero ise pop yapar, ama allocator count'u validate etmez, dolayısıyla bir attacker poison'lanmış entry'nin yanına sadece pozitif bir count yazar.

House of IO'nun katkısı struct'a nasıl ulaştığındır. tcache_perthread_struct thread'in ilk allocation'ında lazy olarak allocate edilir, yani main thread'de heap'in en başında oturur ve normalde sadece bir heap underflow ile erişilebilir. House of IO bunun yerine onun üzerinde sıradan bir write/UAF elde eder.

Walkthrough

glibc ≥ 2.26'ya uygulanır (tcache tanıtıldı); raw entries[] tasarımı 2.34'e kadar devam eder, safe-linking ise chunk fd'sine (bu head'lere değil) 2.32'de eklendi. İki ana yol var:

  1. Underflow varyantı. tcache_perthread_struct heap'teki ilk chunk olduğunda, ilk user chunk'tan yapılan negative-index / underflow write struct'a geriye doğru uzanır. Target bin'in entries[i]'sini target ile overwrite et ve counts[i]'yi artır ki allocator bin'i non-empty sansın.

  2. Heap-reuse varyantı (adını veren "IO"). Bir arena'yı paylaşmaya zorlanacak kadar thread spawn et (thread'ler per-CPU arena cap'ini aştığında). İkincil bir thread'in tcache_perthread_struct'ı bu durumda shared arena'da sıradan bir heap chunk olarak allocate edilir — artık heap base'ine sabitlenmemiş. Şimdi bitişik bir main-thread chunk'tan yapılan düz bir use-after-free, double-free veya linear buffer overflow onun üzerine düşer ve nadir underflow primitive'ine olan ihtiyacı ortadan kaldırır.

  3. Entries slot'unu bul. i bin index'ini poison'lamak için chunk header'ını (0x10) ve counts array'ini (0x80 byte) geçip entries[]'e yazarsın. sz boyutundaki bir chunk için slot, entries[] içinde ((sz - 0x20) / 0x10) * 8 byte'tadır.

  4. Target'ı yaz. target'ı (mangling yok, alignment kısıtı yok) o slot'a sakla ve eşleşen counts entry'sini non-zero bir değere set et.

  5. Reclaim. malloc(sz) entries[i]'yi pop'lar ve target'ı döndürür. İkinci bir malloc(sz) target'ın bir sonraki işaret ettiği şeyi döndürür — bunu libc global'lerine, __free_hook'a, saklı bir object pointer'a vb. bir write'a çevir.

Count neden önemli

tcache get path'i kabaca if (tcache->counts[tc_idx] > 0) { ... return e; } şeklindedir. Poison'lanmış bir entries[i] ama counts[i] == 0 ile bin atlanır ve address'in asla döndürülmez — her zaman iki field'ı birlikte patch'le.

Footgun'lar

  • Heap-reuse varyantı arena sharing'e bağlıdır ki bu sadece arena_max/CPU-count eşiğinin ötesinde devreye girer; single-arena bir target'ta yine underflow'a ihtiyaç duyarsın.
  • counts uint16_t'dir; entries[]'e ulaşmak için counts array'i boyunca 8-byte filler yazmak birçok bin count'unu non-zero yapar — target bin için zararsız ama sonraki allocation'ları bozabilir.

Detection

  • entries[] head'i heap dışına (libc/stack içine) işaret eden bir tcache_perthread_struct belirleyici işarettir.
  • counts[i] ile i bin'i için gerçekten link'li freelist uzunluğu arasındaki uyumsuzluk tampering'i gösterir.

Mitigation

  • Safe-linking (glibc 2.32) chunk fd pointer'larını korur ama bu struct head'lerini korumaz, dolayısıyla House of IO'yu doğrudan durdurmaz.
  • tcache key/double-free guard (glibc 2.29+) struct'a giden double-free path'ini zorlaştırır ama UAF/overflow write'larını değil.
  • tcache_perthread_struct'ı off-heap saklayan veya entries[]'i mangle eden allocator build'leri technique'i kapatır; kaynak UAF/overflow'u kaldırmak prerequisite'i kaldırır.

References