it-swarm.com.de

Warum ist die gets-Funktion so gefährlich, dass sie nicht verwendet werden sollte?

Wenn ich versuche, C-Code zu kompilieren, der die Funktion gets() mit GCC verwendet, wird folgende Warnung angezeigt:

(.text + 0x34): Warnung: Die Funktion "gets" 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 von gets()?

Wenn gets() so gefährlich ist, warum können wir es dann nicht entfernen?

202
vinit dhatrak

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 es nur wissen, wenn Sie genau wissen, welche Daten Sie lesen werden.

Anstatt gets zu verwenden, möchten Sie fgets verwenden, das die Signatur hat

char* fgets(char *string, int length, FILE * stream);

(fgets, wenn es eine ganze Zeile liest, verlässt das '\n' in der Zeichenfolge; damit musst du umgehen.)

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.

159
Thomas Owens

Warum ist gets() gefährlich?

Der erste Internet - Wurm (der Morris Internet Worm ) ist vor ungefähr 30 Jahren (1988-11-02) entkommen und verwendete gets() und einen Pufferüberlauf als eine seiner Methoden von Ausbreitung von System zu System. Das Grundproblem besteht darin, dass die Funktion nicht weiß, wie groß der Puffer ist. Sie liest 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() existiert.

Die C11-Norm ISO/IEC 9899: 2011 beseitigte gets() als Standardfunktion, die A Good Thing ™ ist (sie wurde in ISO/IEC 9899: 1999/Cor formal als "veraltet" und "veraltet" markiert) .3: 2007 - Technical Corrigendum 3 für C99 und dann entfernt in C11). Leider wird es aus Gründen der Abwärtskompatibilität für viele Jahre in Bibliotheken bleiben (was "Jahrzehnte" bedeutet). Wenn es nach mir ginge, würde die Implementierung von gets() folgendermaßen aussehen:

char *gets(char *buffer)
{
    assert(buffer != 0);
    abort();
    return 0;
}

Da Ihr Code sowieso früher oder später abstürzt, ist es besser, die Probleme früher als später zu beseitigen. Ich würde gerne eine Fehlermeldung hinzufügen:

fputs("obsolete and dangerous function gets() called\n", stderr);

Moderne Versionen des Linux-Kompilierungssystems erzeugen Warnungen, wenn Sie gets() verknüpfen - und auch für einige andere Funktionen, die ebenfalls Sicherheitsprobleme aufweisen (mktemp(),…).

Alternativen zu gets()

fgets ()

Wie alle anderen sagten, ist die kanonische Alternative zu gets()fgets() Angeben von stdin als Dateistream.

char buffer[BUFSIZ];

while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
    ...process line of data...
}

Was noch niemand erwähnt hat, ist, dass gets() nicht den Zeilenumbruch enthält, sondern fgets(). Daher müssen Sie möglicherweise einen Wrapper um fgets() verwenden, 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;
}

Wie caf in einem Kommentar und paxdiablo in seiner Antwort zeigt, könnten mit fgets() Daten in einer Zeile verbleiben. Mein Wrapper-Code hinterlässt diese Daten, damit sie beim nächsten Mal gelesen werden können. 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, Zeile gelesen und nicht abgeschnitten, Teilzeile gelesen, aber Daten wurden abgeschnitten.

Dieses Problem tritt bei gets() nicht auf, 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 durcheinander bringt (a - Stapelüberlauf ), wenn der Puffer im Stapel zugeordnet ist, oder Überfahren der Steuerinformationen, wenn der Puffer dynamisch zugeordnet ist, oder Kopieren von Daten über andere wichtige globale (oder Modul-) Variablen, wenn der Puffer statisch zugeordnet ist. Keine davon ist eine gute Idee - sie verkörpern den Ausdruck "undefiniertes Verhalten".


Es gibt auch den TR 24731-1 (Technischer Bericht des C-Standardkomitees), der sicherere Alternativen zu einer Vielzahl von Funktionen bietet, einschließlich gets():

§6.5.4.1 Die Funktion gets_s

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 null noch größer als RSIZE_MAX sein. Beim Lesen von n-1 Zeichen aus stdin tritt ein Zeilenwechsel-, Dateiende- oder Lesefehler auf.25)

3 Liegt eine Verletzung der Laufzeitbeschränkung vor, wird s[0] Auf das Null-Zeichen gesetzt, und die Zeichen werden von stdin gelesen und verworfen, bis ein Zeichen in einer neuen Zeile gelesen wird oder das Ende der Zeile erreicht ist. Datei oder ein Lesefehler tritt auf.

