Ingeniería civil y estructural
Cómo escribir código C seguro para prevenir los flujos de amortiguación
Table of Contents
Las desbordaciones de amortiguación siguen siendo una de las vulnerabilidades de seguridad más persistentes y peligrosas en la programación C. A pesar de estar bien documentados durante décadas, siguen causando problemas graves como la corrupción de datos, los fallos del sistema y la ejecución remota de código C. La escritura de código C seguro requiere una comprensión profunda de cómo se producen las desbordaciones de amortiguación y un enfoque disciplinado para prevenirlas.
Comprender las corrientes de amortiguación
Un flujo de amortiguación ocurre cuando un programa escribe más datos a un bloque contiguo de memoria (un búfer) que el búfer fue asignado a retener. Ya que los búferes residen en la memoria de pila o de salto, exceder sus límites sobrescribe los lugares de memoria adyacentes. Esta corrupción puede alterar el estado del programa, introducir comportamiento impredecible, o ser explotado por un atacante para inyectar y ejecutar código arbitrario.
Las consecuencias dependen de lo que se sobrescribe. La sobreescritura de una dirección de retorno en la pila puede redirigir la ejecución al código controlado por el atacante. Los punteros de sobrescritura pueden conducir a escritos de memoria arbitrarios. Incluso los simples fallos pueden ser aprovechados para los ataques de denegación de servicio.
Desbordamientos basados en las estadísticas
Las variables locales, incluyendo los búferes declarados dentro de las funciones, se almacenan en la pila. La pila también tiene la dirección de retorno, los punteros de marco guardados y otros datos de control. Cuando un búfer lineal como es desbordado, los derrames de datos en la dirección de retorno y más allá. Explotas clásicas como el gusano Morris (1988) utilizaban flujos de la pila para obtener acceso no autorizado.
Desbordamientos de base de heap
Los buffers asignados dinámicamente (a través de , ], etc.) residen en el montón. Los desbordamientos aquí pueden corromper los metadatos utilizados por el aleator, lo que conduce a los choques o la explotación a través de los aerosoles o los ataques sin uso.
Funciones Vulnerables comunes y sus alternativas seguras
La biblioteca estándar C proporciona varias funciones que no realizan controles de límites. Usarlas es la causa más común de los desbordamientos de amortiguadores. Reemplazarlos con contrapartes más seguras es una práctica fundamental.
Copia de la cuerda y Concatenación
- — Inseguro: copias hasta un rescindiente nulo, sin límite de longitud.
] ] ] — copias a la mayoría de los caracteres; note que no se null-termina si la fuente es manualmente nula - Mejor aún: ] — disponible en BSD y muchos sistemas Linux; siempre null-termina y devuelve la longitud de la cadena fuente para la detección de truncación.
- — Unsafe: concatenates without bounds.
] ] — apéndices a la mayoría de los caracteres y siempre nulos.
Producto y entrada con formato
- — Unsafe: escribe la salida formateada a un búfer sin control de tamaño.
] ] — limita la salida a caracteres de tamaño-1 más el terminator nulo. - ] — Riesgo similar; uso en lugar de ello.
- [ Extremadamente peligroso; eliminado de la norma C11. Usar ] en su lugar.
- — No hay controles de límites. Usa o con el especificador de ancho de campo.
Copia de memoria y Muévete
- — Segura solamente si no se verifica que no supere el tamaño del búfer de destino.
] ] [Las manos superponen] y siempre aseguran n ≤ tamaño de desto. - Algunas plataformas proporcionan del anexo K (opcional en C11), pero la adopción es limitada.
Validación y Gestión de Tamaño
Incluso con funciones seguras, debe validar longitudes de entrada, asegurar los tamaños adecuados de amortiguación, y manejar la truncación potencial con gracia.
Compruebe la longitud de entrada
Antes de copiar o procesar la entrada externa (introducción del usuario, datos de red, contenidos de archivo), determinar su longitud máxima aceptable y rechazar o truncar datos que la exceda. Por ejemplo:
#define MAX_INPUT 255
char buffer[MAX_INPUT + 1]; // +1 for null
if (strlen(user_input) > MAX_INPUT) {
// Handle error: reject or truncate
fputs("Input too long", stderr);
return -1;
}
strncpy(buffer, user_input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
Usar amortiguadores de tamaño fijo con límites conocidos
Siempre que sea posible, definir los búferes con un tamaño constante y aplicarlo a lo largo del código. Evite los arrays de longitud variable (VLAs) que pueden causar desbordamientos de pila si se suministran grandes tamaños. En lugar de ello, asigne dinámicamente con comprobaciones de tamaño explícito.
Trunca de mano Explicitamente
Las funciones como y pueden truncar datos. Tenga en cuenta el valor de retorno para detectar la truncación y decidir si los datos truncados son aceptables o si se debe levantar un error. Ignorar la truncación puede dejar los búferes en un estado inesperado.
Banderas de seguridad de los competidores y protección de tiempo de ejecución
Los compiladores modernos ofrecen banderas que añaden detección y mitigación de flujo de amortiguación sin cambios de código. Hágalos en su sistema de construcción.
- ] — Inserta canarios de apilación (valores de aleatorios) antes de las direcciones de retorno. Si un buffer sobrescribe el canario antes de modificar la dirección de retorno, el programa aborta antes de que el exploit termine.
- — Sustitúyase llamadas a funciones inseguras como y con versiones comprobadas que abortan si el búfer de destino es demasiado pequeño. Requiere o mayor optimización.
- — advierte sobre vulnerabilidades de cadenas de formato que pueden conducir a desbordamientos de amortiguación o fugas de información.
- — AddressSanitizer (ASan) código de instrumentos para detectar flujos de amortiguación, usos sin uso y otros errores de memoria en tiempo de ejecución. Despacio la ejecución pero es invaluable para la prueba.
- — Evita optimizar las inspecciones de desbordamiento (utiliza con precaución).
Protección del sistema operativo
Los canarios de estata son sólo una capa. Las tecnologías de mitigación de los gastos en los sistemas operativos modernos incluyen:
- Prevención de la Ejecución de Datos (DEP) / bit NX] — Marcas apilan y saltan como no ejecutables, evitando la ejecución de código de shell.
- Añada espacio Diseño aleatorio (ASLR)] — Aleatoriamente las direcciones de memoria (estrella, montón, bibliotecas compartidas) para hacer más difícil predecir objetivos.
- Relocation Read-Only (RELRO) — Protege el GOT (Global Offset Table) de la sobreescritura.
La habilitación de estas protecciones (normalmente por defecto) eleva la barra para la explotación, pero no reemplaza la codificación segura.
Auditorías de código y análisis estadístico
La revisión humana combinada con análisis estáticos automatizados puede captar problemas de desbordamiento de los buffer temprano. Integrar estos en su flujo de trabajo de desarrollo.
- Manual code review] — Busque los usos de funciones inseguras, cheques de tamaño perdido y bucles que escriben más allá de los límites de amortiguación.
- ] Herramientas de análisis estadístico] — Herramientas como , , , y detectan posibles desbordamientos, uso de funciones peligrosas y errores fuera de uno. Pueden ejecutarse en tuberías de CI.
- Fuzzing] — Use libFuzzer, AFL u otros fuzzers para probar automáticamente el manejo de entrada con datos inesperados que pueden desencadenar desbordamientos.
Ejemplos prácticos de Código Seguro
Copia de cuerda segura con el cheque de los Libras
#include <stdio.h>
#include <string.h>
int safe_string_copy(char *dest, size_t dest_size, const char *src) {
if (!dest || !src || dest_size == 0) {
return -1; // Invalid parameters
}
size_t src_len = strlen(src);
if (src_len >= dest_size) {
// Source too large; truncation or error
// Option: copy what fits and null-terminate
strncpy(dest, src, dest_size - 1);
dest[dest_size - 1] = '\0';
return 1; // Truncation occurred
}
strncpy(dest, src, dest_size);
// strncpy fills remaining with null, so dest_size fits; no need to null-terminate if src shorter
return 0; // Success, no truncation
}
Manejo seguro de entero para tamaños de amortiguación
El flujo de amortiguación también puede resultar de los flujos enteros cuando los tamaños de computación. Siempre comprobar aritmética antes de la asignación.
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
void *safe_malloc_array(size_t nmemb, size_t size) {
if (nmemb == 0 || size == 0) {
return NULL; // Or handle zero-size allocation
}
if (nmemb > SIZE_MAX / size) {
// Integer overflow would occur
errno = ENOMEM;
return NULL;
}
return malloc(nmemb * size);
}
Usando snprintf para cuerdas con formato
char log_message[256];
int ret = snprintf(log_message, sizeof(log_message),
"User %s logged in from %s", username, ip_address);
if (ret < 0) {
// Output error
} else if ((size_t)ret >= sizeof(log_message)) {
// Truncation occurred; handle if needed
}
Prácticas óptimas adicionales
- Initializar los búferes — Siempre los búferes de cero inicialización para evitar filtrar la memoria no inicializada.
- Evitar la recursión con profundidad sin límites] — Las reflujo de ataúd pueden ocurrir desde la recidiva profunda; utilizar la iteración o la profundidad límite.
- Use ] qualifier] — Ayuda al compilador a optimizar y puede captar problemas de apodo, aunque no impide directamente las desbordaciones.
- Prefer ]-corrección] — Evita la modificación accidental de cadenas de entrada y hace cumplir la intención.
- Manejo de errores de implementación] — No ignore los valores de retorno de funciones como , , , etc.
Recursos para el aprendizaje ulterior
- SÉI CERT C Coding Standard — Reglas completas para la codificación C segura.
- CWE-120: Copia de amortiguación sin verificar el tamaño de la entrada] — clasificación de las debilidades de la sobrefluencia del MITRE.
- Desbordamiento de amortiguación de la OPA] — Orientación práctica del Proyecto de Seguridad de Aplicación de la Web abierta.
- GNU C Manual de biblioteca: Utilidades de cuerda y de rayos] — Documentación para funciones de cuerda seguras.
- AgregarSanitizer] — Detector de error de memoria rápido.
Conclusión
Prevenir los flujos de amortiguación en C no es opcional; es una responsabilidad fundamental de cualquier desarrollador que trabaje con el lenguaje. Al entender los mecanismos de desbordamiento, reemplazar funciones peligrosas con alternativas más seguras, validar rigurosamente entradas y tamaños, permitir las protecciones de compilador, y utilizar análisis estáticos y pruebas, puede reducir drásticamente el riesgo de estas vulnerabilidades.