Dlaczego funkcja gets jest tak niebezpieczna, że nie powinna być używana?

Kiedy próbuję skompilować kod C, który używa funkcji gets() z GCC,

I get this

Ostrzeżenie:

(.tekst+0x34): Ostrzeżenie: funkcja "gets" jest niebezpieczna i nie powinna być używana.

Pamiętam, że to ma coś wspólnego z ochroną stosu i bezpieczeństwem, ale nie wiem dokładnie dlaczego?

Czy ktoś może mi pomóc w usunięciu tego ostrzeżenia i wyjaśnić dlaczego jest takie ostrzeżenie o używaniu gets()?

Jeśli gets() jest tak niebezpieczny, więc dlaczego nie możemy go usunąć?

Author: Joe, 2009-11-07

11 answers

Aby bezpiecznie używać gets, musisz dokładnie wiedzieć, ile znaków będziesz czytał, aby twój bufor był wystarczająco duży. Będziesz to wiedział tylko wtedy, gdy wiesz dokładnie, jakie dane będziesz czytać.

Zamiast używać gets, chcesz użyć fgets, który posiada podpis

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

(fgets, jeśli czyta całą linię, pozostawia '\n' w łańcuchu; będziesz musiał sobie z tym poradzić.)

Pozostał oficjalną częścią języka up do normy ISO C z 1999 roku, ale został oficjalnie usunięty przez standard z 2011 roku. Większość implementacji C nadal go wspiera, ale przynajmniej gcc wystawia ostrzeżenie dla dowolnego kodu, który go używa.

 137
Author: Thomas Owens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-05 00:18:33

Dlaczego gets() jest niebezpieczny

[57]}pierwszy robak internetowy (Morris Internet Worm {59]}) uciekł około 30 lat temu (1988-11-02) i użył[7]} i przepełnienia bufora jako jednej z metod propagacji z systemu do systemu. Podstawowym problemem jest to, że funkcja nie wie, jak duży jest bufor, więc kontynuuje czytanie, dopóki nie znajdzie nowej linii lub nie napotka EOF i może przepełnić granice bufora, który został podany.

Powinieneś zapomnieć, że kiedykolwiek słyszałeś, że gets() istniał.

[57]}norma C11 ISO/IEC 9899:2011 wyeliminowała gets() jako standardową funkcję, co jest dobrą rzeczą™ (została formalnie oznaczona jako "przestarzała" i "przestarzała" w ISO/IEC 9899:1999/Cor.3: 2007-techniczne sprostowanie 3 dla C99, a następnie usunięte w C11). Niestety, pozostanie on w bibliotekach przez wiele lat (co oznacza "dekady") ze względu na wsteczną kompatybilność. Gdyby to zależało ode mnie, implementacja gets() stałaby się:
char *gets(char *buffer)
{
    assert(buffer != 0);
    abort();
    return 0;
}

Biorąc pod uwagę, że Twój kod będzie crash tak czy siak, prędzej czy później, lepiej jest skierować problem szybciej niż później. Byłabym gotowa dodać komunikat o błędzie:

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

Nowoczesne wersje systemu kompilacji Linuksa generują ostrzeżenia, jeśli połączysz gets() - a także dla niektórych innych funkcji, które również mają problemy z bezpieczeństwem (mktemp(), ...).

Alternatywy dla gets()

Fgets ()

Jak wszyscy mówili, kanoniczną alternatywą dla gets() jest fgets() określenie stdin jako strumień plików.

char buffer[BUFSIZ];

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

Nikt jeszcze nie wspomniał, że gets() nie zawiera nowej linii, ale fgets() tak. Tak więc może być konieczne użycie owijarki wokół fgets(), która usuwa nowy wiersz:

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;
}

Lub lepiej:

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;
}

Również, jak caf wskazuje w komentarzu I paxdiablo pokazuje w swojej odpowiedzi, z fgets() możesz mieć dane w linii. Mój kod opakowaniowy pozostawia te dane do odczytania następnym razem; można je łatwo zmodyfikować, aby reszta linii danych, jeśli wolisz:

        if (len > 0 && buffer[len-1] == '\n')
            buffer[len-1] = '\0';
        else
        {
             int ch;
             while ((ch = getc(fp)) != EOF && ch != '\n')
                 ;
        }

Pozostały problem polega na tym, jak zgłosić trzy różne stany wyniku - EOF lub błąd, odczyt linii i nie obcięty oraz częściowy odczyt linii, ale dane zostały obcięte.

Ten problem nie pojawia się z gets(), ponieważ nie wie, gdzie kończy się twój bufor i wesoło depcze poza koniec, siejąc spustoszenie w pięknie utrzymanym układzie pamięci, często psując stos powrotu (a przepełnienie stosu), jeśli bufor jest alokowane na stosie lub Deptanie informacji sterujących, jeśli bufor jest alokowany dynamicznie, lub kopiowanie danych nad innymi cennymi zmiennymi globalnymi (lub modułowymi), jeśli bufor jest alokowany statycznie. Żaden z nich nie jest dobrym pomysłem - uosabiają wyrażenie "nieokreślone zachowanie".


