Skip to content

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 yerine nullptr verir/throw eder.
  • union discriminant'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.

References