Estilo fuzzing 1989

Com o início de 2019, é bom lembrar o passado e pensar no futuro. Vamos relembrar 30 anos e refletir sobre os primeiros artigos científicos sobre fuzzing: "Um estudo empírico da confiabilidade dos utilitários UNIX" e o trabalho subsequente de 1995 "Revisão de fuzzing", do mesmo autor Barton Miller .

Neste artigo, tentaremos encontrar bugs nas versões modernas do Ubuntu Linux usando as mesmas ferramentas dos trabalhos de difusão originais. Você deve ler os documentos originais, não apenas pelo contexto, mas também pelo entendimento. Eles se mostraram muito proféticos em relação a vulnerabilidades e façanhas nas próximas décadas. Os leitores atentos poderão notar a data de publicação do artigo original: 1990. Ainda mais atento notará os direitos autorais nos comentários da fonte: 1989.

Breve revisão


Para quem não leu os documentos (embora isso realmente deva ser feito), esta seção contém um breve resumo e algumas citações selecionadas.

O programa de difusão gera fluxos aleatórios de caracteres, com a capacidade de gerar apenas caracteres imprimíveis ou não imprimíveis. Ele usa um certo valor inicial (semente), garantindo resultados reproduzíveis, dos quais os difusores modernos geralmente não têm. Um conjunto de scripts é executado nos programas testados e verifica a presença de despejos básicos. Trava são detectados manualmente. Os adaptadores fornecem entrada aleatória para programas interativos (artigo de 1990), serviços de rede (1995) e aplicativos gráficos X (1995).

Um artigo de 1990 testou quatro arquiteturas de processador (i386, CVAX, Sparc, 68020) e cinco sistemas operacionais (4,3 BSD, SunOS, AIX, Xenix, Dynix). Em um artigo de 1995, uma escolha similar de plataformas. No primeiro artigo, 25-33% dos utilitários falham, dependendo da plataforma. Em um artigo subsequente, esses números variam de 9% a 33%, com GNU (no SunOS) e Linux com a menor taxa de falhas.

Um artigo de 1990 concluiu que 1) os programadores não verificam os limites da matriz ou os códigos de erro, 2) as macros dificultam a leitura e o código de depuração e 3) C é muito inseguro. A função gets extremamente insegura e o sistema de tipos C. foi especialmente mencionado. Durante o teste, os autores encontraram vulnerabilidades no Format String anos antes de sua exploração em massa. O artigo conclui com uma pesquisa de usuários sobre a frequência com que eles corrigem bugs ou os relatam. Aconteceu que relatar bugs era difícil e havia pouco interesse em corrigi-los.

Um artigo de 1995 menciona o software de código aberto e discute por que ele tem menos erros. Citação:

Quando investigamos as causas das falhas, um fenômeno perturbador apareceu: muitos dos bugs (cerca de 40%) relatados em 1990 ainda estão presentes em sua forma exata em 1995. ...

Os métodos usados ​​aqui são simples e principalmente automatizados. É difícil entender por que os desenvolvedores não usam essa fonte fácil e gratuita para aumentar a confiabilidade.

Somente em 15 a 20 anos a técnica de difusão se tornará uma prática padrão para grandes fornecedores.

Parece-me também que esta declaração de 1990 prevê eventos futuros:

Freqüentemente, o estilo lacônico de programação C é levado ao extremo, a forma prevalece sobre a função correta. A possibilidade de um estouro no buffer de entrada é uma falha de segurança em potencial, como mostrou o recente worm da Internet .

Metodologia de teste


Felizmente, 30 anos depois, o Dr. Barton ainda fornece o código-fonte completo, scripts e dados para reproduzir suas descobertas : um exemplo louvável que outros pesquisadores devem seguir. Os scripts funcionam sem problemas, e a ferramenta de difusão requer apenas pequenas alterações para compilar e executar.

