Integer underflow¶
Bir tipin minimum temsil edilebilir değerinin altına çıkacak şekilde çıkarma yapmak, sonucu negatife gitmek yerine büyük bir pozitif (ya da başka türlü yanlış) değere wrap eder.
Mechanism¶
Invariant: bir çıkarmanın sonucu tipin temsil edilebilir aralığında kalmalı
Bu CWE-191'dir (Integer Underflow / Wrap or Wraparound). Fixed-width integer'lar
modüler aritmetiğe uyar: a - b tipin minimumunun altında bir değer ürettiğinde, sonuç
gerçek (aralık-dışı) yanıtı üretmek yerine aralığın ters ucuna wrap eder. unsigned
tipler için minimum 0'dır, dolayısıyla a < b olan herhangi bir a - b,
a - b + 2^width'e wrap eder — dev bir pozitif sayı. signed tipler için INT_MIN'in
altına decrement etmek INT_MAX'e wrap eder (ve C'de undefined behavior'dır). Tehlike,
wrap'lenmiş değerin ardından bir length, index ya da loop bound olarak güvenilmesidir;
böylece görünüşte minik ya da negatif bir miktar attacker-lehine büyük bir miktar olur. En
sık bir integer-overflow-to-buffer-overflow ya da
bir out-of-bounds erişimi besleyen, unsigned/length durumudur.
Walkthrough¶
MITRE'in CWE-191 signed örneği: INT_MIN'den başlayıp 1 çıkarmak INT_MAX'e wrap eder.
#include <stdio.h>
int main(void)
{
int i;
i = -2147483648; /* INT_MIN for 32-bit signed int */
i = i - 1; /* wraps to 2147483647 (INT_MAX) */
return 0;
}
Daha tehlikeli durum, size olarak kullanılan bir unsigned underflow'dur. CWE-191 Örnek 2:
int a = 5, b = 6;
size_t len = a - b; /* a - b == -1, but size_t is unsigned */
char buf[len]; /* len wraps to ~SIZE_MAX -> enormous VLA */
5 - 6 -1'dir; size_t'ye dönüştürmek 0xFFFFFFFFFFFFFFFF verir, dolayısıyla
variable-length array'den (ya da sonraki bir malloc(len) / memcpy(dst, src, len)'den) ~16 EiB istenir.
Klasik len - headersize parse bug'ı
Untrusted input üzerinde length çıkarmaları, gerçek-dünyadaki en yaygın tetikleyicidir:
size_t payload = packet_len - HEADER_SIZE; /* if packet_len < HEADER_SIZE -> wraps */
memcpy(dst, packet + HEADER_SIZE, payload); /* copies ~SIZE_MAX bytes */
packet_len >= HEADER_SIZE'ı kontrol et.
CERT C INT30-C unsigned çıkarma için precondition pattern'ini verir: önce operand'ları karşılaştır, asla çıkarıp sonra (zaten-wrap'lenmiş) farkı test etme.
/* Noncompliant: udiff may have already wrapped */
void func(unsigned int ui_a, unsigned int ui_b) {
unsigned int udiff = ui_a - ui_b; /* ... */
}
/* Compliant: order operands before subtracting */
void func(unsigned int ui_a, unsigned int ui_b) {
unsigned int udiff;
if (ui_a < ui_b){
/* Handle error */
} else {
udiff = ui_a - ui_b;
}
/* ... */
}
Wrap'i gözlemlemek
#include <stdio.h>
int main(void) {
unsigned int a = 5, b = 6;
printf("%u\n", a - b); /* prints 4294967295 on a 32-bit unsigned int */
size_t len = (size_t)(a - b);
printf("%zu\n", len); /* 18446744073709551615 on LP64 */
return 0;
}
int'in UINT_MAX'e wrap'ini gösterir; size_t'ye genişletmek
-1'i SIZE_MAX'e sign-extend eder.
Detection¶
- SAST / UBSan:
-fsanitize=integer(Clang) signed overflow'u ve unsigned wrap'i runtime'da işaretler; static analyzer'lar length sink'lerine ulaşan çıkarmanınunsigned/size_tsonuçlarını işaretler. - Sonucu bir size, index ya da loop bound olarak kullanılan her
len - k/a - b'yi denetle.
Mitigation¶
- Önce operand sıralamasını doğrula:
if (a < b) error; else d = a - b;(INT30-C). - Signed tipler için wrap'e güvenmekten kaçın;
a - b'den öncea < INT_MIN + b'yi kontrol et (INT32-C). - Mevcut olduğunda checked builtin'leri (
__builtin_sub_overflow, C23ckd_sub) kullan. - Untrusted input'tan türetilen length'leri range-check edilene kadar şüpheli kabul et.