Skip to content

Off-by-one error

Doğru değerden tam bir fazla (veya bir eksik) hesaplanan bir sınır, tek bir elemanın amaçlanan aralığın hemen dışında okunmasına ya da yazılmasına yol açar.

Mechanism

Program, doğru değerden tam bir farkla sapan yanlış bir maksimum ya da minimum kullanır. İki klasik şekil, < N yerine <= N koşan bir loop (N index'ine, son geçerli slot'tan bir sonrasına dokunur) ve sonlandıran NUL'ü unutan, N karakterlik boyutlandırılmış bir buffer'dır; böylece N+1. elemanın write'ı allocation'dan bir byte sonraya düşer. Tek aralık-dışı erişim küçüktür, ama komşu byte anlamlıysa — bir uzunluk, bir flag, allocator metadata'sı, kaydedilmiş bir register byte'ı — o tek byte program state'ini bozmaya yeter.

Note

CWE-193. MITRE'nin tanımı: "bir ürün, doğru değerden 1 fazla ya da 1 eksik, yanlış bir maksimum ya da minimum değer hesaplar veya kullanır." Sonuçlar arasında crash'ler ve "loop index değişkenlerini içeren overflow'larda" yüksek bir infinite loop olasılığı, ayrıca wrap-around bir buffer overflow'a dönüştüğünde data corruption ve code execution bulunur. Tek-byte durumu, glibc heap'inde off-by-null-poison-null-byte'ın (o tek byte bir '\0') ve stack/global tek-byte overflow'ların tohumudur.

Walkthrough

Ders kitabındaki loop-bound hatası — < yerine <=:

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

int main(void) {
    char buf[8];
    /* off-by-one: i goes 0..8, writing 9 bytes into an 8-byte buffer */
    for (int i = 0; i <= sizeof(buf); i++)
        buf[i] = 'A';
    printf("done\n");
    return 0;
}
Beklenen çıktı (stack protector ile)
$ gcc -fstack-protector-all off.c -o off && ./off
*** stack smashing detected ***: terminated
Aborted (core dumped)

buf[8]'e yapılan write off-by-one'dır; birçok layout'ta kaydedilmiş canary'nin low byte'ını ezerek protector'ı tetikler.

CWE-193'ün kendi örneği eksik-terminator allocation'ıdır:

WidgetList = malloc(numWidgets * sizeof(Widget *));
for (i = 0; i < numWidgets; i++)
    WidgetList[i] = InitializeWidget();
WidgetList[numWidgets] = NULL;   // writes one element past the buffer

Warning

Size argument'ı NUL'ü tutarsız biçimde dışlayan ya da içeren fonksiyonlar kalıcı bir kaynaktır: strncat n+1 byte'a kadar yazar (n karakter artı NUL), sprintf bir NUL ekler ve read(fd, buf, sizeof buf) ardından gelen buf[n] = '\0', n == sizeof buf olduğunda overflow eder. Ekstra byte'ı açıkça rezerve et.

Detection

  • ASan / -fsanitize=address 1-byte'lık out-of-bounds erişimi tam olarak işaretler.
  • -fstack-protector-all birçok stack off-by-one'ı canary corruption üzerinden yakalar.
  • Static analysis (clang-tidy, Coverity) <=-vs-< ve NUL-terminator boyutlandırma pattern'lerini işaretler.

Mitigation

  • Bir terminator gerektiğinde buffer'ları len + 1 olarak boyutlandır; index sınırları için < kullan.
  • Length-aware API'leri (snprintf, strlcpy) tercih et ve onların NUL semantiğini iki kez kontrol et.
  • Testleri ASan altında build et; sınır-uzunluk input'larını fuzz et.

References