Aikido

Prevención de fallos de segmentación: seguridad de memoria en C y C++

Seguridad

Regla
Prevenir común segmentación fallo patrones.
Nulo puntero desreferencias, búferes desbordamientos,
y uso después de libre errores causan fallos y seguridad de seguridad.

Lenguajes compatibles: C/C++

Introducción

Los fallos de segmentación siguen siendo la fuente más común de fallos y vulnerabilidades explotables en aplicaciones C/C++. Estas violaciones de acceso a memoria se producen cuando el código intenta leer o escribir memoria que no le pertenece, normalmente a través de desviaciones de punteros nulos, desbordamientos de búfer, punteros colgantes o acceso a memoria liberada. Un solo segfault puede hacer caer los servidores de producción, pero lo que es peor, muchos patrones de segfault son explotables para la ejecución de código arbitrario.

Por qué es importante

Implicaciones para la seguridad: Los desbordamientos de búfer y las vulnerabilidades "use-after-free" son la base de la mayoría de los exploits de corrupción de memoria. Los atacantes los aprovechan para sobrescribir direcciones de retorno, inyectar código shell o manipular el flujo de control del programa. La vulnerabilidad Heartbleed de 2014 era una sobrelectura de búfer. Los exploits modernos siguen centrándose en estos patrones porque proporcionan acceso directo a la memoria a los atacantes.

Estabilidad del sistema: Los fallos de segmentación bloquean la aplicación inmediatamente sin degradación gradual. En los sistemas de producción, esto significa que se pierden peticiones, se interrumpen transacciones y se corrompe el estado. A diferencia de las excepciones de lenguajes de alto nivel, que pueden ser capturadas, los fallos de segmentación terminan el proceso, requiriendo procedimientos de reinicio y recuperación.Ampliación de la superficie de ataque: Cada desviación de puntero no comprobada, strcpy, memcpyo el acceso a matrices sin comprobación de límites es un punto de entrada potencial para la explotación. Los atacantes encadenan estas vulnerabilidades, utilizando una para corromper la memoria de forma que permitan explotar otra.

Ejemplos de códigos

❌ No conforme:

void process_user_data(const char* input) {
    char buffer[64];
    strcpy(buffer, input);  // No bounds checking

    char* token = strtok(buffer, ",");
    while (token != NULL) {
        process_token(token);
        token = strtok(NULL, ",");
    }
}

int* get_config_value(int key) {
    int* value = (int*)malloc(sizeof(int));
    *value = lookup_config(key);
    return value;  // Caller must free, but no documentation
}

Por qué es inseguro: En strcpy() provoca un desbordamiento del búfer si la entrada supera los 63 bytes, lo que permite a los atacantes sobrescribir la memoria de la pila. La dirección get_config_value() pierde memoria en cada llamada y crea un riesgo de puntero colgante si los que llaman liberan la memoria mientras otro código sigue haciendo referencia a ella.

✅ Conforme:

void process_user_data(const char* input) {
    if (!input) return;

    size_t input_len = strlen(input);
    char* buffer = malloc(input_len + 1);
    if (!buffer) return;

    strncpy(buffer, input, input_len);
    buffer[input_len] = '\0';

    char* token = strtok(buffer, ",");
    while (token != NULL) {
        process_token(token);
        token = strtok(NULL, ",");
    }

    free(buffer);
}

int get_config_value(int key, int* out_value) {
    if (!out_value) return -1;

    *out_value = lookup_config(key);
    return 0;  // Caller owns out_value memory
}

Por qué es seguro: La comprobación de punteros nulos evita fallos por desreferencia. La asignación dinámica elimina los límites fijos de tamaño de los búferes. Copia con comprobación de límites con strncpy() evita el desbordamiento. Despejar la semántica de propiedad en get_config_value() donde la persona que llama proporciona memoria evita confusiones de asignación y fugas.

Conclusión

La seguridad de memoria en C y C++ requiere una programación defensiva en cada operación de puntero y asignación de memoria. Los fallos de segmentación no son inevitables, se pueden prevenir mediante comprobaciones de nulos consistentes, validación de límites y patrones claros de propiedad de la memoria. La detección de estos patrones antes de la producción evita tanto fallos como vulnerabilidades explotables.

Preguntas frecuentes

¿Tiene alguna pregunta?

¿Cuáles son los patrones de fallos de segmentación más comunes?

Las cinco principales son: desferencias de puntero nulo (acceso a ptr->campo sin comprobar que ptr != NULL), desbordamientos de búfer (strcpy, sprintf, acceso a matrices sin comprobar los límites), use-after-free (acceso a la memoria después de free()), double-free (llamar a free() dos veces con el mismo puntero) y desbordamiento de búfer de pila (escritura más allá de los límites de la matriz local). Éstas representan más del 80% de las vulnerabilidades de corrupción de memoria en bases de código C/C++.

¿Cómo puedo evitar los segfault más comunes?

Compruebe siempre los punteros antes de desreferenciarlos (if (!ptr) return;). Utilice funciones de longitud limitada: strncpy() en lugar de strcpy(), snprintf() en lugar de sprintf(). Controlar explícitamente el tamaño de los búferes y validarlos antes de escribir. En C++, prefiera std::string y std::vector que manejan los límites automáticamente. Inicialice todos los punteros a NULL y póngalos a NULL después de liberarlos.

¿Cuál es la diferencia entre comportamiento indefinido y fallos de segmentación?

Los fallos de segmentación son uno de los posibles resultados de un comportamiento indefinido, pero no el único. Un comportamiento indefinido puede corromper la memoria silenciosamente, producir resultados erróneos, o parecer que funciona bien en pruebas pero fallar en producción. Los segfaults son "afortunados" porque fallan de forma inmediata y visible. La corrupción silenciosa de la memoria es peor porque pasa desapercibida mientras corrompe el estado de la aplicación o crea vulnerabilidades de seguridad.

¿Cuál es el coste de rendimiento de la comprobación de punteros nulos?

Mínimo. La comprobación de un puntero nulo es una única instrucción de comparación que las CPU modernas ejecutan en nanosegundos. La predicción de bifurcación suele ser precisa, ya que la mayoría de los punteros son válidos. El coste de rendimiento es insignificante comparado con el coste de un fallo de producción o una vulnerabilidad de seguridad. Haz un perfil antes de optimizar y verás que las comprobaciones de nulos rara vez aparecen en las rutas calientes.

Asegúrese gratis

Proteja el código, la nube y el tiempo de ejecución en un sistema central.
Encuentre y corrija vulnerabilidades de forma rápida y automática.

No requiere tarjeta de crédito | Escanea resultados en 32segs.