Integer overflow to buffer overflow¶
Bir size hesaplamasındaki aritmetik bir overflow, amaçlanandan-daha-küçük bir allocation'a yol açar; böylece tam istenen size'ı varsayan sonraki write'lar buffer'ın sonunu aşar.
Mechanism¶
Invariant: allocate için kullanılan size ile write için kullanılan size aynı değer olmalı
Bu, birleşik güvenlik açığı CWE-680'dir. Birincil bir
integer overflow/wraparound (CWE-190)'ı, sonuçta
ortaya çıkan bir out-of-bounds write'a (CWE-119 / CWE-787) zincirler. Kök neden, byte
sayısı n * sizeof(T)'nin sabit-genişlikli bir integer tipinde hesaplanmasıdır. Matematiksel
çarpım tipin maksimumunu aştığında 2^width modulo wrap eder, dolayısıyla malloc() küçük
bir argüman alır ve küçük bir buffer döndürür. Çevre kod ise hâlâ n element'e sahip
olduğuna inanır ve n'e kadar index'ler/kopyalar, undersized bölgenin sonunu aşarak yazar.
Overflow tek başına hiçbir şeyi corrupt etmez — "allocate edilen size" ile "yazılan size"
arasındaki eşitliği sessizce bozar ve o bozulmuş invariant bug'dır.
Walkthrough¶
Kanonik MITRE CWE-680 örneği: num_imgs attacker-etkili ve sizeof(img_t) * num_imgs
çarpımı overflow eder.
img_t table_ptr; /* struct containing img data, 10kB each */
int num_imgs;
...
num_imgs = get_num_imgs();
table_ptr = (img_t*)malloc(sizeof(img_t) * num_imgs);
...
sizeof(img_t) 10240 (0x2800) ise ve attacker, çarpımın 32-bit size_t'de yapıldığı bir
target'ta num_imgs = 0x40000 sağlarsa:
0x2800 * 0x40000 = 0xA0000000 (2.5 GiB) -> fits in 32 bits, huge alloc, but...
0x2800 * 0x6334 = 0xFFFD3000 -> still fits
0x2800 * 0x6667 = 0x1_0000_5800 -> truncates to 0x00005800 (22 KiB)
yani gigabyte'lar rezerve etmesi gereken bir istek, bunun yerine ~22 KiB'lık bir buffer
verir, bu sırada table_ptr[0..num_imgs-1]'i dolduran loop onu çok aşar.
sizeof size_t döndürür, ama count çoğu zaman int'tir
Çarpımın tipi operand'ların terfi ettiği her ne ise odur. Signed bir int count'u
unsigned size_t sizeof ile karıştırmak size_t'ye terfi eder, ama negatif ya da çok
büyük bir int yine de wrap'lenmiş bir değer üretir. Count'u her zaman çarpmadan önce
doğrula.
CERT C kuralı INT30-C precondition-check pattern'ini verir: çarpımı gerçekleştirmeden önce
SIZE_MAX'i aşamayacağını doğrula.
/* Noncompliant: product may wrap before reaching malloc() */
pen->vertices = malloc(pen->num_vertices * sizeof(cairo_pen_vertex_t));
/* Compliant: check the upper bound first */
if (pen->num_vertices > SIZE_MAX / sizeof(cairo_pen_vertex_t)) {
/* Handle error */
}
pen->vertices = malloc(pen->num_vertices * sizeof(cairo_pen_vertex_t));
Undersized allocation'ı AddressSanitizer altında yeniden üretmek
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
uint32_t n = 0x6667; /* attacker-controlled count */
size_t bytes = (uint32_t)(0x2800u * n); /* wraps in 32 bits */
char *buf = malloc(bytes); /* tiny: 0x5800 bytes */
memset(buf, 'A', 0x2800u * (size_t)n); /* writes ~640 MiB */
return 0;
}
Detection¶
- SAST: önceki bir bounds check olmadan non-constant integer'ların çarpımı/toplamı olan
malloc/calloc/memcpysize argümanlarını işaretle (taint source → allocation sink). - Runtime: wrap'i ve OOB write'ı yakalamak için AddressSanitizer (
-fsanitize=address) ya da UndefinedBehaviorSanitizer (-fsanitize=integer) ile derle. n * sizeoverflow'unu dahili olarak tespit etmesi zorunlu olancalloc(n, size)'ı tercih et.
Mitigation¶
- Checked-arithmetic builtin'leri kullan: C23
<stdckdint.h>ckd_mul, ya da__builtin_mul_overflow(GCC/Clang). - INT30-C precondition'ını uygula:
if (n > SIZE_MAX / size) { error; }. - El yapımı
malloc(n * size)yerinecalloc()/reallocarray()kullan. - Herhangi bir size aritmetiğinden önce element count'u domain'e-özgü bir maksimuma karşı doğrula.