Mengapa fungsi get sangat berbahaya sehingga tidak boleh digunakan?

Nov 08 2009

Ketika saya mencoba mengompilasi kode C yang menggunakan gets()fungsi dengan GCC, saya mendapatkan peringatan ini:

(.text + 0x34): peringatan: fungsi `mendapat 'berbahaya dan tidak boleh digunakan.

Saya ingat ini ada hubungannya dengan perlindungan tumpukan dan keamanan, tetapi saya tidak yakin persis mengapa.

Bagaimana cara menghapus peringatan ini dan mengapa ada peringatan tentang penggunaan gets()?

Jika gets()sangat berbahaya mengapa kita tidak bisa menghapusnya?

Jawaban

188 ThomasOwens Nov 08 2009 at 01:56

Untuk menggunakan getsdengan aman, Anda harus tahu persis berapa banyak karakter yang akan Anda baca, sehingga Anda dapat membuat buffer Anda cukup besar. Anda hanya akan tahu itu jika Anda tahu persis data apa yang akan Anda baca.

Alih-alih menggunakan gets, Anda ingin menggunakan fgets, yang memiliki tanda tangan

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

( fgets, jika membaca seluruh baris, akan meninggalkan '\n'string; Anda harus mengatasinya.)

Itu tetap menjadi bagian resmi bahasa hingga standar ISO C 1999, tetapi secara resmi dihapus oleh standar 2011. Sebagian besar implementasi C masih mendukungnya, tetapi setidaknya gcc mengeluarkan peringatan untuk kode apa pun yang menggunakannya.

176 JonathanLeffler Nov 30 2010 at 08:51

Mengapa gets()berbahaya

Worm internet pertama ( Morris Internet Worm ) lolos sekitar 30 tahun yang lalu (1988-11-02), dan ia menggunakan gets()dan buffer overflow sebagai salah satu metodenya untuk menyebar dari sistem ke sistem. Masalah dasarnya adalah bahwa fungsi tersebut tidak mengetahui seberapa besar buffer itu, jadi ia terus membaca hingga menemukan baris baru atau menemukan EOF, dan mungkin meluap batas buffer yang diberikan.

Anda harus melupakan Anda pernah mendengar yang gets()ada.

Standar C11 ISO / IEC 9899: 2011 dihilangkan gets()sebagai fungsi standar, yaitu A Good Thing ™ (secara resmi ditandai sebagai 'usang' dan 'tidak digunakan lagi' dalam ISO / IEC 9899: 1999 / Cor.3: 2007 - Technical Corrigendum 3 untuk C99, lalu dihapus di C11). Sayangnya, itu akan tetap ada di perpustakaan selama bertahun-tahun (artinya 'dekade') karena alasan kompatibilitas ke belakang. Jika terserah saya, penerapannya gets()akan menjadi:

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

Mengingat bahwa kode Anda akan macet, cepat atau lambat, lebih baik segera menanganinya daripada nanti. Saya akan siap menambahkan pesan kesalahan:

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

Versi modern dari sistem kompilasi Linux menghasilkan peringatan jika Anda menautkan gets()- dan juga untuk beberapa fungsi lain yang juga memiliki masalah keamanan ( mktemp(),…).

Alternatif untuk gets()

gadget ()

Seperti orang lain berkata, alternatif kanonik untuk gets()yang fgets()menentukan stdinsebagai file streaming.

char buffer[BUFSIZ];

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

Apa yang belum disebutkan oleh orang lain adalah bahwa gets()tidak termasuk baris baru tetapi fgets()tidak. Jadi, Anda mungkin perlu menggunakan pembungkus fgets()yang menghapus baris baru:

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

Atau lebih baik:

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

Juga, seperti yang ditunjukkan caf dalam komentar dan paxdiablo tunjukkan dalam jawabannya, dengan fgets()Anda mungkin memiliki data yang tersisa di satu baris. Kode pembungkus saya membiarkan data itu dibaca di lain waktu; Anda dapat dengan mudah memodifikasinya untuk melahap baris data lainnya jika Anda mau:

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

Masalah sisa adalah bagaimana melaporkan tiga status hasil yang berbeda - EOF atau kesalahan, pembacaan baris dan tidak terpotong, dan pembacaan sebagian baris tetapi data terpotong.

Masalah ini tidak muncul dengan gets()karena ia tidak tahu di mana buffer Anda berakhir dan dengan riang menginjak-injak setelah akhir, mendatangkan malapetaka pada tata letak memori Anda yang cenderung indah, sering mengacaukan tumpukan kembali ( Stack Overflow ) jika buffer dialokasikan pada tumpukan, atau menginjak-injak informasi kontrol jika buffer dialokasikan secara dinamis, atau menyalin data ke variabel global (atau modul) berharga lainnya jika buffer dialokasikan secara statis. Tak satu pun dari ini adalah ide yang bagus - mereka melambangkan frase 'perilaku tidak terdefinisi`.


Ada juga TR 24731-1 (Laporan Teknis dari Komite Standar C) yang memberikan alternatif yang lebih aman untuk berbagai fungsi, termasuk gets():