Istnieje również TR 24731-1 (raport techniczny Komitetu ds. standardów C), który zapewnia bezpieczniejsze alternatywy dla różnych funkcji, w tym gets(): {]}

§6.5.4.1 funkcja gets_s

Synopsis

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

Runtime-constraints

s nie może być wskaźnikiem zerowym. n nie jest równa zeru ani większa niż RSIZE_MAX. W trakcie odczytu powinien wystąpić znak nowej linii, błąd końca pliku lub błąd odczytu n-1 znaki z stdin.25)

3 Jeśli wystąpi naruszenie ograniczeń runtime, s[0] jest ustawiony na znak null, A znaki są odczytywane i odrzucane od stdin do odczytu znaku nowej linii, końca pliku lub wystąpił błąd odczytu.

Opis

4 funkcja gets_s odczytuje co najwyżej o jeden mniej niż liczba znaków określona przez n ze strumienia wskazywanego przez stdin, do tablicy wskazywanej przez s. Brak dodatkowych znaki są odczytywane po znaku nowej linii (który jest odrzucany) lub po końcu pliku. Odrzucony znak nowej linii nie liczy się do liczby znaków Czytaj. A znak null jest zapisywany bezpośrednio po ostatnim znaku wczytanym do tablicy.

5 Jeśli napotkano koniec pliku i nie wczytano żadnych znaków do tablicy, lub jeśli odczytano błąd występuje podczas operacji, wtedy s[0] jest ustawiony na znak null, a drugi elementy s przyjmują nieokreślone wartości.

Zalecana Praktyka

6 funkcja fgets pozwala poprawnie napisanym programom bezpiecznie przetwarzać linie wejściowe długie do zapisania w tablicy wyników. Ogólnie rzecz biorąc, wymaga to, aby osoby dzwoniące z fgets płaciły uwaga na obecność lub brak znaku nowej linii w tablicy wyników. Rozważ używanie fgets (wraz z dowolnym niezbędnym przetwarzaniem opartym na znakach nowej linii) zamiast gets_s.

25) funkcja gets_s, W przeciwieństwie do gets, sprawia, że łamanie ograniczeń w trybie runtime dla linii wejścia do przepełnić bufor, aby go przechowywać. W przeciwieństwie do fgets, gets_s / align = "left" / związek między linie wejściowe i pomyślne wywołania do gets_s. Programy, które używają gets oczekują takiego związku.

[57]}Kompilatory Microsoft Visual Studio implementują przybliżenie do standardu TR 24731-1, ale istnieją różnice między podpisami zaimplementowanymi przez Microsoft i tymi w TR.

Norma C11, ISO / IEC 9899-2011, zawiera TR24731 w załączniku K jako opcjonalną część biblioteki. Niestety, rzadko jest on implementowany na uniksopodobnych systemy.


getline() - POSIX

POSIX 2008 zapewnia również bezpieczną alternatywę dla gets() o nazwie getline(). Przydziela miejsce na linię dynamicznie, więc musisz ją zwolnić. Usuwa więc ograniczenie długości linii. Zwraca również długość danych, które zostały odczytane, lub -1 (a nie EOF!), co oznacza, że bajty null na wejściu mogą być obsługiwane niezawodnie. Istnieje również odmiana "wybierz swój własny ogranicznik jednowarstwowy" wywołane getdelim(); może to być przydatne, jeśli masz do czynienia z wyjściem z find -print0, gdzie końce nazw plików są oznaczone znakiem ASCII nul '\0'.

 119
Author: Jonathan Leffler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-31 22:13:58

Ponieważ getsnie wykonuje żadnej kontroli podczas pobierania bajtów z stdin i umieszczania ich gdzieś. Prosty przykład:

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

gets(array1);

Teraz, po pierwsze, możesz wpisać ile znaków chcesz, gets nie będzie to obchodziło. Po drugie bajty o rozmiarze tablicy, w której je umieścisz (w tym przypadku array1) nadpiszą wszystko, co znajdą w pamięci, ponieważ gets je zapiszą. W poprzednim przykładzie oznacza to, że jeśli wpiszesz "abcdefghijklmnopqrts" może, nieprzewidywalnie, nadpisze również array2 lub cokolwiek innego.

Funkcja jest niebezpieczna, ponieważ przyjmuje spójne dane wejściowe. NIGDY GO NIE UŻYWAJ!

 21
Author: Jack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-07 19:03:26

Nie powinieneś używać gets, ponieważ nie ma sposobu na zatrzymanie przepełnienia bufora. Jeśli użytkownik wpisze więcej danych niż mieści się w buforze, najprawdopodobniej skończysz z uszkodzeniem lub gorzej.

W rzeczywistości ISO podjęły krok usunięcie gets ze standardu C (od C11, choć był przestarzały w C99), który, biorąc pod uwagę jak wysoko oceniają kompatybilność wsteczną, powinien być wskaźnikiem jak zła była ta funkcja.

