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-conversionile 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.