Beschreibung

4 Die Funktion gets_s Liest höchstens ein Zeichen weniger als die von n angegebene Anzahl von Zeichen aus dem von stdin angezeigten Stream in das von s angegebene Array ] _. Nach einem Zeichen in einer neuen Zeile (das verworfen wird) oder nach dem Ende der Datei werden keine zusätzlichen Zeichen gelesen. Das verworfene Zeichen für eine neue Zeile 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 angetroffen wird und keine Zeichen in das Array eingelesen wurden oder wenn während des Vorgangs ein Lesefehler auftritt, wird s[0] Auf das Nullzeichen und die anderen Elemente von s nimmt nicht spezifizierte Werte an.

Empfohlene Praxis

6 Mit der Funktion fgets können ordnungsgemäß geschriebene Programme Eingabezeilen verarbeiten, die zu lang sind, um im Ergebnis-Array gespeichert zu werden. Im Allgemeinen erfordert dies, dass Aufrufer von fgets auf das Vorhandensein oder Fehlen eines Zeilenumbruchs im Ergebnisarray achten. Erwägen Sie die Verwendung von fgets (zusammen mit der erforderlichen Verarbeitung basierend auf Zeilenumbrüchen) anstelle von gets_s.

25) Die Funktion gets_s Macht es im Gegensatz zu gets zu einer Laufzeitbeschränkungsverletzung für eine Eingabezeile, den Puffer zum Speichern zu überlaufen. Im Gegensatz zu fgets unterhält gets_s Eine Eins-zu-Eins-Beziehung zwischen Eingabezeilen und erfolgreichen Aufrufen von gets_s. Programme, die gets verwenden, 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 in der TR.

Die C11-Norm ISO/IEC 9899-2011 enthält TR24731 in Anhang K als optionalen Bestandteil der Bibliothek. Leider wird es selten auf Unix-ähnlichen Systemen implementiert.


getline() - POSIX

POSIX 2008 bietet auch eine sichere Alternative zu gets() mit dem Namen getline() . Sie 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 oder -1 (Und nicht EOF!) Zurück, was bedeutet, dass Null-Bytes in der Eingabe zuverlässig verarbeitet werden können. Es gibt auch eine Variante mit dem Namen getdelim(), bei der Sie ein einzelnes Zeichen als Trennzeichen auswählen können. Dies kann nützlich sein, wenn Sie sich mit der Ausgabe von find -print0 befassen, bei der die Enden der Dateinamen beispielsweise mit einem ASCII NUL '\0' - Zeichen markiert sind .

140

Weil gets keine Prüfung durchführt, während Bytes von stdin abgerufen und irgendwo abgelegt werden. Ein einfaches Beispiel:

char array1[] = "12345";
char array2[] = "67890";

gets(array1);

Nun können Sie zunächst eingeben, wie viele Zeichen Sie möchten. gets kümmert sich nicht darum. Zweitens die Bytes über der Größe des Arrays, in dem Sie sie abgelegt haben (in diesem Fall array1) überschreibt alles, was sie im Speicher finden, weil gets sie schreibt. Im vorherigen Beispiel bedeutet dies, dass bei Eingabe von "abcdefghijklmnopqrts" vielleicht, unvorhersehbar, überschreibt es auch array2 oder Wasauchimmer.

Die Funktion ist unsicher, da sie eine konsistente Eingabe voraussetzt. NIEMALS BENUTZEN!

21
Jack

Sie sollten gets nicht verwenden, da es keine Möglichkeit gibt, einen Pufferüberlauf zu stoppen. Wenn der Benutzer mehr Daten eingibt, als in den Puffer passen, kommt es höchstwahrscheinlich zu Beschädigungen oder Schlimmerem.

Tatsächlich hat die ISO den Schritt Entfernengets aus dem C-Standard (ab C11, obwohl dies in C99 veraltet war) unternommen. sollte ein Hinweis darauf sein, wie schlecht diese Funktion war.

Das Richtige ist, die Funktion fgets mit dem Dateihandle stdin zu verwenden, da Sie die vom Benutzer gelesenen Zeichen einschränken können.

Das hat aber auch Probleme wie:

  • vom Benutzer eingegebene Sonderzeichen werden beim nächsten Mal abgeholt.
  • es erfolgt keine schnelle Benachrichtigung, dass der Benutzer zu viele Daten eingegeben hat.

