Skip to content

_IO_vtable_check bypass

Forge edilmiş bir FILE'ın vtable'ını legitimate in-range bir glibc vtable'ına (ya da read-only vtable array'i içindeki misaligned bir slot'a) yönelt — veya range check'in hiç dokunmadığı ikincil bir vtable'ı (_wide_data->_wide_vtable, House of Apple 2) zincirle — ki glibc 2.24+ vtable range check'i geçsin ama bir virtual call yine de yönlendirilsin.

Mechanism

Note

glibc 2.24 FILE vtable verification ekledi (RH bug 1372306). Her virtual dispatch'ten önce, IO_validate_vtable() vtable pointer'ının read-only __io_vtables array'i içinde durduğunu kontrol eder; durmuyorsa _IO_vtable_check() "Fatal error: glibc detected an invalid stdio handle" ile abort eder. Kritik zayıflık şu: check range'i zorunlu kılar, correctness'i değil: __io_vtables içindeki herhangi bir pointer kabul edilir. Dolayısıyla attacker vtable'ı heap'e yöneltmez (bu abort eder) — onu _IO_str_jumps ya da _IO_wfile_jumps gibi gerçek bir glibc vtable'ına yöneltir, ya da array içinde kasıtlı olarak misaligned bir offset'e yöneltir ki farklı, in-range bir function pointer çağrılsın. Bu, her modern FSOP "House of *" zincirinin etkinleştirici adımıdır.

Walkthrough

Check (glibc libio/libioP.h):

static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable) {
  uintptr_t ptr    = (uintptr_t) vtable;
  uintptr_t offset = ptr - (uintptr_t) &__io_vtables;
  if (__glibc_unlikely (offset >= IO_VTABLES_LEN))   /* range check only */
    _IO_vtable_check ();
  return vtable;
}

__io_vtables tüm libc vtable'larının contiguous bir array'idir; IO_STR_JUMPS, IO_WSTR_JUMPS, IO_FILE_JUMPS, IO_WFILE_JUMPS, IO_COOKIE_JUMPS, IO_PROC_JUMPS, IO_MEM_JUMPS, IO_WMEM_JUMPS gibi macro'larla indekslenir. Bypass reçeteleri:

  1. Legitimate bir vtable'ı reuse et. fp->vtable = &_IO_str_jumps (in range) ayarla ve FILE'ı öyle hazırla ki tetiklenen slot (__overflow_IO_str_overflow) dereference ettiği attacker-controlled bir function pointer'ı çağırsın (bkz. io-str-overflow.md).
  2. Array içinde misalign et. Vtable'ı gerçek bir entry'nin birkaç byte öncesine/sonrasına ayarla ki istenen slot'un offset'i farklı, attacker'a faydalı in-range bir function pointer'a denk gelsin.
  3. Hedefi, checked olmayan ikincil bir vtable'ı dereference eden bir vtable kullan (_IO_wfile_jumps_wide_data->_wide_vtable); range check buna hiç dokunmaz — House of Apple 2 path'i (bkz. io-wfile-jumps.md).

Warning

_IO_vtable_check dar durumlarda hâlâ gerçekten foreign vtable'lara izin verir: non-base bir namespace (l_ns != LM_ID_BASE olan dlopen-loaded bir DSO) ya da IO_accept_foreign_vtables (mangle'lanmış bir flag) set olduğunda. Bunlar genelde ayrı bir primitive olmadan attacker tarafından erişilebilir değildir.

Detection

  • Bir bypass yanlış hesaplandığında "invalid stdio handle" fatal error'u olarak loglanan abort'lar.
  • FILE vtable'ları üzerinden giden indirect call'larda CFI; RELRO __io_vtables'ı kapsamaz (zaten read-only) ama komşu GOT target'larını kapsar.

Mitigation

  • Range check'in kendisi arbitrary heap vtable'lara karşı mitigation'dır; onu yenmek yukarıdaki in-range reuse'u gerektirir.
  • İkincil yapıların pointer-mangling'i (örn. _IO_cookie callback'leri) ve _FORTIFY_SOURCE kullanılabilir in-range slot kümesini azaltır.

References