Kenapa gets()
berbahaya?
Worm internet pertama ( Morris Internet Worm ) melarikan diri sekitar 30 tahun yang lalu (1988-11-02), dan menggunakan gets()
buffer overflow sebagai salah satu metode penyebaran dari sistem ke sistem. Masalah dasarnya adalah bahwa fungsi tersebut tidak tahu seberapa besar buffer, sehingga terus membaca sampai menemukan baris baru atau bertemu EOF, dan mungkin meluap batas buffer yang diberikan.
Anda harus lupa bahwa 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, dan kemudian dihapus di C11). Sayangnya, itu akan tetap di perpustakaan selama bertahun-tahun (yang berarti 'dekade') karena alasan kompatibilitas. Jika terserah saya, implementasi gets()
akan menjadi:
char *gets(char *buffer)
{
assert(buffer != 0);
abort();
return 0;
}
Mengingat kode Anda akan macet, cepat atau lambat, lebih baik untuk mengatasi masalah lebih cepat daripada nanti. Saya 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()
fgets ()
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 yang 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 oleh caf dalam komentar dan paxdiablo menunjukkan dalam jawabannya, dengan fgets()
Anda mungkin memiliki data yang tersisa pada satu baris. Kode pembungkus saya membiarkan data itu untuk dibaca lain kali; Anda dapat dengan mudah memodifikasinya untuk melahap sisa data jika Anda lebih suka:
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
else
{
int ch;
while ((ch = getc(fp)) != EOF && ch != '\n')
;
}
Masalah residual adalah bagaimana melaporkan tiga status hasil yang berbeda - EOF atau kesalahan, pembacaan baris dan tidak terpotong, dan sebagian pembacaan baris tetapi data terpotong.
Masalah ini tidak muncul dengan gets()
karena tidak tahu di mana buffer Anda berakhir dan riang menginjak-injak ujungnya, mendatangkan malapetaka pada tata letak memori cenderung indah Anda, 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 baik - mereka melambangkan ungkapan '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
Fungsi
Ringkasan
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);
Runtime-kendala
s
tidak akan menjadi pointer nol. n
tidak boleh sama dengan nol atau lebih besar dari RSIZE_MAX. Karakter baris baru, end-of-file, atau kesalahan baca akan muncul dalam membaca
n-1
karakter dari stdin
. 25)
3 Jika ada pelanggaran runtime-constraint, s[0]
diatur ke karakter nol, dan karakter dibaca dan dibuang dari stdin
hingga karakter baris baru dibaca, atau end-of-file atau kesalahan baca terjadi.
Deskripsi
4 gets_s
Fungsi membaca paling banyak satu kurang dari jumlah karakter yang ditentukan oleh n
dari aliran menunjuk ke stdin
, ke dalam array yang ditunjuk oleh s
. Tidak ada karakter tambahan dibaca setelah karakter baris baru (yang dibuang) atau setelah akhir file. Karakter baris baru yang dibuang tidak dihitung terhadap jumlah karakter yang dibaca. Karakter nol ditulis segera setelah karakter terakhir membaca ke dalam array.
5 Jika akhir file ditemukan dan tidak ada karakter yang telah dibaca ke dalam array, atau jika kesalahan baca terjadi selama operasi, maka s[0]
diatur ke karakter nol, dan elemen lain dari s
mengambil nilai yang tidak ditentukan.
Latihan yang disarankan
6 fgets
Fungsi ini memungkinkan program yang ditulis dengan benar untuk memproses jalur input dengan aman terlalu lama untuk disimpan dalam array hasil. Secara umum ini mengharuskan penelepon fgets
memperhatikan ada atau tidak adanya karakter baris baru di array hasil. Pertimbangkan untuk menggunakan fgets
(bersamaan dengan pemrosesan yang diperlukan berdasarkan karakter baris baru) alih-alih
gets_s
.
25) The gets_s
fungsi, seperti gets
, membuat pelanggaran runtime-kendala untuk garis input meluap buffer untuk menyimpannya. Tidak seperti fgets
, gets_s
mempertahankan hubungan satu-ke-satu antara jalur input dan panggilan yang berhasil gets_s
. Program yang menggunakan gets
mengharapkan hubungan seperti itu.
Kompiler Microsoft Visual Studio menerapkan perkiraan pada 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 dalam Lampiran K sebagai bagian opsional dari perpustakaan. Sayangnya, ini jarang diimplementasikan pada sistem mirip Unix.
getline()
- POSIX
POSIX 2008 juga menyediakan alternatif yang aman untuk gets()
dipanggil getline()
. Ini mengalokasikan ruang untuk garis secara dinamis, sehingga Anda akhirnya harus membebaskannya. Karena itu menghilangkan batasan pada panjang garis. Ini juga mengembalikan panjang data yang telah dibaca, atau -1
(dan tidak EOF
!), Yang berarti bahwa byte nol dalam input dapat ditangani dengan andal. Ada juga variasi 'pilih pembatas karakter tunggal Anda' yang disebut getdelim()
; ini dapat berguna jika Anda berurusan dengan output dari find -print0
mana ujung nama file ditandai dengan '\0'
karakter ASCII NUL , misalnya.
gets()
Buffer_overflow_attack