Skip to main content

Identificar código C inseguro con Valgrind y corregirlo con Snyk Code

Escrito por
feature snyk supply chain purple

24 de septiembre de 2024

0 minutos de lectura

C y C++ siguen siendo fundamentales para el desarrollo de software crítico. Estos lenguajes impulsan una amplia variedad de sistemas, desde dispositivos integrados hasta aplicaciones de alto rendimiento en la industria manufacturera, la tecnología operativa (OT) y el sector industrial. Su eficiencia, el control que ofrecen sobre los recursos del sistema y su rendimiento los hacen indispensables para los desarrolladores que trabajan en proyectos de misión crítica.

C y C++ son especialmente comunes en Japón, donde los sectores manufacturero e industrial son motores económicos clave. Los desarrolladores japoneses confían en estos lenguajes para crear software sólido y eficiente, que sustenta desde los sistemas automotrices hasta la automatización de fábricas. La precisión y confiabilidad de C y C++ son cruciales para mantener los altos estándares que se esperan en estas industrias.

La importancia de la seguridad del código en C y C++

Aunque C y C++ ofrecen un control y un rendimiento incomparables, también plantean importantes desafíos de seguridad. La falta de funciones de seguridad integradas, como la administración automática de memoria, hace que estos lenguajes sean propensos a vulnerabilidades como los desbordamientos de búfer, el uso después de liberar memoria y las fugas de memoria. Aunque parezcan ingenuas y básicas, estas vulnerabilidades pueden tener consecuencias graves, especialmente en software crítico, donde la confiabilidad y la seguridad son primordiales.

El código C vulnerable provoca una fuga de memoria

Veamos un ejemplo de código C vulnerable que los desarrolladores podrían escribir y que introduce una vulnerabilidad de seguridad relacionada con las fugas de memoria.

Este es el código de nuestro programa en C. ¿Puedes encontrar aquí la vulnerabilidad de seguridad?

#include <stdio.h>
#include <stdlib.h>

void allocateMemory() {
    int *ptr = (int *)malloc(10 * sizeof(int));
    if (ptr == NULL) {
        printf("Memory allocation failed\n");
        return;
    }
}

int main() {
    allocateMemory();
    return 0;
}

Este archivo program1.c contiene un ejemplo de vulnerabilidad de asignación dinámica de memoria (MISRA Dir 4.12, MISRA Rule 21.3). La vulnerabilidad de seguridad de la función allocateMemory() consiste en que usa malloc() para asignar memoria, pero no la libera, lo que provoca una fuga de memoria. Esto se puede evitar siguiendo las pautas de MISRA, que restringen la asignación dinámica de memoria.

Este es un error común al programar en C, que puede hacer que tu programa use cada vez más memoria. Con el tiempo, esto puede hacer que se quede sin memoria y se bloquee, o que otros programas de tu sistema se queden sin memoria.

¿Por qué no podemos usar simplemente la asignación estática de memoria, como int arr[10]? Porque, en algunas situaciones, no puedes asignar memoria en la pila. Por ejemplo, supongamos que estás escribiendo una función para leer un archivo en la memoria. No conoces el tamaño del archivo hasta que se ejecuta el programa, así que no puedes usar un arreglo de tamaño fijo. En su lugar, puedes usar malloc para asignar la cantidad exacta de memoria una vez que conozcas el tamaño del archivo.

Primero, compila el programa y luego ejecútalo:

$ gcc program1.c -o program1
$ ./program1

¿El programa tiene una fuga de memoria? ¿Cómo puedes corregirla? 

Encuentra la vulnerabilidad de seguridad en C con Valgrind

Valgrind es una herramienta eficaz para encontrar fugas de memoria en tu programa. Podemos ejecutarlo con Valgrind para comprobar si hay fugas de memoria.

Instala Valgrind en tu sistema. Si usas un sistema basado en Linux, puedes instalarlo con el administrador de paquetes. Este es un ejemplo para Debian:

sudo apt-get install valgrind

Nota: Si usas macOS con chips basados en ARM, Valgrind no está disponible, así que omitiremos ese paso y ejecutaremos Valgrind dentro de un contenedor de Docker:

docker build -t "valgrind" . -f Dockerfile

Luego, ejecuta el contenedor y asigna nuestro directorio actual al directorio /tmp del contenedor:

docker run -it -v $PWD:/tmp -w /tmp valgrind

Luego, compila el programa program1.c dentro del contenedor:

gcc program1.c -o program1.app

Después, podemos ejecutar el programa con Valgrind para comprobar si hay fugas de memoria:

valgrind --leak-check=full ./program1.app

A continuación, deberías ver el resultado de Valgrind que muestra la fuga de memoria:

/tmp # valgrind --leak-check=full ./program1.app

