Greg Kroah-Hartman ha soltado unos números de cara a las Kernel Recipes de París del mes que viene, y parecen una emergencia de seguridad justo hasta que te sientas un minuto con ellos. El kernel de Linux se está acercando a los 2.000 CVE por versión. Linux 7.3 será seguramente la que rompa el techo.
Lo interesante no es el número, es la pendiente. De la 6.9 a la 6.19 la media rondaba los 500 por versión. Desde la 7.0 va por encima de mil. La 7.2, que salió el 16 de agosto, pasó de 1.500. O sea, se ha triplicado en poco más de un año.
Y nadie empezó a escribir C tres veces peor en primavera. Lo que ha cambiado es que encontrar los fallos se ha vuelto barato.
Todo el mundo se ha comprado una máquina de buscar bugs
La explicación es la poco glamurosa: hay gente apuntando LLMs a los ~34 millones de líneas de C del kernel y dejándolos moler. No un equipo: todo el mundo. Fabricantes, gente de universidad, empresas de seguridad, y un señor con presupuesto de API y un fin de semana libre.
El análisis estático siempre pudo hacer esto en teoría. En la práctica hacía falta alguien que entendiera el kernel lo bastante como para escribir las reglas, cribar la salida y mandar algo que un maintainer no tirara nada más verlo. Esa persona era escasa y cara. Ahora ese paso cuesta unos dólares en tokens y un prompt que dice busca use-after-free.
Y aquí está la parte que impide contar esto como una historia de despreciar la morralla generada por IA: los reportes son en su mayoría reales. No es una ola de tonterías que se está rechazando. Es una ola de bugs de verdad que llevaban ahí, en código que nadie tenía motivo comercial para auditar —el driver de tercera fila de un cacharro que aún tienen nueve personas— y que por primera vez en su vida está pasando por un escáner a escala industrial.
El número no es un medidor de peligro
Conviene recordar qué es exactamente un CVE del kernel. El kernel es su propia CNA desde 2024, y su postura declarada es que, por la capa en la que vive, casi cualquier bug podría acabar siendo explotable y normalmente no se sabe en el momento de arreglarlo. Así que el equipo asigna números con generosidad a los fixes que identifica, en vez de fingir que puede predecir cuáles importan. Greg ha explicado el proceso más de una vez, casi siempre dirigiéndose a gente que está enfadada por ello.
Esa política ya daba que hablar con 500 por versión. Con 2.000 deja de ser polémica y pasa a ser un error de categoría en cualquier panel que estés alimentando.
Porque un recuento de CVE hecho así no mide lo peligroso que es tu kernel. Mide cuánto cómputo se le ha apuntado. Las dos cosas estuvieron más o menos correlacionadas durante años, que es justo por lo que nadie tuvo que pensarlo, y ahora se han desacoplado lo bastante como para que leer esa gráfica como riesgo sea leer un gráfico de rendimiento de escáneres y llamarlo exposición.
Si tienes un proceso de cumplimiento que cuenta CVE del kernel, este año te va a resultar emocionante, y no del bueno.
La mitad que no escala
Vale, el número está inflado. El trabajo no.
Cada uno de esos CVE lleva un parche detrás. Alguien lo revisa. Alguien lo backportea a los árboles estables. Alguien de tu distro decide si va en la actualización normal o en una fuera de ciclo. Alguien reinicia una flota. Toda esa mitad de la tubería son personas, y no se ha triplicado.
Esa es la noticia de verdad, y en el fondo no va de Linux. Encontrar bugs ahora escala con el gasto en GPU. Triarlos escala con Greg. No hace falta un modelo para extrapolar esa curva.
Por eso la respuesta interesante no es encontrarlos más rápido. Es dejar de poder escribirlos.
La respuesta de Greg es un tipo envoltorio
En mayo, en la RustWeek de Utrecht, dio una charla con un título nada sutil: Untrusted data in Linux — How Rust is going to save us. La afirmación: en torno al 80% de los CVE del kernel habrían sido imposibles de escribir en Rust. Viniendo de la persona que ha mirado personalmente todos los fallos de seguridad del kernel desde que se formó el equipo en 2005, no es una estimación a ojo.
El problema concreto con el que se choca una y otra vez es la procedencia. Llegan datos de espacio de usuario, o de un cable, o de los registros de un dispositivo. Habría que tratarlos como hostiles. Los copias dentro y, en cuanto aterrizan, son un u32 como cualquier otro u32: el sistema de tipos ya ha olvidado de dónde venían, y a partir de ahí circulan como cualquier cosa. La respuesta de C son las anotaciones __user que comprueba sparse, que es una herramienta externa que la gente no siempre ejecuta, que es una forma larga de decir que la comprobación es opcional, que es una forma larga de decir que no es una comprobación.
Así que la propuesta, hecha con Benno Lossin, es Untrusted<T>. Es un newtype transparente: un Untrusted<u8> ocupa un byte, con la misma disposición en memoria, coste cero en tiempo de ejecución, es un marcador y nada más. Y no puedes leer lo que hay dentro. Para llegar al valor tienes que pasar por un paso de validación explícito que te devuelve una T de confianza.
Esa es la idea entera. Ya está.
Quería ponerme un poco borde con esto y no me sale. Treinta años de «cuidado con los datos que vienen de usuario» como convención de revisión de código, resueltos con un struct de un campo y sin getter. Es casi insultantemente pequeño. Pero por eso funciona: el compilador deja de depender de tu disciplina. La validación pasa de ser algo que te acuerdas de hacer a algo que no puedes saltarte, y se concentra en un único sitio visible y auditable en vez de quedar desparramada por cuatrocientas líneas de driver.
Junta eso con meter bien la distinción __user en el sistema de tipos y Greg calcula que esos dos cambios solos cubren en torno al 60% de los bugs del kernel.
Cómo no, está atascado detrás de una feature del compilador
Untrusted<T> no está mergeado. Va por su cuarta revisión, Lossin lleva tiempo pidiendo que la gente lo use de verdad y cuente dónde duele, y el bloqueo está aguas arriba, en rustc: Rust todavía no sabe proyectar campos a través de un envoltorio definido por el usuario como sí hace a través de una referencia normal. Si tienes un Untrusted<MiStruct>, no hay forma limpia de llegar a Untrusted<ese_campo> sin escribir boilerplate a mano para cada campo de cada struct, que es exactamente el tipo de peaje que hace que nadie adopte nada. Eso son las field projections, es un objetivo activo del proyecto Rust, y todavía está más cerca del diseño que de la implementación.
Resumiendo: el arreglo estructural para la avalancha de CVE es una característica de lenguaje que aún no existe, que bloquea una API de kernel que no está mergeada, escrita en un lenguaje que supone unas 113.000 líneas de los ~34 millones del kernel.
Lo digo con cariño. Rust dejó de ser un experimento en el kernel con la 7.0, en abril —el parche de Ojeda se titulaba literalmente conclude the Rust experiment— y 113k líneas es una barbaridad de código para algo que se pasó años siendo una discusión de lista de correo. Pero si estás esperando a que esto te ponga el panel de CVE en verde, llévate un libro. Y largo.
Qué me llevo de esto
Dos cosas, y ninguna urgente.
Una: deja de leer el recuento de CVE del kernel como señal de riesgo, y ve a averiguar si algo tuyo lo hace solo. En casi cualquier organización hay un escáner que este año va a emitir cuatro veces más ruido sin que la exposición real haya cambiado, y el modo de fallo no es el pánico, es el cansancio: en cuanto la lista es ilegible, la gente deja de leerla. Lo que sigue funcionando es lo aburrido: cadencia de parcheo, un kernel estable que sigas de verdad, y reinicios que ocurran.
Dos: róbate el patrón. No hace falta Rust en el kernel para quedarte con lo bueno. Si tienes un servicio que parsea cualquier cosa de fuera —cuerpos de peticiones, payloads de webhooks, un CSV que ha subido un cliente— el mismo movimiento está disponible en lo que estés escribiendo hoy. Envuelve lo no fiable en su propio tipo. RawEmail y Email, no string y string. Que el único camino entre los dos sea una función que valida. Cuesta una tarde y convierte toda una categoría de «se nos olvidó comprobar este» en un error de compilación, o como mínimo en un error de tipos que quien revisa ve desde el otro lado del diff.
Así que
El titular es un número sin sentido y el problema de debajo es real, que es una combinación incómoda de sostener a la vez en la cabeza.
Lo que se me queda es que acabamos de construir máquinas que leen código buscando fallos bastante bien, las hemos apuntado a la base de código más auditada del planeta, y han encontrado miles. Y no miles de fallos exóticos: miles de la misma clase cansina que llevamos escribiendo desde los noventa, porque C te deja olvidar de dónde venía un byte.
Los escáneres van a seguir abaratándose. La revisión no. Antes o después la única palanca que queda es hacer que el bug no se pueda escribir, y resulta que esa palanca es un struct con un campo dentro.
En fin. Ve a mirar qué está contando tu escáner de vulnerabilidades.
Comentarios