Warum ist gets()
gefährlich
Der erste Internet-Wurm (der Morris-Internet-Wurm ) ist vor etwa 30 Jahren (1988-11-02) entkommen gets()
und hat einen Pufferüberlauf als eine seiner Methoden zur Ausbreitung von System zu System verwendet. Das Grundproblem besteht darin, dass die Funktion nicht weiß, wie groß der Puffer ist. Daher liest sie weiter, bis sie eine neue Zeile findet oder auf EOF stößt und möglicherweise die Grenzen des Puffers überschreitet, den sie erhalten hat.
Sie sollten vergessen, dass Sie jemals gehört haben, dass gets()
es das gibt.
Die C11-Norm ISO / IEC 9899: 2011 wurde gets()
als Standardfunktion gestrichen. Dies ist A Good Thing ™ (sie wurde in ISO / IEC 9899: 1999 / Cor.3: 2007 - Technische Berichtigung offiziell als „veraltet“ und „veraltet“ gekennzeichnet 3 für C99 und dann in C11 entfernt). Leider wird es aus Gründen der Abwärtskompatibilität viele Jahre (dh "Jahrzehnte") in Bibliotheken verbleiben. Wenn es nach mir gets()
ginge, würde die Implementierung von :
char *gets(char *buffer)
{
assert(buffer != 0);
abort();
return 0;
}
Da Ihr Code früher oder später ohnehin abstürzt, ist es besser, die Probleme eher früher als später zu lösen. Ich wäre bereit, eine Fehlermeldung hinzuzufügen:
fputs("obsolete and dangerous function gets() called\n", stderr);
Moderne Versionen des Linux-Kompilierungssystems generieren Warnungen, wenn Sie eine Verknüpfung herstellen gets()
- und auch für einige andere Funktionen, bei denen ebenfalls Sicherheitsprobleme auftreten ( mktemp()
,…).
Alternativen zu gets()
fgets ()
Wie alle anderen sagten, besteht die kanonische Alternative dazu gets()
darin fgets()
, stdin
als Dateistream anzugeben .
char buffer[BUFSIZ];
while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
...process line of data...
}
Was noch niemand erwähnt hat, ist, dass gets()
es die Newline nicht enthält, aber fgets()
tut. Daher müssen Sie möglicherweise einen Wrapper verwenden fgets()
, der die neue Zeile löscht:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
return buffer;
}
return 0;
}
Oder besser:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
buffer[strcspn(buffer, "\n")] = '\0';
return buffer;
}
return 0;
}
Auch als caf Punkte in einem Kommentar und aus paxdiablo zeigt in seiner Antwort mit fgets()
Ihnen Daten auf einer Linie übrig haben könnten. Mein Wrapper-Code lässt diese Daten beim nächsten Mal gelesen werden. Sie können es leicht ändern, um den Rest der Datenzeile zu verschlingen, wenn Sie es vorziehen:
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
else
{
int ch;
while ((ch = getc(fp)) != EOF && ch != '\n')
;
}
Das verbleibende Problem besteht darin, wie die drei verschiedenen Ergebniszustände gemeldet werden - EOF oder Fehler, Zeilen lesen und nicht abgeschnitten und Teilzeile lesen, aber Daten wurden abgeschnitten.
Dieses Problem tritt nicht auf, gets()
weil es nicht weiß, wo Ihr Puffer endet und fröhlich über das Ende hinaus trampelt, was Ihr wunderschön gepflegtes Speicherlayout verwüstet und häufig den Rückgabestapel (einen Stapelüberlauf) durcheinander bringt, wenn der Puffer zugewiesen ist den Stapel oder das Trampeln über die Steuerinformationen, wenn der Puffer dynamisch zugewiesen ist, oder das Kopieren von Daten über andere wertvolle globale (oder Modul-) Variablen, wenn der Puffer statisch zugewiesen ist. Nichts davon ist eine gute Idee - sie verkörpern den Ausdruck "undefiniertes Verhalten".
Es gibt auch den TR 24731-1 (Technischer Bericht des C-Standardausschusses), der sicherere Alternativen zu einer Vielzahl von Funktionen bietet, darunter gets()
:
§6.5.4.1 Die gets_s
Funktion
Zusammenfassung
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);
Laufzeitbeschränkungen
s
darf kein Nullzeiger sein. n
darf weder gleich Null noch größer als RSIZE_MAX sein. Beim Lesen von n-1
Zeichen aus muss ein Zeichen für eine neue Zeile, ein Dateiende oder ein Lesefehler auftreten
stdin
. 25)
3 Wenn eine Laufzeitbeschränkungsverletzung vorliegt, s[0]
wird diese auf das Nullzeichen gesetzt und die Zeichen werden gelesen und verworfen, stdin
bis ein Zeichen in einer neuen Zeile gelesen wird oder ein Dateiende oder ein Lesefehler auftritt.
Beschreibung
4 Die gets_s
Funktion liest höchstens eins weniger als die Anzahl der Zeichen, die von angegeben sind, n
aus dem Stream, auf den von zeigt stdin
, in das Array, auf das von gezeigt wird s
. Nach einem neuen Zeilenzeichen (das verworfen wird) oder nach dem Dateiende werden keine zusätzlichen Zeichen gelesen. Das verworfene neue Zeilenzeichen zählt nicht für die Anzahl der gelesenen Zeichen. Ein Nullzeichen wird unmittelbar nach dem letzten in das Array eingelesenen Zeichen geschrieben.
5 Wenn das Dateiende festgestellt wird und keine Zeichen in das Array eingelesen wurden oder wenn während des Vorgangs ein Lesefehler auftritt, s[0]
wird das Zeichen Null gesetzt und die anderen Elemente von s
nehmen nicht angegebene Werte an.
Empfohlene Praxis
6 Mit dieser fgets
Funktion können ordnungsgemäß geschriebene Programme Eingabezeilen sicher verarbeiten, die zu lang sind, um sie im Ergebnisarray zu speichern. Im Allgemeinen erfordert dies, dass Anrufer fgets
auf das Vorhandensein oder Fehlen eines neuen Zeilenzeichens im Ergebnisarray achten. Erwägen Sie die Verwendung fgets
(zusammen mit der erforderlichen Verarbeitung basierend auf Zeichen in neuen Zeilen) anstelle von
gets_s
.
25) Im gets_s
Gegensatz dazu gets
macht die Funktion es zu einer Verletzung der Laufzeitbeschränkung für eine Eingabezeile, um den Puffer zum Speichern zu überlaufen. Im Gegensatz dazu fgets
wird gets_s
eine Eins-zu-Eins-Beziehung zwischen Eingabezeilen und erfolgreichen Aufrufen von beibehalten gets_s
. Programme, die verwenden, gets
erwarten eine solche Beziehung.
Die Microsoft Visual Studio-Compiler implementieren eine Annäherung an den TR 24731-1-Standard, es gibt jedoch Unterschiede zwischen den von Microsoft implementierten Signaturen und denen im TR.
Die C11-Norm ISO / IEC 9899-2011 enthält TR24731 in Anhang K als optionalen Teil der Bibliothek. Leider wird es auf Unix-ähnlichen Systemen selten implementiert.
getline()
- POSIX
POSIX 2008 bietet auch eine sichere Alternative zu gets()
aufgerufen getline()
. Es weist der Zeile dynamisch Speicherplatz zu, sodass Sie sie am Ende freigeben müssen. Dadurch wird die Beschränkung der Zeilenlänge aufgehoben. Es gibt auch die Länge der gelesenen Daten zurück oder -1
(und nicht EOF
!), Was bedeutet, dass Null-Bytes in der Eingabe zuverlässig verarbeitet werden können. Es gibt auch eine ‚wählen Sie Ihre eigenen Einzel Zeichenbegrenzer‘ Variation genannt getdelim()
; Dies kann nützlich sein, wenn Sie sich mit der Ausgabe befassen, bei der find -print0
die Enden der Dateinamen beispielsweise mit einem ASCII-NUL- '\0'
Zeichen markiert sind .
gets()
Buffer_overflow_attack