In diesem Kapitel werden die Themen Buffer Overflow und Memory Leaks besprochen. Es soll gezeigt werden, dass diese Probeme kein Mythos sind und wie man diese Vermeiden kann. C C++ C/C++ Sicherheit Buffer Overflow Memory Leaks Sicherheit - Buffer Overflow Memory Leaks Kapitel 26: Sicheres Programmieren

26.2. Memory Leaks (Speicherlecks)            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Wie bei Buffer Overflows sind auch Memory Leaks in den meisten Fällen durch Programmierfehler zu erklären. Der erste Verdacht, es könnte sich bei Memory Leaks um Hardwareprobleme handeln, täuscht.

Ein Memory Leak entsteht, wenn ein Programm dynamisch Speicher allokiert (malloc(), realloc() ...) und diese Speicherressourcen nicht mehr an das System zurückgibt (mittels free()). Es steht nicht unendlich viel Speicher vom Heap dafür zur Verfügung.

Programme wie das jetzt folgende erzeugen keine Probleme, wenn der Speicher nicht mehr an den Heap zurückgegeben wird:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
   char *p;

   p = (char *)malloc(sizeof("Hallo Welt\n"));
   strcpy(p, "Hallo Welt\n");
   if(NULL == p)
      {
         fprintf(stderr, "Abbruch: Speichermangel !!\n");
         exit(0);
      }
   printf("%s",p);
   return 0;
}

Hier bekommt der Heap seinen Speicher bei Beendigung des Programms sofort wieder zurück.

Was ist aber mit Programmen, die dauerhaft im Einsatz sein müssen? Ein gutes Beispiel sind Telefongesellschaften, die jedes laufende, eingehende und ausgehende Gespräch nach dem FIFO-Prinzip auf dem Heap ablegen und ständig für diese Datensätze Speicher auf dem Heap allokieren bzw. für ältere Datensätze wieder freigeben müssen.

Ein (stupides) Beispiel:

#include <stdio.h>
#include <stdlib.h>

int main()
{
   char *p;

   while(p = (char *)malloc(64000))
      {
         if(NULL == p)
            {
               fprintf(stderr, "Speicherplatzmangel!!\n");
               exit(0);
            }
         /*Tu was mit dem reservierten Speicher*/
      }
   return 0;
}

Dieses Programm wird wohl eine Weile ohne Probleme laufen. Aber umso länger das Programm läuft, umso mehr Speicher benötigt es vom Heap. Dies wird sich auf Dauer schlecht auf die Performance des Systems auswirken. Denn der Heap ist ja nicht nur für ein Programm allein da. Die anderen Programme, die ebenfalls Ressourcen benötigen, werden immer langsamer. Am Ende bleibt einem nichts anderes mehr übrig, als das System neu zu starten (abhängig vom Betriebssystem).

Meistens ist es aber schon geschehen, und das Programm längst fertig gestellt, wenn ein Speicherleck gefunden wird. Dann kann guter Rat teuer werden, wenn Sie sich nicht auskennen.

Eine primitive Möglichkeit, sofern Sie im Besitz des Quellcodes sind, ist es, so genannte Wrapper-Makros für speicherallozierte und speicherfreigebende Funktionen zu schreiben. Beispielsweise für die malloc()-Funktion:

#define malloc(size) \
        malloc(size);\
        printf("malloc in Zeile %ld der Datei %s (%ld Bytes) \n"\
        ,__LINE__,__FILE__, size);\
        count_malloc++;

Bei Verwendung der malloc()-Funktion im Programm wird jetzt jeweils eine Ausgabe auf dem Bildschirm erzeugt, die anzeigt, in welcher Zeile, in welchem Programm und wie viel Speicher malloc() verwendet. Außerdem wird die Verwendung von malloc() mitgezählt.

Dasselbe wird anschließend auch mit der Funktion free() vorgenommen. Die Anzahl der gezählten malloc()- und free()-Aufrufe wird am Ende in eine Datei namens DEBUG_FILE geschrieben.

#ifndef MEM_CHECK
#define MEM_CHECK
#define DEBUG_FILE "Debug"

static int count_malloc=0;
static int count_free  =0;
FILE *f;

#define malloc(size) \
        malloc(size);\
        printf("malloc in Zeile %ld der Datei %s (%ld Bytes) \n"\
        ,__LINE__,__FILE__, size);\
        count_malloc++;

#define free(x)\
        free(x); \
        x=NULL;\
        printf("free in Zeile %ld der Datei %s\n",__LINE__,__FILE__);\
        count_free++;

#define return 0; \
        f=fopen(DEBUG_FILE, "w");\
        fprintf(f, "Anzahl malloc : %ld\n",count_malloc);\
        fprintf(f, "Anzahl free   : %ld\n",count_free);\
        fclose(f);\
        printf("Datei : %s erstellt\n", DEBUG_FILE);\
        return 0;

#endif

