Fuzzing Style 1989

Mit Beginn des Jahres 2019 ist es gut, sich an die Vergangenheit zu erinnern und über die Zukunft nachzudenken. Lassen Sie uns 30 Jahre zurückblicken und über die ersten wissenschaftlichen Artikel zum Thema Fuzzing nachdenken: „Eine empirische Studie zur Zuverlässigkeit von UNIX-Dienstprogrammen“ und die anschließende Arbeit „Revision of Fuzzing“ von 1995 des gleichen Autors Barton Miller .

In diesem Artikel werden wir versuchen, Fehler in modernen Versionen von Ubuntu Linux zu finden, indem wir dieselben Tools wie in den ursprünglichen Fuzzing-Arbeiten verwenden. Sie müssen die Originaldokumente nicht nur zum Kontext, sondern auch zum Verständnis lesen. Sie erwiesen sich in Bezug auf Schwachstellen und Exploits für die kommenden Jahrzehnte als sehr prophetisch. Aufmerksame Leser können das Datum der Veröffentlichung des Originalartikels bemerken: 1990. Noch aufmerksamer wird das Urheberrecht in den Quellenkommentaren sein: 1989.

Kurzer Rückblick


Für diejenigen, die die Dokumente nicht gelesen haben (obwohl dies wirklich getan werden sollte), enthält dieser Abschnitt eine kurze Zusammenfassung und einige ausgewählte Zitate.

Das Fuzzing-Programm generiert zufällige Zeichenströme mit der Möglichkeit, nur druckbare oder nicht druckbare Zeichen zu generieren. Es wird ein bestimmter Anfangswert (Seed) verwendet, um reproduzierbare Ergebnisse zu erzielen, die modernen Fuzzern häufig fehlen. Auf den getesteten Programmen wird eine Reihe von Skripten ausgeführt, die auf das Vorhandensein grundlegender Speicherauszüge prüfen. Hänge werden manuell erkannt. Adapter bieten zufällige Eingaben für interaktive Programme (Artikel von 1990), Netzwerkdienste (1995) und grafische X-Anwendungen (1995).

In einem Artikel aus dem Jahr 1990 wurden vier Prozessorarchitekturen (i386, CVAX, Sparc, 68020) und fünf Betriebssysteme (4.3 BSD, SunOS, AIX, Xenix, Dynix) getestet. In einem Artikel von 1995 eine ähnliche Auswahl an Plattformen. Im ersten Artikel fallen je nach Plattform 25-33% der Dienstprogramme aus. In einem nachfolgenden Artikel liegen diese Zahlen zwischen 9% und 33%, wobei GNU (unter SunOS) und Linux die niedrigste Ausfallrate aufweisen.

Ein Artikel aus dem Jahr 1990 kam zu dem Schluss, dass 1) Programmierer keine Array-Grenzen oder Fehlercodes überprüfen, 2) Makros das Lesen und Debuggen von Code erschweren und 3) die C-Sprache sehr unsicher ist. Die extrem unsichere Funktion und das Typsystem C wurden besonders erwähnt. Während des Tests fanden die Autoren Jahre vor ihrer Massenausnutzung Schwachstellen im Format String. Der Artikel schließt mit einer Umfrage unter Benutzern, wie oft sie Fehler beheben oder melden. Es stellte sich heraus, dass das Melden von Fehlern schwierig war und wenig Interesse daran bestand, sie zu beheben.

In einem Artikel aus dem Jahr 1995 wird Open Source-Software erwähnt und erläutert, warum sie weniger Fehler aufweist. Zitat:

Als wir die Ursachen der Fehler untersuchten, trat ein beunruhigendes Phänomen auf: Viele der 1990 gemeldeten Fehler (etwa 40%) sind 1995 noch in ihrer genauen Form vorhanden. ...

Die hier verwendeten Methoden sind einfach und meist automatisiert. Es ist schwer zu verstehen, warum Entwickler diese einfache und kostenlose Quelle nicht verwenden, um die Zuverlässigkeit zu erhöhen.

Erst in 15 bis 20 Jahren wird die Fuzzing-Technik zur Standardpraxis für große Anbieter.

Es scheint mir auch, dass diese Erklärung von 1990 zukünftige Ereignisse vorsieht:

Oft wird der lakonische Programmierstil C auf das Äußerste gebracht, die Form hat Vorrang vor der richtigen Funktion. Die Möglichkeit eines Überlaufs im Eingabepuffer ist eine potenzielle Sicherheitslücke, wie der jüngste Internet-Wurm gezeigt hat.

Testmethodik


