La seguridad de la memoria es el estado de estar protegido contra varios errores de software y vulnerabilidades de seguridad al tratar con el acceso a la memoria , como desbordamientos de búfer y punteros colgantes . [ 1 ] Por ejemplo, Java es seguro en cuanto a la memoria porque su detección de errores en tiempo de ejecución comprueba los límites de los arreglos y las desreferencias de punteros. [ 1 ] Por el contrario, los lenguajes de programación como C , C++ y Fortran permiten aritmética de punteros arbitraria con punteros implementados como direcciones de memoria directas sin provisión para la comprobación de límites , [ 2 ] lo que los hace inseguros en cuanto a la memoria . [ 3 ] El código inseguro en cuanto a la memoria se encuentra típicamente en la programación de bajo nivel , mientras que los lenguajes de programación de alto nivel generalmente incorporan recolección de basura o análisis estático (como en Rust ) para prevenir tales errores.
Historia
Los errores de memoria se consideraron por primera vez en el contexto de la gestión de recursos (computación) y los sistemas de tiempo compartido , en un esfuerzo por evitar problemas como las bombas fork . [ 4 ] Los desarrollos fueron mayormente 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 return-to-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 de búfer y requiere que el atacante utilice heap spraying 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 suelen limitarse a aleatorizar bibliotecas y la ubicación de la pila.
Impacto
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 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 memoria. Muchas otras vulnerabilidades y exploits de alto perfil en software crítico han surgido en última instancia de una falta de seguridad de memoria, incluyendo 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 memoria han llevado a varios investigadores de seguridad a describir la identificación de problemas de seguridad de memoria como "disparar a peces en un barril" . [ 10 ]
Aproches
Algunos lenguajes de programación modernos de alto nivel son seguros en cuanto a la memoria por defecto , aunque no completamente, ya que solo comprueban su propio código y no el sistema con el que interactúan. La gestión automática de la memoria mediante la 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 evita errores comunes como el uso de memoria liberada para todos los datos asignados dentro del entorno de ejecución del lenguaje. [ 11 ] Cuando se combinan con la comprobación automática de límites en todos los accesos a matrices y la ausencia de soporte para la aritmética de punteros sin procesar, los lenguajes con recolección de basura proporcionan fuertes 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 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 críticas en cuanto al rendimiento. [ 1 ]
Para los lenguajes que utilizan 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 mediante análisis estático del programa y demostración automática de teoremas , o bien gestionadas cuidadosamente por el programador en tiempo 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 gran cantidad de software escrito en C y C++ ha motivado el desarrollo de herramientas de análisis estático externas como Coverity , que ofrece análisis estático de memoria 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 memoria por 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 solo protección probabilística contra errores de memoria, pero a menudo se puede implementar fácilmente en software existente volviendo a enlazar 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 comprobación de memoria, lo que garantiza la detección de un subconjunto de errores de memoria en tiempo de ejecución. Sin embargo, suele ralentizar el programa en un factor de 40, [ 17 ] y además debe informarse 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 cada acceso a puntero con respecto a los metadatos para comprobar su validez, 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 con rastreo 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 con 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 ralentización promedio de 2. [ 22 ]
BoundWarden es un nuevo enfoque de control de memoria espacial que utiliza una combinación de técnicas de transformación en tiempo de compilación y monitoreo concurrente en tiempo de ejecución. [ 23 ]
Las pruebas de fuzzing son muy adecuadas para encontrar fallos de seguridad de la memoria y a menudo se utilizan en combinación con verificadores dinámicos como AddressSanitizer.
Clasificación de errores de seguridad de la memoria
Pueden ocurrir muchos tipos diferentes de errores de memoria: [ 24 ] [ 25 ]
- Espacial
- Desbordamiento de búfer : las escrituras fuera de los límites pueden corromper el contenido de los objetos adyacentes, los datos internos (como la información de contabilidad del montón ) o las direcciones de retorno .
- Lectura excesiva del búfer : las lecturas fuera de los límites pueden revelar datos confidenciales o ayudar a los atacantes a eludir la aleatorización del diseño del espacio de direcciones .
- Temporal
- Uso después de su liberación : desreferenciación de un puntero colgante que almacena la dirección de un objeto que ha sido eliminado.
- Doble liberación : las llamadas repetidas a free pueden liberar prematuramente un nuevo objeto en la misma dirección. Si no se ha reutilizado la dirección exacta, pueden producirse otros tipos de corrupción, especialmente en asignadores que utilizan listas de objetos libres .
- Variables no inicializadas : se utiliza una variable a la que no se le ha asignado un valor. Puede contener información confidencial o bits que no son válidos para ese tipo de dato.
- Los punteros salvajes surgen cuando se utiliza un puntero antes de la inicialización a un estado conocido. Presentan el mismo comportamiento errático que los punteros colgantes, aunque es menos probable que pasen desapercibidos.
- Liberación inválida : pasar una dirección inválida a free puede corromper el montón .
- Liberación no coincidente : cuando se utilizan varios asignadores, se intenta liberar memoria con una función de desasignación de un asignador diferente. [ 26 ]
- Espaciotemporal
- Se pueden observar desgarros estructurales o lecturas no atómicas cuando se leen de la memoria datos mayores que el tamaño de palabra de la CPU. [ 27 ]
Contribuir con errores
Dependiendo del idioma y del entorno, otros tipos de errores pueden contribuir a la inseguridad de la memoria:
- Agotamiento de la pila : ocurre cuando un programa se queda sin espacio en la pila, generalmente debido a una recursión demasiado profunda . Una página de protección normalmente detiene el programa, evitando la corrupción de memoria, pero las funciones con marcos de pila grandes pueden omitir la página, y el código del kernel puede no beneficiarse de las páginas de protección.
- Agotamiento de la memoria dinámica : el programa intenta asignar más memoria de la disponible. En algunos lenguajes, esta condición debe comprobarse manualmente después de cada asignación.
- Fuga de memoria : No devolver la memoria al asignador puede propiciar el agotamiento del montón (ver arriba). No ejecutar el destructor de un objeto RAII puede generar resultados inesperados. [ 28 ] [ 29 ]
- Desreferenciación de puntero nulo : Una desreferenciación de puntero nulo suele provocar una excepción o la terminación del programa en la mayoría de los entornos, pero puede causar corrupción en núcleos de sistemas operativos o sistemas sin protección de memoria , o cuando el uso del puntero nulo implica un desplazamiento grande o negativo. En C++, debido a que la desreferenciación de un puntero nulo es un comportamiento indefinido , las optimizaciones del compilador pueden provocar la eliminación de otras comprobaciones, lo que genera vulnerabilidades en otras partes del código. [ 30 ] [ 31 ]
Algunas listas también pueden incluir condiciones de carrera (lecturas/escrituras concurrentes en memoria compartida) como parte de la seguridad de la memoria (por ejemplo, para el control de acceso). El lenguaje de programación Rust previene por defecto muchos tipos de condiciones de carrera basadas en la memoria, ya que garantiza que haya como máximo un escritor o uno o más lectores. Muchos otros lenguajes de programación, como Java, no previenen automáticamente las condiciones de carrera basadas en la memoria, pero aun así se consideran lenguajes generalmente seguros en cuanto a la memoria. Por lo tanto, contrarrestar las condiciones de carrera generalmente no se considera necesario para que un lenguaje se considere seguro en cuanto a la memoria.
Referencias
- 1 2 3 4 Dhurjati, Dinakar; Kowshik, Sumant; Adve, Vikram; Lattner, Chris (11 de julio de 2003). "Seguridad de la memoria sin comprobaciones en tiempo de ejecución ni recolección de basura" (PDF) . Actas de la conferencia ACM SIGPLAN de 2003 sobre lenguaje, compilador y herramienta para sistemas embebidos . ACM. págs. 69–80 . doi : 10.1145/780732.780743 . ISBN 1-58113-647-1. S2CID 1459540 . Consultado el 13 de marzo de 2025 .
- ↑ Koenig, Andrew. "Cómo C dificulta la comprobación de los límites de los arreglos" . Dr. Dobb's . Consultado el 13 de marzo de 2025 .
- ↑ Akritidis, Periklis (junio de 2011). "Seguridad práctica de la memoria para C" (PDF) . Informe técnico - Universidad de Cambridge. Laboratorio de Computación . Universidad de Cambridge, Laboratorio de Computación. ISSN 1476-2986 . UCAM-CL-TR-798 . Consultado el 13 de marzo de 2025 .
- ↑ Anderson, James P. (octubre de 1972). "Estudio de planificación de seguridad informática" (PDF) .
- 1 2 van der Veen, Victor; dutt-Sharma, Nitish; Cavallaro, Lorenzo; Bos, Herbert (2012). "Errores de memoria: pasado, presente y futuro" (PDF) . Investigación en ataques, intrusiones y defensas . Notas de clase en informática. Vol. 7462. págs. 86–106 . doi : 10.1007/978-3-642-33338-5_5 . ISBN 978-3-642-33337-8Consultado el 13 de marzo de 2017 .
- ↑ Wojtczuk, Rafal. "Cómo derrotar el parche de pila no ejecutable de Solar Designer" . insecure.org . Consultado el 13 de marzo de 2017 .
- ↑ "Microsoft: el 70 por ciento de todos los fallos de seguridad son problemas de seguridad de la memoria" . ZDNET . Consultado el 21 de septiembre de 2022 .
- ↑ "CVE-2014-0160" . Vulnerabilidades y exposiciones comunes . Mitre. Archivado del original el 24 de enero de 2018. Consultado el 8 de febrero de 2018 .
- ↑ Goodin, Dan (4 de febrero de 2020). "Grave fallo que acechaba en sudo durante 9 años otorga privilegios de root" . Ars Technica .
- ↑ "Fish in a Barrel" . fishinabarrel.github.io . Consultado el 21 de septiembre de 2022 .
- 1 2 Crichton, Will. "CS 242: Seguridad de la memoria" . stanford-cs242.github.io . Consultado el 22 de septiembre de 2022 .
- ↑ "Referencias" . The Rustonomicon . Rust.org . Consultado el 13 de marzo de 2017 .
- ↑ Bessey, Al; Engler, Dawson; Block, Ken; Chelf, Ben; Chou, Andy; Fulton, Bryan; Hallem, Seth; Henri-Gros, Charles; Kamsky, Asya; McPeak, Scott (1 de febrero de 2010). "Unos cuantos miles de millones de líneas de código después". Communications of the ACM . 53 (2): 66– 75. doi : 10.1145/1646353.1646374 . S2CID 2611544 .
- ↑ Berger, Emery D.; Zorn, Benjamin G. (1 de enero de 2006). «DieHard: Seguridad de memoria probabilística para lenguajes inseguros» (PDF) . Actas de la 27.ª Conferencia ACM SIGPLAN sobre Diseño e Implementación de Lenguajes de Programación . ACM. págs. 158–168 . doi : 10.1145/1133981.1134000 . ISBN 1-59593-320-4. S2CID 8984358 . Consultado el 14 de marzo de 2017 .
- ↑ Novark, Gene; Berger, Emery D. (1 de enero de 2010). «DieHarder: Asegurando el montón» (PDF) . Actas de la 17.ª conferencia ACM sobre seguridad informática y de comunicaciones . ACM. págs. 573–584 . doi : 10.1145/1866307.1866371 . ISBN 978-1-4503-0245-6. S2CID 7880497 . Consultado el 14 de marzo de 2017 .
- ↑ "Depuración de memoria en Allinea DDT" . Archivado del original el 3 de febrero de 2015.
- ↑ Gyllenhaal, John. "Uso de la herramienta Memcheck de Valgrind para detectar errores y fugas de memoria" . computing.llnl.gov . Archivado del original el 7 de noviembre de 2018. Consultado el 13 de marzo de 2017 .
- ↑ "Memcheck: un detector de errores de memoria" . Manual de usuario de Valgrind . valgrind.org . Consultado el 13 de marzo de 2017 .
- ↑ Kreinin, Yossi. "Por qué los asignadores/grupos personalizados son difíciles" . Proper Fixation . Consultado el 13 de marzo de 2017 .
- ↑ "Uso del recolector de basura como detector de fugas" . www.hboehm.info . Consultado el 14 de marzo de 2017 .
- ↑ "Semantic Designs: CheckPointer comparado con otras herramientas de verificación de seguridad" . www.semanticdesigns.com . Semantic Designs, Inc.
- ↑ "Números de rendimiento de AddressSanitizer" . GitHub .
- ↑ Dhumbumroong, Smith (2020). "BoundWarden: Seguridad de memoria espacial reforzada por subprocesos mediante transformaciones en tiempo de compilación". Science of Computer Programming . 198 102519. doi : 10.1016/j.scico.2020.102519 . S2CID 224925197 .
- ↑ Gv, Naveen. "Cómo evitar, encontrar (y corregir) errores de memoria en tu código C/C++" . Cprogramming.com . Consultado el 13 de marzo de 2017 .
- ↑ "CWE-633: Debilidades que afectan la memoria" . Enumeración de debilidades de la comunidad . MITRE . Consultado el 13 de marzo de 2017 .
- ↑ "CWE-762: Rutinas de gestión de memoria incompatibles" . Enumeración de debilidades de la comunidad . MITRE . Consultado el 13 de marzo de 2017 .
- ↑ "CWE-366: Condición de carrera dentro de un hilo" . Enumeración de debilidades comunes . MITRE . Consultado el 12 de diciembre de 2025 .
- ↑ "Destructores - la referencia de Rust" .
- ↑ "Fugas - el Rustonomicon" .
- ↑ "Fallos de seguridad causados por optimizaciones del compilador" . www.redhat.com . Consultado el 26 de junio de 2024 .
- ↑ "NVD - CVE-2009-1897" . nvd.nist.gov . Consultado el 26 de junio de 2024 .
- Errores de software
- vulnerabilidades de seguridad informática
- Implementación del lenguaje de programación