Skip to content

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 */
Output of the check in isolation:
$ printf '%d\n%zu\n' -1 $((0xFFFFFFFFFFFFFFFF))
-1
18446744073709551615
Signed check 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_t tercih 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 -1 ile overload etmek yerine error kanallarını out-of-band tut (ayrı bir bool ok / errno).
  • memcpy/read wrapper'larına runtime uzunluk check'leri eklemek için FORTIFY_SOURCE ile derle.

References