- TRACTOR de DARPA es un programa de investigación que busca convertir automáticamente código C heredado a Rust para eliminar toda una categoría de vulnerabilidades de seguridad de seguridad de memoria en programas escritos en C
- Los problemas de seguridad de memoria en C y C++ se han abordado durante más de 20 años, pero se ha consolidado la idea de que las herramientas de detección de errores por sí solas no son suficientes
- La alternativa es migrar el código existente a un lenguaje de programación seguro que rechace programas inseguros en tiempo de compilación
- El resultado de la conversión apunta a la misma calidad y estilo que tendría código escrito por un desarrollador experto en Rust, combinando análisis estático y dinámico con aprendizaje automático basado en modelos de lenguaje a gran escala
- MIT Lincoln Laboratory estará a cargo de las pruebas y evaluación, y los benchmarks, proyectos por hitos y herramientas se publican en una página pública
Objetivos de TRACTOR
- TRACTOR es un programa cuyo nombre significa “Translating All C to Rust” y tiene como objetivo convertir automáticamente código C heredado a Rust
- En términos de seguridad, busca eliminar toda la categoría de vulnerabilidades de seguridad de memoria presentes en programas en C
- El código convertido no solo debe funcionar, sino que también apunta a la calidad y estilo de un desarrollador experimentado de Rust
Por qué se necesita convertir a Rust
- Los problemas de seguridad de memoria en C y C++ son un desafío que la comunidad de ingeniería de software ha enfrentado durante más de 20 años
- Se ha formado un consenso en que depender solo de herramientas de detección de errores no basta para resolver suficientemente el problema
- El enfoque preferido es usar un lenguaje de programación seguro capaz de rechazar programas inseguros en tiempo de compilación
- TRACTOR utiliza Rust como lenguaje de destino
Tecnologías usadas en la conversión automática
- TRACTOR combina varias técnicas de análisis de software y de aprendizaje automático para trasladar código C heredado a Rust
-
Técnicas de análisis
- Utiliza en conjunto análisis estático y análisis dinámico
- También incluye técnicas de aprendizaje automático, entre ellas modelos de lenguaje a gran escala
- Esta combinación constituye el eje tecnológico clave para comprender la estructura y el comportamiento del código C y convertirlo automáticamente a código Rust
Pruebas, evaluación y materiales públicos
- El equipo de MIT Lincoln Laboratory realiza las pruebas y la evaluación del programa de investigación
- Los benchmarks, proyectos por hitos y herramientas están publicados en la página de TRACTOR de MIT Lincoln Laboratory
- Como contenido relacionado, se enlaza Eliminating Memory Safety Vulnerabilities Once and For All
1 comentarios
Opiniones en Hacker News
Como referencia, también vale la pena ver https://sam.gov/opp/1e45d648886b4e9ca91890285af77eb7/view
Se indica que el objetivo de este evento es explicar los objetivos técnicos y desafíos de TRACTOR, responder preguntas de posibles proponentes y darles la oportunidad de evaluar cómo sus investigaciones podrían alinearse con los objetivos del programa TRACTOR
Esto se ve realmente difícil. En particular, el Rust idiomático escrito por alguien con experiencia no se parece en nada a C, y la mayoría del código interesante está escrito en C++
Al final, siento que es algo equivalente a determinar estáticamente todos los tiempos de vida de las asignaciones de un programa en C. Incluyendo asignaciones que pasan por allocators definidos por el usuario o bibliotecas propietarias. Ha habido investigación en esta área desde hace mucho tiempo, pero no muchos grandes éxitos, y los programas en C/C++ pueden vincular la vida útil de una asignación a qué botón presiona el usuario, incluso sin salvaguardas como conteo de referencias. No es una buena idea, pero se puede hacer
Otro problema evidente es que, por definición, el programa que se analiza es código con bugs. Si los tiempos de vida tuvieran sentido, no habría vulnerabilidades de seguridad de memoria y no habría necesidad de reemplazarlo. Las investigaciones que detectan estáticamente “cómo deberían ser los tiempos de vida” por lo general asumen que el código analizado es correcto desde el principio. Podría plantearse como objetivo un programa que encuentre los puntos donde no puede determinar la vida útil y pida ayuda al desarrollador
Dicho eso, no es la primera vez que DARPA intenta la traducción automática de programas o la traducción automática a Rust[2]
[1]: https://www.urbandictionary.com/define.php?term=DARPA%20hard
[2]: https://c2rust.com/
El proyecto es bastante genial porque tiene un árbol de sintaxis abstracta unificado que soporta estos lenguajes prácticamente con el mismo conjunto de tipos de nodos. Tuve la oportunidad de trabajar con él durante una pasantía de verano en Livermore en 2014
También es open source: http://rosecompiler.org/ROSE_HTML_Reference/index.html
Si ya parece difícil pelearse con el borrow checker, traducir automáticamente a código que el borrow checker apruebe, considerando todo el espacio de programas posibles en C y su famoso comportamiento indefinido, es mucho más difícil. Uno de los problemas clásicos de la construcción de compiladores es que el espacio de programas válidos es mucho mayor que el espacio de programas compilables
Buscando rápido, veo que también existen intentos como c2rust[1]. Me da curiosidad saber en qué se diferencia TRACTOR
[1] https://github.com/immunant/c2rust
Sería más interesante si, en vez de introducir el programa original, se introdujera el manual del programa. Pero los manuales rara vez capturan todos los comportamientos sutiles de un programa, así que es muy probable que, al menos para obtener valores de referencia reales, haya que mirar el código fuente
Hace tiempo que sabía de esta iniciativa propuesta, y es interesante que ahora se haga pública. Es una propuesta muy ambiciosa, y creo que ese nivel de ambición encaja con la misión de DARPA; espero que salga bien.
Como alguien que defiende Rust en este ámbito, he intentado moderar las expectativas de quienes impulsan esta propuesta sobre la posibilidad de traducir automáticamente de C a Rust. El obstáculo fundamental que veo es que el código fuente en C contiene menos información que el código fuente en Rust. Para traducir código C a código Rust, alguien o algo tiene que generar esa información faltante. Es fácil demostrar que generar perfectamente esa información faltante es imposible, por la misma razón por la que ampliar mucho una imagen no puede crear sin errores bits de información que no fueron capturados en la imagen original. Al final, hay que extrapolar, es decir, inventar, la información que falta en el código fuente existente. Para extrapolar correctamente se necesita criterio y, sobre todo cuando lo hacen modelos de lenguaje no supervisados a gran escala, es un proceso inevitablemente propenso a errores. He propuesto una solución que creo que podría mitigar en cierta medida este problema, pero no entraré en detalles.
En última instancia, creo que este proyecto podría lograr cierto éxito, pero debe avanzar con expectativas prudentes y moderadas. Al mismo tiempo, también existe la posibilidad de que no haya ningún resultado público, así que diría que no hay que sobreinterpretarlo en esta etapa. En particular, el gobierno no es una entidad monolítica, por lo que este proyecto no debe interpretarse como un rechazo total de C ni como una bendición total de Rust. Cada organismo definirá por su cuenta la dirección y el calendario para adoptar tecnologías de seguridad de memoria. Por ejemplo, NIST recomienda no solo Rust, sino también Ada SPARK y varios dialectos reforzados de C/C++.
https://cpp-rust-assisted-migration.gitlab.io/blog/
Los proyectos reales de rustificación suelen funcionar de esta manera. Los fuzzers también pueden ayudar a generar datos de prueba adicionales para aumentar la cobertura de ramas.
Personalmente, no me gusta la mentalidad de “reescribamos todo el mundo en Rust”. Aun así, si se quiere portar un proyecto a un lenguaje o plataforma nuevos, la traducción mecánica es una mala forma de hacerlo.
Hay que tomarse el tiempo para planear una mejor arquitectura, diseñar un mejor sistema de software y luego encontrar una forma de reemplazarlo pieza por pieza. Si construyes castillos en el aire, nunca llegarán al suelo. Si decidiste usar Rust para este sistema, está bien, pero debes escribirlo de manera idiomática en Rust. No intentes hacer un backport de C a Rust.
Creo que un proceso mucho mejor y más maduro es actualizar C a C moderno y verificar la seguridad de memoria, recursos y operaciones enteras con un verificador de modelos como CBMC. Se puede obtener una seguridad similar a la de una reescritura gradual en Rust, pero conservando la base de código, la base de conocimiento y los desarrolladores.
Estoy de acuerdo en que reemplazarlo por una versión en Rust con una arquitectura diseñada a mano es claramente una mejor solución, pero también cuesta más. Aquí parece que apuntan a un producto al estilo RLBox: “mejorar mucho la seguridad casi sin esfuerzo”. Si tienes los recursos, no significa que no debas hacer una reescritura manual completa; solo digo que esto es mejor que no hacer nada.
Si le preguntas a ChatGPT por el problema original que intentas resolver, acierta con más frecuencia, pero aun así, en general, no sale bien. Para que incluso lo básico que genera funcione, todavía hace falta mucho ajuste y reflexión.
No es el atractivo de que sea una idea obviamente buena. Es muy probable que el “Rust mediocre” generado por traducción sea mucho peor para trabajar que C con ruido de fondo en forma de bugs. Gracias a una traducción fiel, esos bugs también seguirán presentes en el “Rust mediocre”. En C, la gente sabe en cierta medida cómo lidiar con los problemas, pero el “Rust mediocre” es literalmente mediocre porque la gente de Rust no está acostumbrada a esas cosas.
Aun así, no es cero la posibilidad de que alguien desarrolle técnicas para limpiarlo iterativamente hasta dejarlo en un estado tolerable. Además, como resultado no objetivo del proyecto, podría surgir retroalimentación de linter del tipo “no se puede traducir a Rust tolerable por x, y, z”. Los desarrolladores de C podrían revisarla y, si el código ya se volvió traducible a buen Rust, también habría menos razones para traducirlo.
Si se dieran estos resultados, para algunas personas podría ser más fácil describir la solución como C ejecutable y dejar que el “traductor/linter” las guíe hacia un enfoque que no esté roto. Creo que estos resultados positivos son bastante poco probables, pero apostar de vez en cuando a un tapado parece estar bastante cerca de la descripción del trabajo de DARPA.
El punto central es empezar a reducir los CVE de seguridad de memoria que, de hecho, han demostrado ser un problema muchas veces.
Estoy de acuerdo con que, si se pudiera traducir de C/C++ a Rust de forma automática y confiable, ya se habría hecho. Pero en algún punto del proceso de planear una mejor arquitectura y sistema para reemplazarlo pieza por pieza, la gente puede ganar confianza y pensar: “ahora estamos escribiendo C mucho mejor, así que ya no vamos a crear bugs de seguridad de memoria; ¿no podríamos detenernos aquí?”, y de hecho lo hará. Entonces, seis meses después, aparece otro CVE de desbordamiento de búfer tragicómico.
Actualizar C a C moderno y verificarlo con CBMC también es una inversión enorme. Si vas a llegar a ese punto, mejor migrarlo a Rust. Decir que se obtiene la misma seguridad que con una reescritura gradual en Rust puede que sea posible, pero parece bastante incierto o lejos de una conclusión clara.
Mucha gente está leyendo esto como una petición u orden de traducir todo el código C y C++ a Rust, pero, a pesar del nombre llamativo del proyecto, el resumen no se lee así. Hay dos párrafos relacionados entre sí, pero separados
Primero, C y C++ no son suficientemente seguros a gran escala. Incluso programando con cuidado y usando buenas herramientas, su diseño inherentemente inseguro genera demasiadas vulnerabilidades. Por lo tanto, hay que traducir o reescribir tanto código como sea posible en lenguajes “seguros”, en particular lenguajes que garanticen seguridad de memoria
Segundo, se trata de financiar y convocar propuestas para software que traduzca código C existente a Rust
No es un consenso para reescribir el mundo en Rust. Es un consenso para migrar a lenguajes seguros, Rust es uno de los ejemplos, y significa que existe un programa que apunta a Rust dentro de esa migración
unsafe, ¿cuáles son las reglas para usarlas? Si no hay un conjunto de reglas definido para esto, al final solo se vuelve al punto de partidaRust tiene un modo seguro. Rust no es un lenguaje seguro. Para hacer cosas interesantes se necesitan bloques
unsafe. Con esto no se gana muchoPor otro lado, hay muchos lenguajes con recolección de basura que ni siquiera permiten a los programadores tocar punteros. ¿Por qué no se consideran esos lenguajes? La razón es el rendimiento. Como los programadores de Rust “valoran” tanto el rendimiento, ese lenguaje nunca podrá resolver el problema de fondo
¿Quieres rendimiento o quieres seguridad? No puedes tener ambas cosas
Es realmente sorprendente que esto pueda funcionar con algún tipo de automatización. No se puede transcribir línea por línea un programa C común a Rust
En los programas C hay punteros y aliasing por todas partes, y Rust bloquea explícitamente esos conceptos. A menos que se envuelva todo en
unsafe, reescribir un programa C en Rust exige repensar muchas estructuras típicas a un nivel más altoTampoco hace falta traducir todo de una vez. Uno de los valores del compilador de Rust es que entrega mucha información concreta que se puede volver a alimentar a un LLM para iterar
En mi startup grit.io hemos trabajado en problemas similares, y creo que C -> Rust será abordable en el corto plazo. Sin duda no es fácil, pero es un problema resoluble
Si es así, se podría tratar eso como un subproblema y tomar como otro problema detectar cómo se comparten los datos entre hilos. En este último caso, muchos programas tendrán reglas implícitas de propiedad como “el hilo T1 pone algo en la cola Q y el hilo T2 lo toma”. Eso puede traducirse como “al ponerlo en la cola, se transfiere la propiedad”
Detectar reglas así no será fácil, pero tampoco parece completamente fuera de alcance, y como proyecto de investigación puede tener bastante sentido
unsafe, compilar al mismo binario y luego ir quitandounsafelentamente mientras se sigue verificando la equivalencia?Así al menos se podría llevar a Rust la mayor cantidad posible, y tratar aparte solo los conceptos que los ingenieros deban repensar
unsafeSupongo que para que la IA produzca un programa realmente útil y válido tendría que entender lifetimes y demás. Si de verdad lo hace, sería impresionante
Se pueden obtener resultados similares en C/C++ si se siguen prácticas de codificación muy estrictas y se integra una herramienta de verificación estática de terceros que obliga a llenar el código de anotaciones propietarias
O simplemente se puede usar Rust
¿Quién en su sano juicio defendería lenguajes con defectos de diseño tan grandes y evidentes cuando existe una alternativa real?
No creo que esto vaya a salir bien. C tiene abstracciones que no se pueden replicar en Rust sin cambios grandes
En C es común que dos estructuras de datos distintas que contienen el mismo puntero escriban en ese puntero. Eso no se puede replicar de forma trivial en Rust y requiere una intervención bastante ingeniosa
Pero no se obtendrá una salida con menos bugs ni un proceso que genere automáticamente las estructuras de programa que producen la confiabilidad de Rust
¿Se supone que esto debe ser automático? Si es así, ¿un programa capaz de portar automáticamente C a Rust no tendría que incluir necesariamente todas las capacidades para hacer seguro el propio código C?
Parte del código C no es simplemente “no se puede verificar su corrección”, sino que está “realmente mal desde el punto de vista de la seguridad de memoria”. Ese código no se traducirá automáticamente sin intervención humana. ¿Cómo podría, si no existe un código equivalente correcto? La herramienta necesita una salida para decir: “no sé qué intenta hacer este código, y lo que efectivamente hace viola el contrato con el compilador, así que también sé que no es lo que se pretendía. Humano, ayúdame”
En teoría, es imposible hacer que esa salida se use solo cuando realmente ocurre comportamiento indefinido. Por el teorema de Rice. En la práctica, tampoco se debería traducir a ciegas código excesivamente críptico, así que probablemente ni siquiera sea deseable intentarlo
Al final imagino que la herramienta hará la mayor parte del trabajo, pero será una herramienta interactiva guiada por una persona. Y como resultado de esa guía humana, el código producido no será exactamente equivalente, sino uno que cumpla el mismo propósito
Si 1) Rust no tiene bugs de memoria y 2) C puede traducirse automáticamente a Rust, entonces eso significaría que todos los bugs de memoria podrían corregirse automáticamente
Casi con toda seguridad esto no es cierto. Es muy probable que esta tarea sea completamente imposible en el caso general
Por ejemplo, se podría reemplazar toda asignación dinámica de memoria por un búfer fijo definido en el momento de la traducción y rechazar cualquier entrada que no quepa en ese búfer