Poprawną rzeczą jest użycie Funkcja fgets z uchwytem pliku stdin, ponieważ można ograniczyć znaki odczytywane od użytkownika.

Ale to również ma swoje problemy, takie jak:

  • Dodatkowe znaki wprowadzone przez Użytkownika zostaną odebrane następnym razem.
  • nie ma szybkiego powiadomienia, że użytkownik wprowadził zbyt dużo danych.

W tym celu prawie każdy programista C w pewnym momencie swojej kariery napisze bardziej przydatny wrapper wokół fgets. Oto moje:

#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;
}

Z jakimś kodem testowym:

// 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;
}

Zapewnia takie same zabezpieczenia jak fgets w tym, że zapobiega przepełnieniu bufora, ale także powiadamia wywołującego o tym, co się stało i usuwa nadmiarowe znaki, aby nie miały wpływu na następną operację wejścia.

Możesz używać go tak, jak chcesz, niniejszym wydaję go na licencji "rób, co chcesz": -)

 16
Author: paxdiablo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-10 04:59:31

Fgets .

Do odczytu ze stdin:

char string[512];

fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
 10
Author: Thiago Silveira,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-10 19:33:12

Nie można usunąć funkcji API bez ich złamania. Gdyby tak było, wiele aplikacji w ogóle by się nie kompilowało ani nie uruchamiało.

To jest powód, dla którego jedna Referencja daje:

Czytanie linii, która przepełnia tablica wskazywana przez S powoduje nieokreślone zachowanie. Korzystanie z fgets() jest zalecane.

 6
Author: Gerd Klima,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-07 18:58:21

Czytałem ostatnio, w USENETOWYM poście do comp.lang.c, to gets() jest usuwane ze standardu. WOOHOO

Będziesz szczęśliwy wiedząc, że Komisja właśnie głosowała (jednogłośnie, jako okazuje się), aby usunąć gets () z także szkic.

 4
Author: pmg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-07 19:21:20

W C11(ISO/IEC 9899:201x), gets() został usunięty. (Jest przestarzały w ISO/IEC 9899:1999/Cor.3:2007 (E))

Oprócz fgets(), C11 wprowadza nową bezpieczną alternatywę gets_s():

C11 K. 3. 5. 4. 1 Funkcja gets_s

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

Jednak w sekcji Zalecana Praktyka , fgets() jest nadal preferowana.

Funkcja fgets pozwala poprawnie napisanym programom bezpiecznie przetwarzać linie wejściowe długo przechowywać w wyniku / align = "left" / Generalnie wymaga to, aby osoby dzwoniące z fgets płaciły uwaga na obecność lub brak znaku nowej linii w tablicy wyników. Rozważ używanie fgets (wraz z dowolnym niezbędnym przetwarzaniem na podstawie znaków nowej linii) zamiast gets_s.

 4
Author: Yu Hao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-06 06:15:49

Chciałbym serdecznie zaprosić wszystkich opiekunów biblioteki C, którzy nadal włączają gets do swoich bibliotek "na wszelki wypadek, gdyby ktoś nadal od niej zależy": proszę zastąpić swoją implementację odpowiednikiem

char *gets(char *str)
{
    strcpy(str, "Never use gets!");
    return str;
}
To pomoże upewnić się, że nikt nadal na tym nie polega. Dziękuję.
 3
Author: Steve Summit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-31 21:52:25

gets() jest to niebezpieczne, ponieważ użytkownik może zawiesić program, wpisując zbyt dużo w monit. Nie może wykryć końca dostępnej pamięci, więc jeśli przydzielisz zbyt małą ilość pamięci do tego celu, może to spowodować usterkę seg i awarię. Czasami wydaje się bardzo mało prawdopodobne, aby użytkownik wpisał 1000 liter w monit przeznaczony dla nazwy danej osoby, ale jako programiści musimy uczynić nasze programy kuloodpornymi. (może to być również zagrożenie bezpieczeństwa, jeśli użytkownik może zawiesić system program wysyłając zbyt dużo danych).

fgets() pozwala określić, ile znaków zostanie wyjętych ze standardowego bufora wejściowego, aby nie przekroczyły wartości zmiennej.

 3
Author: Aradhana Mohanty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-22 10:09:05

Funkcja C gets jest niebezpieczna i była bardzo kosztownym błędem. Tony Hoare wyróżnił to za konkretną wzmiankę w swoim przemówieniu "Null References: the Billion Dollar Mistake":

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

Cała godzina jest warta obejrzenia, ale za Jego komentarze widok z 30 minut na konkretne dostaje krytykę około 39 minut.

Mam nadzieję, że to zaostrzy Twój apetyt na całą rozmowę, który zwraca uwagę na to, jak potrzebujemy bardziej formalnych dowodów poprawności w językach i jak projektanci języków powinni być obwiniani za błędy w ich językach, a nie programista. Wydaje się, że był to cały wątpliwy powód dla projektantów złych języków, aby zrzucić winę na programistów pod pozorem "wolności programisty".

 2
Author: user3717661,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-01 01:00:46