Saltearse al contenido

Integer overflow y bugs de enteros

Los enteros en C tienen un tamaño fijo (32 o 64 bits) y un rango limitado. Cuando una operación produce un valor fuera de ese rango, “da la vuelta” (overflow/underflow), y si un número con signo se trata como sin signo (o al revés), su valor cambia drásticamente (signedness). Estos bugs son peligrosos porque esos enteros suelen usarse como tamaños de buffer o índices: un cálculo erróneo lleva a reservar poca memoria, leer/escribir fuera de límites, o saltarse una comprobación — y de ahí a un overflow clásico.

Integer overflow x + y supera el máximo -> da la vuelta a un valor pequeño/negativo
# en 32 bits: 0xFFFFFFFF + 1 = 0 (vuelve a 0)
Integer underflow 0 - 1 en unsigned -> 0xFFFFFFFF (enorme)
Signedness un int negativo tratado como unsigned -> número gigante
# -1 (int) interpretado como unsigned = 4294967295
Truncation asignar un int (32) a un short (16) -> se pierden bits altos
Multiplication len * sizeof(x) desborda -> reserva menos de lo esperado

El peligro no es el entero en sí, sino para qué se usa:

# 1) tamaño de reserva controlado -> heap/stack overflow
int n = user_input; // atacante mete un valor que desborda
char *buf = malloc(n * 8); // n*8 desborda -> malloc reserva poco
// luego se escriben n elementos -> overflow del heap
# 2) saltarse una comprobación de límites
if (index < MAX) { array[index] = x; } // si index es negativo (signed) pasa el check
// pero array[index] escribe FUERA del array (índice negativo)
# 3) underflow en un tamaño -> copia gigantesca
size_t len = header_len - 20; // si header_len < 20, len = enorme -> overflow en memcpy

El patrón clave: un entero controlado por el atacante que alimenta un malloc, un memcpy, un índice de array o una comprobación if (x < limite).

# en reversing/código: buscar operaciones aritméticas sobre tamaños/índices
# antes de malloc, memcpy, read, bucles
# comprobaciones de límites que usan signed donde debería ser unsigned
# fuzzing con valores extremos: 0, -1, 0xFFFFFFFF, INT_MAX, INT_MIN, tamaños grandes
1. Encontrar el entero controlado que alimenta un tamaño/índice/check
2. Elegir un valor que provoque el efecto deseado:
- overflow -> reserva pequeña -> heap/stack overflow (ver pwn-stack/pwn-heap)
- signedness/negativo -> saltarse el límite -> escritura fuera de rango
- underflow -> tamaño gigante -> copia masiva
3. Convertir la corrupción de memoria resultante en control del flujo (como un overflow clásico)

Es frecuente que el integer bug sea el primer eslabón que habilita un overflow clásico de stack o heap.

  • Comprobaciones de rango correctas: validar antes de operar; usar tipos sin signo para tamaños (size_t) y verificar límites superiores e inferiores.
  • Detección de overflow: funciones seguras (__builtin_add_overflow, ckd_add de C23), -ftrapv, UBSan (UndefinedBehaviorSanitizer) en desarrollo.
  • No calcular tamaños con aritmética sin verificar desbordamiento (if (n > MAX/8) error; antes de n*8).
  • Fuzzing con valores límite; revisión de código centrada en aritmética de tamaños.
  • Identificar enteros controlados que alimentan tamaños/índices/checks
  • Probar valores límite (0, -1, 0xFFFFFFFF, INT_MAX/MIN)
  • Overflow en cálculo de tamaño → reserva pequeña → overflow
  • Signedness/negativo → saltarse comprobación de límite
  • Underflow → tamaño gigante → copia masiva
  • Encadenar con stack/heap overflow (Stack buffer overflow/Explotación de heap)
  • Convertir en control del flujo
  • Blue: ¿validación de rango, size_t, UBSan?