Mengapa fungsi get sangat berbahaya sehingga tidak boleh digunakan?
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
Untuk menggunakan gets
dengan 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.
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 stdin
sebagai 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_s
FungsiRingkasan
#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> char *gets_s(char *s, rsize_t n);
Batasan waktu proses
s
tidak akan menjadi penunjuk nol.n
tidak boleh sama dengan nol atau lebih besar dari RSIZE_MAX. Karakter baris baru, akhir file, atau kesalahan baca akan terjadi dalamn-1
karakter membaca daristdin
. 25)3 Jika ada pelanggaran batasan waktu proses,
s[0]
disetel ke karakter nol, dan karakter dibaca dan dibuangstdin
hingga karakter baris baru dibaca, atau terjadi kesalahan baca atau akhir file.Deskripsi
4
gets_s
Fungsi membaca paling banyak satu kurang dari jumlah karakter yang ditentukan olehn
dari aliran yang ditunjuk olehstdin
, ke dalam larik yang ditunjuk olehs
. 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 lains
mengambil nilai yang tidak ditentukan.Latihan yang direkomendasikan
6
fgets
Fungsi 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 pemanggilfgets
memperhatikan ada atau tidaknya karakter baris baru dalam larik hasil. Pertimbangkan untuk menggunakanfgets
(bersama dengan pemrosesan yang diperlukan berdasarkan karakter baris baru) daripadagets_s
.25) The
gets_s
fungsi, sepertigets
, membuat pelanggaran runtime-kendala untuk garis input meluap buffer untuk menyimpannya. Tidak sepertifgets
,gets_s
mempertahankan hubungan satu-ke-satu antara jalur input dan panggilan yang berhasil kegets_s
. Program yang digunakangets
mengharapkan 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 -print0
yang ujung nama berkasnya ditandai dengan '\0'
karakter ASCII NUL , misalnya.
Karena gets
tidak 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, gets
tidak peduli. Kedua, byte yang melebihi ukuran array tempat Anda meletakkannya (dalam hal ini array1
) akan menimpa apa pun yang mereka temukan di memori karena gets
akan menuliskannya. Dalam contoh sebelumnya, ini berarti bahwa jika Anda memasukkan "abcdefghijklmnopqrts"
mungkin, secara tidak terduga, itu akan menimpa juga array2
atau apa pun.
Fungsi ini tidak aman karena mengasumsikan masukan yang konsisten. JANGAN PERNAH MENGGUNAKANNYA!
Anda tidak boleh menggunakan gets
karena 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 fgets
fungsi dengan stdin
pegangan 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 fgets
juga. 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 fgets
yang 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" :-)
gadget .
Untuk membaca dari stdin:
char string[512];
fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
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 ().
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.
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_s
Fungsi#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
fgets
Fungsi memungkinkan benar-ditulis program untuk aman jalur input proses terlalu lama ke toko di array hasil. Secara umum, hal ini mengharuskan pemanggilfgets
memperhatikan ada atau tidaknya karakter baris baru dalam larik hasil. Pertimbangkan untuk menggunakanfgets
(bersama dengan pemrosesan yang diperlukan berdasarkan karakter baris baru) daripadagets_s
.
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.
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'.
Saya ingin menyampaikan undangan yang sungguh-sungguh kepada pengelola perpustakaan C di luar sana yang masih termasuk gets
di 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.