Skip to content

Numeric truncation error

Bir değeri daha küçük bir integer type'a cast etmek high-order bit'leri düşürür, böylece saklanan değer sessizce orijinalinden farklı olur.

Mechanism

Daha geniş bir integer daha dar olanına atandığında — örn. int'ten short'a ya da size_t'den int'e — sığmayan high-order bit'ler atılır. Geriye kalan low bit'ler küçük type içinde yeniden yorumlanır; bu da çok daha küçük ya da (truncate edilen değer hedefin sign bit'ini set ettiğinde) negatif bir değer üretebilir. Eğer bu bozuk değer sonradan bir allocation size, bir uzunluk, bir loop bound ya da bir array index olarak kullanılırsa, alt katmandaki kod artık gerçeğe uymayan bir sayıya dayanarak karar verir.

Note

CWE-197. MITRE'nin ifadesiyle, "bir primitive daha küçük boyutlu bir primitive'e cast edilir ve dönüşümde veri kaybolur" — "büyük değerin high order bit'leri dönüşümde kaybolur, bu da orijinal değere eşit olmayan beklenmedik bir değerle sonuçlanabilir." Klasik exploit şekli, doğrulanan ardından malloc, memcpy ya da bir bounds check'e geçirilirken 32'ye (veya 16'ya) truncate edilen 64-bit'lik bir uzunluktur; böylece küçük bir allocation büyük bir copy alır — bir heap buffer overflow. Bu, integer-overflow-wraparound'dan (type'ın aralığını aşan aritmetik) farklıdır ve integer-coercion-error (genel olarak signed/unsigned ve genişlik uyuşmazlıkları) ile örtüşür.

Walkthrough

16-bit'lik bir unsigned short'a truncate edilen 64-bit'lik bir uzunluk:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void copy(unsigned long len_in) {
    unsigned short len = len_in;          /* truncation: keeps low 16 bits */
    char *buf = malloc(len);              /* allocates the SMALL value     */
    printf("requested=%lu, truncated=%u\n", len_in, len);
    memset(buf, 'A', len_in);             /* copies the LARGE value -> OOB */
    free(buf);
}

int main(void) { copy(0x10000 + 8); return 0; }
Expected output
requested=65544, truncated=8
*** buffer overflow detected ***: terminated   (with _FORTIFY_SOURCE)
Aborted (core dumped)

0x10008 & 0xFFFF == 0x0008. malloc(8) 8 byte'lık bir buffer döndürür, ama copy tam 0x10008 uzunluğunu kullanır ve heap'i overflow eder.

Warning

Truncation en çok bir değerin bir genişlikte check edilip başka bir genişlikte kullanıldığı yerde tehlikelidir. Bir maksimuma karşı size_t olarak doğrulanan, ardından parametresi int ya da unsigned short olan bir fonksiyona geçirilen bir uzunluk check'i bertaraf eder. 64-bit kodda size_t'den int'e daralmaya ve uzunluk/index üzerindeki her açık (short), (char), (uint16_t) cast'ine özel dikkat göster.

Detection

  • -Wconversion -Wsign-conversion ile compile et; GCC/Clang implicit daralma cast'lerinde uyarı verir.
  • Static analyzer'lar (Coverity, clang-tidy bugprone-narrowing-conversions, cppcheck) değer kaybettiren dönüşümleri işaretler.
  • Sınır uzunluklarıyla (tam 0x100, 0x10000, 0x100000000 üzerinde) yapılan fuzzing genişliğe bağlı crash'leri ortaya çıkarır.

Mitigation

  • CWE-197'ye göre: "Daha büyük boyutlu bir primitive'den ya da daha küçük boyutlu bir primitive'e hareket eden, implicit ya da explicit hiçbir cast'in gerçekleşmediğinden emin ol."
  • Cast'lemeden önce değeri hedef type'ın maksimumuna karşı doğrula (örn. USHRT_MAX / INT_MAX'a karşı check et).
  • Size ve index'leri baştan sona tek tutarlı bir type'ta (size_t) taşı; bir kez doğrulandıktan sonra tekrar daraltmaktan kaçın.

References