§6.5.4.1 gets_sFungsi

Ringkasan

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

Batasan waktu proses

stidak akan menjadi penunjuk nol. ntidak boleh sama dengan nol atau lebih besar dari RSIZE_MAX. Karakter baris baru, akhir file, atau kesalahan baca akan terjadi dalam n-1karakter membaca dari stdin. 25)

3 Jika ada pelanggaran batasan waktu proses, s[0]disetel ke karakter nol, dan karakter dibaca dan dibuang stdinhingga karakter baris baru dibaca, atau terjadi kesalahan baca atau akhir file.

Deskripsi

4 gets_sFungsi membaca paling banyak satu kurang dari jumlah karakter yang ditentukan oleh ndari aliran yang ditunjuk oleh stdin, ke dalam larik yang ditunjuk oleh s. Tidak ada karakter tambahan yang dibaca setelah karakter baris baru (yang dibuang) atau setelah akhir file. Karakter baris baru yang dibuang tidak diperhitungkan dalam jumlah karakter yang dibaca. Karakter null ditulis segera setelah karakter terakhir dibaca ke dalam array.

5 Jika end-of-file ditemukan dan tidak ada karakter yang telah dibaca ke dalam array, atau jika kesalahan pembacaan terjadi selama operasi, maka s[0]set ke karakter null, dan elemen lain smengambil nilai yang tidak ditentukan.

Latihan yang direkomendasikan

6 fgetsFungsi ini memungkinkan program yang ditulis dengan benar untuk dengan aman memproses baris masukan yang terlalu panjang untuk disimpan dalam larik hasil. Secara umum, hal ini mengharuskan pemanggil fgetsmemperhatikan ada atau tidaknya karakter baris baru dalam larik hasil. Pertimbangkan untuk menggunakan fgets(bersama dengan pemrosesan yang diperlukan berdasarkan karakter baris baru) daripada gets_s.

25) The gets_sfungsi, seperti gets, membuat pelanggaran runtime-kendala untuk garis input meluap buffer untuk menyimpannya. Tidak seperti fgets, gets_smempertahankan hubungan satu-ke-satu antara jalur input dan panggilan yang berhasil ke gets_s. Program yang digunakan getsmengharapkan hubungan seperti itu.

Kompiler Microsoft Visual Studio menerapkan perkiraan ke standar TR 24731-1, tetapi ada perbedaan antara tanda tangan yang diterapkan oleh Microsoft dan yang ada di TR.

Standar C11, ISO / IEC 9899-2011, termasuk TR24731 di Lampiran K sebagai bagian opsional dari perpustakaan. Sayangnya, ini jarang diterapkan pada sistem mirip Unix.


getline() - POSIX

POSIX 2008 juga menyediakan alternatif yang aman untuk gets()dipanggil getline(). Ini mengalokasikan ruang untuk baris secara dinamis, jadi Anda akhirnya perlu membebaskannya. Oleh karena itu, ini menghilangkan batasan pada panjang baris. Ini juga mengembalikan panjang data yang telah dibaca, atau -1(dan tidak EOF!), Yang berarti bahwa null byte dalam input dapat ditangani dengan andal. Ada juga variasi 'pilih pembatas karakter tunggal Anda' yang disebut getdelim(); ini dapat berguna jika Anda berurusan dengan keluaran find -print0yang ujung nama berkasnya ditandai dengan '\0'karakter ASCII NUL , misalnya.

23 Jack Nov 08 2009 at 02:03

Karena getstidak melakukan pemeriksaan apa pun saat mendapatkan byte dari stdin dan meletakkannya di suatu tempat. Contoh sederhana:

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

gets(array1);

Sekarang, pertama-tama Anda diizinkan untuk memasukkan berapa banyak karakter yang Anda inginkan, getstidak peduli. Kedua, byte yang melebihi ukuran array tempat Anda meletakkannya (dalam hal ini array1) akan menimpa apa pun yang mereka temukan di memori karena getsakan menuliskannya. Dalam contoh sebelumnya, ini berarti bahwa jika Anda memasukkan "abcdefghijklmnopqrts"mungkin, secara tidak terduga, itu akan menimpa juga array2atau apa pun.

Fungsi ini tidak aman karena mengasumsikan masukan yang konsisten. JANGAN PERNAH MENGGUNAKANNYA!

17 paxdiablo Nov 30 2010 at 08:56

Anda tidak boleh menggunakan getskarena tidak ada cara untuk menghentikan buffer overflow. Jika pengguna mengetik lebih banyak data daripada yang bisa muat di buffer Anda, kemungkinan besar Anda akan berakhir dengan korupsi atau lebih buruk.

Faktanya, ISO sebenarnya telah mengambil langkah untuk menghapus gets dari standar C (pada C11, meskipun sudah tidak digunakan lagi di C99) yang, mengingat seberapa tinggi mereka menilai kompatibilitas ke belakang, seharusnya menjadi indikasi seberapa buruk fungsi itu.

Hal yang benar untuk dilakukan adalah menggunakan fgetsfungsi dengan stdinpegangan file karena Anda dapat membatasi karakter yang dibaca dari pengguna.

