La seguridad de la memoria es el estado de protección contra diversos errores de software y vulnerabilidades de seguridad cuando se trata de acceso a la memoria , como desbordamientos del 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 los punteros. [1] Por el contrario, C y C++ permiten aritmética de punteros arbitraria con punteros implementados como direcciones de memoria directa sin provisión para 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 fork . [4] Los desarrollos fueron en su mayoría teóricos hasta que apareció el gusano Morris , que aprovechó un desbordamiento del buffer en fingerd . [5] El campo de la seguridad informática se desarrolló rápidamente a partir de entonces, intensificándose 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 previene la mayoría de los ataques de desbordamiento del búfer y requiere que el atacante utilice la 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 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 fueron causadas por 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 fueron causados por problemas de seguridad de la memoria. Muchas otras vulnerabilidades y exploits de alto perfil en software crítico se deben en última instancia a 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 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 "cazar peces en un barril". [10]
La mayoría de los [ cita necesaria ] lenguajes de programación modernos de alto nivel son seguros para la memoria de forma predeterminada, aunque no del todo, ya que solo verifican su propio código y no el sistema con el que interactúan. La gestión automática de la 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 la memoria, ya que previene errores comunes de seguridad de la memoria, como el uso después de la liberación, para todos los datos asignados dentro del tiempo de ejecución del lenguaje. [11] Cuando se combinan con la verificación automática de límites en todos los accesos a la matriz y sin soporte para la aritmética de punteros sin formato, los lenguajes de recolección de basura brindan sólidas garantías de seguridad de la memoria (aunque las garantías pueden ser más débiles para operaciones de bajo nivel marcadas explícitamente como inseguras, como el uso de un 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 determinadas aplicaciones críticas para el rendimiento. [1]
Para los lenguajes que utilizan administración de memoria manual , el tiempo de ejecución generalmente no garantiza la seguridad de la memoria. En cambio, las propiedades de seguridad de la memoria deben ser garantizadas por el compilador mediante un análisis estático del programa y una demostración automatizada de teoremas o deben ser administradas cuidadosamente por el programador en tiempo de ejecución. [11] Por ejemplo, el lenguaje de programación Rust implementa un verificador de préstamo 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 externas de análisis estático como Coverity , que ofrece análisis de memoria estática para C. [13]
DieHard, [14] su rediseño DieHarder, [15] y Allinea Distributed Debugging Tool 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 depende de la protección de la memoria del hardware y, por lo tanto, la sobrecarga no suele ser sustancial, aunque puede aumentar significativamente si el programa hace un uso intensivo de la asignación. [16] La aleatorización proporciona sólo protección probabilística contra errores de memoria, pero a menudo se puede implementar fácilmente en el software existente volviendo 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 proporciona una detección garantizada 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, se debe informar explícitamente 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 la validez de cada acceso a puntero con los metadatos, como el recolector de basura Boehm . [20] En general, la seguridad de la memoria se puede garantizar de forma segura mediante la recolección de basura de seguimiento y la inserción de comprobaciones de tiempo de ejecución en cada acceso a la memoria; Este enfoque tiene gastos generales, pero menos que el 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 del código en tiempo de compilación para realizar comprobaciones de seguridad de la 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]
Pueden ocurrir muchos tipos diferentes de errores de memoria: [24] [25]
{{cite journal}}
: Citar diario requiere |journal=
( ayuda )