21.6 Hilfe beim Finden von Bugs
Dieser Abschnitt wird sich damit befassen, wie man mithilfe verschiedener Programme, Bugs in Softwareprojekten finden kann. Zu diesem Zweck werden wir das folgende C-Programm analysieren, das ich absichtlich mit diversen Bugs versehen habe.
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <err.h> int main(int argc, char *argv[]) { char buf[16] = { '\0' }; char *p; if (argc > 1) strcpy(buf, argv[1]); p = (char *) calloc(15, 1); if (p) { int i; for (i = 0; i <= 16; i++) p[i] = buf[i]; printf("%s\n", p); } else err(1, "calloc"); return 0; }
Listing 21.28 bug1.c
Dieses Programm ist anfällig für den typischen Buffer-Overflow. Das bedeutet, dass ein statischer Buffer (hier mit einer Größe von 16 Byte (0xf)) überschrieben wird. Überschrieben werden kann der Buffer, falls das erste Argument, das dem Programm übergeben wurde, größer als 15 Bytes ist. Denn in diesem Fall wird das sechzehnte Zeichen, das eigentlich eine abschließende Null sein sollte, überschrieben. Werden noch mehr Bytes überschrieben, so werden andere Variablen der Stackframe oder auch auf dem Stack gesicherte Registerwerte überschrieben. Dieses Problem haben wir bereits sehr genau in unserem Buch »Praxisbuch Netzwerksicherheit« beschrieben.
Das zweite große Problem ist, dass der Pointer »p« kleiner als der Buffer »buf« ist. Da in ihn allerdings der gesamte Buffer sowie zwei zusätzliche Bytes kopiert werden, wird auch dieser Heap-Buffer überschrieben. <Auch zum Thema Heap-Overflows finden Sie weitere Informationen in unserem »Praxisbuch Netzwerksicherheit«.> Man bezeichnet ein solches Problem, bei dem ein Buffer um ein Byte überschrieben wird, als Off-by-One Overflow. In unserem Fall liegt ein zweifacher Off-by-One Bug vor, da die Schleife erstens nur bis 15 (und nicht 16) durchlaufen sollte und zweitens ein < anstelle von <= verwendet werden müsste.
21.6.1 ProPolice
Seit der Version 3.4 ist der sogenannte ProPolice Patch – eine Entwicklung von IBM – für den GNU C Compiler verfügbar. <Seit gcc-4.1 ist sie fester Bestandteil des gcc.> Diese Erweiterung macht es möglich, dass Buffer-Overflows innerhalb einer Stackframe verhindert werden können. Zu diesem Zweck wird der Stack etwas modifiziert: Lokale Stack-Variablen, die keine Buffer sind, werden vor den Buffern plaziert, sodass Buffer (deren Elemente in die entgegengesetzte Richtung aufsteigen) nur andere Buffer überschreiben können. Hinter den Buffern wird zudem ein spezieller Canary-Wert eingefügt.
Dieser Zufallswert wird bei einem Overflow überschrieben, wodurch die Veränderung des Wertes entdeckt werden kann. In diesem Fall wird das Programm abgebrochen.
Wir setzen die ProPolice-Eerweiterung seit geraumer Zeit in der Entwicklung der »Hardened Linux«-Distribution ein. Auch andere Distributionen (etwa Ubuntu oder Adamantix) benutzen diese Erweiterung mittlerweile standardmäßig.
Ein Test
Versuchen wir nun, das obige Programm zu einem Overflow zu führen. Anschließend werden wir es mit ProPolice schützen (-fstack-protector) und sehen, dass das Programm gekillt wird.
Achtung: Bei Ubuntu muss zur Übersetzung ohne die Stack Protection explizit -fno-stack-protector gesetzt werden, da sie automatisch aktiv ist.
$ gcc -o bug1 bug1.c $ ./bug1 123 123 $ ./bug1 `perl -e 'print "A"x99'` AAAAAAAAAAAAAAAA Segmentation fault (core dumped) $ gcc -o bug1 bug1.c -fstack-protector $ ./bug1 `perl -e 'print "A"x99'` AAAAAAAAAAAAAAAAA *** stack smashing detected ***: ./bug1 terminated Abort (core dumped)
Listing 21.29 ProPolice Protection
Mit ProPolice steht dem Programmierer also eine Erweiterung zur Verfügung, die oft in der Lage ist, Schlimmeres zu verhindern. <Es gibt Möglichkeiten, ProPolice zu umgehen, aber diese hier zu besprechen würde zu weit führen.>
21.6.2 flawfinder und RATS
Besser noch ist es, wenn man in der Lage ist, solche Bugs während der Entwicklung zu finden, um die gezielte Programmbeendigung durch die ProPolice gar nicht erst stattfinden zu lassen.
Hierbei helfen Programme wie flawfinder, das mittlerweile nicht mehr weiterentwickelte pscan oder RATS.
Für all diese Tools gilt allerdings, dass ihre Ausgaben von Hand überprüft werden müssen. Es handelt sich bei allen Angaben immer nur um mögliche Bugs und Sicherheitsprobleme.
flawfinder
flawfinder wurde von David Wheeler entwickelt und wird zur Analyse von C/C++-Code eingesetzt. Es untersucht den Quellcode nach bestimmten tückischen Funktionen, die zu Problemen führen können. Zudem erkennt es einige gängige Fehlerquellen.
$ flawfinder bug1.c Flawfinder version 1.26, (C) 2001-2004 David A. Wheeler. Number of dangerous functions in C/C++ ruleset: 158 Examining bug1.c bug1.c:13: [4] (buffer) strcpy: Does not check for buffer overflows when copying to destination. Consider using strncpy or strlcpy (warning, strncpy is easily misused). bug1.c:9: [2] (buffer) char: Statically-sized arrays can be overflowed. Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length. Hits = 2 Lines analyzed = 26 in 0.54 seconds (678 lines/second) Physical Source Lines of Code (SLOC) = 21 Hits@level = [0] 0 [1] 0 [2] 1 [3] 0 [4] 1 [5] 0 Hits@level+ = [0+] 2 [1+] 2 [2+] 2 [3+] 1 [4+] 1 [5+] 0 Hits/KSLOC@level+ = [0+] 95.2381 [1+] 95.2381 [2+] 95.2381 [3+] 47.619 [4+] 47.619 [5+] 0 Minimum risk level = 1 Not every hit is necessarily a security vulnerability. There may be other security vulnerabilities; review your code!
Listing 21.30 flawfinder untersucht bug.cc
Wie Sie sehen, weist flawfinder auf zwei Probleme hin: Zum einen wird die unsichere Funktion strcpy() verwendet, die einen Buffer ohne Längenüberprüfung in einen anderen kopiert. Dies kann durch entsprechende Verwendung von strncpy oder auch strlcpy() verhindert werden, aber auch diese Funktionen müssen fehlerfrei verwendet werden, damit sie nicht zu Bugs führen.
Zum anderen wird auf die statische Größe des Buffers hingewiesen, was oftmals zu Overflow-Problemen führen kann. Den Off-by-One Bug hat flawfinder also nicht entdeckt.
RATS
Das Programm RATS funktioniert ähnlich wie flawfinder, unterstützt aber auch PHP-, Python- und Perl-Code. Wie Sie sehen, findet RATS in unserem Fall keine weiteren Fehler und liefert ein gleichwertiges Ergebnis wie flawfinder.
$ rats bug1.c Entries in perl database: 33 Entries in python database: 62 Entries in c database: 336 Entries in php database: 55 Analyzing bug1.c bug1.c:9: High: fixed size local buffer Extra care should be taken to ensure that character arrays that are allocated on the stack are used safely. They are prime targets for buffer overflow attacks. bug1.c:13: High: strcpy Check to be sure that argument 2 passed to this function call will not copy more data than can be handled, resulting in a buffer overflow. Total lines analyzed: 27 Total time 0.019469 seconds 1386 lines per second
Listing 21.31 RATS scannt bug1.c.
21.6.3 Electric Fence
Um den Heap-Overflow in bug1.c zu finden, brauchen wir ein weiteres Tool: Electric Fence.
Electric Fence ist eine Bibliothek, die in der Lage ist, Zugriffe auf nicht reservierten Speicher zu finden. Oftmals gehört ein Speicher, der überschrieben wurde, noch zum Kontext des Programms, wodurch das Programm weiterläuft und der Bug nicht aufgedeckt wird. Flawfinder sorgt hingegen dafür, dass das Programm in einem solchen Fall sofort abstürzt.
Wie wir sehen, stürzt das Programm in unserem Fall tatsächlich nicht ab, auch wenn wir dem Programm ganze 16 Bytes übergeben. Beachten Sie zudem, dass die Anzahl der übergebenen Bytes auf den Heap-Buffer keinen Einfluss hat, da immer so viel Bytes in ihn kopiert werden, wie es Schleifendurchläufe gibt.
Achtung: Auch hier gilt, dass bei Distributionen wie Ubuntu, die standardmäßig die Stack Protection aktiviert haben, diese explizit abgestellt werden muss.
$ gcc -o bug1 bug1.c $ ./bug1 `perl -e 'print "A"x16;'` AAAAAAAAAAAAAAAA
Listing 21.32 Das Programm stürzt nicht ab.
Nun übersetzen wir das Programm mit der Electrice Fence Library. Um es im GNU Debugger analysieren zu können, übergeben wir zudem den Parameter -g. Da die Anzahl der Bytes für den Overflow im Heap-Buffer nicht relevant ist, können wir das Programm ohne Parameter starten.
$ gcc -g -o bug1 bug1.c -fno-stack-protector -lefence
$ ./bug1
Electric Fence 2.1 Copyright (C) 1987-1998 Bruce Perens.
Segmentation fault (core dumped)
Listing 21.33 Electrice Fence
Wie Sie sehen, lässt Electric Fence das Programm tatsächlich abstürzen. Nun werden wir eine Analyse im GNU Debugger durchführen.
$ gdb bug1 GNU gdb 6.6-debian Copyright (C) 2006 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i486-linux-gnu"... Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1". (gdb) run Starting program: /home/swendzel/books/kompendium2/examples/bugs/bug1 [Thread debugging using libthread_db enabled] [New Thread –1210296640 (LWP 6405)] Electric Fence 2.1 Copyright (C) 1987-1998 Bruce Perens. Program received signal SIGSEGV, Segmentation fault. [Switching to Thread –1210296640 (LWP 6405)] 0x0804857c in main (argc=1, argv=0xbfb06284) at bug1.c:19 19 p[i] = buf[i]; (gdb) list 19 14 15 p = (char *) calloc(15, 1); 16 if (p) { 17 int i; 18 for (i = 0; i <= 16; i++) 19 p[i] = buf[i]; 20 printf("%s\n", p); 21 } else 22 err(1, "calloc"); 23 (gdb) quit The program is running. Exit anyway? (y or n) y
Listing 21.34 bug1 im gdb
Fazit
Electric Fence hat unseren Speicherzugriffsfehler gefunden. In Kombination mit einem anderen Programm wie flawfinder oder RATS steht einem hiermit eine sehr vernünftige Toolchain zur Analyse von Code zur Verfügung.