3 puntos por GN⁺ 2024-07-31 | 1 comentarios | Compartir por WhatsApp
  • 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

1 comentarios

 
GN⁺ 2024-07-31
Opiniones en Hacker News
  • Como referencia, también vale la pena ver https://sam.gov/opp/1e45d648886b4e9ca91890285af77eb7/view

    • Enlace directo al PDF con información de la sesión informativa para proponentes: https://sam.gov/api/prod/opps/v3/opportunities/resources/fil...
      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

    • Es una tarea muy difícil, y a DARPA le gusta financiar cosas difíciles[1]
      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/
    • Ya que hablamos de cosas difíciles, el DOE también financia desde hace más de 20 años un proyecto llamado ROSE. Cubre análisis estático y traducción automática entre C/C++/Cuda, además de lenguajes de alto nivel como Python y variantes de C/C++ para HPC
      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
    • En el caso general, imagino que se traducirá a unsafe Rust, y que solo de vez en cuando algunos nodos hoja aislados se traducirán a Rust seguro
      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
    • Hay una razón por la que los desafíos de DARPA se llaman DARPA-hard
    • El enfoque probablemente será algo como: “una IA resume la funcionalidad del programa en algún lenguaje de especificación y luego sintetiza código Rust que cubra el mismo conjunto de funciones”
      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++.

    • ¿Qué relación tiene con el esfuerzo CRAM de Grammatech?
      https://cpp-rust-assisted-migration.gitlab.io/blog/
    • Si no se intenta preservar la semántica formal del código C y se considera suficiente que, después de la traducción, solo pase la suite de pruebas, hay mucho más margen para la traducción.
      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.

    • No hay posibilidad. CBMC es increíble, pero ¿alguna vez has verificado formalmente un programa “real”?
      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.
    • El C moderno tiene exactamente las mismas vulnerabilidades de seguridad en arreglos y cadenas que el C clásico, y eso no ha cambiado en 50 años.
    • Esto es definitivamente un desafío DARPA de castillos en el aire que sería bueno tener al salir de sistemas legados. Pero si pones una función o método de un lenguaje en ChatGPT y le pides que lo traduzca a otro, por lo general no sale bien.
      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.
    • Si se trata de código inactivo que se ejecuta en todas partes pero que nadie toca, tiene cierto atractivo la idea de “traducirlo a Rust mediocre antes de volver a tocarlo”.
      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.
    • La mentalidad de “reescribamos todo el mundo en Rust” no existe en ningún lado. Hay muchísimo software que conviene mucho más dejar en lenguajes dinámicos o en lenguajes estáticos con recolección de basura como Go. Un buen ingeniero entiende la idea de usar la herramienta adecuada para cada trabajo.
      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

    • Si esos lenguajes tienen construcciones 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 partida
      Rust tiene un modo seguro. Rust no es un lenguaje seguro. Para hacer cosas interesantes se necesitan bloques unsafe. Con esto no se gana mucho
      Por 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 alto

    • La traducción línea por línea es imposible, y por eso hay que usar IA para hacer un razonamiento semántico más amplio
      Tampoco 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
    • Excepto en programas multihilo, ¿el aliasing de larga duración realmente está por todas partes en los programas C? En muchos programas esperaría que la mayor parte ocurra dentro del alcance de una sola función, o a través de llamadas a funciones dentro de ese alcance. En ese caso, ¿no se resuelve con borrowing?
      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
    • Preguntando desde una postura de principiante ingenuo: ¿no se podría ir línea por línea, envolver todo en unsafe, compilar al mismo binario y luego ir quitando unsafe lentamente 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
    • Para una traducción línea por línea ni siquiera hace falta mucha “IA”. Creo que se podría crear una traducción aproximada a Rust, en su mayoría unsafe
      Supongo 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
    • Me da curiosidad cómo funcionaría esto especialmente en bases de código que usan muchas macros
  • 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

    • Es bastante gracioso ver a la comunidad de C/C++ reaccionar contra Rust. Décadas de esfuerzo y experiencia con esos lenguajes claramente están sobrescribiendo sus circuitos de razonamiento
      ¿Quién en su sano juicio defendería lenguajes con defectos de diseño tan grandes y evidentes cuando existe una alternativa real?
    • Quise decir anotaciones propietarias, no “situación propietaria”. Fue por el autocompletado del celular
  • 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

    • Creo que se puede crear algo que compile y que sea “Rust” y también “IA”. Cumple por partida doble con los requisitos de palabras de moda, así que también sirve para justificar un proyecto de investigación
      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?

    • Leyendo esa frase de forma razonable, es difícil pensar que signifique automatización completa, así que la respuesta a la pregunta es no
      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

    • Como no se especificó que se quiera preservar todo el comportamiento del código C, este problema tiene una solución trivial
      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