O uso de um compilador C desatualizado é um risco à segurança?

139

Temos alguns sistemas de construção em produção com os quais ninguém se importa e essas máquinas executam versões antigas do GCC como GCC 3 ou GCC 2.

E não consigo convencer a gerência a atualizá-la para uma versão mais recente: eles dizem: "se não está quebrado, não conserte".

Como mantemos uma base de código muito antiga (escrita nos anos 80), esse código C89 compila muito bem nesses compiladores.

Mas não tenho certeza se é uma boa ideia usar essas coisas antigas.

Minha pergunta é:

O uso de um compilador C antigo pode comprometer a segurança do programa compilado?

ATUALIZAR:

O mesmo código foi criado pelo Visual Studio 2008 para destinos do Windows, e o MSVC ainda não suporta C99 ou C11 (não sei se o MSVC mais recente suporta), e posso construí-lo na minha caixa Linux usando o GCC mais recente. Portanto, se introduzirmos um GCC mais recente, provavelmente criaremos tão bem quanto antes.

Calmarius
fonte
5
Pergunta interessante - isso também pode valer a pena ler rapidamente - developers.slashdot.org/story/13/10/29/2150211/… .. para que os compiladores mais novos também comprometam a segurança ao otimizar.
Neil
6
Essas versões antigas do gcc oferecem suporte à compilação no PIC / PIE for ASLR? Eles suportam canários de pilha? W ^ X (NX)? Caso contrário, a falta de mitigação de vulnerabilidades é um bom motivo para atualizar.
EOF
12
Olhar apenas os avisos do gcc 4.x pode revelar imediatamente toda uma carga de falhas de segurança existentes que você não sabia que tinha.
OrangeDog 27/05
7
@OrangeDog: Por que o gcc 4.x? O gcc6 é a série de lançamentos atual e o gcc 5 já existe há algum tempo. Mas sim, corrigir qualquer problema identificado pelo -O3 -Wall -Wextra -fsanitize=undefinedgcc e pelo clang moderno deve ajudar.
Peter Cordes 28/05
4
O @OrangeDog GCC foi para os números de versão de marketing. O GCC 5 mereceu um grande aumento de versão, porque alterou os padrões C e C ++ padrão e a ABI libstdc ++. O GCC 6 deveria ter sido chamado de 5.1.
Zwol

Respostas:

102

Na verdade, eu argumentaria o contrário.

Existem vários casos em que o comportamento é indefinido pelo padrão C, mas é óbvio o que aconteceria com um "compilador burro" em uma determinada plataforma. Casos como permitir que um número inteiro assinado transborde ou acessar a mesma memória através de variáveis ​​de dois tipos diferentes.

Versões recentes do gcc (e clang) começaram a tratar esses casos como oportunidades de otimização, não importando se eles mudam o comportamento do binário na condição "comportamento indefinido". Isso é muito ruim se sua base de código foi escrita por pessoas que trataram C como um "montador portátil". Com o passar do tempo, os otimizadores começaram a examinar blocos cada vez maiores de código ao fazer essas otimizações, aumentando a chance de o binário acabar fazendo algo diferente de "o que um binário construído por um compilador burro" faria.

Existem opções de compilador para restaurar o comportamento "tradicional" (-fwrapv e -fno-strict-aliasing para os dois que eu mencionei acima), mas primeiro você precisa saber sobre eles.

Embora, em princípio, um bug do compilador possa transformar um código compatível em uma falha de segurança, eu consideraria o risco disso insignificante no grande esquema das coisas.

plugwash
fonte
13
Mas esse argumento funciona nos dois sentidos. Se um compilador tem previsível "comportamento indefinido", então ele pode ser sem dúvida mais fácil de fazer uso malicioso dele ...
André
18
@ André O código compilado tem um comportamento indefinido previsível de qualquer maneira. Ou seja, depois que o código foi compilado, qualquer comportamento imprevisível agora é previsível nessa versão compilada específica.
User253751 28/05
6
people who treated C like a "portable assembler"não é o que C é?
Max
10
@ Max Esta resposta adverte precisamente sobre o fato de que a noção de "montador portátil" está pelo menos desatualizada na prática, devido aos otimizadores modernos. E eu argumentaria que nunca foi conceitualmente correto para começar.
Theodoros Chatzigiannakis
6
Aqui não há simpatia por aqueles que confiam em comportamento indefinido e depois começam a colher o que plantaram. Isso não significa que os compiladores mais novos sejam inerentemente menos seguros - significa que o código não compatível foi uma bomba-relógio. A culpa deve ser repartida em conformidade.
Underscore_d
52

