Skip to content

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:

size_t tc_idx = csize2tidx (size);   /* derived from the live size field */

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.

  1. 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.

  2. Victim'i bir kere free et. S1 boyutunda bir chunk free et. tidx(S1) index'inde insert edilir, key field'ı tcache pointer'ıyla damgalanır ve next field'ı onu o list'e link'ler.

  3. Bin-içi size'ı corrupt et. Bitişik canlı bir chunk üzerindeki overflow'u kullanarak, freed victim'in size header'ını S1'den S2'ye overwrite et; burada tidx(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ç.

  1. Victim'i tekrar free et. _int_free artık tc_idx = tidx(S2) hesaplar. e->key == tcache testi eşleşebilir ama doğrulayan scan tidx(S2)'deki list'i dolaşır — chunk'ın hiç insert edilmediği yer — dolayısıyla duplicate bulunmaz ve free kabul edilir. Chunk artık S2 bin'ine de link'li.

  2. İki kere reclaim et. İki allocation (biri S1 boyutunda, biri S2 boyutunda) aynı address'i döndürür. Orijinal writeup iki allocation'ın da 0x55fbe22862c0 dö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

  1. 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/next tutarlı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.

References