Glücklicherweise stellt Dr. Barton 30 Jahre später immer noch den vollständigen Quellcode, die Skripte und die Daten zur Verfügung, um seine Ergebnisse zu reproduzieren : ein lobenswertes Beispiel, dem andere Forscher folgen sollten. Skripte funktionieren ohne Probleme, und das Fuzzing-Tool erforderte nur geringfügige Änderungen zum Kompilieren und Ausführen.

Für diese Tests haben wir Skripte und Eingaben aus dem fuzz-1995-basic-Repository verwendet , da es die neueste Liste der getesteten Anwendungen gibt . Laut README sind hier die gleichen zufälligen Eingaben wie in der ursprünglichen Studie. Die folgenden Ergebnisse für modernes Linux werden genau mit demselben Fuzzing-Code und denselben Eingabedaten wie in den Originalartikeln erhalten. Nur die Liste der zu testenden Dienstprogramme hat sich geändert.

Dienstprogramm ändert sich über 30 Jahre


Offensichtlich haben sich in den letzten 30 Jahren einige Änderungen an Linux-Softwarepaketen ergeben, obwohl einige bewährte Dienstprogramme ihren Stammbaum seit Jahrzehnten beibehalten haben. Wo immer möglich, haben wir moderne Versionen derselben Programme aus einem Artikel von 1995 übernommen. Einige Programme sind nicht mehr verfügbar, wir haben sie ersetzt. Begründung für alle Ersetzungen:

  • cfecc1 : Entspricht dem C-Präprozessor aus dem Artikel von 1995.
  • dbxgdb : Entspricht dem Debugger von 1995.
  • ditroffgroff : ditroff nicht mehr verfügbar.
  • dtblgtbl : Entspricht GNU Troff des alten Dienstprogramms dtbl .
  • clispclisp : Die Standardimplementierung von lisp.
  • moreless : Weniger ist mehr!
  • prologswipl : Es gibt zwei Optionen für Prolog: SWI Prolog und GNU Prolog. SWI Prolog ist vorzuziehen, da es sich um eine ältere und vollständigere Implementierung handelt.
  • awkgawk : GNU-Version von awk .
  • ccgcc : Der Standard-C-Compiler.
  • compressgzip : GZip ist der konzeptionelle Nachkomme des alten Unix-Dienstprogramms compress .
  • lintsplint : lint unter der GPL umgeschrieben.
  • /bin/mail/usr/bin/mail : Äquivalentes Dienstprogramm auf andere Weise.
  • f77fort77 : Es gibt zwei Varianten des Fortan77-Compilers: GNU Fortran und Fort77. Die erste wird für Fortran 90 und die zweite für Fortran77-Unterstützung empfohlen. Das f2c Programm f2c aktiv unterstützt, seine Liste der Änderungen wird seit 1989 beibehalten.

Ergebnisse


Die Fuzzing-Technik von 1989 findet 2018 immer noch Fehler. Aber es gibt einige Fortschritte.

Um den Fortschritt zu messen, benötigen Sie eine Grundlage. Glücklicherweise gibt es ein solches Framework für Linux-Dienstprogramme. Obwohl Linux zum Zeitpunkt des ursprünglichen Artikels im Jahr 1990 noch nicht existierte, startete ein zweiter Test 1995 den gleichen Fuzzing-Code für Dienstprogramme aus der Slackware 2.1.0-Distribution von 1995. Die entsprechenden Ergebnisse sind in Tabelle 3 des Artikels von 1995 (S. 7-9) angegeben . Im Vergleich zu kommerziellen Wettbewerbern sieht GNU / Linux sehr gut aus:

Der Prozentsatz der Dienstprogrammabstürze in der kostenlosen Linux-Version von UNIX war mit 9% am zweithöchsten.

Vergleichen wir also die Linux-Dienstprogramme von 1995 und 2018 mit den Fuzzing-Tools von 1989:

Ubuntu 18.10 (2018)Ubuntu 18.04 (2018)Ubuntu 16.04 (2016)Ubuntu 14.04 (2014)Slackware 2.1.0 (1995)
Abstürze1 (f77)1 (f77)2 (f77, ul)2 (swipl, f77)4 (ul, flex, indent, gdb)
Friert ein1 (Zauber)1 (Zauber)1 (Zauber)2 (Zauber, Einheiten)1 (ctags)
Insgesamt getestet8181818155
Fehler / Einfrieren,%2%2%4%5%9%

Überraschenderweise ist die Anzahl der Abstürze und Einfrierungen von Linux selbst unter der neuesten Version von Ubuntu immer noch größer als Null. Daher ruft f77 das Programm f77 mit einem Segmentierungsfehler auf, und das f77 hängt an zwei Versionen der f77 .

Welche Fehler?


Ich konnte die Grundursache einiger Fehler manuell herausfinden. Einige Ergebnisse, wie z. B. ein Fehler in glibc, waren unerwartet, während andere, z. B. sprintf mit einer festen Puffergröße, vorhersehbar waren.

