A segurança de memória é necessária, mas não suficiente

Governos e órgãos de padronização estão começando a pressionar por linguagens de programação “memory-safe”, mas desenvolvedores argumentam que isso é apenas uma parte do quebra-cabeça de segurança de software. Comentadores comparam Rust, Go, Swift, Java, C e C++ em temas como comportamento indefinido, data races, FFI, ferramentas e ecossistemas de pacotes, observando que Rust reduz muito a corrupção de memória, mas ainda permite outros bugs sérios e escapes inseguros. Muitos veem os requisitos de segurança de memória como um avanço bem-vindo, mas enfatizam a necessidade de abordagens mais amplas — de melhor design de linguagem e sistema de tipos a sandboxing, métodos formais e práticas mais disciplinadas de cadeia de suprimentos.

Política governamental e linguagens com segurança de memória

  • A NDAA do ano fiscal de 2024 dos EUA orienta o DoD a adotar a orientação da NSA sobre linguagens e ferramentas com segurança de memória; exemplos da NSA incluem C#, Go, Java, Ruby, Rust e Swift.
  • Não está claro se a legislação da UE tem uma linguagem explicitamente semelhante sobre segurança de memória; uma avaliação de impacto da UE menciona Rust e Go, mas não requisitos.
  • Alguns esperam que futuras regras de procurement imponham uma avaliação mais rigorosa de software, potencialmente incluindo critérios de segurança de memória e controles de importação.

Rust, “unsafe” e comparação com outras linguagens

  • O unsafe do Rust é visto como mais contornável do que o unsafe típico/FFI em Java/Swift: o sistema de tipos permite construir abstrações seguras com fortes garantias de tempo de vida e uso.
  • No entanto, unsafe ainda reintroduz comportamento indefinido ao estilo C se invariantes forem violadas; bugs podem ser sutis e se propagar longe.
  • Há discordância sobre se Rust, no geral, reduz os defeitos totais ou apenas os desloca de bugs de memória para falhas lógicas/do tipo panic; alguns pedem estudos formais.
  • Swift é destacado como convergindo para ownership/borrowing ao estilo Rust e como candidato a “TypeScript para C++” via interoperabilidade estreita com C++.
  • Go e linguagens gerenciadas são apontadas como fornecedoras antigas de segurança de memória, mas com trade-offs diferentes (GC, limitações do sistema de tipos, peculiaridades de concorrência).

Segurança de memória vs correção geral

  • Muitos argumentam que segurança de memória é necessária, mas não suficiente: bugs lógicos, SQL injection, path traversal, erros criptográficos e problemas de desserialização continuam em todas as linguagens, inclusive Rust.
  • Alguns se preocupam com “cultura de panic” e uso excessivo de crashes em vez de recuperação graciosa, especialmente em contextos não críticos para segurança.

Comportamento indefinido, C/C++ e otimização

  • Grandes subthreads debatem o UB de C:
    • Um lado: UB é essencial para alta otimização (proveniência de ponteiros, aliasing etc.).
    • Outro lado: muitos casos de UB poderiam ser definidos com perda modesta de desempenho; compiladores modernos são “adversariais” e complexos demais.
  • Contexto histórico: o design de C favoreceu a facilidade de implementação do compilador e a portabilidade em vez de segurança e completude; isso ajudou sua difusão, mas deixou um legado de insegurança.
  • Flags como -fno-strict-aliasing e semelhantes são discutidas como mitigações parciais, ao custo de dialetos não padronizados e alguma perda de desempenho.

Data races e segurança

  • É feita uma distinção entre:
    • data races de baixo nível (acesso concorrente não atômico à memória) e
    • condições de corrida de nível mais alto (TOCTOU, corridas distribuídas).
  • Vários afirmam que data races em processo ainda não são uma grande classe explorada em comparação com corrupção clássica de memória; outros citam exploits em kernel e baseados em corridas e argumentam que não devemos normalizar UB só porque é raro.
  • Alguns questionam por que as linguagens não podem definir data races para resultar em “um dos valores escritos”; as respostas citam tearing, rematerialização pelo compilador e perda de otimizações.

Sandboxing, capabilities e dependências

  • Cresce a preocupação com grandes árvores de dependências e risco de cadeia de suprimentos; há desejo por formas de colocar bibliotecas em sandbox dentro de um processo.
  • Mecanismos propostos: sandboxes no nível da linguagem (por exemplo, Java SecurityManager, isolates do GraalVM), WebAssembly e modelos de object-capability em que acesso a recursos (sistema de arquivos, rede) é passado explicitamente como capacidades.

Python e outras ressalvas de linguagens “memory-safe”

  • Python geralmente é rotulado como memory-safe, mas participantes observam:
    • FFI (ctypes, extensões C) quebra a segurança facilmente.
    • Mesmo Python puro pode acionar erros de memória do interpretador por meio de bytecode elaborado ou APIs de baixo nível; corrigir isso exigiria maquinaria significativa de verificação.
  • Consenso geral: “linguagem com segurança de memória” é um rótulo pragmático que significa “segura no uso normal”, não uma garantia absoluta.

Direções futuras de linguagens e sucessores de C++

  • Quatro estratégias de “sucessor de C++” foram identificadas: não fazer nada, adicionar segurança mantendo compatibilidade, quebrar compatibilidade mas continuar inseguro, ou quebrar compatibilidade e ser memory-safe por padrão.
  • Alguns questionam medidas pela metade: se você já vai quebrar compatibilidade, por que não ir direto para memory-safe-by-default?
  • Há um espaço de design percebido para linguagens de sistemas que sejam memory-safe por padrão, mas façam escolhas muito diferentes de Rust (escopo da stdlib, tratamento de erros, sintaxe, modelo de concorrência, políticas de alocação, linking e gerenciamento de pacotes).