Skip to content

Out-of-bounds write

Amaçlanan bir buffer'ın sonundan sonrasına (ya da başından öncesine) veri yazmak, allocator ya da compiler'ın oraya yerleştirdiği komşu memory'yi bozar.

Mechanism

Bir out-of-bounds (OOB) write, kod yazmayı amaçladığı buffer'ın sınırları dışındaki bir memory konumuna veri sakladığında olur. Buffer'ın tanımlı bir size'ı ve başlangıç adresi vardır; write ya son geçerli index'in ötesine ilerler ya da daha az sıklıkla ilk geçerli index'ten öncesine geçer. Her iki durumda da store, başka bir şeye ait memory'ye düşer — komşu bir allocation, allocator metadata'sı, kaydedilmiş bir register ya da bir return address.

Note

Kırılan invariant basittir: her store, hedef nesnesinin [base, base + size) aralığına düşmelidir. C ve C++ bunu runtime'da zorlamaz, dolayısıyla invariant yalnızca programcının aritmetiğinde yaşar. Write için kullanılan index, uzunluk ya da size bounds check olmadan (ya da yanlış bounds check ile) hesaplandığında ihlal edilir. CWE-787, olağan kök nedenleri sıralar: yetersiz bounds check, yanlış size hesaplamaları, loop ya da index koşullarındaki off-by-one hataları, check edilmemiş input uzunlukları ve eksik boyutlu bir allocation üreten integer overflow'lar. Sonuç yalnızca bir crash değildir — komşu memory genellikle anlamlı olduğundan (function pointer'lar, list pointer'ları, size'lar, return address'ler), kontrollü bir OOB write control-flow hijack'e doğrudan bir basamak taşıdır.

OOB write, bu bilgi tabanındaki neredeyse her memory-corruption primitive'inin ebeveyn zayıflığıdır. Bir stack buffer overflow stack frame'ine bir OOB write'tır; bir heap buffer overflow bir sonraki chunk'ın metadata'sına bir OOB write'tır; bir off-by-one tek byte'lık bir OOB write'tır. Hepsini CWE-787 örnekleri olarak ele almak, exploitation'ın yalnızca target'ın yanında ne yaşadığına göre farklılaştığını netleştirir.

Walkthrough

Kanonik CWE-787 örneği, sınırlarının bir eleman ötesinden index'lenen bir array'dir:

int id_sequence[3];   /* valid indices: 0, 1, 2 */

id_sequence[0] = 123;
id_sequence[1] = 234;
id_sequence[2] = 345;
id_sequence[3] = 456;  /* OUT OF BOUNDS: writes 4 bytes past the array */

id_sequence[3]'e yapılan write, array'in hemen ardından gelen memory'ye saklar. Stack'te o memory başka bir local, kaydedilmiş bir frame pointer, canary ya da kaydedilmiş return address olabilir — bu yüzden görünüşte zararsız görünen bir off-by-one, kaydedilmiş return address'i overwrite etme yoluyla code execution'a dönüşebilir.

Daha gerçekçi bir biçim, uzunluğu input'tan gelen check edilmemiş bir copy'dir:

void copy(char *src, size_t n) {
    char dst[64];
    memcpy(dst, src, n);   /* if n > 64, OOB write past dst */
}
AddressSanitizer ile bir OOB write'ı tespit etmek

$ gcc -fsanitize=address -g oob.c -o oob && ./oob
=================================================================
==12345==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd...
WRITE of size 4 at 0x7ffd... thread T0
    #0 0x... in main oob.c:8
  Address 0x7ffd... is located in stack of thread T0 at offset 28
  in frame #0 main
  This frame has 1 object(s):
    [16, 28) 'id_sequence' <== Memory access at offset 28 overflows this variable
SUMMARY: AddressSanitizer: stack-buffer-overflow oob.c:8 in main
ASan, size 4'lük write'ı, overflow edilen nesneyi ve tam satırı raporlar — sessiz corruption'ı anında, teşhis edilebilir bir abort'a dönüştürür.

Warning

Crash etmeyen bir OOB write tehlikeli durumdur. Bir heap buffer'ın ötesine birkaç byte yazmak, hiçbir anlık fault olmadan bir sonraki chunk'ın size'ını ya da fd/bk pointer'larını bozabilir; process çalışmaya devam eder ve corruption yalnızca sonraki bir malloc/free'de "tahsil edilir". Bir crash'in yokluğu güvenlik kanıtı değildir.

Detection

AddressSanitizer (-fsanitize=address), Valgrind/Memcheck ve guard-page allocator'ları, write sınırı geçtiği anda işaretler. -fsanitize=bounds (UBSan), compiler'ın aralık dışı olduğunu kanıtlayabildiği array-index OOB write'larını yakalar. Static analyzer'lar ve fuzzer'lar (libFuzzer, AFL++) OOB path'ini sürer ve onu yeniden üretilebilir bir crash olarak ortaya çıkarır.

Mitigation

Write'tan önce her index'i ve uzunluğu hedefin gerçek size'ına karşı doğrula; bounded API'leri (snprintf, strlcpy, explicit n check'leri) ve size-aware container'ları (std::vector::at, Rust slice'ları) tercih et. Test build'lerinde _FORTIFY_SOURCE=2/3, stack canary'leri ve ASan ile compile et. Bunların hiçbiri bug'ı ortadan kaldırmaz — onu sessiz corruption'dan kontrollü bir abort'a dönüştürür.

References