Para esses testes, usamos scripts e entradas do repositório fuzz-1995-basic , porque há a lista mais recente de aplicativos testados . De acordo com o README , aqui estão as mesmas entradas aleatórias do estudo original. Os resultados abaixo para o Linux moderno são obtidos exatamente no mesmo código de difusão e dados de entrada que nos artigos originais. Somente a lista de utilitários para teste foi alterada.

Alterações de utilidade ao longo de 30 anos


Obviamente, houve algumas mudanças nos pacotes de software Linux nos últimos 30 anos, embora algumas utilidades comprovadas continuem com seu pedigree por décadas. Sempre que possível, utilizamos versões modernas dos mesmos programas em um artigo de 1995. Alguns programas não estão mais disponíveis, nós os substituímos. Justificação para todas as substituições:

  • cfecc1 : Equivalente ao pré-processador C do artigo de 1995.
  • dbxgdb : Equivalente ao depurador de 1995.
  • ditroffgroff : o ditroff não ditroff mais disponível.
  • dtblgtbl : Equivalente ao GNU Troff do antigo utilitário dtbl .
  • lispclisp : A implementação padrão do lisp.
  • more less : menos é mais!
  • prolog swipl : Existem duas opções para o prólogo: SWI Prolog e GNU Prolog. O SWI Prolog é preferível porque é uma implementação mais antiga e completa.
  • awkgawk : versão GNU do awk .
  • ccgcc : O compilador C padrão.
  • compressgzip : GZip é o descendente conceitual do antigo utilitário compress Unix.
  • lint : lint reescrito sob a GPL.
  • /bin/mail/usr/bin/mail : Utilitário equivalente de uma maneira diferente.
  • f77fort77 : Existem duas variações do compilador Fortan77: GNU Fortran e Fort77. O primeiro é recomendado para o Fortran 90 e o segundo para o suporte do Fortran77. O programa f2c apoiado ativamente; sua lista de alterações é mantida desde 1989.

Resultados


A técnica de difusão de 1989 ainda encontra erros em 2018. Mas há algum progresso.

Para medir o progresso, você precisa de alguma base. Felizmente, essa estrutura existe para utilitários Linux. Embora o Linux não existisse na época do artigo original em 1990, um segundo teste em 1995 lançou o mesmo código difuso nos utilitários da distribuição Slackware 2.1.0 de 1995. Os resultados correspondentes são apresentados na tabela 3 do artigo de 1995 (p. 7-9) . Comparado aos concorrentes comerciais, o GNU / Linux parece muito bom:

A porcentagem de falhas no utilitário na versão Linux gratuita do UNIX foi a segunda mais alta: 9%.

Então, vamos comparar os utilitários Linux de 1995 e 2018 com as ferramentas de difusão de 1989:

Ubuntu 18.10 (2018)Ubuntu 18.04 (2018)Ubuntu 16.04 (2016)Ubuntu 14.04 (2014)Slackware 2.1.0 (1995)
Crashes1 (f77)1 (f77)2 (f77, ul)2 (swipl, f77)4 (ul, flex, travessão, gdb)
Congela1 (feitiço)1 (feitiço)1 (feitiço)2 (feitiço, unidades)1 (ctags)
Total testado8181818155
Falhas / congelamentos,%2%2%4%5%9%

Surpreendentemente, o número de travamentos e congelamentos do Linux ainda é maior que zero, mesmo na versão mais recente do Ubuntu. Portanto, o f77 chama o programa f2c com um erro de segmentação e o programa spell fica f2c em duas versões da entrada de teste.

Quais erros?


Consegui descobrir manualmente a causa raiz de alguns erros. Alguns resultados, como um erro na glibc, foram inesperados, enquanto outros, como sprintf com um tamanho de buffer fixo, eram previsíveis.

Ul falha


