_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:
- 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). - 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.
- 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_cookiecallback'leri) ve_FORTIFY_SOURCEkullanılabilir in-range slot kümesini azaltır.