30.6 Hilfe beim Finden von Bugs
Dieser Abschnitt erläutert, wie Sie mithilfe verschiedener Programme Bugs in Softwareprojekten finden können. Zu diesem Zweck werden wir das folgende C-Programm analysieren, das ich absichtlich mit diversen Fehlern versehen habe.
Listing 30.28 bug1.c
#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;
}
Dieses Programm ist anfällig für einen typischen Buffer-Overflow. Das bedeutet, dass ein statischer Puffer (hier mit einer Größe von 16 Byte [0xf]) überschrieben wird. Überschrieben werden kann der Puffer, falls das erste Argument, das dem Programm übergeben wurde, größer als 15 Bytes ist. In diesem Fall wird nämlich das sechzehnte Zeichen, das eigentlich eine abschließende Null sein sollte, überschrieben. Werden noch mehr Bytes kopiert, so werden andere Variablen der Stackframes oder auch auf dem Stack gesicherte Registerwerte überschrieben. Dieses Problem haben wir bereits in unserem Buch »Praxisbuch Netzwerksicherheit« im Detail beschrieben.
Das zweite große Problem besteht darin, dass sich an der Adresse, auf die der Pointer p zeigt, weniger Speicher befindet, als im Puffer buf vorhanden ist. Da in p allerdings der gesamte Puffer sowie zwei zusätzliche Bytes kopiert werden, wird auch dieser Heap-Puffer überschrieben. [Fn. Auch zum Thema Heap-Overflows finden Sie weitere Informationen in unserem »Praxisbuch Netzwerksicherheit«.] Man bezeichnet ein solches Problem, bei dem ein Puffer 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 werden sollte und zweitens ein < anstelle von <= verwendet werden müsste.
30.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. [Fn. Seit gcc-4.1 ist sie fester Bestandteil des gcc.] Mit dieser Erweiterung können Buffer- Overflows innerhalb eines Stackframes verhindert werden. Zu diesem Zweck wird der Stack etwas modifiziert: Lokale Stack-Variablen, die keine Puffer sind, werden vor diesen plaziert, so dass Puffer (deren Elemente in die entgegengesetzte Richtung aufsteigen) nur andere Buffer überschreiben können. Hinter den Puffern wird zudem ein spezieller Canary-Wert eingefügt. Dieser Zufallswert wird bei einem Overflow überschrieben, wodurch eine Veränderung des Werts entdeckt werden kann. In diesem Fall wird das Programm abgebrochen.
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 Stack Protection explizit der gcc-Parameter -fno-stack-protector gesetzt werden, da die Stack Protection automatisch aktiv ist.
Listing 30.29 ProPolice Protection
$ 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)
Mit ProPolice steht dem Programmierer also eine Erweiterung zur Verfügung, die oft in der Lage ist, Schlimmeres zu verhindern. [Fn. Es gibt Möglichkeiten, ProPolice zu umgehen, aber diese hier zu besprechen, würde zu weit gehen.]
30.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 sowie 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
Der Flawfinder wurde von David Wheeler entwickelt und wird zur Analyse von C/C++-Code eingesetzt. Das Programm untersucht den Quellcode nach bestimmten tückischen Funktionen, die zu Problemen führen können. Zudem erkennt es einige häufige Fehlerquellen.
Listing 30.30 Flawfinder untersucht bug.cc
$ 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!
Wie Sie sehen, weist Flawfinder auf zwei Probleme hin: Zum einen wird die unsichere Funktion strcpy() verwendet, die einen Puffer 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 Puffers 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.
Listing 30.31 RATS scannt bug1.c.
$ 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
30.6.3 Electric Fence
Um den Heap-Overflow in bug1.c zu finden, brauchen wir ein weiteres Tool namens Electric Fence, eine Bibliothek, die Zugriffe auf nicht reservierten Speicher findet. Oftmals gehört ein Speicher, der überschrieben wurde, noch zum Kontext des Programms, wodurch dieses weiterläuft und der Bug nicht aufgedeckt wird. Electric Fence sorgt hingegen dafür, dass das Programm in einem solchen Fall sofort abstürzt. Wie Sie sehen, stürzt das Programm in unserem Fall tatsächlich nicht ab, auch wenn wir ihm ganze 16 Bytes übergeben. Beachten Sie zudem, dass die Anzahl der übergebenen Bytes auf den Heap-Puffer keinen Einfluss hat, da immer so viel Bytes in den Puffer kopiert werden, wie es Schleifendurchläufe gibt.
[»]Achtung: Auch hier gilt, dass bei Distributionen wie Ubuntu, die standardmäßig Stack Protection aktiviert haben, diese explizit abgestellt werden muss.
Listing 30.32 Das Programm stürzt nicht ab.
$ gcc -o bug1 bug1.c
$ ./bug1 `perl -e 'print "A"x16;'`
AAAAAAAAAAAAAAAA
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.
Listing 30.33 Electrice Fence
$ 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)
Wie Sie sehen, lässt Electric Fence das Programm tatsächlich abstürzen. Nun führen wir eine Analyse
im GNU Debugger durch.
Listing 30.34 bug1 im gdb
$ 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
Fazit
Electric Fence hat unseren Speicherzugriffsfehler gefunden. In Kombination mit einem anderen Programm wie Flawfinder oder RATS verfügen Sie also über eine sehr effektive Toolchain zur Analyse von Code.
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.