Skip to content

Integer overflow / wraparound

Integer tipinin aralığını aşan aritmetik bir sonuç modulo 2^N wrap eder, böylece "daha büyük" bir değer minik ya da negatif bir değere dönüşür — en tehlikelisi o değer bir size ya da length olduğunda.

Mechanism

Invariant

Fixed-width integer'lar yalnızca sınırlı bir aralıktaki değerleri tutabilir. Kod, bir hesaplamanın monotonik olarak büyüdüğünü varsayar — a + b >= a olduğunu, ya da count * size'ın en az size olduğunu. Donanım bu varsayımı ihlal eder: unsigned aritmetik modulo 2^N wrap edecek şekilde tanımlıdır (ör. UINT_MAX + 1 == 0) ve signed overflow compiler'ın suistimal edebileceği bir undefined behavior'dır. Güvenlik hatası, wrap'lenmiş sonuç bir size, length ya da index olarak kullanıldığında olur: gigabyte vermesi gereken bir çarpım birkaç byte'a wrap eder, malloc minik bir buffer döndürür ve sonraki loop — hâlâ amaçlanan büyük count'u kullanarak — onu çok aşar. Bug, nihai kopyalama değil, kontrolsüz aritmetiktir.

Walkthrough

CWE-190'ın allocation örneği arketiptir — sizeof(img_t) * num_imgs çarpımı malloc onu görmeden önce overflow eder:

img_t table_ptr;
int num_imgs;
num_imgs = get_num_imgs();                       // attacker-influenced
table_ptr = (img_t*)malloc(sizeof(img_t)*num_imgs); // BUG: product can wrap

Gerçek OpenSSH challenge-response bug'ı sonucu somut olarak gösterir:

nresp = packet_get_int();                  // attacker controls nresp
if (nresp > 0) {
    response = xmalloc(nresp*sizeof(char*)); // nresp * 4 wraps to small value
    for (i = 0; i < nresp; i++)              // loop still runs nresp times
        response[i] = packet_get_string(NULL); // heap overflow
}

nresp = 1073741824 ve 4-byte pointer'larla nresp * sizeof(char*) = 2^32 ≡ 0, dolayısıyla xmalloc(0) minimal bir allocation döndürürken loop onun içine nresp pointer yazar — kontrollü bir heap overflow.

Aynı tehlike, sentinel'inin altına wrap eden bir bytes-received counter'ında belirir:

short int bytesRec = 0;
char buf[SOMEBIGNUM];
while (bytesRec < MAXGET) {            // BUG: bytesRec is a short
    bytesRec += getFromInput(buf+bytesRec); // wraps past SHRT_MAX, stays < MAXGET
}

bytesRec SHRT_MAX (32767)'i overflow ettiğinde negatif olur, < MAXGET testi sonsuza dek true kalır ve buf + bytesRec bounds dışına çıkar.

Unsigned size wrap'i yeniden üretmek

#include <stdio.h>
#include <stdlib.h>
int main(void) {
    unsigned int count = 0x40000000;          // 2^30
    size_t need = count * 4u;                 // 2^32 -> wraps to 0 on 32-bit size_t
    printf("count=%u need=%zu\n", count, need);
    void *p = malloc(need);                   // tiny / zero allocation
    printf("malloc(%zu) = %p\n", need, p);
    return 0;
}
32-bit size_t'li bir target'ta need 0 yazdırır ve p üzerinden count element yazmak heap'i overrun eder.

Çarpmadan önce kontrol et, sonra değil

if (count * size > LIMIT) test etmek işe yaramaz — çarpma zaten wrap etti. Bir precondition testi kullan (CERT C INT30-C): if (count != 0 && size > SIZE_MAX / count) { /* error */ }, ya da bir checked-arithmetic builtin: if (ckd_mul(&need, count, size)) { /* error */ } (C23 <stdckdint.h>) / __builtin_mul_overflow.

CERT C INT30-C kanonik safe-addition şeklini verir:

#include <limits.h>
void func(unsigned int ui_a, unsigned int ui_b) {
   unsigned int usum;
  if (UINT_MAX - ui_a < ui_b) {   // precondition: would wrap?
    /* Handle error */
  } else {
    usum = ui_a + ui_b;
  }
}

Detection

  • UBSan (-fsanitize=signed-integer-overflow,unsigned-integer-overflow) overflow'u runtime'da trap eder; fuzzing ile eşle.
  • Static analyzer'lar size hesaplamalarını untrusted input'tan allocator'lara kadar izler.

Mitigation

  • Numeric input'u kullanmadan önce explicit min/max aralıklarına karşı doğrula.
  • Ham a*b / a+b size matematiğini checked builtin'ler (__builtin_mul_overflow, C23 ckd_*) ya da denetlenmiş kütüphanelerle değiştir.
  • Post-hoc sonuç kontrolleri yerine precondition testleri (INT30-C) kullan; operand'ların wrap edemeyeceği kadar geniş tipler tercih et.

References