Type confusion¶
Bir tip olarak allocate/initialize edilen bir kaynağa daha sonra uyumsuz bir tip olarak erişilir, böylece kod, yorumlanan offset'lerde var olmayan field'ları okur veya yazar.
Mechanism¶
CWE-843, type confusion'ı bir kaynağı (bir pointer, object veya variable) bir tip kullanarak allocate veya initialize etmek, sonra o kaynağa uyumsuz bir tip kullanarak erişmek olarak tanımlar. Bellekteki byte'lar kendi başlarına asla yorumlarını değiştirmez — program değiştirir. Yeni tip, byte'ların gerçekte temsil ettiğinden farklı bir field layout, vtable pointer veya size beklediği için, bir field'ı dereference etmek attacker'ın seçtiği belleği okur veya yazar, ya da aslında bir integer olan bir pointer'ı (veya tam tersi).
Note
İhlal edilen invariant, "typed bir erişimin object'in gerçek runtime tipiyle eşleşmesi"dir.
Aynı storage'da bir char *str ile bir int id'yi üst üste bindiren bir union, ders kitabı
örneğidir: integer field'ı yazmak pointer bit'lerini sessizce yeniden yazar, böylece str'in
sonraki bir okuması attacker-controlled byte'ları dereference eder (CWE-843'ün kendi
gösterici örneği). C++'ta bu, sibling bir tipe doğru kontrol edilmeyen bir
static_cast/reinterpret_cast ile veya freed storage'ı (UAF) farklı bir object olarak
yeniden kullanarak olur, böylece eski vtable pointer yeni bir tane olarak yorumlanır.
V8 gibi JIT engine'lerde TurboFan, bir object'in Map'ini (hidden class) speculate eder; bir
bug gerçek Map'in speculate edilenden ayrılmasına izin verirse, compile edilmiş kod object'e
yanlış field offset'lerinde erişir — güçlü, primitive'siz bir read/write. Type confusion'ın
bir vtable-hijacking veya fake-vtable zincirinde bu kadar sık ilk halka olmasının nedeni
budur.
Walkthrough¶
İlgisiz iki layout'u karıştıran güvensiz bir downcast'in minimal bir C++ illüstrasyonu:
struct Base { virtual void f(); };
struct A : Base { long data; }; // {vptr, data}
struct B : Base { void (*cb)(); }; // {vptr, cb} -- function pointer field
Base *p = new A(); // really an A
B *b = static_cast<B*>(p); // UNCHECKED: now treated as a B
b->cb = (void(*)())0xdeadbeef; // writes A::data offset as a code pointer
b->cb(); // calls attacker-controlled "callback"
İkinci word'deki byte'lar A::data idi (düz integer storage); B üzerinden geri okunduğunda,
bir function pointer olarak yorumlanır ve invoke edilir.
CWE-843'ten gelen, bir write'ın bir pointer'ı bozduğu union formu:
union { char *str; int id; } u;
u.str = name; // store a pointer
u.id = 0x41414141; // overwrite low bits as an "int"
puts(u.str); // dereference of a now-corrupt pointer -> OOB / crash
Warning
Type confusion sıklıkla bir use-after-free'nin üzerine biner: A tipi bir object'i free et, aynı slot'u B tipi olarak reallocate et, sonra dangling A pointer'ını kullanmaya devam et. "İki tip" kaynakta asla bir arada bulunmaz — allocator aynı byte'ları her ikisine de verir. Yeniden kullanılan object'in şeklinin farklılaştığı herhangi bir UAF'yi, kılık değiştirmiş bir type confusion olarak ele al.
Gerçek dünya sınıfı: V8 TurboFan Map confusion
Browser exploitation'da JIT compiler bir object'in hidden class'ını (Map) speculate eder.
Bir modelleme bug'ı (örn. CVE-2020-16009, CVE-2021-30551), gerçek Map'in compile edilmiş
kodun varsaydığından deprecate olmasına/ayrılmasına izin verir, böylece üretilen kod object'i
farklı bir şekle ait offset'lerde okur/yazar — önce bir memory-corruption bug'ı gerekmeden
doğrudan bir out-of-bounds read/write primitive'i verir.
Detection¶
- C++'ı
-fsanitize=vptr(UBSan'ın parçası) ile compile et; CFI (-fsanitize=cfi-derived-cast,-fsanitize=cfi-unrelated-cast) hatalı downcast'leri call site'ta işaretler. - RTTI'nin mevcut olduğu yerde
dynamic_cast'i tercih et; başarısız bir cast sessizce alias'lamak yerinenullptrverir/throw eder. uniondiscriminant'larını,reinterpret_cast'leri ve yeniden kullanılan allocation'ın orijinalden farklı bir layout'a sahip olduğu herhangi bir UAF'yi denetle.
Mitigation¶
- Erişimden önce object tiplerini tag'le ve check et (discriminated union'lar,
dynamic_cast, runtime type ID'leri); doğrulama olmadan sibling tipler arasında asla cast yapma. - Memory-safe diller (Rust, managed runtime'lar) C/C++ OOB sonucunu ortadan kaldırır, gerçi logic seviyesindeki confusion hâlâ yanlış davranışa neden olabilir.
- JIT engine'lerde, altta yatan Map değiştiğinde speculate edilmiş tip varsayımlarının deoptimize olması için code dependency'leri kur.