Skip to content

Reference count overflow/underflow

Bir resource'un reference counter'ı yanlış güncellenir (genişliğinin ötesine over-increment edilir ya da çok erken decrement edilir), böylece object hâlâ canlı reference'lar varken free edilir — use-after-free'ye yol açar; CWE-911.

Mechanism

Invariant

Reference counting tek bir invariant'ı korur: counter, bekleyen reference sayısına eşittir ve resource tam olarak bu sayı sıfıra ulaştığında free edilir. Bir reference alan her code path increment etmeli, bir tanesini bırakan her path decrement etmelidir — simetrik olarak. CWE-911 ("Improper Update of Reference Count") bug'ı şöyle tanımlar: bir ürün "bir resource'u yönetmek için bir reference count kullanır, ama reference count'u güncellemez veya yanlış günceller."

İki failure mode invariant'ı kırar, ikisi de dangling bir object'le biter:

  • Underflow / premature decrement — bir error path ya da çoğaltılmış bir put çok erken ya da iki kez decrement eder. Bir reference hâlâ tutulurken count sıfıra iner (ya da sıfırın altına wrap eder); object free edilir; hayatta kalan reference dangling bir pointer olur → use-after-free.
  • Overflow — counter fixed-width bir integer'dır (Linux kernel'de yaygın olarak 32-bit atomic_t). Eşleşen bir decrement olmadan increment eden bir leak path, attacker'ın count'u maksimumuna kadar pompalayıp 0'a wrap etmesine izin verir. Kernel o zaman hâlâ referans verilen object'i referanssız sayar ve onu free eder — yine bir UAF, ama erken bir bırakma yerine counter'ı doyurarak (saturate) ulaşılan.

Her iki durumda da tehlikeli an aynıdır: object'e bir pointer hayatta kalırken free() çalışır. O pointer'ı sonra dereference eden her kim olursa, free edilmiş (ve potansiyel olarak yeniden tahsis edilmiş, attacker-controlled) bellek üzerinde çalışır.

Walkthrough

Ders kitabı vakası CVE-2016-0728'dir; Linux keyring'inde bir refcount-overflow UAF (Perception Point, 2016). security/keys/process_keys.c içindeki join_session_keyring(), find_keyring_by_name() üzerinden bir reference alır, ama bir process session keyring'ini aynı keyring ile değiştirmeye çalıştığında, eşleşen key_put()'u atlayan bir error label'a atlar — çağrı başına bir reference leak eder. Reference count 32-bit bir atomic_t olduğundan, tekrarlı çağrılar onu sıfıra overflow edebilir.

/* Conceptual shape of the leaking path (per the Perception Point analysis):
 * each KEYCTL_JOIN_SESSION_KEYRING on the SAME name leaks one refcount.   */
for (i = 0; i < 0xfffffffd; i++)              /* ~4.29 billion iterations  */
    keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");
/* key->usage (atomic_t, 32-bit) now near overflow; a few more wrap it to 0,
 * the keyring is freed while our process still references it -> UAF.       */

Loop'un 32-bit bir counter'ı ~2^32 increment boyunca sürmesi gerektiğinden, PoC milyarlarca keyctl syscall çalıştırır (yayınlanan exploit 0xfffffffd'ye kadar döner); asıl overflow-ve-free son birkaç increment'tir.

# On a vulnerable kernel (< 4.4.1) the PoC runs for minutes pumping the refcount:
$ gcc cve-2016-0728.c -o poc -lkeyutils && ./poc
uid=0 ...        # after the keyring is freed and reclaimed with a fake key_type,
                 # KEYCTL_REVOKE invokes an attacker-controlled key_type->revoke
Overflow neden code execution'a dönüşür

key->usage 0'a wrap ettiği anda keyring object'i free edilir ama dangling bir reference kalır. Exploit, free edilen slot'u bir struct key şeklinde attacker-controlled veriyle reclaim etmek için heap object'leri spray eder, böylece gömülü key_type (kendi revoke/destroy function pointer'larıyla) attacker belleğine işaret eder. Sonraki bir KEYCTL_REVOKE, key_type->revoke'u dereference eder ve kernel control flow'unu yönlendirir — klasik commit_creds(prepare_kernel_cred(0)) privilege escalation. Refcount overflow primitive'dir; UAF ve reclaim ardından gelen exploitation'dır.

Genişlik ve timing tuzakları

  • Counter genişliği önemlidir: 32-bit bir atomic_t dakikalar süren syscall'larla erişilebilir; 64-bit'e genişletmek ya da refcount_t kullanmak (wrap etmek yerine saturate eder) overflow path'ini tamamen etkisizleştirir.
  • Decrement bug'ları overflow bug'larından çok daha ucuzdur — bir error path'teki tek bir eşleşmeyen put count'u anında düşürür. Leak edilmiş ya da iki kez bırakılmış bir reference için her early-return/goto err'i denetle.

Detection

  • Linux refcount_t: atomic_t reference counter'larını refcount_t'ye çevirmek, kernel'in overflow/underflow'da WARN verip saturate etmesini sağlar ve bug'ı sessizce wrap etmek yerine yüzeye çıkarır.
  • KASAN/ASan, counting bug'ının kendisi sessiz olsa bile ortaya çıkan use-after-free'yi dereference noktasında yakalar.
  • Statik analiz ve kod review: take/put'un tüm path'lerde, özellikle error goto chain'lerinde dengeli olduğunu kontrol et.

Mitigation

  • Saturate eden reference tipleri kullan (kernel'de refcount_t; güvenli dillerde kontrollü Rc/Arc semantiği) ki overflow/underflow wrap edilmek yerine tespit edilsin.
  • take/put'u simetrik ve merkezi yap; error path'lere dağılmış manuel increment/decrement'ten kaçın.
  • Counter'ları genişlet ve/veya işlemleri rate-limit et ki bir attacker bir counter'ı gerçekçi olarak tüm aralığı boyunca süremesin.

References

İlgili kernel-kategorisi tekniği: refcount overflow use-after-free.