Como proteger C



A linguagem C é muito poderosa e é usada muito onde - especialmente no kernel do Linux -, mas também é muito perigosa. Um desenvolvedor de kernel do Linux descreveu como lidar com vulnerabilidades de segurança em C.

Você pode fazer quase tudo em C, mas isso não significa que precisa ser feito. O código C é muito rápido, mas é transportado sem cintos de segurança. Mesmo se você for um especialista, como a maioria dos desenvolvedores de kernel do Linux , os erros assassinos ainda são possíveis.

Além de armadilhas como aliases de ponteiros , C possui erros fundamentais não corrigidos que aguardam suas vítimas. Essas são as vulnerabilidades que Case Cook , engenheiro de segurança do kernel do Google Linux, abordou na Linux Security Conference em Vancouver.

“C é uma espécie de montador. É quase um código de máquina ”, disse Cook, referindo-se a uma audiência de centenas de colegas que entendem e apreciam a velocidade dos aplicativos no C. Mas a má notícia é que“ o C vem com alguma bagagem perigosa, comportamento vago e outras fraquezas que levam a brechas de segurança e infraestrutura vulnerável ".

Se você usa C em seus projetos, preste atenção nos problemas de segurança.

Proteção de kernel Linux


Com o tempo, Cook e colegas descobriram vários problemas com o C. nativo. Para resolvê-los, o Kernel Self Protection Project foi lançado. Ele trabalha lenta e constantemente para proteger o kernel do Linux de ataques, removendo o código problemático de lá.

Isso é complicado, diz Cook, porque "o kernel precisa fazer coisas muito específicas a uma arquitetura específica para gerenciamento de memória, manipulação de interrupções, regulamentação e assim por diante". Uma grande quantidade de código refere-se a tarefas específicas que precisam ser cuidadosamente verificadas. Por exemplo, "C não possui uma API para definir tabelas de páginas ou mudar para o modo de 64 bits", disse ele.

Com essa carga e com bibliotecas padrão fracas em C, há muito comportamento vago. Cook citou - e concordou - com o artigo de Raf Levien, "Com comportamento indefinido, tudo é possível" .

Cook deu exemplos específicos: “Qual é o conteúdo das variáveis“ não inicializadas ”? Isso é tudo o que estava na minha memória antes! Não existem tipos nos ponteiros nulos, mas as funções digitadas podem ser chamadas através deles? Claro! A montagem é a mesma: você pode entrar em contato com qualquer endereço! Por que memcpy() tem o argumento 'comprimento máximo de destino'? Não importa, basta fazer o que você diz; todas as áreas de memória são iguais! ”

Ignorando avisos ... mas nem sempre


Alguns desses recursos são relativamente fáceis de manusear. Cook comentou: “Linus [Torvalds] gosta da idéia de sempre inicializar variáveis ​​locais. Então faça isso.

Mas com uma reserva. Se você inicializar uma variável local no switch, receberá um aviso: “A instrução nunca será executada [-Wswitch-unreachable] devido à maneira como o compilador processa o código. Este aviso pode ser ignorado.

Mas nem todos os avisos podem ser ignorados. "Matrizes de comprimento variável são sempre ruins", disse Cook. Outros problemas incluem esgotamento de pilha, estouro de linha e violações de proteção de página. Além disso, Cook chamou a atenção para a lentidão do VLA . A remoção de todos os VLAs do kernel aumentou o desempenho em 13%. Melhorar a velocidade e a segurança é um benefício duplo.

Embora os VLAs tenham sido quase removidos do kernel, eles ainda permanecem em algum código. Felizmente, é fácil encontrar os -Wvla usando o -Wvla compilador -Wvla .

Outro problema está oculto na semântica de C. Se falta uma interrupção no comutador, o que o programador quis dizer? Ignorar quebra pode levar à execução do código de várias condições; Esse é um problema bem conhecido.

Se você estiver procurando instruções de interrupção / opção no código existente, poderá usar -Wimplicit-fallthrough para adicionar uma nova instrução de opção. Este é realmente um comentário, mas os compiladores modernos o analisam. Você também pode marcar explicitamente a ausência de interrupção com um comentário de "avanço" .

Cook também encontrou um impacto no desempenho ao verificar os limites para alocação de memória de laje . Por exemplo, marcar a strcpy()-family reduz o desempenho em 2%. Alternativas como strncpy() seus próprios problemas. Acontece que Strncpy nem sempre termina com um caractere nulo. Infelizmente, Cook dirigiu-se ao público: "Onde posso obter as melhores APIs?"

Durante uma sessão de perguntas e respostas, um desenvolvedor Linux perguntou: "Posso me livrar de APIs antigas e ruins?" Cook respondeu que o Linux suportava o conceito de APIs herdadas por algum tempo. No entanto, Torvalds rejeitou essa ideia, argumentando que, se alguma API está desatualizada, ela deve ser completamente descartada. No entanto, abandonar para sempre a API é "politicamente perigoso", acrescentou Cook. Então, enquanto estamos presos.

Solução a longo prazo para o problema? Mais desenvolvedores que entendem problemas de segurança


Cook prevê uma jornada longa e difícil. A idéia de criar um dialeto Linux C já pareceu atraente, mas não será. O verdadeiro problema com o código perigoso é que "as pessoas não querem fazer o trabalho de limpar o código - não apenas código ruim, mas o próprio C", disse ele. Como em todos os projetos de código aberto, "precisamos de mais desenvolvedores, revisores, testadores e especialistas em backport dedicados".

C perigoso: lições


  • C é uma linguagem madura e poderosa, mas cria dificuldades técnicas e problemas de segurança.
  • Os desenvolvedores de Linux prestam atenção especial à proteção do C (sem perder o poder), porque a maior parte do sistema operacional está escrita nele.
  • O engenheiro de segurança do kernel Linux Linux identificou vulnerabilidades específicas de idiomas e explicou como evitá-las.

Source: https://habr.com/ru/post/pt422623/


All Articles