Existem riscos nos dois cursos de ação.


Compiladores mais antigos têm a vantagem da maturidade, e o que quer que tenha sido quebrado neles provavelmente (mas não há garantia) foi resolvido com êxito.

Nesse caso, um novo compilador é uma fonte potencial de novos bugs.


Por outro lado, os compiladores mais novos vêm com ferramentas adicionais :

  • Agora, o GCC e o Clang agora contam com desinfetantes que podem instrumentar o tempo de execução para detectar comportamentos indefinidos de vários tipos (Chandler Carruth, da equipe do Google Compiler, afirmou no ano passado que espera que eles atinjam a cobertura total)
  • O Clang, pelo menos, apresenta proteção , por exemplo, Control Flow Integrity é para detectar oi-jack do fluxo de controle, também existem dispositivos de proteção para proteger contra ataques de esmagamento de pilha (separando a parte do fluxo de controle da pilha da parte de dados) ; os recursos de proteção geralmente têm uma sobrecarga baixa (<1% da CPU)
  • O Clang / LLVM também está trabalhando no libFuzzer , uma ferramenta para criar testes unitários de fuzz instrumentados que exploram o espaço de entrada da função sob teste de maneira inteligente (ajustando a entrada para usar caminhos de execução ainda não explorados)

A instrumentação do seu binário com os desinfetantes (desinfetante de endereço, desinfetante de memória ou desinfetante de comportamento indefinido) e depois difundi-lo (usando o American Fuzzy Lop, por exemplo) descobriu vulnerabilidades em vários softwares de alto nível, consulte, por exemplo, este artigo do LWN.net .

Essas novas ferramentas e todas as ferramentas futuras estão inacessíveis para você, a menos que você atualize seu compilador.

Ao permanecer em um compilador com pouca potência, você está colocando a cabeça na areia e cruzando os dedos para que nenhuma vulnerabilidade seja encontrada. Se o seu produto for um alvo de alto valor, peço que você reconsidere.


Nota: mesmo se você NÃO atualizar o compilador de produção, convém usar um novo compilador para verificar a vulnerabilidade; esteja ciente de que, como esses são compiladores diferentes, as garantias são diminuídas.

Matthieu M.
fonte
1
+1 por se preocupar em mencionar os casos em que os novos compiladores podem ser mais seguros, em vez de se amontoar na banda de 'b-but my old UB' das outras respostas. isso está além das muitas outras melhorias que eles oferecem, que não estão diretamente relacionadas à segurança, mas fornecem ainda mais ímpeto para que seja razoavelmente moderno.
Underscore_d
Embora pareça defender a "segurança através da obscuridade"; os erros que afetam os compiladores antigos são conhecidos e públicos. Embora eu concorde que os novos compiladores apresentem bugs, esses bugs ainda não são públicos, como os das versões anteriores, o que é alguma segurança se você estiver atualizando o aplicativo com frequência.
The6P4C
Chandler Carruth é tão fofo e fala de coisas maravilhosas. Eu me casaria com ele se pudesse.
Daniel Kamil Kozar
46

Seu código compilado contém bugs que podem ser explorados. Os bugs vêm de três fontes: bugs no seu código-fonte, bugs no compilador e nas bibliotecas e comportamento indefinido no código-fonte que o compilador se transforma em um bug. (O comportamento indefinido é um erro, mas ainda não um erro no código compilado. Como exemplo, i = i ++; em C ou C ++ é um erro, mas em seu código compilado ele pode aumentar i em 1 e ficar OK ou definir i para algum lixo e ser um bug).

A taxa de erros no seu código compilado é presumivelmente baixa devido a testes e à correção de erros devido a relatórios de erros do cliente. Portanto, pode ter havido um grande número de bugs inicialmente, mas isso diminuiu.