Tetapi ini juga memiliki masalah seperti:

  • karakter tambahan yang dimasukkan oleh pengguna akan diambil di lain waktu.
  • tidak ada pemberitahuan cepat bahwa pengguna memasukkan terlalu banyak data.

Untuk itu, hampir setiap pembuat kode C di beberapa titik dalam karir mereka akan menulis pembungkus yang lebih berguna fgetsjuga. Ini milik saya:

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

dengan beberapa kode tes:

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

Ini memberikan perlindungan yang sama seperti fgetsyang mencegah buffer overflows tetapi juga memberi tahu pemanggil tentang apa yang terjadi dan membersihkan karakter berlebih sehingga tidak mempengaruhi operasi input Anda berikutnya.

Jangan ragu untuk menggunakannya sesuai keinginan, dengan ini saya merilisnya di bawah lisensi "lakukan apa yang sangat Anda inginkan" :-)

14 ThiagoSilveira Nov 30 2010 at 08:28

gadget .

Untuk membaca dari stdin:

char string[512];

fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
9 GerdKlima Nov 08 2009 at 01:58

Anda tidak dapat menghapus fungsi API tanpa merusak API. Jika Anda mau, banyak aplikasi tidak lagi dapat dikompilasi atau dijalankan sama sekali.

Inilah alasan yang diberikan oleh satu referensi :

Membaca garis yang meluap dari larik yang ditunjukkan oleh menghasilkan perilaku tidak terdefinisi. Direkomendasikan untuk menggunakan fgets ().

5 pmg Nov 08 2009 at 02:21

Saya baru-baru ini membaca, dalam postingan USENET kecomp.lang.c , yang gets()dihapus dari Standar. WOO HOO

Anda akan senang mengetahui bahwa komite baru saja memberikan suara (dengan suara bulat, ternyata) untuk menghapus get () dari draf juga.

5 YuHao Oct 06 2013 at 13:15

Di C11 (ISO / IEC 9899: 201x), gets()telah dihapus. (Ini tidak digunakan lagi dalam ISO / IEC 9899: 1999 / Cor.3: 2007 (E))

Selain itu fgets(), C11 memperkenalkan alternatif baru yang aman gets_s():

C11 K.3.5.4.1 gets_sFungsi

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

Namun, di bagian latihan yang Direkomendasikan , fgets()masih lebih disukai.

The fgetsFungsi memungkinkan benar-ditulis program untuk aman jalur input proses terlalu lama ke toko di array hasil. Secara umum, hal ini mengharuskan pemanggil fgetsmemperhatikan ada atau tidaknya karakter baris baru dalam larik hasil. Pertimbangkan untuk menggunakan fgets(bersama dengan pemrosesan yang diperlukan berdasarkan karakter baris baru) daripada gets_s.

5 AradhanaMohanty Aug 22 2017 at 16:19

gets()berbahaya karena bisa saja pengguna merusak program dengan mengetik terlalu banyak pada prompt. Itu tidak dapat mendeteksi akhir dari memori yang tersedia, jadi jika Anda mengalokasikan jumlah memori yang terlalu kecil untuk tujuan tersebut, itu dapat menyebabkan kesalahan dan crash. Kadang-kadang tampaknya sangat tidak mungkin bahwa pengguna akan mengetik 1000 huruf ke dalam prompt yang ditujukan untuk nama seseorang, tetapi sebagai programmer, kita perlu membuat program kita antipeluru. (ini juga dapat menjadi risiko keamanan jika pengguna dapat merusak program sistem karena mengirimkan terlalu banyak data).

fgets() memungkinkan Anda untuk menentukan berapa banyak karakter yang diambil dari buffer input standar, sehingga mereka tidak membanjiri variabel.

3 user3717661 May 01 2016 at 08:00

Fungsi C berbahaya dan telah menjadi kesalahan yang sangat merugikan. Tony Hoare memilihnya secara khusus dalam ceramahnya "Referensi Null: Kesalahan Miliaran Dolar":

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

Seluruh jam layak untuk ditonton tetapi untuk komentarnya dilihat dari 30 menit dengan spesifik mendapat kritik sekitar 39 menit.

Mudah-mudahan ini membangkitkan selera Anda untuk keseluruhan pembicaraan, yang menarik perhatian pada bagaimana kita membutuhkan bukti kebenaran yang lebih formal dalam bahasa dan bagaimana desainer bahasa harus disalahkan atas kesalahan dalam bahasa mereka, bukan programmernya. Ini tampaknya menjadi alasan yang meragukan bagi perancang bahasa yang buruk untuk menyalahkan programmer dengan kedok 'kebebasan programmer'.

2 SteveSummit Apr 01 2016 at 04:52

Saya ingin menyampaikan undangan yang sungguh-sungguh kepada pengelola perpustakaan C di luar sana yang masih termasuk getsdi perpustakaan mereka "kalau-kalau ada yang masih bergantung padanya": Harap ganti penerapan Anda dengan yang setara dengan

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

Ini akan membantu memastikan tidak ada yang masih bergantung padanya. Terima kasih.