Improper validation of array index¶
Untrusted bir değeri, array'in içine düştüğünü doğrulamadan array subscript olarak kullanmak, tek bir integer'ı bir out-of-bounds read ya da write'a çevirir.
Mechanism¶
Invariant
Bir T arr[N] array'i için tek tanımlı subscript'ler 0 <= i < N'dir. C standardı bu aralığın dışındaki arr[i]'yi undefined behavior yapar — compiler implicit bounds check olmadan ham bir pointer hesaplaması *(arr + i) üretir. Dolayısıyla 0 <= i < N invariant'ı programcı tarafından erişimden önce zorlanmalıdır. Yalnızca üst sınırı (i < N) doğrulayıp alt sınırı yok sayan bir kontrol, negatif index'leri sessizce kabul eder ve negatif bir int index array'in öncesine bir pointer üretir — 64-bit bir target'ta arr[-8], &arr[0]'ın 32 byte altındaki byte'ları read/write eder. Kusur erişimin kendisi değil, eksik ya da kısmi validation'dır; CWE-129'un, ürettiği out-of-bounds read/write primitive'lerinin upstream'inde yaşamasının nedeni budur.
Walkthrough¶
CWE-129'un kanonik örneği yalnızca maksimum sınırı kontrol eder. "Reddetme" path'i olması gereken else branch'i aslında out-of-bounds erişimi gerçekleştirir:
int getValueFromArray(int *array, int len, int index) {
int value;
// BUG: only checks index < len, never index >= 0
if (index < len) {
value = array[index];
}
else {
// attacker reaches here with index >= len; still dereferences array[index]
printf("Value is: %d\n", array[index]);
value = -1;
}
return value;
}
Düzeltilmiş form invariant'ın her iki kenarını da zorlar:
int getValueFromArray(int *array, int len, int index) {
int value;
if (index >= 0 && index < len) { // both minimum and maximum bounds
value = array[index];
}
else {
printf("Index out of bounds\n");
value = -1;
}
return value;
}
Negatif-index footgun'ının somut reprodüksiyonu:
#include <stdio.h>
int main(void) {
int arr[4] = {10, 20, 30, 40};
int idx = -8; // imagine this came from the network
if (idx < 4) { // upper-bound-only check: passes!
printf("%d\n", arr[idx]); // reads *(arr + (-8)) — 32 bytes below arr
}
return 0;
}
AddressSanitizer ile derle ve erişim anında yakalanır:
Negatif-index read için ASan çıktısı
Yalnızca-üst-sınır validation'ı klasik kaçırılan noktadır
if (index < len) tam hissettirir ama her negatif değeri kabul eder. CERT C ARR30-C tam olarak bunu işaretler: index >= 0 olmadan index < TABLESIZE'ı reddetmek undefined behavior'dır. İki güvenli şekil: (1) index >= 0 && index < N kontrolü, ya da (2) index'i size_t yap; böylece negatif değerler tek index < N testinde başarısız olan dev pozitiflere dönüşür — ama bunu yalnızca değer gerçekten anlamlı şekilde negatif olamayacağında yap.
Index bir read yerine bir write'ı (arr[index] = v) beslediğinde, aynı eksik kontrol kontrollü bir relative write — bir out-of-bounds write primitive — verir; bu sık sık bir return address, function pointer ya da heap metadata'sını overwrite etmeye giden ilk adımdır.
Detection¶
- Static analysis: data-flow / constraint-tabanlı araçlar (untrusted kaynaktan subscript'e taint) doğrulanmamış index'leri yüksek etkinlikle işaretler.
- Dynamic analysis: AddressSanitizer (
-fsanitize=address) ve UBSan'ın bounds kontrolleri (-fsanitize=bounds) OOB subscript'leri runtime'da yakalar; coverage için fuzzing ile birleştir.
Mitigation¶
- Her iki sınırı da doğrula:
0 <= index < N. Bir accept-known-good aralığı uygula ve geri kalan her şeyi reddet. - Negatiflerin anlamsız olduğu yerlerde
size_tindex'leri tercih et (kontrolüindex < N'e indirger), CERT C ARR30-C uyarınca. - Uygun olduğunda built-in bounds checking'i olan diller/container'lar kullan; ASLR/DEP'i kontrolün yerine değil, defense-in-depth olarak tut.