gets()

gets() ist in der stdio definiert, die in C über stdio.h, bzw in C++ über cstdio eingebunden wird.

:!: gets() wurde mit C11 aus dem C-Standard entfernt. Details finden sich am Ende dieser Seite.

Funktion

gets() liest einen String durch Benutzereingaben ein. Da sie aber für ihre Pufferüberlaufsschwächen gefürchtet ist, sollte sie nie benutzt werden. Stattdessen sollte auf die Funktion fgets() ausgewichen werden. Dieser Artikel beschreibt der Vollständigkeit halber die Anwendung, soll aber vor allem zeigen, warum diese Funktion so gefährlich ist.

Signatur

#include <stdio.h>
char * gets( char * str );

str: Ein char-Array, welches der String geschrieben werden soll.
„Return Value“: NULL im Fehlerfall, ansonsten der übergebene Parameter str.

Fehlerquellen

Die Funktion gets() ist schon fast ein Symbol für Pufferüberläufe. Sie ist die bekannteste und gefürchtetste aller von Bufferoverflows gefährdeten Funktionen, weil sie, wie ihre die anderen, die Länge der Eingabe nicht überprüft.

Im folgenden Beispiel darf man maximal 15 Zeichen eingeben. Was passiert aber intern, wenn man mehr als 15 Zeichen eingibt? Die nachfolgenden Werte werden dann in einem anderen Speicherbereich überschrieben, weil str nur ein für 16 Zeichen (inklusive binärem Nullzeichen) reserviertes Array ist.

Leider kommt es noch häufig vor, dass einige (meist veraltete) Lehrbücher ohne jegliche Warnung gets() als normal nutzbare Eingabefunktion beschreiben. Es ist sogar noch in der heutigen Zeit nicht selten, dass diese Funktion in Anwendungen implementiert wird.

Umstrittene Programmierstile, wie z.B. goto, können noch in einigen wenigen Fällen nützlich sein. Das kann man von der Nutzung von gets() nicht behaupten. Es gibt keinen sinnvollen Anwendungszweck dafür. gets() sollte also ohne Ausnahme niemals benutzt werden.

Eine sichere Alternative zu gets() ist die Funktion fgets().

Außerdem wäre es eine Überlegung wert, sofern es der vorgesehene Programmablauf zulässt, die Eingabe vor dem Programmstart über Kommandozeilenparameter zu verwalten.

Beispiel

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
  char third[16] = "Third";
  char str[16];
  char second[16] = "Second";
  printf("Enter String: ");
  gets(str);
 
  printf( "Eingabe: %x - %.10s\n", (unsigned int)str, str );
  printf( "Second : %x - %.10s\n", (unsigned int)second, second );
  printf( "Third  : %x - %.10s\n", (unsigned int)third, third  );
  return EXIT_SUCCESS;
}

Das binäre Nullzeichen '\0' wird automatisch angefügt. Das bedeutet automatisch, dass die einzulesende Zeichenkette nur 9 Zeichen groß sein darf. Ansonsten gibt es unter Umständen die Fehlermeldung 'Segmentation fault' (Unix).

Beim Kompilieren erzeugt der GCC-Compiler bereits eine Warnung:

xin@trinity:~/proggen.org/clib/stdio$ gcc gets.c 
/tmp/ccWHKm2Q.o: In function `main':
gets.c:(.text+0x4e): warning: the `gets' function is dangerous and should not be used.

Ausgabe:

Enter String: First
Eingabe: 184ef7e0 - First
Second : 184ef7d0 - Second
Third  : 184ef7f0 - Third

Buffer Overflow

Wir haben am vorherigen Beispiel gesehen, dass die Strings 0x10 (also 16) Bytes auseinanderliegen. Also geben wir zuerst 16 Bytes ein, um den gültigen Buffer zu füllen und anschließend überfüllen wir den Buffer mit unserer Eingabe:

Enter String: 0123456789012345First
Eingabe: 2a451580 - 0123456789
Second : 2a451570 - Second
Third  : 2a451590 - First

Die Variable third liegt im Speicher hinter string, also der Eingabe. Sie wird daher überschrieben und erhält den neuen Wert „First“, obwohl third niemals hätte überschrieben werden sollen.

Eine detailliertere Erklärung zu Bufferoverflows und wie sie zu vermeiden sind findet sich im Memory Corruption Tutorial.

Entfernung aus dem Standard (C11)

In der Version C11 wurde gets() schließlich aufgrund der oben genannten Probleme komplett aus C entfernt. Stattdessen wurde eine sicherere Variante mit dem Namen gets_s() eingeführt, die als zusätzlichen Parameter noch die maximale Anzahl an zu lesenden Zeichen (inklusive '\0') übergeben bekommt:

char *gets_s( char *str, rsize_t n );

gets_s() entspricht somit der Anwendung von fgets() auf stdin:

fgets( str, n, stdin );

siehe auch