Una hoja de ruta de seguridad de memoria es un plan con responsables y fechas, y puede redactar una este trimestre sin tocar la mayor parte de su código en C y C++. La reescritura llega después y abarca solo las partes que la justifiquen. Lo que necesita ahora es una imagen clara de dónde vive el código inseguro, una forma de contenerlo y un registro de lo que ha decidido.
El documento de CISA The Case for Memory Safe Roadmaps describe cómo los fabricantes de software pueden migrar a lenguajes con seguridad de memoria (MSL, por sus siglas en inglés) para eliminar las vulnerabilidades de seguridad de memoria. Pide a los fabricantes que publiquen hojas de ruta para que los clientes vean cómo asumen la responsabilidad de los resultados de seguridad, practican una transparencia radical y adoptan un enfoque de arriba abajo. Son principios de Secure by Design, y el documento se dirige tanto a directivos como a ingenieros. Esa segunda audiencia importa: una hoja de ruta sin patrocinador se queda olvidada en una wiki.
Qué cubren las fuentes y qué no
La página de resumen de CISA menciona únicamente la transición a los MSL y la creación y publicación de una hoja de ruta. No muestra el contenido completo, así que no afirmamos nada sobre lo que el documento dice acerca del fuzzing o del aislamiento de procesos. Esos dos pasos que siguen son práctica nuestra para los años que median entre «hoy tenemos C++» y «tenemos un lenguaje más seguro en las partes que importan». Considérelos controles provisionales, no un sustituto de la hoja de ruta.
Las fuentes tampoco ofrecen cifras sobre cuánto riesgo elimina cada paso. No vamos a inventarlas. Mídalo usted mismo a partir de su propio historial de fallos y vulnerabilidades.
Paso 1: inventariar el código inseguro
No se puede priorizar lo que no se ha listado. Elabore una tabla por componente, no por repositorio, porque un mismo repositorio suele mezclar una utilidad que se puede dejar como está con un parser de red.
| Campo | Por qué importa |
|---|---|
| Lenguaje y estándar | Indica qué se puede migrar y qué no |
| ¿Accesible desde el exterior? | Red, archivo, USB, puerto serie, canal de actualización |
| ¿Procesa entradas no confiables? | Determina la prioridad de fuzzing y de reescritura |
| ¿Con qué privilegios se ejecuta? | Determina cuánto ayuda el aislamiento |
| ¿De terceros o propio? | Determina quién puede corregirlo |
| Responsable y último cambio sustancial | El código muerto a menudo se puede eliminar |
Incluya las bibliotecas vendorizadas y los componentes de firmware. Los equipos de hardware, en particular, suelen tener código C antiguo en bootloaders, drivers y gestores de actualización que nadie abre desde hace años. Esos componentes merecen una fila.
Paso 2: aislar lo que aún no puede reemplazar
Para cada componente marcado como accesible y que procesa entradas no confiables, pregúntese qué ocurre si queda totalmente comprometido. Si la respuesta es «todo», ese es su primer cambio de diseño. Mueva el parser a su propio proceso, reduzca sus privilegios y retírele el acceso a los archivos, sockets y claves que no necesite.
Esto no corrige el fallo. Limita lo que el fallo vale para un atacante. Encaja con el objetivo que fija el Secure Software Development Framework (SP 800-218) del NIST: mitigar el impacto potencial de la explotación de vulnerabilidades que todavía no ha encontrado.
Paso 3: hacer fuzzing de los puntos de entrada
Tome del inventario los componentes que procesan entradas no confiables y escriba un fuzz harness para cada uno. Ejecute los harnesses en CI con un límite de tiempo y conserve el corpus de crashes. Empiece por los formatos de archivo, los mensajes de protocolo y los paquetes de actualización, ya que son las rutas a las que puede llegar alguien externo.
Compile los binarios de fuzzing con la detección de errores de memoria del compilador activada, de modo que una lectura fuera de límites silenciosa se convierta en un fallo visible. Cada crash que analice le aporta algo que la hoja de ruta necesita: evidencia de qué componentes merecen una reescritura. Si un harness no encuentra nada tras ejecuciones prolongadas, también es útil, porque le permite situar ese componente más abajo en el orden de prioridad.
Paso 4: elegir una lista corta de reescrituras
Use los tres primeros pasos para elegir candidatos. Una buena primera reescritura reúne estas propiedades:
- Procesa entradas no confiables.
- Tiene una interfaz estrecha, de modo que puede sustituirse detrás de la misma API.
- Ha producido crashes durante el fuzzing o tiene historial de errores.
- Es lo bastante pequeña como para que un equipo la termine en uno o dos ciclos de release.
Deje el código estable, interno y no expuesto en C o C++ y dígalo en la hoja de ruta. Escribir «no planificado, aislado, con fuzzing» junto a un componente es honesto, y los clientes pueden trabajar con ello. Esta es nuestra interpretación de la transparencia radical: usted expone su posición, y no todas las filas tienen que acabar en una reescritura. El resumen de CISA nombra el principio, pero no dice qué implica para los componentes que siguen en C o C++.
Los componentes nuevos son el lugar más barato para empezar. Decida ahora qué lenguaje usará el nuevo código expuesto a la red y déjelo escrito en sus estándares de ingeniería.
Paso 5: documentarlo y vincularlo a su proceso
El NIST describe el SSDF como un conjunto básico de prácticas de alto nivel que se integran en el ciclo de vida de desarrollo que ya se utilice, con el objetivo de abordar las causas raíz para que las vulnerabilidades no se repitan. Incorpore sus reglas de seguridad de memoria a ese proceso existente: elección de lenguaje para el código nuevo, fuzzing en CI y una puerta de revisión para las construcciones inseguras. El SSDF también ofrece a compradores y proveedores un vocabulario común, lo cual ayuda cuando el cuestionario de seguridad de un cliente pregunta por la seguridad de memoria.
En cuanto a las listas de verificación de codificación, el proyecto OWASP Secure Coding Practices Quick Reference Guide está archivado. Sus listas se han trasladado a la OWASP Developer Guide, así que utilice esa versión en lugar de un PDF obsoleto.
Por dónde empezar
- Exporte una lista de todos los componentes en C y C++, incluido el código vendorizado y el de firmware, y rellene la tabla de inventario anterior. Fije un límite de dos semanas.
- Marque los componentes que leen entradas no confiables y anote a qué puede acceder cada uno si se compromete.
- Traslade los tres componentes expuestos más importantes a procesos separados y de bajos privilegios, o programe el cambio con un responsable concreto.
- Escriba fuzz harnesses para esos mismos componentes y ejecútelos en CI. Revise los crashes cada semana.
- Elija un componente para reescribirlo en un lenguaje con seguridad de memoria y fije una regla de lenguaje para todo el código nuevo expuesto a la red.
- Publique una hoja de ruta interna de una página con responsables y fechas, y después decida qué comparte con los clientes. Si desea una revisión externa, nuestro servicio de desarrollo de software seguro y las pruebas de penetración pueden revisar el inventario y probar los componentes aislados.
