La seguridad de memoria es necesaria, no suficiente

Los gobiernos y los organismos de estándares están empezando a impulsar lenguajes de programación “seguros en memoria”, pero los desarrolladores sostienen que esto es solo una pieza del rompecabezas de la seguridad del software. Los comentaristas comparan Rust, Go, Swift, Java, C y C++ en cuestiones como el comportamiento indefinido, las data races, FFI, las herramientas y los ecosistemas de paquetes, y señalan que Rust reduce enormemente la corrupción de memoria, pero sigue permitiendo otros errores serios y vías de escape inseguras. Muchos ven con buenos ojos los requisitos de seguridad de memoria, pero enfatizan la necesidad de enfoques más amplios: desde un mejor diseño de lenguajes y sistemas de tipos hasta sandboxing, métodos formales y prácticas más disciplinadas en la cadena de suministro.

Política gubernamental y lenguajes seguros en memoria

  • La NDAA del año fiscal 24 de EE. UU. instruye al DoD a adoptar la guía de la NSA sobre lenguajes y herramientas seguros en memoria; entre los ejemplos de la NSA se incluyen C#, Go, Java, Ruby, Rust y Swift.
  • No está claro si la legislación de la UE tiene un lenguaje explícito similar sobre seguridad de memoria; una evaluación de impacto de la UE menciona Rust y Go, pero no requisitos.
  • Algunos esperan que futuras normas de contratación impongan una verificación más estricta del software, incluyendo potencialmente criterios de seguridad de memoria y controles de importación.

Rust, unsafe y comparación con otros lenguajes

  • El unsafe de Rust se considera más contenido que el unsafe típico o FFI en Java/Swift: el sistema de tipos permite construir abstracciones seguras con fuertes garantías de vida útil y uso.
  • Sin embargo, unsafe sigue reintroduciendo comportamiento indefinido al estilo C si se violan los invariantes; los errores pueden ser sutiles y propagarse ampliamente.
  • Hay desacuerdo sobre si Rust reduce en general los defectos totales o solo los desplaza de errores de memoria a fallos de lógica o tipo panic; algunos piden estudios formales.
  • Swift se destaca por converger hacia la propiedad y el préstamo al estilo Rust, y como candidato a “TypeScript para C++” gracias a una interoperabilidad estrecha con C++.
  • Se señala que Go y los lenguajes gestionados han proporcionado seguridad de memoria durante mucho tiempo, pero con diferentes compensaciones (GC, limitaciones del sistema de tipos, peculiaridades de concurrencia).

Seguridad de memoria frente a corrección general

  • Muchos sostienen que la seguridad de memoria es necesaria pero no suficiente: los errores lógicos, SQL injection, path traversal, fallos criptográficos y problemas de deserialización siguen presentes en todos los lenguajes, incluido Rust.
  • Algunos temen una “cultura del panic” y el uso excesivo de fallos abruptos en lugar de una recuperación elegante, especialmente en contextos no críticos para la seguridad.

Comportamiento indefinido, C/C++ y optimización

  • Grandes subhilos debaten el comportamiento indefinido (UB) de C:
    • Un lado: el UB es esencial para una alta optimización (procedencia de punteros, aliasing, etc.).
    • El otro lado: muchos casos de UB podrían definirse con una pérdida de rendimiento modesta; los compiladores modernos son “adversariales” y demasiado complejos.
  • Contexto histórico: el diseño de C favoreció la facilidad de implementación del compilador y la portabilidad sobre la seguridad y la completitud; esto ayudó a su expansión, pero dejó un legado de inseguridad.
  • Se discuten banderas como -fno-strict-aliasing y similares como mitigaciones parciales, a costa de dialectos no estándar y algo de rendimiento.

Data races y seguridad

  • Se hace una distinción entre:
    • Data races de bajo nivel (acceso concurrente no atómico a memoria) y
    • Race conditions de más alto nivel (TOCTOU, races distribuidas).
  • Varios afirman que las data races en proceso no son todavía una clase explotada importante en comparación con la corrupción clásica de memoria; otros señalan exploits del kernel y basados en carreras, y argumentan que no deberíamos normalizar el UB solo porque sea raro.
  • Algunos cuestionan por qué los lenguajes no pueden definir las data races para producir “uno de los valores escritos”; las respuestas citan tearing, rematerialización del compilador y optimizaciones perdidas.

Sandboxing, capacidades y dependencias

  • Crece la preocupación por los enormes árboles de dependencias y el riesgo de la cadena de suministro; se desea poder aislar bibliotecas dentro de un proceso.
  • Mecanismos propuestos: sandboxes a nivel de lenguaje (por ejemplo, Java SecurityManager, aislados de GraalVM), WebAssembly y modelos de objet-capacidad en los que el acceso a recursos (sistema de archivos, red) se pasa explícitamente como capacidades.

Cautelas sobre Python y otros lenguajes “seguros en memoria”

  • Python suele etiquetarse como seguro en memoria, pero los participantes señalan:
    • FFI (ctypes, extensiones C) rompe fácilmente la seguridad.
    • Incluso Python puro puede desencadenar errores de memoria del intérprete mediante bytecode elaborado o APIs de bajo nivel; corregir esto requeriría maquinaria de verificación significativa.
  • Consenso general: “lenguaje seguro en memoria” es una etiqueta pragmática que significa “seguro en uso normal”, no una garantía absoluta.

Direcciones futuras del lenguaje y sucesores de C++

  • Se identifican cuatro estrategias de “sucesor de C++”: no hacer nada, añadir seguridad manteniendo compatibilidad, romper la compatibilidad pero seguir siendo inseguro, o romper la compatibilidad y ser seguro en memoria por defecto.
  • Algunos cuestionan las medias tintas: si de todos modos vas a romper la compatibilidad, ¿por qué no ser completamente seguro en memoria por defecto?
  • Se percibe un espacio de diseño para lenguajes de sistemas seguros en memoria por defecto, pero que tomen decisiones muy distintas a Rust (alcance de la stdlib, manejo de errores, sintaxis, modelo de concurrencia, políticas de asignación, enlazado, gestión de paquetes).