Se você atualizar para um compilador mais recente, poderá perder erros introduzidos por erros do compilador. Mas todos esses erros seriam erros que, a seu conhecimento, ninguém encontrou e ninguém explorou. Mas o novo compilador pode ter bugs por conta própria e, o mais importante é que os compiladores mais novos tendem a transformar comportamentos indefinidos em bugs no código compilado.

Então você terá muitos novos bugs no seu código compilado; todos os bugs que os hackers poderiam encontrar e explorar. E, a menos que você faça muitos testes e deixe seu código com os clientes para encontrar bugs por um longo tempo, será menos seguro.

gnasher729
fonte
6
Então, em outras palavras ... não há uma maneira fácil de dizer quais problemas o compilador apresenta e, ao mudar tudo o que você faz é obter um conjunto diferente de problemas desconhecidos?
Jeremy Kato
1
@ JeremyKato: bem, há alguns casos em que você também está tendo um conjunto diferente de problemas conhecidos. Não sei ao certo quais falhas de segurança conhecidas existem no próprio compilador, mas, a bem de um exemplo concreto, suponha que atualizar para um novo compilador signifique também poder fazer o último libc (enquanto usar o antigo significa não poder) para fazer isso), você saberia que está corrigindo essa falha em getaddrinfo(): access.redhat.com/articles/2161461 . Na verdade, esse exemplo não é uma falha de segurança do compilador, mas há mais de 10 anos provavelmente haverá algumas falhas fixas conhecidas.
Steve Jessop 27/05
2
Heh, na verdade, essa falha foi introduzida apenas em 2008, para que o interlocutor estivesse seguro. Mas meu argumento não é sobre esse exemplo específico, é que existem erros conhecidos que uma cadeia de ferramentas antiga colocará em seu código. Portanto, quando você atualiza, é verdade que você introduz um novo conjunto de incógnitas, mas isso não é tudo o que você faz . Você basicamente precisa adivinhar se está "mais seguro", deixando na falha crítica conhecida que a cadeia de ferramentas mais recente corrige ou assumindo as consequências desconhecidas de rolar os dados novamente com todo o comportamento indefinido em seu próprio código.
Steve Jessop
19

Se não estiver quebrado, não conserte

Seu chefe parece certo ao dizer isso, no entanto, o fator mais importante é a proteção de entradas, saídas e estouros de buffer. A falta deles é invariavelmente o elo mais fraco da cadeia desse ponto de vista, independentemente do compilador usado.

No entanto, se a base de código for antiga e foram criados trabalhos para mitigar os pontos fracos do K&R C usado, como falta de segurança de tipo, recursos inseguros, etc., avalie a questão " Atualizando o compilador para um C99 mais moderno / Padrões C11 quebram tudo? "

Desde que haja um caminho claro para migrar para os padrões C mais recentes, o que pode induzir efeitos colaterais, pode ser melhor tentar uma bifurcação da antiga base de código, avaliá-la e colocar verificações de tipo extras, verificações de sanidade e determinar se está atualizando para o compilador mais recente tem algum efeito nos conjuntos de dados de entrada / saída.

Em seguida, você pode mostrá-lo ao seu chefe: " Aqui está a base de código atualizada, refatorada, mais alinhada com os padrões C99 / C11 aceitos pelo setor ... ".

Essa é a aposta que teria que ser ponderada com muito cuidado , a resistência à mudança pode aparecer lá naquele ambiente e pode se recusar a tocar nas coisas mais recentes.

EDITAR

Apenas relaxei por alguns minutos, percebi isso, o código gerado pela K&R poderia estar sendo executado em uma plataforma de 16 bits, é provável que a atualização para um compilador mais moderno pudesse realmente quebrar a base de código, estou pensando em termos de arquitetura, o código de 32 bits seria gerado , isso pode ter efeitos colaterais engraçados nas estruturas usadas para conjuntos de dados de entrada / saída, que é outro grande fator a ser ponderado com cuidado.

Além disso, como o OP mencionou o uso do Visual Studio 2008 para criar a base de código, o uso do gcc poderia induzir a introdução no ambiente do MinGW ou Cygwin, que poderia causar uma mudança de impacto no ambiente, a menos que o alvo seja o Linux, seria vale a pena tentar, pode ser necessário incluir opções adicionais no compilador para minimizar o ruído na antiga base de códigos K&R; a outra coisa importante é realizar muitos testes para garantir que nenhuma funcionalidade seja quebrada; pode ser um exercício doloroso.

