Saya menjalankan file a.out saya. Setelah eksekusi, program berjalan selama beberapa waktu kemudian keluar dengan pesan:
**** stack smashing detected ***: ./a.out terminated*
*======= Backtrace: =========*
*/lib/tls/i686/cmov/libc.so.6(__fortify_fail+0x48)Aborted*
Apa yang mungkin menjadi alasan untuk ini dan bagaimana cara memperbaikinya?
Jawaban:
Stack Smashing di sini sebenarnya disebabkan karena mekanisme perlindungan yang digunakan oleh gcc untuk mendeteksi kesalahan buffer overflow. Misalnya dalam cuplikan berikut:
Kompiler, (dalam hal ini gcc) menambahkan variabel perlindungan (disebut kenari) yang memiliki nilai yang diketahui. String input dengan ukuran lebih besar dari 10 menyebabkan korupsi pada variabel ini yang mengakibatkan SIGABRT menghentikan program.
Untuk mendapatkan beberapa wawasan, Anda dapat mencoba menonaktifkan perlindungan gcc ini menggunakan opsi
-fno-stack-protector
saat kompilasi. Dalam hal ini Anda akan mendapatkan kesalahan yang berbeda, kemungkinan besar kesalahan segmentasi saat Anda mencoba mengakses lokasi memori ilegal. Catatan yang-fstack-protector
harus selalu dinyalakan untuk rilis build karena merupakan fitur keamanan.Anda bisa mendapatkan beberapa informasi tentang titik overflow dengan menjalankan program dengan debugger. Valgrind tidak bekerja dengan baik dengan kesalahan yang berhubungan dengan stack, tetapi seperti debugger, ini dapat membantu Anda menunjukkan lokasi dan alasan crash.
sumber
Contoh reproduksi minimal dengan analisis pembongkaran
main.c
GitHub hulu .
Kompilasi dan jalankan:
gagal seperti yang diinginkan:
Diuji pada Ubuntu 16.04, GCC 6.4.0.
Membongkar
Sekarang kita melihat pembongkaran:
yang mengandung:
Perhatikan komentar berguna secara otomatis ditambahkan oleh
objdump
's modul kecerdasan buatan .Jika Anda menjalankan program ini beberapa kali melalui GDB, Anda akan melihat bahwa:
myfunc
adalah apa yang memodifikasi alamat kenariKenari diacak dengan menyetelnya
%fs:0x28
, yang berisi nilai acak seperti yang dijelaskan di:Upaya debug
Mulai sekarang, kami memodifikasi kode:
sebagai gantinya:
menjadi lebih menarik.
Kami kemudian akan mencoba untuk melihat apakah kami dapat menentukan
+ 1
panggilan pelakunya dengan metode yang lebih otomatis daripada hanya membaca dan memahami seluruh kode sumber.gcc -fsanitize=address
untuk mengaktifkan Sanitizer Alamat Google (ASan)Jika Anda mengkompilasi ulang dengan flag ini dan menjalankan program, itu output:
diikuti oleh beberapa keluaran yang lebih berwarna.
Ini dengan jelas menunjukkan garis bermasalah 12.
Kode sumber untuk ini adalah di: https://github.com/google/sanitizers tetapi seperti yang kita lihat dari contoh, kode itu sudah di-upstream ke GCC.
ASan juga dapat mendeteksi masalah memori lain seperti kebocoran memori: Bagaimana menemukan kebocoran memori dalam kode / proyek C ++?
Valgrind SGCheck
Seperti yang disebutkan oleh orang lain , Valgrind tidak pandai memecahkan masalah seperti ini.
Itu memang memiliki alat percobaan yang disebut SGCheck :
Jadi saya tidak terlalu terkejut ketika tidak menemukan kesalahan:
Pesan kesalahan akan terlihat seperti ini rupanya: Valgrind missing error
GDB
Pengamatan penting adalah bahwa jika Anda menjalankan program melalui GDB, atau memeriksa
core
file setelah fakta:kemudian, seperti yang kita lihat di majelis, GDB akan mengarahkan Anda ke akhir fungsi yang melakukan pemeriksaan kenari:
Dan karena itu masalahnya kemungkinan ada di salah satu panggilan yang dibuat fungsi ini.
Selanjutnya kita mencoba untuk menentukan panggilan gagal yang tepat dengan langkah pertama setelah kenari ditetapkan:
dan memperhatikan alamat:
Sekarang, ini benar-benar membuat kita berada pada instruksi yang menyinggung:
len = 5
dani = 4
, dalam kasus khusus ini, memang mengarahkan kita ke jalur pelakunya 12.Namun, jejak balik rusak, dan berisi beberapa sampah. Backtrace yang benar akan terlihat seperti:
jadi mungkin ini bisa merusak tumpukan dan mencegah Anda melihat jejak.
Juga, metode ini membutuhkan mengetahui apa panggilan terakhir dari fungsi pemeriksaan kenari jika tidak Anda akan memiliki false positive, yang tidak akan selalu layak, kecuali jika Anda menggunakan debugging terbalik .
sumber
Silakan lihat situasi berikut:
Ketika saya menonaktifkan stack smashing protector, tidak ada kesalahan yang terdeteksi, yang seharusnya terjadi ketika saya menggunakan "./a.out wepassssssssssssssssssss"
Jadi untuk menjawab pertanyaan Anda di atas, pesan "** penghancuran tumpukan terdeteksi: xxx" ditampilkan karena pelindung penghancuran tumpukan Anda aktif dan menemukan bahwa ada kelebihan tumpukan di program Anda.
Cari tahu di mana itu terjadi, dan perbaiki.
sumber
Anda dapat mencoba men-debug masalah menggunakan valgrind :
sumber
Itu berarti bahwa Anda menulis ke beberapa variabel di stack dengan cara ilegal, kemungkinan besar sebagai hasil dari Buffer overflow .
sumber
Satu skenario akan menjadi dalam contoh berikut:
Dalam program ini, Anda dapat membalikkan String atau bagian dari string jika misalnya Anda memanggil
reverse()
dengan sesuatu seperti ini:Jika Anda memutuskan untuk melewatkan panjang array seperti ini:
Bekerja dengan baik juga.
Tetapi ketika Anda melakukan ini:
Anda mendapatkan:
Dan ini terjadi karena dalam kode pertama, panjang
arr
diperiksa direvSTR()
dalamnya baik-baik saja, tetapi dalam kode kedua di mana Anda melewati panjang:Panjangnya sekarang lebih panjang dari panjang sebenarnya yang Anda lewati ketika Anda mengatakannya
arr + 2
.Panjang
strlen ( arr + 2 )
! =strlen ( arr )
.sumber
gets
danscrcpy
. Saya ingin tahu apakah kita bisa memperkecil jika lebih jauh. Saya akan setidaknya menyingkirkanstring.h
dengansize_t len = sizeof( arr );
. Diuji pada gcc 6.4, Ubuntu 16.04. Saya juga akan memberikan contoh gagal denganarr + 2
meminimalkan copy paste.Tumpukan korupsi yang disebabkan oleh buffer overflows. Anda dapat bertahan melawan mereka dengan pemrograman defensif.
Setiap kali Anda mengakses sebuah array, beri penegasan di depan array untuk memastikan aksesnya tidak di luar batas. Sebagai contoh:
Ini membuat Anda berpikir tentang batasan array dan juga membuat Anda berpikir tentang menambahkan tes untuk memicu mereka jika memungkinkan. Jika beberapa dari pernyataan ini dapat gagal selama penggunaan normal mengubahnya menjadi biasa
if
.sumber
Saya mendapatkan kesalahan ini saat menggunakan malloc () untuk mengalokasikan sebagian memori ke struct * setelah menghabiskan beberapa kode debug ini, saya akhirnya menggunakan fungsi bebas () untuk membebaskan memori yang dialokasikan dan kemudian pesan kesalahan hilang :)
sumber
Sumber lain dari smashing stack adalah penggunaan yang salah (
vfork()
bukan)fork()
.Saya baru saja mendebat kasus ini, di mana proses anak tidak dapat mencapai
execve()
target yang dapat dieksekusi dan mengembalikan kode kesalahan daripada menelepon_exit()
.Karena
vfork()
telah melahirkan anak itu, ia kembali ketika sebenarnya masih mengeksekusi di dalam ruang proses induk, tidak hanya merusak tumpukan induk, tetapi menyebabkan dua set diagnostik yang berbeda dicetak oleh kode "hilir".Mengubah
vfork()
untukfork()
tetap kedua masalah, seperti yang dilakukan mengubah anakreturn
pernyataan untuk_exit()
gantinya.Tetapi karena kode anak mendahului
execve()
panggilan dengan panggilan ke rutinitas lain (untuk mengatur uid / gid, dalam kasus khusus ini), secara teknis tidak memenuhi persyaratanvfork()
, jadi mengubahnya untuk digunakanfork()
adalah benar di sini.(Perhatikan bahwa
return
pernyataan bermasalah sebenarnya tidak dikodekan seperti itu - sebagai gantinya, makro dipanggil, dan makro itu memutuskan apakah akan_exit()
ataureturn
didasarkan pada variabel global. Jadi tidak segera jelas bahwa kode anak tidak sesuai untukvfork()
penggunaan. )Untuk informasi lebih lanjut, lihat:
Perbedaan antara fork (), vfork (), exec () dan clone ()
sumber