BLOG | ESCRITÓRIO DO DIRETOR DE TECNOLOGIA

Além do C

Publicado em 07 de fevereiro de 2023

Durante décadas, C e seus descendentes imediatos foram as linguagens de programação preferidas para programação de sistemas. No início da década de 1970, quando C foi desenvolvido, foi um importante avanço em relação à linguagem assembly. Cinquenta anos depois, podemos fazer melhor.

A segurança de computadores não estava no radar da maioria das pessoas em 1970 e por muitos anos depois disso. A primeira grande vulnerabilidade de segurança na internet ocorreu muitos anos depois, em 1988. Ele era conhecido como worm da internet e se aproveitava de um estouro de buffer, onde uma matriz na memória era indexada fora de seus limites.  

Na família da linguagem C, que inclui C++, os arrays não têm seus limites verificados. Cabe ao programador garantir que o array seja acessado corretamente. Portanto, erros de estouro de buffer são comuns. Pior, é fácil causar deliberadamente um estouro de buffer e, assim, acessar memória que não deveria.

O estouro de buffer é apenas um exemplo de insegurança de memória . Outros exemplos relacionados incluem aritmética de ponteiros e ponteiros pendurados (também conhecidos como bugs de uso após liberação). Hoje, temos muitas linguagens de programação que empregam uma variedade de técnicas para garantir que qualquer programa escrito nessas linguagens esteja livre de problemas de segurança de memória. A família de linguagens C não oferece tal garantia; a segurança da memória nunca foi um objetivo de design dessas linguagens.

Cerca de 70% das vulnerabilidades de segurança são devidas a violações da segurança da memória. Essa afirmação é apoiada por dados esmagadores. A segurança da memória é responsável por:

  • 67% das vulnerabilidades exploradas foram divulgadas e detectadas na natureza (conforme estimativa do Google Project Zero 2021 ).
  • 90% das vulnerabilidades do Android ( segundo o Google ). Embora esse número tenha diminuído recentemente, devido ao uso do Rust, 89% das vulnerabilidades exploráveis remotamente continuam vinculadas à segurança da memória.
  • 70% das vulnerabilidades da Microsoft (por Microsoft).
  • A maioria das correções recentes de vulnerabilidades da Apple (em Catalina , Big Sur , Monterey , Safari , iOS , tvOS , watchOS ).

Em julho de 2022, 5/6 das vulnerabilidades corrigidas no Chrome 103.0.5060.134 eram problemas de segurança de memória. Claramente, eliminar bugs de segurança de memória seria extremamente útil. A indústria sabe como fazer isso há muitos anos: usar linguagens de programação com segurança de memória. Historicamente, o problema sempre foi o custo associado em termos de desempenho. Devido à importância da segurança da memória, as pessoas têm trabalhado em novas estratégias para alcançá-la sem a sobrecarga tradicional. Hoje sabemos como eliminar erros de segurança de memória com pouco ou nenhum custo de tempo de execução. Linguagens como Rust usam inovações no design do sistema de tipos para garantir a segurança da memória sem suporte de tempo de execução dispendioso.

Isso leva a uma conclusão inevitável: pare de escrever novos códigos de sistemas em C/C++ (ou, mais genericamente, em linguagens inseguras em termos de memória).

Isto não é um apelo para reescritas massivas e indiscriminadas do código existente. Substituir software existente é caro e não isento de riscos. No entanto, a indústria deve parar de agravar o problema adicionando mais código inseguro em termos de memória às bases de código existentes. Para o código existente, priorize a reescrita dos componentes mais sensíveis: aqueles que são responsáveis por validar ou consumir entradas de usuários não confiáveis, aqueles que são executados em um contexto privilegiado, aqueles que são executados fora de uma sandbox, etc.

Essa posição, embora amplamente defendida, ainda é controversa para alguns. Aqui estão alguns dos argumentos comuns apresentados em favor do status quo e nossas respostas a eles.

  • Os bugs não são específicos de linguagens de programação.  
    • A maioria das vulnerabilidades resulta de bugs de segurança de memória. Não é possível ter bugs de segurança de memória em linguagens que protegem a memória, como Rust. Portanto, usar uma linguagem de memória segura evitará que a maioria das vulnerabilidades sejam criadas em primeiro lugar.
  • Revisões de código detectarão bugs.
    • Estudos mostram que, embora as revisões de código possam melhorar as coisas, elas geralmente deixam de detectar muitos problemas; elas certamente não encontram tudo.
  • Módulos inseguros tornam todo o ponto irrelevante.
    • Código inseguro é explicitamente delineado em seções muito pequenas de código, para que os recursos possam ser concentrados na validação dele.
  • Uma implementação de linguagem com memória segura pode apresentar bugs.
    • De fato, mas as probabilidades são muito menores. Os compiladores são relativamente pequenos e rigorosamente testados. E consertar um compilador corrigirá automaticamente todos os programas compilados com ele, em vez de corrigir um bug em um único programa.
  • Somente programadores novatos ou incompetentes cometem esses erros.
    • Pelo contrário, os dados acima são retirados de grandes projetos de sistemas, como o navegador Chrome e os sistemas operacionais Windows e MacOS. Esses projetos contam com alguns dos desenvolvedores mais competentes e experientes do setor e, ainda assim, apresentam problemas com segurança de memória.
  • Esses problemas podem ser evitados seguindo as melhores práticas.
    • Veja acima. As equipes mencionadas seguem práticas muito rigorosas e usam as melhores ferramentas disponíveis.
  • Não é prático reescrever todo o código.
    • Ninguém está defendendo uma reescrita completa de todo o código. Em vez disso, é recomendada uma abordagem que se concentre em elementos-chave (alto privilégio, grande superfície de ataque, central para garantias de segurança) e na escrita de novo código em uma linguagem de memória segura.
  • O desempenho é inadequado.
    • O desempenho do Rust é praticamente igual ao do C/C++. Pequenas diferenças não justificam riscos de segurança. Há também situações em que os programas Rust podem ser mais rápidos.
  • Linguagens de programação de sistemas de segurança de memória como Rust são muito novas.
  • Existem outras soluções, como análise estática, fuzzing e sandbox.

Veja Quantificação da Insegurança da Memória e Reações a Ela para uma discussão detalhada dos pontos acima.

Apesar das objeções, o ímpeto para as mudanças necessárias está crescendo. Grandes investimentos estão sendo feitos na Open Source Software Security Foundation (apoiada pela Linux Foundation), por exemplo. A segurança da memória foi discutida nos EUA Relatórios do Senado e da NSAA Consumer Reports também trabalha para identificar os vários incentivos que poderiam acelerar esse movimento, oferecendo uma série de recomendações para empresas e agências estaduais.

Para resumir:

A importância da computação para a sociedade cresceu enormemente nos últimos cinquenta anos. O cenário de ameaças à nossa infraestrutura de computação também mudou radicalmente nas últimas décadas. Entretanto, as linguagens de programação que usamos para construir nossos sistemas de computação não mudaram de acordo. A falta de segurança de memória é a maior fonte de vulnerabilidades de segurança em software. Isso não é peculiar a nenhum tipo específico de software, pois em todos os lugares onde linguagens inseguras de memória são usadas, problemas de segurança de memória abundam. E em números, nenhuma outra classe de vulnerabilidade chega perto.

Agora temos os meios para resolver esse problema crescente e é crucial que nós, como indústria, façamos isso. Felizmente, o reconhecimento da situação está se espalhando, mas o problema é urgente e não há tempo a perder.