Warum ist die Get-Funktion so gefährlich, dass sie nicht verwendet werden sollte?
Wenn ich versuche, C-Code zu kompilieren, der die gets()
Funktion mit GCC verwendet, wird folgende Warnung angezeigt:
(.text + 0x34): Warnung: Die Funktion "get" ist gefährlich und sollte nicht verwendet werden.
Ich erinnere mich, dass dies etwas mit Stapelschutz und Sicherheit zu tun hat, aber ich weiß nicht genau warum.
Wie kann ich diese Warnung entfernen und warum gibt es eine solche Warnung zur Verwendung gets()
?
Wenn gets()
es so gefährlich ist, warum können wir es dann nicht entfernen?
Antworten
Um gets
sicher zu verwenden , müssen Sie genau wissen, wie viele Zeichen Sie lesen werden, damit Sie Ihren Puffer groß genug machen können. Sie werden das nur wissen, wenn Sie genau wissen, welche Daten Sie lesen werden.
Anstatt zu verwenden gets
, möchten Sie verwenden fgets, die die Signatur hat
char* fgets(char *string, int length, FILE * stream);
( fgets
Wenn es eine ganze Zeile liest, bleibt das '\n'
in der Zeichenfolge; Sie müssen sich darum kümmern.)
Es blieb ein offizieller Teil der Sprache bis zur ISO C-Norm von 1999, wurde jedoch durch die Norm von 2011 offiziell entfernt. Die meisten C-Implementierungen unterstützen es weiterhin, aber zumindest gibt gcc eine Warnung für jeden Code aus, der es verwendet.
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 angegebenen Puffers überschreitet.
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;
}
Angesichts der Tatsache, dass 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 bisher noch niemand erwähnt hat, ist, dass gets()
der Zeilenumbruch nicht enthalten ist, dies aber der Fall fgets()
ist. 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
FunktionZusammenfassung
#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 vonn-1
Zeichen aus muss ein Zeichen für eine neue Zeile, ein Dateiende oder ein Lesefehler auftretenstdin
. 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 neues Zeilenzeichen gelesen wird oder das 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 zeigtstdin
, in das Array, auf das von gezeigt wirds
. 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 vons
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 Anruferfgets
auf das Vorhandensein oder Fehlen eines neuen Zeilenzeichens im Ergebnisarray achten. Erwägen Sie die Verwendungfgets
(zusammen mit der erforderlichen Verarbeitung basierend auf Zeilenumbrüchen) anstelle vongets_s
.25) Im
gets_s
Gegensatz dazugets
macht die Funktion es zu einer Verletzung der Laufzeitbeschränkung für eine Eingabezeile, um den Puffer zum Speichern zu überlaufen. Im Gegensatz dazufgets
wirdgets_s
eine Eins-zu-Eins-Beziehung zwischen Eingabezeilen und erfolgreichen Aufrufen von beibehaltengets_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 .
Weil gets
es keine Überprüfung durchführt, während Bytes von stdin abgerufen und irgendwo abgelegt werden. Ein einfaches Beispiel:
char array1[] = "12345";
char array2[] = "67890";
gets(array1);
Zunächst dürfen Sie eingeben, wie viele Zeichen Sie möchten, gets
ohne sich darum zu kümmern. Zweitens array1
überschreiben die Bytes über die Größe des Arrays, in das Sie sie eingefügt haben (in diesem Fall ), alles, was sie im Speicher finden, weil gets
sie sie schreiben. Im vorherigen Beispiel bedeutet dies, dass bei einer "abcdefghijklmnopqrts"
unvorhersehbaren Eingabe auch array2
oder was auch immer überschrieben wird .
Die Funktion ist unsicher, da sie eine konsistente Eingabe voraussetzt. NIEMALS BENUTZEN!
Sie sollten es nicht verwenden, gets
da es keine Möglichkeit gibt, einen Pufferüberlauf zu stoppen. Wenn der Benutzer mehr Daten eingibt, als in Ihren Puffer passen, kommt es höchstwahrscheinlich zu Beschädigungen oder Schlimmerem.
Tatsächlich hat ISO den Schritt unternommen , gets
aus dem C-Standard zu entfernen (ab C11, obwohl er in C99 veraltet war), was angesichts der hohen Bewertung der Abwärtskompatibilität ein Hinweis darauf sein sollte, wie schlecht diese Funktion war.
Das Richtige ist, die fgets
Funktion mit dem stdin
Dateihandle zu verwenden, da Sie die vom Benutzer gelesenen Zeichen einschränken können.
Dies hat aber auch folgende Probleme:
- Vom Benutzer eingegebene zusätzliche Zeichen werden beim nächsten Mal erfasst.
- Es gibt keine schnelle Benachrichtigung, dass der Benutzer zu viele Daten eingegeben hat.
Zu diesem Zweck wird fast jeder C-Codierer irgendwann in seiner Karriere auch einen nützlicheren Wrapper herumschreiben fgets
. Hier ist meins:
#include <stdio.h>
#include <string.h>
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
int ch, extra;
// Get line with buffer overrun protection.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
if (buff[strlen(buff)-1] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[strlen(buff)-1] = '\0';
return OK;
}
mit etwas Testcode:
// Test program for getLine().
int main (void) {
int rc;
char buff[10];
rc = getLine ("Enter string> ", buff, sizeof(buff));
if (rc == NO_INPUT) {
printf ("No input\n");
return 1;
}
if (rc == TOO_LONG) {
printf ("Input too long\n");
return 1;
}
printf ("OK [%s]\n", buff);
return 0;
}
Es bietet den gleichen Schutz wie fgets
insofern, als es Pufferüberläufe verhindert, aber es benachrichtigt den Anrufer auch darüber, was passiert ist, und löscht die überschüssigen Zeichen, damit sie Ihre nächste Eingabeoperation nicht beeinträchtigen.
Fühlen Sie sich frei, es zu verwenden, wie Sie möchten, ich veröffentliche es hiermit unter der Lizenz "Mach was du verdammt gut willst" :-)
fgets .
Aus dem Standard lesen:
char string[512];
fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
Sie können API-Funktionen nicht entfernen, ohne die API zu beschädigen. Wenn Sie dies tun würden, würden viele Anwendungen überhaupt nicht mehr kompiliert oder ausgeführt.
Dies ist der Grund, den eine Referenz gibt:
Das Lesen einer Zeile, die über das Array läuft, auf das s zeigt, führt zu undefiniertem Verhalten. Die Verwendung von fgets () wird empfohlen.
Ich habe kürzlich in einem USENET-Beitrag zucomp.lang.c gelesen , dass dies gets()
aus dem Standard entfernt wird. WOOHOO
Sie werden froh sein zu wissen, dass das Komitee gerade (wie sich herausstellt, einstimmig) dafür gestimmt hat, get () ebenfalls aus dem Entwurf zu entfernen.
In C11 (ISO / IEC 9899: 201x) gets()
wurde entfernt. (Es ist in ISO / IEC 9899: 1999 / Cor.3: 2007 (E) veraltet.)
Zusätzlich fgets()
stellt C11 eine neue sichere Alternative vor gets_s()
:
C11 K.3.5.4.1 Die
gets_s
Funktion#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> char *gets_s(char *s, rsize_t n);
Im Abschnitt Empfohlene Praxisfgets()
wird dies jedoch weiterhin bevorzugt.
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 Anruferfgets
auf das Vorhandensein oder Fehlen eines neuen Zeilenzeichens im Ergebnisarray achten. Erwägen Sie die Verwendungfgets
(zusammen mit der erforderlichen Verarbeitung basierend auf Zeilenumbrüchen) anstelle vongets_s
.
gets()
ist gefährlich, da der Benutzer das Programm abstürzen kann, indem er zu viel in die Eingabeaufforderung eingibt. Das Ende des verfügbaren Speichers kann nicht erkannt werden. Wenn Sie also eine zu kleine Speichermenge für diesen Zweck zuweisen, kann dies zu einem Seg-Fehler und einem Absturz führen. Manchmal scheint es sehr unwahrscheinlich, dass ein Benutzer 1000 Buchstaben in eine Eingabeaufforderung eingibt, die für den Namen einer Person bestimmt ist, aber als Programmierer müssen wir unsere Programme kugelsicher machen. (Es kann auch ein Sicherheitsrisiko darstellen, wenn ein Benutzer ein Systemprogramm durch Senden zu vieler Daten zum Absturz bringen kann.)
fgets()
Mit dieser Option können Sie angeben, wie viele Zeichen aus dem Standardeingabepuffer entfernt werden sollen, damit die Variable nicht überschritten wird.
Die C-Funktion ist gefährlich und ein sehr kostspieliger Fehler. Tony Hoare hebt es in seinem Vortrag "Null References: The Billion Dollar Mistake" besonders hervor:
http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare
Die ganze Stunde ist sehenswert, aber für seine Kommentare bekommt die Ansicht ab 30 Minuten mit der spezifischen Kritik rund 39 Minuten.
Hoffentlich macht dies Appetit auf das ganze Gespräch, was die Aufmerksamkeit darauf lenkt, wie wir formellere Korrektheitsnachweise in Sprachen benötigen und wie Sprachdesigner für die Fehler in ihren Sprachen verantwortlich gemacht werden sollten, nicht für den Programmierer. Dies scheint der zweifelhafte Grund für Designer schlechter Sprachen gewesen zu sein, Programmierern die Schuld unter dem Deckmantel der "Programmiererfreiheit" zu geben.
Ich möchte alle C-Bibliotheksverwalter, die noch gets
in ihre Bibliotheken aufgenommen haben, ernsthaft einladen, "nur für den Fall, dass noch jemand davon abhängig ist": Bitte ersetzen Sie Ihre Implementierung durch das Äquivalent von
char *gets(char *str)
{
strcpy(str, "Never use gets!");
return str;
}
Dies wird dazu beitragen, dass niemand mehr davon abhängig ist. Danke.