Zu diesem Zweck wird fast jeder C-Programmierer zu einem bestimmten Zeitpunkt seiner Karriere auch einen nützlicheren Wrapper um fgets schreiben. 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 einigem 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, indem es Pufferüberläufe verhindert, den Aufrufer jedoch darüber informiert, was passiert ist, und die überschüssigen Zeichen löscht, damit sie Ihre nächste Eingabeoperation nicht beeinträchtigen.

Fühlen Sie sich frei, es zu verwenden, wie Sie es wünschen, ich gebe es hiermit unter der Lizenz "Mach was du verdammt gut willst" frei :-)

16
paxdiablo

fgets .

Aus dem stdin lesen:

char string[512];

fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
11
Thiago Silveira

Sie können API-Funktionen nicht entfernen, ohne die API zu beschädigen. Andernfalls würden viele Anwendungen überhaupt nicht mehr kompiliert oder ausgeführt.

Dies ist der Grund, dass eine Referenz gibt:

Das Lesen einer Zeile, die über das Array hinausgeht, auf das s zeigt, führt zu undefiniertem Verhalten. Die Verwendung von fgets () wird empfohlen.

6
Gerd Klima

In C11 (ISO/IEC 9899: 201x) wurde gets() entfernt. (Es ist veraltet in ISO/IEC 9899: 1999/Cor.3: 2007 (E))

Zusätzlich zu fgets() führt C11 eine neue sichere Alternative gets_s() ein:

C11 K.3.5.4.1 Die Funktion gets_s

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);

Im Abschnitt Empfohlene Vorgehensweise wird fgets() jedoch weiterhin bevorzugt.

Die Funktion fgets ermöglicht es ordnungsgemäß geschriebenen Programmen, Eingabezeilen sicher zu lange zu verarbeiten, um sie im Ergebnis-Array zu speichern. Im Allgemeinen erfordert dies, dass Aufrufer von fgets auf das Vorhandensein oder Fehlen eines neuen Zeilenzeichens im Ergebnisarray achten. Erwägen Sie die Verwendung von fgets (zusammen mit der erforderlichen Verarbeitung basierend auf Zeilenumbrüchen) anstelle von gets_s.

4
Yu Hao

Ich habe kürzlich in einem SENET-Beitrag zu comp.lang.c gelesen, dass gets() aus dem Standard entfernt wird. [~ # ~] woohoo [~ # ~]

Sie werden froh sein zu wissen, dass das Komitee (wie sich herausstellt) gerade einstimmig abgestimmt hat, um gets () ebenfalls aus dem Entwurf zu entfernen.

4
pmg

gets() ist gefährlich, da der Benutzer das Programm möglicherweise abstürzen lässt, indem er zu viel in die Eingabeaufforderung eingibt. Das Ende des verfügbaren Arbeitsspeichers wird nicht erkannt. Wenn Sie also zu wenig Arbeitsspeicher für diesen Zweck zuweisen, kann dies einen Seg-Fehler und einen Absturz verursachen. 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 sein, wenn ein Benutzer ein Systemprogramm abstürzen lässt, indem er zu viele Daten sendet.).

Mit fgets() können Sie angeben, wie viele Zeichen aus dem Standardeingabepuffer entnommen werden, damit die Variable nicht übersteuert wird.

3

Ich möchte alle C-Bibliotheksbetreuer, die noch gets in ihre Bibliotheken aufnehmen, ernsthaft einladen, "nur für den Fall, dass noch jemand davon abhängig ist": Ersetzen Sie Ihre Implementierung durch das Äquivalent von

char *gets(char *str)
{
    strcpy(str, "Never use gets!");
    return str;
}

So stellen Sie sicher, dass niemand mehr darauf angewiesen ist. Vielen Dank.

3
Steve Summit

Die C gets Funktion ist gefährlich und war ein sehr kostspieliger Fehler. Tony Hoare hebt es in seinem Vortrag "Null Referenzen: Der Milliarden-Dollar-Fehler" besonders hervor:

http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

Die ganze Stunde ist es wert, gesehen zu werden, aber für seine Kommentare wird ab 30 Minuten Kritik laut.

Hoffentlich macht dies Ihren Appetit auf den gesamten Vortrag, der darauf aufmerksam macht, wie wir formellere Korrektheitsnachweise in Sprachen benötigen und wie Sprachdesigner für die Fehler in ihren Sprachen verantwortlich gemacht werden sollten, nicht der Programmierer. Dies scheint der ganze zweifelhafte Grund für Designer von schlechten Sprachen gewesen zu sein, die Programmierer unter dem Deckmantel der "Programmiererfreiheit" zu beschuldigen.

2
user3717661