- O TRACTOR da DARPA é um programa de pesquisa que busca converter automaticamente código C legado para Rust, eliminando a categoria de vulnerabilidades de segurança de segurança de memória em programas C
- Os problemas de segurança de memória em C e C++ vêm sendo tratados há mais de 20 anos, mas consolidou-se o entendimento de que ferramentas de detecção de bugs, por si só, não são suficientes
- A alternativa é migrar o código existente para uma linguagem de programação segura que rejeite programas inseguros em tempo de compilação
- O resultado da conversão busca a mesma qualidade e estilo de código escrito por desenvolvedores Rust experientes, combinando análise estática, análise dinâmica e aprendizado de máquina baseado em grandes modelos de linguagem
- O MIT Lincoln Laboratory é responsável por teste e avaliação, e benchmarks, projetos de marcos e ferramentas são publicados em uma página pública
Objetivo do TRACTOR
- O TRACTOR é o programa “Translating All C to Rust”, com o objetivo de converter automaticamente código C legado para Rust
- Do ponto de vista da segurança, ele busca eliminar toda a categoria de vulnerabilidades de segurança de segurança de memória presentes em programas C
- O código convertido não visa apenas funcionar, mas também alcançar a mesma qualidade e estilo de código produzido por desenvolvedores Rust experientes
Por que a conversão para Rust é necessária
- Os problemas de segurança de memória em C e C++ são um desafio que a comunidade de engenharia de software vem enfrentando há mais de 20 anos
- Formou-se um consenso de que depender apenas de ferramentas de detecção de bugs não é suficiente para resolver o problema de forma adequada
- A abordagem mais preferida é usar uma linguagem de programação segura capaz de rejeitar programas inseguros em tempo de compilação
- O TRACTOR usa Rust como linguagem de destino
Tecnologias usadas na conversão automática
- O TRACTOR combina várias técnicas de análise de software e aprendizado de máquina para migrar código C legado para Rust
-
Técnicas de análise
- Utiliza análise estática e análise dinâmica em conjunto
- Técnicas de aprendizado de máquina, incluindo grandes modelos de linguagem, também fazem parte da combinação
- Essa combinação é o eixo central de tecnologias necessário para compreender a estrutura e o comportamento do código C e convertê-lo automaticamente em código Rust
Teste, avaliação e materiais públicos
- A equipe do MIT Lincoln Laboratory realiza o teste e avaliação do programa de pesquisa
- Benchmarks, projetos de marcos e ferramentas são publicados na página do TRACTOR do MIT Lincoln Laboratory
- Como conteúdo relacionado, há o link Eliminating Memory Safety Vulnerabilities Once and For All
1 comentários
Opiniões no Hacker News
Para referência, https://sam.gov/opp/1e45d648886b4e9ca91890285af77eb7/view também vale a pena ver
Diz que o objetivo do evento é explicar as metas técnicas e os desafios do TRACTOR, responder a perguntas de potenciais proponentes e dar a eles a oportunidade de avaliar como suas pesquisas poderiam se alinhar aos objetivos do programa TRACTOR
Isso parece realmente difícil. Em especial, Rust idiomático escrito por pessoas experientes não se parece em nada com C, e a maior parte do código interessante é escrita em C++
No fim, acho que isso equivale a determinar estaticamente todos os tempos de vida das alocações de um programa em C. Incluindo até alocações que passam por alocadores definidos pelo usuário ou bibliotecas proprietárias. Há pesquisas nessa área há muito tempo, mas não houve muitos grandes sucessos, e programas em C/C++ conseguem atrelar o tempo de vida de alocações ao botão que o usuário aperta, mesmo sem mecanismos de segurança como contagem de referências. Não é uma boa ideia, mas é possível
Outro problema óbvio é que o programa analisado, por definição, é código com bugs. Se os tempos de vida fizessem sentido, não haveria vulnerabilidades de segurança de memória e não haveria necessidade de substituí-lo. Pesquisas que detectam estaticamente “como os tempos de vida deveriam ser” geralmente presumem que o código analisado estava correto desde o início. Talvez dê para mirar em um programa que encontre pontos em que não é possível determinar os tempos de vida e peça ajuda ao desenvolvedor
Dito isso, esta não é a primeira vez que a DARPA tenta tradução automática de programas, nem tradução automática para Rust[2]
[1]: https://www.urbandictionary.com/define.php?term=DARPA%20hard
[2]: https://c2rust.com/
O projeto é bem legal porque tem uma árvore de sintaxe abstrata unificada que dá suporte a essas linguagens essencialmente com o mesmo conjunto de tipos de nós. Tive a oportunidade de trabalhar nele durante um estágio de verão em Livermore, em 2014
Também é open source: http://rosecompiler.org/ROSE_HTML_Reference/index.html
Se só brigar com o borrow checker já parece difícil, traduzir automaticamente para código aprovado pelo borrow checker levando em conta todo o espaço de programas possíveis em C e até o infame comportamento indefinido é muito mais difícil. Um dos problemas clássicos da construção de compiladores é que o espaço de programas válidos é muito maior do que o espaço de programas compiláveis
Pesquisando rapidamente, há tentativas como o c2rust[1]. Fico curioso para saber em que o TRACTOR é diferente
[1] https://github.com/immunant/c2rust
Seria mais interessante se, em vez de inserir o programa original, fosse inserido o manual do programa. Mas manuais raramente capturam todos os comportamentos sutis de um programa, então é provável que seja necessário olhar o código-fonte ao menos para obter os valores de referência reais
Esta iniciativa proposta eu já conhecia havia algum tempo, e é interessante vê-la agora se tornar pública. É uma proposta muito ambiciosa, e vejo esse nível de ambição como algo compatível com a missão da DARPA; espero que dê certo
Como alguém que defende Rust nessa área, tenho tentado moderar as expectativas das pessoas que impulsionam essa proposta quanto à possibilidade de tradução automática de C para Rust. O obstáculo fundamental que vejo é que o código-fonte em C contém menos informação do que o código-fonte em Rust. Para traduzir código C para código Rust, alguém ou alguma coisa precisa criar essa informação ausente. Pelo mesmo motivo pelo qual não é possível ampliar muito uma imagem e recriar sem erros bits de informação que não foram capturados na imagem original, é fácil provar que é impossível gerar perfeitamente essa informação ausente. No fim, é preciso extrapolar — isto é, inventar — informação que não está no código-fonte existente. Extrapolar corretamente exige julgamento e, especialmente quando feito em massa por modelos de linguagem sem supervisão, é um processo inevitavelmente sujeito a erros. Já propus uma solução que acredito poder mitigar isso em certa medida, mas não vou entrar em detalhes
No fim das contas, acredito que esse projeto possa alcançar algum grau de sucesso, mas deve avançar com expectativas cuidadosas e contidas. Ao mesmo tempo, também existe a possibilidade de que nenhum resultado público saia disso, então eu diria para não interpretar demais nesta fase. Em especial, o governo não é uma entidade única; portanto, este projeto não deve ser interpretado como uma rejeição total a C nem como uma bênção total a Rust. Cada órgão definirá por conta própria a direção e o cronograma de adoção de tecnologias de segurança de memória. Por exemplo, o NIST recomenda não apenas Rust, mas também Ada SPARK e vários dialetos reforçados de C/C++
https://cpp-rust-assisted-migration.gitlab.io/blog/
Projetos reais de “Rustificação” muitas vezes funcionam assim. Fuzzers também podem ajudar a gerar dados de teste adicionais para aumentar a cobertura de branches
Pessoalmente, não gosto da mentalidade de “reescrever o mundo inteiro em Rust”. Ainda assim, se você está tentando portar um projeto para uma nova linguagem ou plataforma, tradução mecânica é uma má abordagem
É preciso dedicar tempo a planejar uma arquitetura melhor e projetar um sistema de software melhor, depois encontrar uma forma de substituir as partes aos poucos. Se você constrói castelos no céu, eles nunca tocam o chão. Se a decisão for usar Rust nesse sistema, tudo bem, mas é preciso escrever Rust de forma idiomática. Não se deve tentar retroportar C para Rust
Vejo como um processo muito melhor e mais maduro atualizar C para C moderno e verificar a segurança de memória, recursos e operações inteiras com um verificador de modelos como o CBMC. É possível obter a mesma segurança de uma reescrita incremental em Rust, mantendo ao mesmo tempo a base de código, a base de conhecimento e os desenvolvedores
Concordo que substituir por uma versão em Rust com arquitetura projetada à mão é claramente uma solução melhor, mas também é mais cara. Aqui parece que o objetivo é um produto no estilo RLBox: “melhorar muito a segurança com quase nenhum esforço”. Se houver recursos, isso não quer dizer que não se deva fazer uma reescrita manual completa; é apenas melhor do que não fazer nada
Se você perguntar ao ChatGPT o problema que queria resolver originalmente, ele acerta com mais frequência, mas ainda assim em geral não dá muito certo. Para fazer até o básico gerado funcionar, ainda é preciso muito ajuste e raciocínio
Não é um apelo de que isso seja obviamente uma boa ideia. O “Rust meia-boca” produzido pela tradução provavelmente será muito pior de trabalhar do que C com um ruído de fundo de bugs. Graças à tradução fiel, esses bugs também estarão no “Rust meia-boca”. Em C, as pessoas sabem até certo ponto como lidar com os problemas; já “Rust meia-boca” é literalmente meia-boca porque pessoas de Rust não estão acostumadas com esse tipo de coisa
Ainda assim, a chance de alguém desenvolver técnicas para limpá-lo repetidamente até chegar a um estado tolerável não é zero. Além disso, como subproduto não objetivado do projeto, pode surgir um feedback de linter do tipo “não é possível traduzir para Rust tolerável por causa de x, y, z”. Desenvolvedores C poderiam olhar isso; e, se o código se tornar traduzível para bom Rust, também diminui o motivo para traduzi-lo
Se esse tipo de resultado surgir, talvez para algumas pessoas seja mais fácil descrever a solução em C executável e deixar o “tradutor/linter” guiá-las para uma abordagem que não esteja quebrada. Acho que esses resultados positivos são bem improváveis, mas fazer uma aposta em azarão de vez em quando não parece algo próximo da descrição do trabalho da DARPA?
O ponto central é começar a reduzir os CVEs de segurança de memória que comprovadamente já causaram problemas várias vezes
Concordo que, se fosse possível traduzir automaticamente e de forma confiável de C/C++ para Rust, isso já teria sido feito. Mas, em algum ponto do processo de planejar uma arquitetura e um sistema melhores e substituir as partes aos poucos, as pessoas podem ganhar confiança do tipo “agora estamos escrevendo C muito melhor, então não vamos mais criar bugs de segurança de memória; talvez possamos parar por aqui”, e de fato farão isso. Então, seis meses depois, aparece outro CVE tragicômico de buffer overflow
Atualizar C para C moderno e verificá-lo com CBMC também é um investimento enorme. Se é para fazer tudo isso, é melhor migrar logo para Rust. A afirmação de que se obtém a mesma segurança de uma reescrita incremental em Rust pode até ser possível, mas parece bastante incerta ou longe de uma conclusão clara
Muita gente lê isso como uma exigência ou ordem para traduzir todo código C e C++ para Rust, mas, apesar do nome chamativo do projeto, o resumo não parece dizer isso. Há dois parágrafos relacionados, mas distintos
Primeiro, C e C++ não são seguros o suficiente em larga escala. Mesmo programando com cuidado e usando boas ferramentas, seu design fundamentalmente inseguro gera vulnerabilidades demais. Portanto, deve-se traduzir ou reescrever tanto código quanto possível em linguagens “seguras”, especialmente aquelas que garantem segurança de memória
Segundo, trata-se de financiar e lançar uma chamada pública para software que traduza código C existente para Rust
Não é um consenso para reescrever o mundo em Rust. É um consenso para migrar para linguagens seguras, com Rust sendo um dos exemplos, e significa que há um programa voltado a Rust nessa migração
unsafe, quais seriam as regras para usá-las? Sem um conjunto definido de regras para isso, acabamos voltando ao ponto de partidaRust tem um modo seguro. Rust não é uma linguagem segura. Para fazer coisas interessantes, são necessários blocos
unsafe. Isso não traz tanto ganho assimPor outro lado, há muitas linguagens com garbage collection que nem deixam o programador mexer com ponteiros. Por que essas linguagens não são consideradas? O motivo é desempenho. Como programadores Rust “valorizam” tanto desempenho, essa linguagem jamais conseguirá resolver o problema fundamental
Você quer desempenho ou quer segurança? Não dá para ter os dois
É realmente surpreendente que isso possa funcionar de alguma forma automatizada. Não dá para transcrever um programa C comum para Rust linha por linha
Em programas C, ponteiros e aliases estão por toda parte, e Rust bloqueia explicitamente esse tipo de conceito. A menos que você envolva tudo em
unsafe, reescrever um programa C em Rust exige repensar muitas estruturas típicas em um nível mais altoTambém não é necessário traduzir tudo de uma vez. Um dos valores do compilador Rust é que ele fornece muitas informações concretas que podem ser realimentadas em um LLM para iteração
Na minha startup, grit.io, temos lidado com problemas semelhantes, e vejo C -> Rust como algo tratável em um futuro próximo. Com certeza não é fácil, mas é um problema solucionável
Se for assim, dá para tratar isso como um subproblema e considerar como outro problema detectar como os dados são compartilhados entre threads. Neste último caso, muitos programas provavelmente terão regras implícitas de propriedade, como “a thread T1 coloca na fila Q, e a thread T2 pega”. Isso pode ser traduzido como “ao colocar na fila, a propriedade é transferida”
Detectar essas regras não será fácil, mas também não parece algo completamente fora de alcance, e pode fazer bastante sentido como projeto de pesquisa
unsafe, compilar para o mesmo binário e então ir removendounsafeaos poucos enquanto se verifica continuamente a equivalência?Assim, pelo menos seria possível trazer para Rust o máximo de partes possível e tratar separadamente apenas os conceitos que os engenheiros precisam repensar
unsafePresumo que, para a IA produzir um programa realmente útil e válido, ela teria que entender lifetimes etc. Se ela realmente fizer isso, será impressionante
Se você seguir práticas de codificação muito rigorosas e integrar ferramentas de verificação estática de terceiros que exigem um monte de anotações proprietárias no código, dá para obter resultados semelhantes com C/C++
Ou então basta usar Rust
Com uma alternativa real disponível, quem, em sã consciência, defenderia uma linguagem com falhas de design tão grandes e óbvias?
Acho que isso não vai dar certo. C tem abstrações que não podem ser replicadas em Rust sem grandes mudanças
Em C, é comum que duas estruturas de dados diferentes que contêm o mesmo ponteiro escrevam por meio desse ponteiro. Isso não pode ser replicado trivialmente em Rust e exige uma intervenção bastante inteligente
Mas você não obterá uma saída com menos bugs, nem um processo que gere automaticamente as estruturas de programa que tornam Rust confiável
Isso precisa ser automático? Se sim, um programa capaz de portar C para Rust automaticamente não teria necessariamente que incluir todos os recursos para tornar o próprio código C seguro?
Alguns códigos C não são simplesmente “impossíveis de verificar quanto à correção”, mas “realmente errados em termos de segurança de memória”. Esse tipo de código não será traduzido automaticamente sem intervenção humana. Como seria possível, se não existe código equivalente correto? A ferramenta precisa de uma saída para dizer: “não sei o que este código está tentando fazer, e o que ele de fato faz viola o contrato com o compilador, então também sei que não é isso que se pretendia. Humano, ajude”
Em teoria, é impossível fazer com que essa saída seja usada apenas quando comportamento indefinido realmente ocorre, por causa do teorema de Rice. Na prática, também não se deve traduzir cegamente código excessivamente obscuro, então é bem provável que tentar fazer isso nem seja desejável
No fim, imagino que a ferramenta acabe sendo uma ferramenta interativa que faz a maior parte do trabalho, mas com orientação humana. E, como resultado dessa orientação humana, o código produzido não será exatamente equivalente, mas sim um código que cumpre o mesmo propósito
Se 1) Rust não tiver bugs de memória e 2) for possível traduzir C automaticamente para Rust, isso significaria que todos os bugs de memória poderiam ser corrigidos automaticamente
É quase certo que isso não é verdade. É muito provável que, no caso geral, essa tarefa seja totalmente impossível
Por exemplo, basta substituir todas as alocações dinâmicas de memória por um buffer fixo definido no momento da tradução e rejeitar todas as entradas que não couberem nesse buffer