Ul Versagen


Der Fehler in ul ist eigentlich ein Fehler in glibc. Insbesondere wurde es hier und hier (eine andere Person fand es in ul ) im Jahr 2016 gemeldet. Laut Bug-Tracker ist der Fehler immer noch nicht behoben. Da der Fehler unter Ubuntu 18.04 und höher nicht reproduziert werden kann, wird er auf Verteilungsebene behoben. Nach den Kommentaren zum Bug-Tracker zu urteilen, kann das Hauptproblem sehr ernst sein.

Absturz f77


Das Programm f77 ist im Paket fort77 enthalten, das selbst ein Shell-Skript für f2c , den Quellübersetzer von Fortran77 nach C. Das Debuggen von f2c zeigt, dass ein Fehler auftritt, wenn die errstr Funktion eine zu lange Fehlermeldung druckt. Der f2c-Quellcode zeigt, dass die sprintf-Funktion verwendet wird, um eine Zeichenfolge variabler Länge in einen Puffer fester Größe zu schreiben:

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

Es scheint, dass dieser Code seit der Erstellung von f2c erhalten geblieben ist. Das Programm hat seit mindestens 1989 eine Geschichte des Wandels . 1995 wurde der Fortran77-Compiler beim erneuten Fuzzing nicht getestet, da das Problem sonst früher gefunden worden wäre.

Zauber einfrieren


Ein großartiges Beispiel für einen klassischen Deadlock. spell ispell durch eine Pipe. spell liest den Text zeilenweise und erzeugt einen ispell mit der Größe der Zeile in ispell . ispell liest jedoch maximal BUFSIZ/2 Bytes gleichzeitig (4096 Bytes auf meinem System) und gibt einen Blockierungsdatensatz aus, um sicherzustellen, dass der Client Validierungsdaten erhalten hat, die bisher verarbeitet wurden. Zwei verschiedene ispell zwangen den spell , eine Zeichenfolge mit mehr als 4096 Zeichen für ispell zu schreiben, was zu einem Deadlock führte: Der spell wartet darauf, dass ispell die gesamte Zeichenfolge liest, während ispell auf den spell wartet, um spell bestätigen, dass die ursprünglichen Rechtschreibkorrekturen gelesen wurden.

Einheiten aufhängen


Auf den ersten Blick scheint es eine Endlosschleifenbedingung zu geben. Der Hang scheint in libreadline und nicht in units , obwohl neuere Versionen von units nicht unter diesem Fehler leiden. Das Änderungsprotokoll zeigt an, dass eine Eingabefilterung hinzugefügt wurde, die dieses Problem versehentlich beheben könnte. Eine gründliche Untersuchung der Gründe würde jedoch den Rahmen dieses Blogs sprengen. Vielleicht ist der Weg, libreadline aufzuhängen, libreadline noch da.

Swipl Absturz


Der Vollständigkeit halber möchte ich den swipl Absturz erwähnen, obwohl ich ihn nicht sorgfältig untersucht habe, da der Fehler seit langem behoben wurde und von ziemlich hoher Qualität zu sein scheint. Fehler ist eigentlich eine Anweisung (d. H. Eine, die niemals passieren sollte), die beim Konvertieren von Zeichen aufgerufen wird:

[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


Der Absturz ist immer schlimm, aber zumindest hier kann das Programm einen Fehler melden, der früh und laut abstürzt.

Fazit


In den letzten 30 Jahren war das Fuzzing ein einfacher und zuverlässiger Weg, um Fehler zu finden. Obwohl in diesem Bereich aktive Forschung betrieben wird , findet selbst der Fuzzer von vor 30 Jahren erfolgreich Fehler in modernen Linux-Dienstprogrammen.

Der Autor der Originalartikel sagte die Sicherheitsprobleme voraus, die C in den kommenden Jahrzehnten verursachen würde. Er argumentiert überzeugend, dass unsicherer Code zu einfach in C zu schreiben ist und nach Möglichkeit vermieden werden sollte. Insbesondere zeigen die Artikel, dass Fehler auch bei einfachster Phaseneinstellung auftreten, und solche Tests sollten in die Standardpraxis der Softwareentwicklung einbezogen werden. Leider wird dieser Rat seit Jahrzehnten nicht mehr befolgt.

Ich hoffe, Ihnen hat diese 30-jährige Retrospektive gefallen. Warten Sie auf den nächsten Fuzzing in 2000-Artikel, in dem wir untersuchen, wie robust Windows 10-Anwendungen im Vergleich zu ihren Windows NT / 2000-Entsprechungen sind, wenn sie mit Fuzzer getestet werden . Ich denke, die Antwort ist vorhersehbar.

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


All Articles