Cómo difieren los CVEs de memory safety entre Rust y C/C++
¿Por qué seguimos midiendo la seguridad de un lenguaje por cantidad de CVEs si sabemos que ese número depende tanto del tamaño de la base instalada como de cualquier propiedad del lenguaje en sí? Llevó años de debate y un par de papers de NSA y CISA para que el ecosistema tomara en serio la pregunta — y aun así, la respuesta que circula en la mayoría de los hilos es demasiado simple para ser útil.
Mi tesis es esta: la diferencia en CVEs de memory safety entre Rust y C/C++ es real, documentable y técnicamente interesante. Pero convertirla en "migrá todo a Rust" o en "el borrow checker lo resuelve todo" es un error de categoría. El dato útil no está en el titular — está en qué tipo de vulnerabilidades desaparecen, cuáles persisten y bajo qué condiciones el modelo de seguridad de Rust tiene fricciones propias.
El problema real: no todos los CVEs de memoria son iguales
Cuando CISA, NSA o el White House Office of the National Cyber Director publican reportes recomendando lenguajes memory-safe (y lo hicieron de forma pública entre 2022 y 2023), la categoría que apuntan es específica: vulnerabilidades causadas por comportamiento indefinido en la gestión manual de memoria. Use-after-free, buffer overflow, double-free, null pointer dereference no chequeado — la familia clásica de C/C++.
La distinción técnica importa:
| Clase de vulnerabilidad | C/C++ | Rust (safe) | Rust (unsafe) |
|---|---|---|---|
| Use-after-free | Común | Imposible por diseño | Posible |
| Buffer overflow (stack/heap) | Común | Imposible por diseño | Posible |
| Data race en multithreading | Común | Imposible por diseño | Posible |
| Integer overflow | Posible | Debug: panic / Release: wrapping | Igual |
| Logic bugs | Siempre posible | Siempre posible | Siempre posible |
| Unsafe block mal usado | N/A | Posible | Posible |
La tabla no es un benchmark de producción — es un mapa de qué garantías da el compilador de Rust en código safe vs. qué queda fuera de esas garantías. La fuente no es un claim propio: el modelo de ownership y las reglas del borrow checker están descritos formalmente en The Rust Reference y en el capítulo de unsafe del libro oficial.
El punto que más se ignora en las discusiones de Twitter/HN: aproximadamente el 70% del código en un proyecto Rust típico puede vivir en safe, pero cualquier integración con C via FFI, cualquier unsafe block para operaciones de bajo nivel, y cualquier dependencia de crate que use unsafe internamente recae en el mismo territorio de C. No es un problema hipotético — libs como tokio, serde y ring tienen bloques unsafe revisados y auditados, pero el contrato de seguridad del compilador no aplica ahí.
Evidencia disponible: qué podés verificar sin tener acceso a producción ajena
No hace falta un dataset de CVEs propietario para validar algo concreto. Hay al menos tres fuentes públicas reproducibles:
1. RustSec Advisory Database El repositorio rustsec/advisory-db en GitHub mantiene advisories de seguridad para el ecosistema Rust. Es auditable, tiene categorías por tipo de vulnerabilidad y fecha. Podés correr:
# Instalar cargo-audit si no lo tenés
cargo install cargo-audit
# Auditar dependencias de cualquier proyecto Rust
cargo audit
# Ver advisories activos con detalle
cargo audit --json | jq '.vulnerabilities.list[] | {id: .advisory.id, title: .advisory.title, categories: .advisory.categories}'El resultado te dice no solo si hay vulnerabilidades conocidas, sino de qué categoría son. Ese dato es concreto y reproducible en cualquier máquina.
2. CVE Details / NVD por CWE El NVD (National Vulnerability Database) categoriza CVEs por CWE (Common Weakness Enumeration). CWE-119 (buffer errors), CWE-416 (use-after-free) y CWE-476 (null pointer dereference) son las categorías que concentran la mayor parte de los CVEs históricos de proyectos C/C++. Podés filtrar por lenguaje y año en nvd.nist.gov y observar la distribución. El volumen es asimétrico — y parte de esa asimetría es base instalada, no solo seguridad del lenguaje.
3. Chromium y Microsoft como casos de referencia pública Google publicó datos internos mostrando que ~70% de los CVEs severos de Chrome en un período dado eran memory safety issues en C++. Microsoft hizo lo equivalente para sus productos. Esos números son citados frecuentemente, pero el contexto importa: son proyectos C++ de decenas de millones de líneas, con décadas de deuda técnica. No son representativos de un proyecto C++ nuevo y bien auditado.
Donde se equivoca la gente: la receta demasiado rápida
El error más común que veo en discusiones técnicas es tratar la comparación de CVEs como un argumento de adopción directa. El razonamiento suele ser:
"Rust tiene menos CVEs de memoria → migrá el stack → problema resuelto."
Hay tres costos que esa receta oculta:
Costo 1: El unsafe invisible de las dependencias. Si tomás una dependencia de crates.io sin revisar sus advisories, estás importando potencialmente unsafe no auditado. cargo audit lo detecta si hay un advisory registrado — pero hay unsafe en crates sin advisory porque nadie lo auditó todavía. El borrow checker no te protege de lo que no ve.
Costo 2: La curva de unsafe en FFI. Si el sistema que querés proteger hace FFI con C (drivers, librerías de hardware, bindings a OpenSSL/libsodium), esa interfaz es territorio unsafe. Un wrapper mal escrito puede introducir exactamente los mismos bugs que querías evitar. La garantía de Rust termina en la frontera del bloque unsafe.
Costo 3: Logic bugs y vulnerabilidades de lógica de negocio. El borrow checker resuelve una clase específica de bugs — los de gestión de memoria. Un TOCTOU, una condición de carrera en lógica de negocio, una validación de input mal hecha, un schema de permisos incorrecto: ninguno de esos desaparece por cambiar de lenguaje. Esto no es un argumento en contra de Rust — es un argumento en contra de creer que el lenguaje resuelve la seguridad de forma holística.
Una lectura honesta de esto me conecta con algo que aprendí más de una vez mirando esquemas de validación: el lugar donde más duele no es el que protege el compilador, sino el que asumís que está cubierto y no está. Igual que cuando creés un schema Zod una vez y esperás que valide todo el flujo — pero hay tres formas en que se rompe en runtime que no son obvias hasta que las ves.
Matriz de decisión: cuándo este dato cambia algo y cuándo no
Antes de usar la comparación de CVEs como argumento técnico, pasala por esta checklist:
CHECKLIST: ¿El dato de CVEs de Rust vs C/C++ es relevante para mi decisión?
[ ] ¿El sistema que estoy evaluando tiene C/C++ con gestión manual de memoria?
→ Si no, la comparación es irrelevante para vos.
[ ] ¿La superficie de ataque principal son vulnerabilidades de memoria (UAF, overflow)?
→ Si el riesgo dominante es lógica de negocio o autenticación, Rust no cambia eso.
[ ] ¿Tengo capacidad de revisar unsafe blocks en dependencias críticas?
→ Si no, la ganancia se reduce: importás riesgo opaco igual que en C.
[ ] ¿El proyecto usa FFI extensivo con C?
→ El beneficio del borrow checker se acota a la porción safe del código.
[ ] ¿Estoy evaluando código nuevo vs. migrar código existente?
→ Migrar una base C/C++ grande tiene un costo real de reescritura y periodo de coexistencia.
→ Código nuevo en Rust sobre un dominio bien acotado: el beneficio es inmediato y verificable.
[ ] ¿El equipo tiene experiencia con el modelo de ownership?
→ La curva de aprendizaje del borrow checker es real. Un equipo sin experiencia en Rust
puede introducir más bugs durante la transición que los que evita a mediano plazo.
Para proyectos donde el dominio es computación de bajo nivel, parsing de formatos binarios no confiables, daemons de sistema o componentes de red con alta exposición, el argumento a favor de Rust es sólido y respaldado por evidencia pública. Para un backend de negocio en TypeScript o Java donde el riesgo dominante es inyección, autenticación mal implementada o lógica de permisos rota, el debate de memory safety es casi una distracción.
Esto conecta con algo que también aplica al árbol de decisión de tokens de autenticación: la elección técnica correcta depende del threat model real, no del lenguaje o protocolo con mejor marketing de seguridad.
FAQ
¿Rust elimina todos los CVEs de memoria?
No. Elimina los CVEs de memoria en código safe — que es la mayoría del código de un proyecto bien estructurado. Cualquier bloque unsafe, FFI con C, o crate externo con unsafe no auditado queda fuera de esa garantía. El borrow checker es un contrato con el compilador, no un escáner de seguridad global.
¿Los proyectos Rust tienen cero CVEs de memory safety?
No exactamente. La base de datos pública rustsec/advisory-db tiene advisories de proyectos Rust, algunos con categorías de memory safety originadas en bloques unsafe o en crates con bugs previos a una auditoría. Son menos en términos absolutos y proporcionales que en proyectos C/C++ equivalentes, pero "menos" no es "cero".
¿Tiene sentido migrar un backend TypeScript/Node a Rust por seguridad? En la mayoría de los casos, no. El threat model de un backend de negocio no está dominado por vulnerabilidades de gestión de memoria — está dominado por lógica de autenticación, validación de input y configuración de infraestructura. Rust no cambia eso. La decisión tiene más sentido para componentes de parsing, criptografía o networking de bajo nivel.
¿Qué diferencia hay entre un CVE de C/C++ y uno de Rust en la práctica? Los CVEs de C/C++ en memoria suelen ser explotables directamente: un buffer overflow puede llevar a ejecución de código arbitrario. Los CVEs en Rust tienden a ser más acotados — panic, leak de memoria, o comportamiento incorrecto en edge cases — y menos frecuentemente escalan a ejecución arbitraria. Esa diferencia de severidad promedio es técnicamente significativa aunque el conteo bruto sea menor.
¿cargo audit es suficiente para auditar un proyecto Rust?
Es un buen primer paso, pero no es completo. Cubre advisories registrados en rustsec/advisory-db. No detecta unsafe no auditado sin advisory, bugs lógicos, ni vulnerabilidades en dependencias de sistema (librerías C enlazadas dinámicamente). Para una auditoría seria, se complementa con cargo-geiger (que cuenta e identifica unsafe en el árbol de dependencias) y revisión manual de las dependencias críticas.
¿Esto cambia algo para alguien que trabaja principalmente con TypeScript/Next.js? Directamente, poco. El modelo de seguridad relevante para ese stack es diferente: validación de esquemas, gestión de sesiones, headers HTTP, permisos de base de datos. Entender la comparación Rust/C++ es útil para tomar decisiones de arquitectura cuando hay componentes de bajo nivel o para evaluar si una dependencia nativa (un addon Node.js en C++) vale el riesgo. No es una decisión cotidiana en un proyecto Next.js estándar.
Cierre: lo que el dato dice y lo que no puede decir
La diferencia en CVEs de memory safety entre Rust y C/C++ es un fenómeno real, documentado en fuentes públicas y técnicamente explicable por el modelo de ownership del compilador. No es hype — hay una razón estructural por la que cierta clase de bugs es imposible en código Rust safe.
Lo incómodo es que ese dato se usa frecuentemente para justificar decisiones que están mal planteadas. Si el threat model de un sistema no está dominado por gestión manual de memoria, la comparación de CVEs no resuelve nada. Si el equipo no tiene capacidad de revisar unsafe en dependencias, la garantía del compilador se diluye en la práctica.
Mi postura después de mirar esto desde varios ángulos: Rust es una herramienta sólida para dominios específicos donde la gestión de memoria es el riesgo principal. Como argumento universal de seguridad, es insuficiente. El paso práctico que recomendaría antes de cualquier decisión: corré cargo audit y cargo geiger en cualquier proyecto Rust que estés evaluando adoptar, mirá qué porcentaje del árbol de dependencias tiene unsafe, y decidí con ese número en la mano — no con el titular.
Lo mismo aplica a decisiones de arquitectura en general: los métodos formales tienen un techo claro, y creer que una herramienta resuelve la seguridad de forma holística es la forma más elegante de bajar la guardia donde más importa.
Artículos Relacionados
Lo que las entrevistas de trabajo me enseñaron sobre Kubernetes
Las entrevistas técnicas de Kubernetes tienen un problema que nadie nombra: te preguntan por objetos que jamás vas a tocar en producción, pero ignoran los errores que sí rompen sistemas reales. Acá está el mapa que me faltaba.
Métodos formales y el futuro de la programación: qué vale la pena probar y dónde está el techo
Formal methods aparece cada tanto en el radar técnico como la solución que la industria ignoró. Mi lectura: el problema que señala es real, pero la receta que circula omite costos que cambian la ecuación.
El "LLM propio" de Río de Janeiro parece ser un merge: qué leer entre líneas
Un municipio anuncia un LLM "propio" y la comunidad técnica descubre que podría ser un merge de un modelo existente. Mi lectura: el problema real no es el fraude, es que casi nadie sabe cómo verificarlo. Acá está el checklist.
Comentarios (0)
¿Qué pensás de esto?
Dejá tu comentario en 10 segundos.
Usamos tu login solo para mostrar tu nombre y avatar. Nada de spam.