t0mm13b
fonte
O mesmo código foi criado pelo Visual Studio 2008 para destinos do Windows, e o MSVC ainda não suporta C99 ou C11 (não sei se o MSVC mais recente suporta), e posso construí-lo na minha caixa Linux usando o GCC mais recente. Portanto, se introduzirmos um GCC mais recente, provavelmente criaremos tão bem quanto antes.
Calmarius 27/05
@Calmarius graças a cabeça para cima, talvez ele poderia ser melhor para editar sua pergunta para incluir o comentário, Isso é importante :) E deveria ter estado lá; D
t0mm13b
O @Calmarius editou minha resposta, que é o meu pensamento sobre a pergunta recém-atualizada.
t0mm13b 27/05
2
“Poderia ser executado em uma plataforma de 16 bits, as chances são, a atualização para o compilador mais moderno poderia realmente quebrar a base de código, estou pensando em termos de arquitetura, código de 32 bits” Eu não acho que a pergunta é sobre portar código para nova implementação definida parâmetros.
Pascal Cuoq
Acordado. É possível que uma vulnerabilidade de tempo de execução possa ser criada por um bug do compilador. Mas é muito mais provável que o código contenha vulnerabilidades de tempo de execução devido a coisas como excedentes de buffer e pilha. Portanto, quando você investe tempo em tornar essa base de código mais segura, deve investir em ações como verificar o comprimento das cadeias de entrada para garantir que elas não excedam os limites do seu programa. Obter um compilador mais novo não ajudará muito. Reescrever o código do zero em um idioma com objetos nativos de string e array ajudará bastante. Mas seu chefe não vai pagar por isso.
O. Jones
9

O uso de um compilador C antigo pode comprometer a segurança do programa compilado?

Claro que sim, se o compilador antigo contiver erros conhecidos que você sabe que afetariam seu programa.

A questão é, não é? Para ter certeza, você precisaria ler todo o registro de alterações da sua versão até a data atual e verificar todos os erros corrigidos ao longo dos anos.

Se você não encontrar evidências de bugs do compilador que afetariam seu programa, a atualização do GCC apenas por isso parece um pouco paranóica. Você deve ter em mente que as versões mais recentes podem conter novos bugs, que ainda não foram descobertos. Muitas mudanças foram feitas recentemente com o suporte do GCC 5 e C11.

Dito isto, o código escrito nos anos 80 provavelmente já está cheio de brechas de segurança e confiança em comportamentos mal definidos, independentemente do compilador. Estamos falando de C pré-padrão aqui.

Lundin
fonte
6
Eu não acho que é paranóia; Acho que o OP está tentando inventar razões para convencer seu chefe. Provavelmente, o OP realmente quer um novo compilador, porque eles fazem melhor asm (incluindo otimização de arquivos cruzados com LTO), têm diagnósticos / avisos mais úteis e permitem recursos e sintaxe de linguagem moderna. (por exemplo, C11 estdatômico).
Peter Cordes
9

Há um risco de segurança em que um desenvolvedor mal-intencionado pode invadir a porta dos fundos por meio de um bug do compilador. Dependendo da quantidade de bugs conhecidos no compilador em uso, o backdoor pode parecer mais ou menos discreto (em qualquer caso, o ponto é que o código está correto, mesmo que complicado, no nível de origem. um compilador sem buggy não encontrará a porta dos fundos, porque a porta dos fundos não existe nessas condições). Para obter pontos extras de negação, o desenvolvedor mal-intencionado também pode procurar por erros de compilador anteriormente desconhecidos por conta própria. Novamente, a qualidade da camuflagem dependerá da escolha dos erros do compilador encontrados.

Este ataque é ilustrado no programa sudo neste artigo . O bcrypt escreveu um ótimo acompanhamento para os minificadores de Javascript .

Além desta preocupação, a evolução dos compiladores C tem sido a de explorar o comportamento indefinido mais e mais e mais agressivamente, assim que o código antigo C que foi escrito em boa fé na verdade seria mais seguro compilado com um compilador C do tempo, ou compilado pelo -O0 (mas algumas novas otimizações de exploração de UBs que quebram programas são introduzidas em novas versões de compiladores, mesmo em -O0 ).

Pascal Cuoq
fonte
7

