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;
}
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+bsize matematiğini checked builtin'ler (__builtin_mul_overflow, C23ckd_*) 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.