House of Kauri¶
Freed bir chunk bir bin'de otururken size'ını değiştirerek tcache double-free check'ini şaşırt; böylece duplicate-detection scan'i yanlış size index'ine karşı çalışır.
Mechanism¶
Suistimal edilen invariant: tcache double-free check'i güncel chunk size'ıyla index'lenir
glibc 2.29+'ta tcache_entry bir key field taşır ve _int_free double
free'lere karşı iki adımla koruma sağlar: e->key'i tcache'e karşı karşılaştırır
ve bu eşleşirse, chunk'ın gerçekten orada bulunduğunu doğrulamak için tcache
list'ini free edilen chunk'ın size index'inde dolaşır. Ölümcül varsayım,
bir chunk'ın size'ının ilk ve ikinci free'si arasında stabil olduğudur. Size
index'i her free'de yeniden hesaplanır:
Eğer bir attacker freed chunk'ın size header'ını bir tcache bin'e girdikten
sonra değiştirirse, ikinci free() farklı bir tc_idx hesaplar.
Duplicate scan o zaman chunk'ın hiç insert edilmediği bir list'i dolaşır,
eşleşme bulamaz ve double free'yi kabul eder. Chunk sonunda aynı anda iki ayrı
tcache bin'ine link'lenmiş olur ki bu aynı bellek için iki ayrı pointer verir —
tcache poisoning'in temeli. Bu eksik bir check değil, bir metadata-confusion
bug'ı: check çalışır ama yanlış list'e karşı.
Walkthrough¶
Target: glibc 2.31 / ptmalloc2, Ubuntu 20.04'te test edildi. Zaten freed olmuş bir chunk'ın size field'ına bir heap overflow (veya herhangi bir write) gerektirir.
tcache binning size-driven'dır. csize2tidx bir chunk size'ını bir bin index'ine
map'ler, dolayısıyla iki farklı size tcache_perthread_struct içinde iki farklı
singly-linked list'e çözülür.
-
tcache'i doldur. Chunk'lar allocate edip free et ki victim size'ı için bin zaten var olsun ve per-bin counter öngörülebilir davransın.
-
Victim'i bir kere free et.
S1boyutunda bir chunk free et.tidx(S1)index'inde insert edilir,keyfield'ı tcache pointer'ıyla damgalanır venextfield'ı onu o list'e link'ler. -
Bin-içi size'ı corrupt et. Bitişik canlı bir chunk üzerindeki overflow'u kullanarak, freed victim'in size header'ını
S1'denS2'ye overwrite et; buradatidx(S2) != tidx(S1).
!!! warning "Footgun: değiştirilen size'ı legal tut"
S2 hâlâ index-range türetmesini geçmeli (geçerli bir small tcache size)
yoksa free() farklı bir code path'e düşer. Temiz şekilde farklı, geçerli
bir tcache bin'ine map'lenen bir size seç.
-
Victim'i tekrar free et.
_int_freeartıktc_idx = tidx(S2)hesaplar.e->key == tcachetesti eşleşebilir ama doğrulayan scantidx(S2)'deki list'i dolaşır — chunk'ın hiç insert edilmediği yer — dolayısıyla duplicate bulunmaz ve free kabul edilir. Chunk artıkS2bin'ine de link'li. -
İki kere reclaim et. İki allocation (biri
S1boyutunda, biriS2boyutunda) aynı address'i döndürür. Orijinal writeup iki allocation'ın da0x55fbe22862c0döndürdüğünü gösterir.
??? example "Effect: aliased allocation'lar"
ptr_a = malloc(S1); // pulled from bin tidx(S1)
ptr_b = malloc(S2); // pulled from bin tidx(S2)
assert(ptr_a == ptr_b); // same chunk handed out twice
- Escalate et. Bir chunk'a iki canlı alias ile, tcache poisoning yapmak için
next(fd) pointer'ını overwrite et ve o boyutun sonraki request'inde bir arbitrary allocation elde et.
Detection¶
- Aynı address'in ilk ve ikinci free'si arasında değişen bir size field anormaldir; free anında size'ı snapshot'layan allocator shim'leri bunu yakalayabilir.
- Aynı fiziksel address'in aynı anda iki farklı tcache bin'inde görünmesi heap-sanitizer tooling altında güçlü bir corruption sinyalidir.
Mitigation¶
- tcache index'ini güvenilir bir kaynaktan yeniden hesaplayan veya sabitleyen
sonraki hardening ve
key/nexttutarlılığını validate etmeye yönelik genel hareket bu pencereyi daraltır. - Safe-linking (glibc 2.32+)
next'i obfuscate eder ama duplicate scan'in size-confusion'ını tek başına fix etmez; safe-linking'in zorlaştırdığı şey primitive'in poisoning adımıdır.