Compiladores mais antigos podem não ter proteção contra ataques de hackers conhecidos. A proteção contra quebra de pilha, por exemplo, não foi introduzida até o GCC 4.1 . Então, sim, o código compilado com compiladores mais antigos pode ser vulnerável de maneiras que os compiladores mais novos protegem.

DrMcCleod
fonte
6

Outro aspecto para se preocupar é o desenvolvimento de um novo código .

Compiladores mais antigos podem ter um comportamento diferente para alguns recursos de linguagem do que é padronizado e esperado pelo programador. Essa incompatibilidade pode atrasar o desenvolvimento e introduzir erros sutis que podem ser explorados.

Compiladores mais antigos oferecem menos recursos (incluindo recursos de idioma!) E também não otimizam. Os programadores irão solucionar essas deficiências - por exemplo, reimplementando os recursos ausentes ou escrevendo códigos inteligentes que são obscuros, mas que correm mais rapidamente - criando novas oportunidades para a criação de bugs sutis.


fonte
5

Não

O motivo é simples: o compilador antigo pode ter bugs e explorações antigos, mas o novo compilador terá novos bugs e explorações.

Você não está "corrigindo" nenhum bug atualizando para um novo compilador. Você está trocando bugs e explorações antigas por novos bugs e explorações.

coteyr
fonte
3
Isso parece muito simplista: o novo compilador pode ter seus pontos fracos, mas eu esperaria que eles fossem menores do que no compilador antigo, e é provável que ele detecte várias vulnerabilidades de código que se tornaram conhecidas desde então.
PJTraill
Mas o novo compilador pode ter novas fraquezas desconhecidas. O compilador por si só não é um risco de segurança que precisa ser atualizado. Você não está reduzindo sua área de superfície. Você está negociando um conjunto conhecido de problemas por um conjunto desconhecido.
Coteyr
As ferramentas para ajudar a encontrar bugs melhoraram enormemente desde os primeiros dias do GCC, e essas ferramentas (análise estática, análise dinâmica de código instrumentado / sanitizadores, fuzzers etc.) também foram aplicadas ao código do compilador, para ajudar a melhorar a qualidade. Foi muito mais difícil encontrar todas as classes de bugs na era do GCC 2. Comparação de erros do compilador em relação às versões - consulte a página 7: cs.utah.edu/~regehr/papers/pldi11-preprint.pdf O GCC 4.5 e o LLVM 2.8 (mais recente na publicação) têm o menor número possível de erros de difusão.
Jetski S-type
2

Bem, há uma probabilidade maior de que todos os erros no compilador antigo sejam bem conhecidos e documentados, em vez de usar um novo compilador, para que ações possam ser tomadas para evitar esses erros, codificando-os. Portanto, de uma maneira que não é suficiente como argumento para a atualização. Temos as mesmas discussões em que trabalho, usamos o GCC 4.6.1 em uma base de código para software embarcado e há uma grande relutância (entre os gerentes) em atualizar para o compilador mais recente devido ao medo de novos erros não documentados.

AndersK
fonte
0

Sua pergunta se divide em duas partes:

  • Explícito: "Existe um risco maior de usar o compilador antigo" (mais ou menos como no seu título)
  • Implícito: "Como convencer o gerenciamento a atualizar"

Talvez você possa responder encontrando uma falha explorável em sua base de código existente e mostrando que um compilador mais recente a teria detectado. É claro que seu gerenciamento pode dizer "você achou isso com o compilador antigo", mas pode ressaltar que isso custa um esforço considerável. Ou você pode executá-lo no novo compilador para encontrar a vulnerabilidade e depois explorá-la, se você puder / puder compilar o código com o novo compilador. Você pode precisar de ajuda de um hacker amigável, mas isso depende de confiar neles e poder mostrar a eles o código (e usar o novo compilador).

Mas se o seu sistema não estiver exposto a hackers, talvez você esteja mais interessado em saber se uma atualização do compilador aumentaria sua eficácia: a Análise de código do MSVS 2013 geralmente encontra erros em potencial muito mais cedo que o MSVS 2010 e suporta mais ou menos o C99 / C11 - não tenho certeza se isso acontece oficialmente, mas as declarações podem seguir as instruções e você pode declarar variáveis ​​em for-loops.

PJTraill
fonte