Style de fuzz 1989

Avec le début de 2019, il est bon de se souvenir du passé et de penser à l'avenir. Regardons 30 ans en arrière et réfléchissons sur les premiers articles scientifiques sur le fuzzing: «An Empirical Study of the Reliability of UNIX Utilities» et le travail ultérieur de 1995 «Revision of Fuzzing» du même auteur Barton Miller .

Dans cet article, nous allons essayer de trouver des bogues dans les versions modernes d'Ubuntu Linux en utilisant les mêmes outils que dans les travaux de fuzzing d'origine. Vous devez lire les documents originaux non seulement pour le contexte, mais aussi pour la compréhension. Ils se sont révélés très prophétiques en ce qui concerne les vulnérabilités et les exploits pour les décennies à venir. Les lecteurs attentifs peuvent remarquer la date de publication de l'article original: 1990. Encore plus attentif remarquera le droit d'auteur dans les commentaires source: 1989.

Brève revue


Pour ceux qui n'ont pas lu les documents (bien que cela devrait vraiment être fait), cette section contient un bref résumé et quelques citations sélectionnées.

Le programme de fuzzing génère des flux aléatoires de caractères, avec la possibilité de générer uniquement des caractères imprimables ou non imprimables. Il utilise une certaine valeur initiale (graine), garantissant des résultats reproductibles, ce qui fait souvent défaut aux fuzzers modernes. Un ensemble de scripts est exécuté sur les programmes testés et vérifie la présence de vidages de base. Les blocages sont détectés manuellement. Les adaptateurs fournissent une entrée aléatoire pour les programmes interactifs (article de 1990), les services réseau (1995) et les applications graphiques X (1995).

Un article de 1990 a testé quatre architectures de processeur (i386, CVAX, Sparc, 68020) et cinq systèmes d'exploitation (4.3 BSD, SunOS, AIX, Xenix, Dynix). Dans un article de 1995, un choix similaire de plateformes. Dans le premier article, 25 à 33% des utilitaires échouent, selon la plate-forme. Dans un article ultérieur, ces chiffres varient de 9% à 33%, GNU (sous SunOS) et Linux ayant le taux d'échec le plus faible.

Un article de 1990 concluait que 1) les programmeurs ne vérifient pas les limites des tableaux ou les codes d'erreur, 2) les macros rendent la lecture et le débogage difficiles, et 3) C est très dangereux. La fonction d'extraction extrêmement dangereuse et le système de type C ont été particulièrement mentionnés. Lors des tests, les auteurs ont trouvé des vulnérabilités de Format String des années avant leur exploitation de masse. L'article se termine par une enquête auprès des utilisateurs sur la fréquence à laquelle ils corrigent des bogues ou les signalent. Il s'est avéré que le signalement des bogues était difficile et il y avait peu d'intérêt à les corriger.

Un article de 1995 mentionne un logiciel open source et explique pourquoi il contient moins d'erreurs. Citation:

Lorsque nous avons enquêté sur les causes des échecs, un phénomène inquiétant est apparu: de nombreux bugs (environ 40%) signalés en 1990 sont toujours présents sous leur forme exacte en 1995. ...

Les méthodes utilisées ici sont simples et principalement automatisées. Il est difficile de comprendre pourquoi les développeurs n'utilisent pas cette source facile et gratuite pour augmenter la fiabilité.

Ce n'est que dans 15 à 20 ans que la technique du fuzzing deviendra une pratique courante pour les grands fournisseurs.

Il me semble également que cette déclaration de 1990 prévoit des événements futurs:

Souvent, le style laconique de programmation C est poussé à l'extrême, la forme prévaut sur la fonction correcte. La possibilité d'un débordement dans le tampon d'entrée est un trou de sécurité potentiel, comme l'a montré le récent ver Internet .

Méthodologie de test


Heureusement, 30 ans plus tard, le Dr Barton fournit toujours le code source complet, les scripts et les données pour reproduire ses résultats : un exemple louable que d'autres chercheurs devraient suivre. Les scripts fonctionnent sans problème et l'outil de fuzzing n'a nécessité que des modifications mineures pour être compilé et exécuté.

Pour ces tests, nous avons utilisé des scripts et des entrées du référentiel fuzz-1995-basic , car il existe la dernière liste des applications testées . Selon README , voici les mêmes entrées aléatoires que dans l'étude originale. Les résultats ci-dessous pour Linux moderne sont obtenus exactement sur le même code de fuzzing et les mêmes données d'entrée que dans les articles originaux. Seule la liste des utilitaires de test a changé.

Changements d'utilité sur 30 ans


De toute évidence, il y a eu quelques changements dans les progiciels Linux au cours des 30 dernières années, bien qu'un certain nombre d'utilitaires éprouvés aient poursuivi leur ascendance pendant des décennies. Dans la mesure du possible, nous avons repris des versions modernes des mêmes programmes d'un article de 1995. Certains programmes ne sont plus disponibles, nous les avons remplacés. Justification de tous les remplacements:

  • cfecc1 : Equivalent au préprocesseur C de l'article de 1995.
  • dbxgdb : équivalent au débogueur 1995.
  • ditroffgroff : ditroff n'est plus disponible.
  • dtblgtbl : équivalent à GNU Troff de l'ancien utilitaire dtbl .
  • lispclisp : implémentation standard de lisp.
  • moreless : moins c'est plus!
  • prologswipl : Il existe deux options pour prolog: SWI Prolog et GNU Prolog. SWI Prolog est préférable car il s'agit d'une implémentation plus ancienne et plus complète.
  • awkgawk : version GNU de awk .
  • ccgcc : Le compilateur C standard.
  • compressgzip : GZip est le descendant conceptuel de l'ancien utilitaire compress Unix.
  • lintsplint : lint réécrite sous la GPL.
  • /bin/mail/usr/bin/mail : utilitaire équivalent d'une manière différente.
  • f77fort77 : Il existe deux variantes du compilateur Fortan77: GNU Fortran et Fort77. Le premier est recommandé pour Fortran 90 et le second pour le support Fortran77. Le programme f2c activement soutenu; sa liste de modifications est maintenue depuis 1989.

