3 pontos por GN⁺ 2024-07-31 | 1 comentários | Compartilhar no WhatsApp
  • 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

1 comentários

 
GN⁺ 2024-07-31
Opiniões no Hacker News
  • Para referência, https://sam.gov/opp/1e45d648886b4e9ca91890285af77eb7/view também vale a pena ver

    • Link direto para o PDF com informações da sessão de esclarecimento para proponentes: https://sam.gov/api/prod/opps/v3/opportunities/resources/fil...
      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

    • É uma tarefa muito difícil, e a DARPA gosta de financiar coisas difíceis[1]
      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/
    • Já que o assunto são tarefas difíceis, o DOE também financia há mais de 20 anos um projeto chamado ROSE. Ele cobre análise estática e tradução automática entre C/C++/Cuda, além de linguagens de alto nível como Python e variantes de C/C++ para HPC
      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
    • No caso geral, imagino que seja traduzido para unsafe Rust, e que só alguns nós folha isolados acabem sendo traduzidos para Rust seguro
      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
    • Há um motivo para tarefas da DARPA serem chamadas de DARPA-hard
    • A abordagem provavelmente será algo como “a IA resume a funcionalidade do programa em alguma linguagem técnica e depois sintetiza código Rust que cobre o mesmo conjunto de funcionalidades”
      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++

    • Qual é a relação com o esforço CRAM da Grammatech?
      https://cpp-rust-assisted-migration.gitlab.io/blog/
    • Se a ideia não for preservar o significado formal do código C, e bastar que ele passe na suíte de testes depois da tradução, há muito mais margem para a tradução
      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

    • Sem chance. O CBMC é incrível, mas você já tentou verificar formalmente um programa “real”?
      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
    • C moderno tem exatamente as mesmas vulnerabilidades de segurança em arrays e strings que o C clássico, e nada mudou em 50 anos
    • Isso é claramente um desafio DARPA meio castelo no ar que seria bom ter ao sair de sistemas legados. Mas mesmo colocando uma função ou método de uma linguagem no ChatGPT e pedindo para traduzir para outra linguagem, em geral não dá muito certo
      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
    • Para código dormente que roda em todo lugar, mas no qual ninguém mexe, há certo apelo em “traduzir para Rust meia-boca antes de tocar de novo”
      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?
    • A mentalidade de “reescrever o mundo inteiro em Rust” não existe em lugar nenhum. Há uma enorme quantidade de software que é muito melhor deixar como está em linguagens dinâmicas ou em linguagens estaticamente tipadas com coleta de lixo, como Go. Bons engenheiros entendem a ideia de usar a ferramenta adequada para o trabalho
      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

    • Se essas linguagens têm construções unsafe, quais seriam as regras para usá-las? Sem um conjunto definido de regras para isso, acabamos voltando ao ponto de partida
      Rust 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 assim
      Por 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 alto

    • Tradução linha por linha é impossível, então é preciso usar IA para fazer um raciocínio semântico mais amplo
      També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
    • Excluindo programas multithread, aliases de longo prazo realmente estão por toda parte nos programas C? Em muitos programas, eu esperaria que a maior parte acontecesse dentro do escopo de uma única função, ou atravessando chamadas de função dentro dela. Nesse caso, isso não se resolve com empréstimos?
      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
    • Perguntando da perspectiva ingênua de um iniciante: não daria para ir linha por linha, envolver tudo em unsafe, compilar para o mesmo binário e então ir removendo unsafe aos 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
    • Uma tradução linha por linha nem exigiria muita “IA”. Parece possível criar uma tradução aproximada para Rust, majoritariamente unsafe
      Presumo 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
    • Em codebases que usam muitas macros, fico especialmente curioso para saber como isso se comportaria
  • 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

    • É bem engraçado ver a comunidade C/C++ reagindo contra Rust. Décadas de esforço e experiência com essas linguagens claramente sobrescreveram seus circuitos de raciocínio
      Com uma alternativa real disponível, quem, em sã consciência, defenderia uma linguagem com falhas de design tão grandes e óbvias?
    • Eu quis dizer anotações proprietárias, não “situação proprietária”. Culpa do corretor automático do celular
  • 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

    • Acho que dá para criar algo que compile e que seja “Rust” e também “IA”. Atende em dobro aos requisitos de palavras da moda, então é bom para justificar um projeto de pesquisa
      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?

    • Uma leitura razoável dessa frase dificilmente indicaria automação completa, e, nesse caso, a resposta à pergunta é não
      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

    • Como não foi declarado que se deseja preservar todos os comportamentos do código C, há uma solução trivial para esse problema
      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