==29== Memcheck, a memory error detector
==29== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==29== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==29== Command: ./program1
==29==
==29==
==29== HEAP SUMMARY:
==29==     in use at exit: 40 bytes in 1 blocks
==29==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
==29==
==29== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==29==    at 0x48E978C: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-arm64-linux.so)
==29==    by 0x108823: allocateMemory (in /tmp/program1)
==29==    by 0x108857: main (in /tmp/program1)
==29==
==29== LEAK SUMMARY:
==29==    definitely lost: 40 bytes in 1 blocks
==29==    indirectly lost: 0 bytes in 0 blocks
==29==      possibly lost: 0 bytes in 0 blocks
==29==    still reachable: 0 bytes in 0 blocks
==29==         suppressed: 0 bytes in 0 blocks
==29==
==29== For lists of detected and suppressed errors, rerun with: -s
==29== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Valgrind es una excelente herramienta, pero para quienes desarrollan en C a diario, este flujo de trabajo no es escalable y requiere compilar y crear un programa completo para analizarlo.

Snyk Code te ayudará a identificar esta vulnerabilidad en tu base de código sin siquiera tener que compilar. Solo tienes que instalar la extensión y abrir el archivo para que se muestre la vulnerabilidad. Esto es posible porque Snyk Code es una herramienta de análisis estático de código que aplica técnicas de aprendizaje automático para identificar código estático, sin necesidad de compilar ni de un paso de compilación. Este enfoque permite obtener resultados confiables, con pocos falsos positivos y rápidamente, cuando Snyk analiza tu código en busca de vulnerabilidades de seguridad.

Editor de código que muestra un programa en C con una advertencia de Snyk por no liberar la memoria después de asignarla.

Detectar el recorrido de rutas, el desbordamiento de búfer y otras vulnerabilidades en C

El motor SAST que impulsa Snyk Code puede detectar más tipos de vulnerabilidades que las fugas de memoria causadas por malloc.

Veamos un ejemplo más complejo que combina varias prácticas de codificación inseguras en C que introducen vulnerabilidades de seguridad:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(int argc, char *argv[]) {
    // get the filename from the first command line argument 
    char *filename = argv[1];
    // append the filename to the current directory
    char path[50] = "./";
    strcat(path, filename);

    FILE *file = fopen(path, "r");
    if (file == NULL) {
        printf("Error opening file!\n");
        exit(1);
    }

    // read the contents of the file into memory and print the size of the file:
    fseek(file, 0, SEEK_END);
    long fsize = ftell(file);
    fseek(file, 0, SEEK_SET);
    char *string = malloc(fsize + 1);
    fread(string, fsize, 1, file);
    free(string);

    printf("Size of the file: %ld\n", fsize);
    printf("Contents of the file: %s\n", string);

    if (string != NULL) {
        free(string);
    }

    if (fsize == 0) {
        string[0] = 'A';
    }

    fclose(file);
    return 0;
}

El programa en C del ejemplo anterior está repleto de vulnerabilidades, pero es una forma muy concisa e ingenua de presentar código inseguro por motivos de brevedad. Aun así, algunas prácticas de codificación sutiles podrían aparecer fácilmente en bases de código reales.

Una vez compilado el programa, puedes ejecutarlo:

$ ./program3.app "text.txt"

Si tienes un archivo llamado text.txt en el mismo directorio y no está vacío, el programa lo leerá e imprimirá su contenido.

Por ejemplo:

Size of the file: 44
Contents of the file: FROM alpine:latest
RUN apk add g++ valgrind

Parece estar bien. ¿Qué pasa si le pasas un archivo que recorre la estructura de directorios?

$ ./program3.app "../../../../../etc/passwd"

El programa tiene una vulnerabilidad de recorrido de rutas, que permite a un atacante leer archivos confidenciales del sistema.

Para probar otras vulnerabilidades, intenta:

  • Pasar un archivo que no existe

  • Pasar un archivo vacío

  • Pasar un nombre de archivo o una ruta completa demasiado largos (más de 50 caracteres)

Algunos de estos problemas de seguridad van mucho más allá de las habilidades de programación en C de un desarrollador. Por ejemplo, el recorrido de rutas requiere conocer la seguridad de las aplicaciones, además de saber administrar la memoria de forma segura y contar con experiencia consolidada en el desarrollo con C.

Por suerte, cuando pegas el código del programa en el IDE, la extensión de Snyk analiza el código C en busca de convenciones de codificación inseguras y problemas comunes de seguridad de las aplicaciones. Los reporta rápidamente y muestra el contexto directamente en el código: 

VS Code muestra código C con Snyk, que resalta vulnerabilidades de desbordamiento de búfer, liberación doble y uso después de liberar.

Protege tu código C y C++ con Snyk

Una brecha de seguridad en los sectores manufacturero e industrial puede tener consecuencias catastróficas, como interrupciones operativas, riesgos para la seguridad y pérdidas financieras. Garantizar la seguridad del código en C y C++ no es solo una buena práctica: es una necesidad. Snyk ofrece a los desarrolladores las herramientas para identificar y corregir vulnerabilidades desde las primeras etapas del proceso de desarrollo.

Protege tu código con información de vanguardia

Conoce todas las funcionalidades de SAST de Snyk Code en solo 30 minutos.