Résultats


La technique de fuzzing de 1989 trouve toujours des erreurs en 2018. Mais il y a des progrès.

Pour mesurer les progrès, vous avez besoin d'une base. Heureusement, un tel cadre existe pour les utilitaires Linux. Bien que Linux n'existait pas au moment de l'article original en 1990, un deuxième test en 1995 a lancé le même code flou sur les utilitaires de la distribution Slackware 2.1.0 de 1995. Les résultats correspondants sont donnés dans le tableau 3 de l'article de 1995 (p. 7-9) . Comparé à ses concurrents commerciaux, GNU / Linux a l'air très bien:

Le pourcentage de pannes d'utilitaires dans la version Linux gratuite d'UNIX était le deuxième plus élevé: 9%.

Comparons donc les utilitaires Linux de 1995 et 2018 avec les outils de fuzzing 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, retrait, gdb)
Gèle1 (sort)1 (sort)1 (sort)2 (sort, unités)1 (ctags)
Total testé8181818155
Échecs / gels,%2%2%4%5%9%

Étonnamment, le nombre de plantages et de gels Linux est toujours supérieur à zéro, même sur la dernière version d'Ubuntu. Ainsi, f77 appelle le programme f2c avec une erreur de segmentation et le programme spell bloque sur deux versions de l'entrée de test.

Quels bugs?


J'ai pu découvrir manuellement la cause première de certains bugs. Certains résultats, comme une erreur de glibc, étaient inattendus, tandis que d'autres, comme sprintf avec un tampon de taille fixe, étaient prévisibles.

Échec Ul


Le bogue dans ul est en fait un bogue dans la glibc. En particulier, il a été signalé ici et ici (une autre personne l'a trouvé à ul ) en 2016. Selon le bug tracker, l'erreur n'est toujours pas corrigée. Étant donné que le bogue ne peut pas être reproduit sur Ubuntu 18.04 et versions ultérieures, il est corrigé au niveau de la distribution. A en juger par les commentaires sur le bug tracker, le problème principal peut être très grave.

Crash f77


Le programme f77 est fourni dans le package fort77, qui est lui-même un script shell autour de f2c , le traducteur source de Fortran77 vers C. Le débogage f2c montre qu'un échec se produit lorsque la fonction errstr imprime un message d'erreur trop long. Le code source f2c montre que la fonction sprintf est utilisée pour écrire une chaîne de longueur variable dans un tampon de taille fixe:

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

Il semble que ce code ait été conservé depuis la création de f2c . Le programme a une histoire de changement depuis au moins 1989. En 1995, lors du re-flou, le compilateur Fortran77 n'a pas été testé, sinon le problème aurait été découvert plus tôt.

Gel du sort


Un excellent exemple de blocage classique. spell délégués ispell dans un tube. spell lit le texte ligne par ligne et produit un enregistrement de blocage de la taille de la ligne dans ispell . Cependant, ispell lit un maximum de BUFSIZ/2 octets à la fois (4096 octets sur mon système) et émet un enregistrement de blocage pour s'assurer que le client a reçu les données de validation qui ont été traitées jusqu'à présent. Deux entrées de test différentes ont forcé l' spell à écrire une chaîne de plus de 4096 caractères pour ispell , ce qui entraîne un blocage: le spell attend ispell lise la chaîne entière, tandis ispell attend le spell confirmer qu'il a lu les corrections orthographiques originales.

Accrochez les unités


À première vue, il semble qu'il existe une condition de boucle infinie. Le blocage semble être en libreadline et non en units , bien que les nouvelles versions d' units ne souffrent pas de cette erreur. Le journal des modifications indique qu'un filtrage d'entrée a été ajouté, ce qui pourrait résoudre accidentellement ce problème. Cependant, une enquête approfondie sur les raisons dépasse le cadre de ce blog. Peut-être que le moyen de bloquer libreadline toujours là.

Accident de Swipl


Par souci d'exhaustivité, je tiens à mentionner l'accident swipl , même si je ne l'ai pas étudié attentivement, car le bogue a longtemps été corrigé et semble être de qualité assez élevée. L'échec est en fait une instruction (c'est-à-dire qui ne devrait jamais se produire) qui est appelée lors de la conversion des caractères:

[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


Le crash est toujours mauvais, mais au moins ici, le programme peut signaler une erreur, se plantant tôt et fort.

Conclusion


Au cours des 30 dernières années, le fuzzing est resté un moyen simple et fiable de trouver des bugs. Bien que des recherches actives soient en cours dans ce domaine , même le flou d'il y a 30 ans trouve avec succès des erreurs dans les utilitaires Linux modernes.

L'auteur des articles originaux a prédit les problèmes de sécurité que C causerait au cours des prochaines décennies. Il soutient de manière convaincante que le code non sûr est trop facile à écrire en C et devrait être évité si possible. En particulier, les articles démontrent que les bogues apparaissent même avec le phasage le plus simple, et ces tests devraient être inclus dans les pratiques de développement de logiciels standard. Malheureusement, ce conseil n'a pas été suivi depuis des décennies.

J'espère que vous avez apprécié cette rétrospective de 30 ans. Attendez le prochain article «Fuzzing in 2000», où nous examinerons la robustesse des applications Windows 10 par rapport à leurs équivalents Windows NT / 2000 lorsqu'elles sont testées avec fuzzer . Je pense que la réponse est prévisible.

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


All Articles