O erro no ul é realmente um erro no glibc. Em particular, foi relatado aqui e aqui (outra pessoa encontrou em ul ) em 2016. De acordo com o rastreador de erros, o erro ainda não foi corrigido. Como o bug não pode ser reproduzido no Ubuntu 18.04 e posterior, ele é corrigido no nível de distribuição. A julgar pelos comentários no rastreador de erros, o principal problema pode ser muito sério.

Crash f77


O programa f77 vem no pacote fort77, que é um script de shell em torno de f2c , o tradutor de origem do Fortran77 para C. A depuração do f2c mostra que ocorre uma falha quando a função errstr imprime uma mensagem de erro muito longa. O código-fonte f2c mostra que a função sprintf é usada para gravar uma cadeia de comprimento variável em um buffer de tamanho fixo:

 errstr(const char *s, const char *t) #endif { char buff[100]; sprintf(buff, s, t); err(buff); } 

Parece que esse código foi preservado desde a criação do f2c . O programa tem um histórico de mudanças desde pelo menos 1989. Em 1995, quando re-difundindo, o compilador Fortran77 não foi testado, caso contrário, o problema teria sido encontrado anteriormente.

Congelar feitiço


Um ótimo exemplo de impasse clássico. spell delega a ispell spell através de um cano. spell lê o texto linha por linha e produz um registro de bloqueio do tamanho da linha em ispell . No entanto, o ispell lê no máximo BUFSIZ/2 bytes por vez (4096 bytes no meu sistema) e emite um registro de bloqueio para garantir que o cliente tenha recebido dados de validação que foram processados ​​até o momento. Duas entradas de teste diferentes forçaram a spell a escrever uma sequência de mais de 4096 caracteres para ispell , o que resultou em um impasse: a spell aguarda que a ispell leia a sequência inteira, enquanto a ispell aguarda a spell confirmar que leu as correções ortográficas originais.

Pendurar unidades


À primeira vista, parece que há uma condição de loop infinito. O travar parece estar em libreadline e não em units , embora as versões mais recentes das units não sofram esse erro. O log de alterações indica que foi adicionada a filtragem de entrada que pode corrigir acidentalmente esse problema. No entanto, uma investigação completa dos motivos está além do escopo deste blog. Talvez a maneira de travar a libreadline ainda libreadline lá.

Swipl crash


Por uma questão de completude, quero mencionar a falha swipl , embora não a tenha estudado com cuidado, pois o bug foi corrigido há muito tempo e parece ter uma qualidade bastante alta. Falha é, na verdade, uma declaração (isto é, que nunca deve acontecer) que é chamada ao converter caracteres:

[Thread 1] pl-fli.c:2495: codeToAtom: Assertion failed: chrcode >= 0
C-stack trace labeled "crash":
[0] __assert_fail+0x41
[1] PL_put_term+0x18e
[2] PL_unify_text+0x1c4


O travamento é sempre ruim, mas pelo menos aqui o programa pode relatar um erro, travando cedo e em voz alta.

Conclusão


Nos últimos 30 anos, a difusão permaneceu uma maneira simples e confiável de encontrar bugs. Embora a pesquisa ativa esteja em andamento nessa área , mesmo o fuzzer de 30 anos atrás encontra erros nos utilitários modernos do Linux.

O autor dos artigos originais previu os problemas de segurança que C causaria nas próximas décadas. Ele argumenta de forma convincente que código inseguro é muito fácil de escrever em C e deve ser evitado, se possível. Em particular, os artigos demonstram que os erros aparecem mesmo com as fases mais simples e esses testes devem ser incluídos na prática padrão de desenvolvimento de software. Infelizmente, esse conselho não é seguido há décadas.

Espero que você tenha gostado desta retrospectiva de 30 anos. Aguarde o próximo artigo “Fuzzing in 2000”, onde examinaremos o quão robustos os aplicativos Windows 10 são comparados aos seus equivalentes Windows NT / 2000 quando testados com fuzzer . Eu acho que a resposta é previsível.

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


All Articles