La seguridad de la memoria es el estado de estar protegido de varios errores de software y vulnerabilidades de seguridad cuando se trata del acceso a la memoria , como desbordamientos de búfer y punteros colgantes . [1] Por ejemplo, se dice que Java es seguro para la memoria porque su detección de errores en tiempo de ejecución verifica los límites de la matriz y las desreferencias de punteros. [1] Por el contrario, C y C++ permiten una aritmética de punteros arbitraria con punteros implementados como direcciones de memoria directa sin ninguna disposición para la verificación de límites , [2] y, por lo tanto, son potencialmente inseguros para la memoria . [3]
Los errores de memoria se consideraron por primera vez en el contexto de la gestión de recursos (informática) y los sistemas de tiempo compartido , en un esfuerzo por evitar problemas como las bombas de bifurcación . [4] Los desarrollos fueron principalmente teóricos hasta el gusano Morris , que explotó un desbordamiento de búfer en fingerd . [5] El campo de la seguridad informática se desarrolló rápidamente a partir de entonces, escalando con multitud de nuevos ataques como el ataque de retorno a libc y técnicas de defensa como la pila no ejecutable [6] y la aleatorización del diseño del espacio de direcciones . La aleatorización evita la mayoría de los ataques de desbordamiento de búfer y requiere que el atacante use pulverización de montón u otros métodos dependientes de la aplicación para obtener direcciones, aunque su adopción ha sido lenta. [5] Sin embargo, las implementaciones de la tecnología generalmente se limitan a aleatorizar las bibliotecas y la ubicación de la pila.
En 2019, un ingeniero de seguridad de Microsoft informó que el 70% de todas las vulnerabilidades de seguridad se debían a problemas de seguridad de la memoria. [7] En 2020, un equipo de Google informó de manera similar que el 70% de todos los "errores de seguridad graves" en Chromium se debían a problemas de seguridad de la memoria. Muchas otras vulnerabilidades y exploits de alto perfil en software crítico se han derivado en última instancia de una falta de seguridad de la memoria, incluido Heartbleed [8] y un error de escalada de privilegios de larga data en sudo . [9] La omnipresencia y la gravedad de las vulnerabilidades y exploits que surgen de problemas de seguridad de la memoria han llevado a varios investigadores de seguridad a describir la identificación de problemas de seguridad de la memoria como "disparar a peces en un barril". [10]
Algunos lenguajes de programación de alto nivel modernos son seguros para la memoria por defecto [ cita requerida ] , aunque no completamente ya que solo verifican su propio código y no el sistema con el que interactúan. La gestión automática de memoria en forma de recolección de basura es la técnica más común para prevenir algunos de los problemas de seguridad de memoria, ya que evita errores comunes de seguridad de memoria como el uso después de la liberación para todos los datos asignados dentro del entorno de ejecución del lenguaje. [11] Cuando se combinan con la verificación automática de límites en todos los accesos a matrices y sin soporte para aritmética de punteros sin formato , los lenguajes con recolección de basura brindan fuertes garantías de seguridad de memoria (aunque las garantías pueden ser más débiles para operaciones de bajo nivel marcadas explícitamente como no seguras, como el uso de una interfaz de función externa ). Sin embargo, la sobrecarga de rendimiento de la recolección de basura hace que estos lenguajes no sean adecuados para ciertas aplicaciones de rendimiento crítico. [1]
En el caso de los lenguajes que utilizan la gestión manual de memoria , la seguridad de la memoria no suele estar garantizada por el entorno de ejecución. En cambio, las propiedades de seguridad de la memoria deben ser garantizadas por el compilador a través del análisis estático del programa y la demostración automatizada de teoremas o gestionadas cuidadosamente por el programador en el entorno de ejecución. [11] Por ejemplo, el lenguaje de programación Rust implementa un verificador de préstamos para garantizar la seguridad de la memoria, [12] mientras que C y C++ no ofrecen garantías de seguridad de la memoria. La importante cantidad de software escrito en C y C++ ha motivado el desarrollo de herramientas de análisis estático externo como Coverity , que ofrece análisis de memoria estática para C. [13]
DieHard, [14] su rediseño DieHarder, [15] y la herramienta de depuración distribuida Allinea son asignadores de montón especiales que asignan objetos en su propia página de memoria virtual aleatoria, lo que permite detener y depurar lecturas y escrituras no válidas en la instrucción exacta que las causa. La protección se basa en la protección de la memoria del hardware y, por lo tanto, la sobrecarga normalmente no es sustancial, aunque puede crecer significativamente si el programa hace un uso intensivo de la asignación. [16] La aleatorización proporciona solo protección probabilística contra errores de memoria, pero a menudo se puede implementar fácilmente en el software existente al volver a vincular el binario.
La herramienta memcheck de Valgrind utiliza un simulador de conjunto de instrucciones y ejecuta el programa compilado en una máquina virtual de verificación de memoria, lo que garantiza la detección de un subconjunto de errores de memoria en tiempo de ejecución. Sin embargo, normalmente ralentiza el programa en un factor de 40 [17] y, además, debe recibir información explícita sobre los asignadores de memoria personalizados. [18] [19]
Con acceso al código fuente, existen bibliotecas que recopilan y rastrean valores legítimos para punteros ("metadatos") y verifican cada acceso de puntero contra los metadatos para su validez, como el recolector de basura Boehm . [20] En general, la seguridad de la memoria se puede garantizar de manera segura mediante el rastreo de la recolección de basura y la inserción de comprobaciones en tiempo de ejecución en cada acceso a la memoria; este enfoque tiene una sobrecarga, pero menor que la de Valgrind. Todos los lenguajes de recolección de basura adoptan este enfoque. [1] Para C y C++, existen muchas herramientas que realizan una transformación en tiempo de compilación del código para realizar comprobaciones de seguridad de memoria en tiempo de ejecución, como CheckPointer [21] y AddressSanitizer , que impone un factor de desaceleración promedio de 2. [22]
BoundWarden es un nuevo enfoque de aplicación de memoria espacial que utiliza una combinación de transformación en tiempo de compilación y técnicas de monitoreo concurrente en tiempo de ejecución. [23]
Las pruebas fuzz son adecuadas para encontrar errores de seguridad de la memoria y a menudo se utilizan en combinación con verificadores dinámicos como AddressSanitizer.
Pueden ocurrir muchos tipos diferentes de errores de memoria: [24] [25]
Dependiendo del idioma y el entorno, otros tipos de errores pueden contribuir a la inseguridad de la memoria:
Algunas listas también pueden incluir condiciones de carrera (lecturas/escrituras simultáneas en la memoria compartida) como parte de la seguridad de la memoria (por ejemplo, para el control de acceso). El lenguaje de programación Rust evita muchos tipos de condiciones de carrera basadas en la memoria de forma predeterminada, porque garantiza que haya como máximo un escritor o uno o más lectores. Muchos otros lenguajes de programación, como Java, no evitan automáticamente las condiciones de carrera basadas en la memoria, pero aún así se consideran generalmente lenguajes "seguros para la memoria". Por lo tanto, contrarrestar las condiciones de carrera generalmente no se considera necesario para que un lenguaje se considere seguro para la memoria.
{{cite journal}}
: Requiere citar revista |journal=
( ayuda )