Signed to unsigned conversion error¶
Bir unsigned tipe cast edilen negatif signed bir değer, devasa bir pozitif sayıya wrap olur ve size/bounds check'lerini etkisiz kılar — CWE-195.
Mechanism¶
Invariant
Bir signed integer'ı aynı genişlikteki bir unsigned tipe dönüştürmek, negatif input'larda clamp veya error yapmaz — two's-complement bit pattern'ini modulo 2^N olarak yeniden yorumlar. Yani (unsigned int)(-1), 32-bit bir tipte 0xFFFFFFFF = 4,294,967,295 olur. CWE-195'e göre, "the product uses a signed primitive and performs a cast to an unsigned primitive, which can produce an unexpected value if the value of the signed primitive cannot be represented using an unsigned primitive."
Bu, ters yönlü kuzeni unsigned to signed conversion error (CWE-196) ile karıştırılmamalı: orada büyük bir unsigned değer negatif'e döner ve underwrite/negatif-index üretir; burada ise negatif bir signed değer devasa pozitif unsigned'a wrap olarak OOB overwrite veya devasa allocation üretir.
Tehlike şu ki birçok API, -1'in error anlamına geldiği bir signed değer döndürür, ama değer sonra unsigned olan bir parametreye akar — en kritik olarak memcpy, malloc, read'e geçirilen bir size_t uzunluk ya da bir loop/bounds check. Implicit conversion, error sentinel -1'i temsil edilebilir en büyük size'a çevirir. if (len <= sizeof buf) gibi bir "bu sığar" karşılaştırması da, len signed-negatif olduğunda ama karşılaştırma her iki tarafı unsigned'a promote ettiğinde hatalı davranabilir. Sonuç bir out-of-bounds copy ya da devasa yanlış bir allocation'dır — CWE-195 "most commonly associated with integer overflow and buffer overflow vulnerabilities."
Walkthrough¶
CWE-195 gösterici pattern'i: bir error path, unsigned-tipli bir değere -1 döndürür ve bu sonra bir uzunluk olarak kullanılır.
#include <stdio.h>
#include <string.h>
/* Mimics CWE-195: returns -1 on error, but the type is unsigned */
unsigned int readPacketLength(int err) {
int amount = 0;
if (err) /* error path */
amount = -1; /* sentinel... silently becomes 0xFFFFFFFF on return */
return amount;
}
int main(void) {
char dst[64];
char src[64] = {0};
unsigned int len = readPacketLength(1); /* error -> 4294967295 */
printf("len = %u\n", len); /* len = 4294967295 */
memcpy(dst, src, len); /* catastrophic OOB write */
return 0;
}
Derle ve çalıştır — conversion görünür ve memcpy taşar:
$ gcc -g -fsanitize=address conv.c -o conv && ./conv
len = 4294967295
=================================================================
==5481==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe...
WRITE of size 4294967295 at 0x7ffe... thread T0
#0 ... in memcpy
#1 ... in main conv.c:18
Bir signedness karşılaştırması bir bounds check'i nasıl bypass eder
int parse_len(void); /* attacker can make this return -1 */
char buf[128];
int n = parse_len(); /* n = -1 */
if (n > (int)sizeof(buf)) return -1; /* -1 > 128 is FALSE -> check passes */
read(fd, buf, n); /* read() arg is size_t: -1 -> SIZE_MAX */
n > 128, n = -1 için geçer, ama read() SIZE_MAX görür.
Detection¶
- Implicit signed→unsigned narrowing'i yüzeye çıkarmak için
-Wconversion -Wsign-conversion(GCC/Clang) ile derle. - CERT C INT31-C'ye ("Ensure that integer conversions do not result in lost or misinterpreted data") karşı statik analiz.
- ASan/UBSan, conversion'ın kendisi "legal" C olsa bile ortaya çıkan OOB erişimini runtime'da yakalar.
Mitigation¶
- Size'lar için tutarlı bir tip kullan: uçtan uca
size_ttercih et ve dönüştürmeden önce error sentinel'leri kontrol et. - Aralıkları açıkça valide et:
if (n < 0 || (size_t)n > sizeof buf) error();— sign'ı unsigned karşılaştırmadan önce test et. - Bir size değerini
-1ile overload etmek yerine error kanallarını out-of-band tut (ayrı birbool ok/errno). memcpy/readwrapper'larına runtime uzunluk check'leri eklemek için FORTIFY_SOURCE ile derle.