Hier wurde eine Headerdatei namens mem_check.h erstellt, womit alle Aufrufe von malloc() und free() auf dem Bildschirm ausgeben werden. Und zwar darüber, in welcher Datei und welcher Zeile sich ein Aufruf dieser Funktion befindet. Außerdem wird auch die Anzahl der malloc()- und free()-Aufrufe mitgezählt. Sind mehr malloc()-Aufrufe als free()-Aufrufe vorhanden, wurde auf jeden Fall ein Speicherleck im Programm gefunden. Hier ein Listing zum Testen:

#include <stdio.h>
#include <stdlib.h>
#include "mem_check.h"

int main()
{
   char *p;

   p = (char *)malloc(sizeof("Hallo Welt\n"));
   if(NULL == p)
      {
         fprintf(stderr, "Speichermangel!!!\n");
         exit(0);
      }
   strcpy(p, "Hallo Welt\n");
   printf("%s",p);
   malloc(1024);
   free(p);
   return 0;
}

In der Praxis und bei größeren Projekten ist diese Version, Memory Leaks aufzuspüren, nur bedingt geeignet. Mit dem Makro return 0 habe ich es mir allzu leicht gemacht. Dies setzt nämlich voraus, dass ein Programm auch damit beendet wird. Oft haben Sie es aber mit dauerhaft laufenden Programmen zu tun.

Genauso sieht es mit der Zuordnung des allokierten und freigegebenen Speichers aus. Welches malloc() gehört zu welchem free()? Aber das Prinzip dürfte verstanden sein. Wenn Sie Fehler wie Memory Leaks finden wollen, haben Sie notfalls mit Wrapper-Makros eine gute Möglichkeit.

Meistens werden Sie schon eher auf eines der mittlerweile vielen Tools oder auf eine der Bibliotheken, die dafür programmiert wurden, zurückgreifen.

26.2.1 Bibliotheken und Tools zu Memory Leaks
Es gibt mittlerweile eine unüberschaubare Menge von solchen Debugging Tools. Daher erfolgt hier ein kleiner Überblick mit Angabe der Bezugsquellen. Meistens finden Sie dabei auf diesen Webseiten gleich die Dokumentation für die Anwendung.

ccmalloc
Bezugsquelle: http://www.inf.ethz.ch/personal/biere/projects/ccmalloc/
ccmalloc wird mit dem C/C++-Programm verlinkt und gibt nach Beendigung des Programms einen Bericht über Memory Leaks aus. ccmalloc ist nicht geeignet, um festzustellen, ob versucht wurde, aus illegalen Speicherbereichen zu lesen.

dbmalloc
Bezugsquelle: ftp://ftp.digital.com/pub/usenet/comp.sources.misc/volume32/dbmalloc/
dbmalloc ist in einer kommerziellen und einer kostenlosen Version erhältlich. Besondere Merkmale von dbmalloc sind:

Generell wird dbmalloc, wie die meisten anderen Memory-Leaks-Tools zu einem Programm hinzugelinkt. Sie müssen also im Besitz des Quellcodes sein, um diesen neu zu übersetzen. Eine gute Anleitung in deutscher Sprache zu dieser Bibliothek finden Sie unter der URL: http://www.c-handbuch.org/

mpatrol
Bezugsquelle: http://www.cbmamiga.demon.co.uk/mpatrol/
mpatrol ist ein leistungsfähiges Tool zum Auffinden von Memory Leaks, was sich leider auf die Performance des Programms negativ auswirkt. Folgende Funktionsmerkmale stehen Ihnen dabei zur Verfügung:

Um ein Programm mit mpatrol zu testen, ist genau wie bei den meisten anderen Tools ein Überschreiben der speicheranfordernden und freigebenden Funktionsaufrufe notwendig. Bei mpatrol können Sie dies auf zwei Arten machen: Entweder Sie linken das Programm zu der statischen oder dynamischen Bibliothek oder Sie binden diese später durch einen Aufruf von

mpatrol --dynamc ./testprog -i file 

dynamisch mit in das Programm ein. Die letzte Möglichkeit funktioniert allerdings nur, wenn das Programm schon dynamisch zur Standard-C-Bibliothek übersetzt wurde und selbst dann nur auf einigen wenigen Systemen, die diesen Befehl unterstützen. Für eine deutsche Dokumentation sei auch hier wieder auf die Webseite http://www.c-handbuch.org/ verwiesen.

Es gibt außer diesen hier genannten Tools noch eine Reihe weiterer sehr guter Tools zum Auffinden von Memory Leaks. Einen guten Überblick können Sie sich auf der Seite http://www.cs.colorado.edu/~zorn/MallocDebug.html verschaffen.

26.3. Ausblick Sicherheitsprobleme            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Es gibt noch eine Menge weiterer Sicherheitsprobleme, welche Sie auf den ersten Blick gar nicht als solche erkennen können. Schließlich haben Sie keine Garantie, dass Ihr Programm vor allen Problemen geschützt ist, wenn Sie auf sichere Funktionen zurückgreifen.

Zum Abschluss des Kapitels folgt hier noch ein kleiner Leitfaden zum Thema Sicherheit. Wenn Sie diese Punkte beherzigen, sollten sich Sicherheitsprobleme auf ein Minimum reduzieren lassen.

Weiter mit Kapitel 27: CGI mit C            zum Inhaltsverzeichnis