Apakah ide yang baik untuk memanggil perintah shell dari dalam C?

50

Ada perintah shell unix ( udevadm info -q path -n /dev/ttyUSB2) yang ingin saya panggil dari program C. Dengan mungkin sekitar satu minggu perjuangan, saya bisa menerapkannya sendiri, tetapi saya tidak ingin melakukannya.

Apakah ini praktik baik yang diterima secara luas untuk saya panggil saja popen("my_command", "r");, atau akankah hal itu menimbulkan masalah keamanan yang tidak dapat diterima dan meneruskan masalah kompatibilitas? Rasanya salah bagi saya untuk melakukan sesuatu seperti ini, tetapi saya tidak dapat menjelaskan mengapa itu buruk.

johnny_boy
sumber
8
Satu hal yang perlu dipertimbangkan adalah serangan injeksi.
MetaFight
8
Saya terutama memikirkan kasus di mana Anda memberikan input pengguna sebagai argumen perintah shell.
MetaFight
8
Bisakah seseorang menempatkan udevadmexecutable berbahaya dalam direktori yang sama dengan program Anda dan menggunakannya untuk meningkatkan hak istimewa? Saya tidak tahu banyak tentang keamanan unix, tetapi apakah program Anda akan dijalankan setuid root?
Andrew mengatakan Reinstate Monica
7
@AndrewPiliser Jika direktori saat ini tidak pada PATH, maka tidak - itu harus dijalankan dengan ./udevadm. IIRC Windows akan menjalankan executable direktori yang sama.
Izkata
4
Kapan pun memungkinkan jalankan program yang diinginkan secara langsung tanpa melalui shell.
CodesInChaos

Jawaban:

59

Ini tidak terlalu buruk, tetapi ada beberapa peringatan.

  1. seberapa portabel solusi Anda? Apakah biner yang Anda pilih beroperasi di mana-mana, mengeluarkan hasil dalam format yang sama, dll.? Apakah akan dihasilkan secara berbeda pada pengaturan LANGdll.?
  2. berapa banyak beban tambahan yang ditambahkan pada proses Anda? Forking binary menghasilkan beban lebih banyak dan membutuhkan lebih banyak sumber daya daripada mengeksekusi panggilan pustaka (secara umum). Apakah ini dapat diterima dalam skenario Anda?
  3. Apakah ada masalah keamanan? Bisakah seseorang mengganti biner yang Anda pilih dengan yang lain, dan melakukan perbuatan jahat sesudahnya? Apakah Anda menggunakan arg yang disediakan pengguna untuk biner Anda, dan bisakah mereka memberikan ;rm -rf /(misalnya) (perhatikan bahwa beberapa API akan memungkinkan Anda untuk menentukan args lebih aman daripada hanya menyediakannya di baris perintah)

Saya biasanya senang menjalankan binari ketika saya berada di lingkungan yang dikenal yang dapat saya prediksi, ketika keluaran biner mudah diurai (jika diperlukan - Anda mungkin hanya perlu kode keluar) dan saya tidak perlu melakukannya juga sering.

Seperti yang telah Anda catat, masalah lainnya adalah berapa banyak pekerjaan yang harus dilakukan untuk mereplikasi apa yang dilakukan biner? Apakah itu menggunakan perpustakaan Anda juga dapat memanfaatkan?

Brian Agnew
sumber
13
Forking a binary results in a lot more load and requires more resources than executing library calls (generally speaking). Jika ini pada Windows, saya setuju. Namun, OP menetapkan Unix di mana forking adalah biaya yang cukup rendah sehingga sulit untuk mengatakan apakah secara dinamis memuat perpustakaan lebih buruk daripada forking.
wallyk
1
Saya biasanya mengharapkan perpustakaan dimuat sekali , sedangkan biner dapat dieksekusi beberapa kali. Seperti biasa, ini tergantung pada skenario penggunaan, tetapi perlu dipertimbangkan (jika kemudian diberhentikan)
Brian Agnew
3
Tidak ada "forking" pada Windows, sehingga kutipan memiliki menjadi Unix-spesifik.
Cody Grey
5
Kernel NT memang mendukung forking, meskipun tidak diekspos melalui Win32. Bagaimanapun, saya akan percaya biaya menjalankan program eksternal akan datang lebih banyak execdaripada dari fork. Memuat bagian, menautkan objek bersama, menjalankan kode init untuk masing-masing. Dan kemudian harus mengubah semua data menjadi teks untuk diuraikan di sisi lain, baik untuk input dan output.
spektrum
8
Agar adil, bagaimanapun udevadm info -q path -n /dev/ttyUSB2juga tidak akan bekerja pada Windows sehingga seluruh diskusi forking sedikit berteori.
MSalters
37

Dibutuhkan sangat hati-hati untuk menjaga terhadap kerentanan injeksi setelah Anda memperkenalkan vektor potensial. Ini berada di garis depan pikiran Anda sekarang, tetapi nanti Anda mungkin perlu kemampuan untuk memilih ttyUSB0-3, maka daftar itu akan digunakan di tempat lain sehingga akan diperhitungkan untuk mengikuti prinsip tanggung jawab tunggal, maka pelanggan akan memiliki persyaratan untuk menempatkan perangkat sewenang-wenang dalam daftar, dan pengembang membuat yang perubahan akan tidak tahu bahwa daftar akhirnya akan digunakan dengan cara yang tidak aman.

Dengan kata lain, kode seolah-olah pengembang paling ceroboh yang Anda kenal membuat perubahan yang tidak aman di bagian kode yang tampaknya tidak terkait.

Kedua, output dari alat CLI umumnya tidak dianggap sebagai antarmuka yang stabil kecuali jika dokumentasi secara khusus menandainya. Anda mungkin akan mengandalkan skrip yang Anda jalankan yang bisa Anda atasi sendiri, tetapi tidak untuk sesuatu yang Anda gunakan untuk pelanggan.

Ketiga, jika Anda menginginkan cara mudah untuk mengekstraksi nilai dari perangkat lunak Anda, kemungkinan orang lain juga menginginkannya. Cari perpustakaan yang sudah melakukan apa yang Anda inginkan. libudevsudah diinstal di sistem saya:

#include <libudev.h>
#include <sys/stat.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
    struct stat statbuf;

    if (stat("dev/ttyUSB2", &statbuf) < 0)
        return -1;
    struct udev* udev = udev_new();
    struct udev_device *dev = udev_device_new_from_devnum(udev, 'c', statbuf.st_rdev);

    printf("%s\n", udev_device_get_devpath(dev));

    udev_device_unref(dev);
    udev_unref(udev);
    return 0;
}

Ada fungsi berguna lainnya di perpustakaan itu. Dugaan saya adalah jika Anda memerlukan ini, beberapa fungsi terkait mungkin berguna juga.

Karl Bielefeldt
sumber
2
TERIMA KASIH. Saya menghabiskan begitu lama mencari libudev. Aku tidak bisa menemukannya!
johnny_boy
2
Saya tidak melihat bagaimana "kerentanan injeksi" menjadi masalah di sini kecuali jika program OP dijalankan ketika pengguna lain memiliki kendali atas lingkungannya, yang terutama merupakan kasus jika terjadi suid (juga mungkin, tetapi pada tingkat lebih rendah, hal-hal seperti cgi dan ssh paksa-perintah). Tidak ada alasan mengapa program normal perlu dikeraskan terhadap pengguna yang sedang mereka jalankan.
R ..
2
@R ..: "Kerentanan injeksi" adalah ketika Anda menggeneralisasi ttyUSB2dan menggunakannya sprintf(buf, "udevadm info -q path -n /dev/%s", argv[1]). Sekarang tampaknya cukup aman, tetapi bagaimana jika argv[1]itu "nul || echo OOPS"?
MSalters
3
@R ..: Anda hanya perlu lebih banyak imajinasi. Pertama, ini adalah string yang tetap, kemudian argumen yang disediakan oleh pengguna, lalu argumen yang disediakan oleh beberapa program lain yang dijalankan oleh pengguna tetapi mereka tidak mengerti, lalu argumen yang diambil oleh browser mereka dari halaman web. Jika semuanya terus "menurun", maka pada akhirnya seseorang harus mengambil tanggung jawab untuk membersihkan input, dan Karl mengatakan bahwa sangat hati-hati untuk mengidentifikasi titik itu, di mana pun itu datang.
Steve Jessop
6
Meskipun jika prinsip kehati-hatian benar-benar adalah "menganggap pengembang yang paling ceroboh yang Anda tahu membuat perubahan yang tidak aman", dan saya harus menulis kode sekarang untuk menjamin yang tidak dapat menghasilkan exploit, maka secara pribadi saya tidak bisa bangun dari tempat tidur. Pengembang yang paling ceroboh yang saya tahu membuat Machievelli terlihat seperti dia bahkan tidak mencoba .
Steve Jessop
16

Dalam kasus spesifik Anda, di mana Anda ingin memohon udevadm, saya curiga Anda bisa masuk udevsebagai perpustakaan dan membuat fungsi yang sesuai dipanggil sebagai alternatif?

mis., Anda dapat melihat apa yang sedang dilakukan udevadm ketika menjalankan mode "info": https://github.com/gentoo/eudev/blob/master/src/udev/udevadm-info.c dan melakukan panggilan yang sama tentang udevadm yang mereka buat.

Ini akan menghindari banyak kelemahan-pengorbanan yang disebutkan dalam jawaban luar biasa Brian Agnew - misalnya, tidak mengandalkan biner yang ada di jalur tertentu, menghindari biaya forking, dll.

Adam Krouskop
sumber
Terima kasih, saya menemukan repo yang tepat, dan saya akhirnya melihat melalui sedikit.
johnny_boy
1
… Dan libudev tidak terlalu sulit untuk digunakan. Penanganan kesalahannya agak kurang, tetapi enumerasi, kueri, dan pemantauan API cukup mudah. Perjuangan rasa takut Anda mungkin akan muncul kurang dari 2 jam jika Anda mencobanya.
Spektrum
7

Pertanyaan Anda sepertinya membutuhkan jawaban hutan, dan jawaban di sini seperti jawaban pohon, jadi saya pikir saya akan memberi Anda jawaban hutan.

Ini sangat jarang bagaimana program C ditulis. Selalu bagaimana skrip shell ditulis, dan kadang-kadang bagaimana program Python, perl atau Ruby ditulis.

Orang-orang biasanya menulis dalam bahasa C agar mudah menggunakan perpustakaan sistem dan mengarahkan akses tingkat rendah ke panggilan sistem OS serta untuk kecepatan. Dan C adalah bahasa yang sulit untuk ditulis, jadi jika orang tidak membutuhkan hal-hal itu, maka mereka tidak menggunakan C. Juga program C biasanya hanya bergantung pada pustaka bersama dan file konfigurasi.

Keluar ke sub-proses tidak terlalu cepat, dan tidak memerlukan akses yang baik dan terkendali ke fasilitas sistem tingkat rendah, dan memperkenalkan ketergantungan yang mungkin mengejutkan pada eksekusi eksternal, sehingga tidak biasa untuk melihat dalam program C.

Ada beberapa kekhawatiran tambahan. Masalah keamanan dan portabilitas yang disebutkan orang sepenuhnya valid. Mereka sama-sama valid untuk skrip shell tentu saja, tetapi orang-orang mengharapkan masalah semacam itu dalam skrip shell. Tetapi program C biasanya tidak diharapkan memiliki kelas masalah keamanan ini, yang membuatnya lebih berbahaya.

Tapi, menurut saya, masalah terbesar yang harus dilakukan dengan cara popenakan berinteraksi dengan seluruh program Anda. popenharus membuat proses anak, membaca outputnya dan mengumpulkan status keluarnya. Sementara itu, stderr proses itu akan terhubung ke stderr yang sama dengan program Anda, yang dapat menyebabkan output membingungkan, dan stdinnya akan sama dengan program Anda, yang dapat menyebabkan masalah menarik lainnya. Anda dapat menyelesaikannya dengan memasukkan </dev/null 2>/dev/nullstring yang Anda lewati popenkarena ditafsirkan oleh shell.

Dan popenmenciptakan proses anak. Jika Anda melakukan sesuatu dengan penanganan sinyal atau proses forking sendiri, Anda mungkin akan mendapatkan SIGCHLDsinyal aneh . Panggilan Anda untuk waitdapat berinteraksi secara aneh dengan popendan mungkin menciptakan kondisi balapan yang aneh.

Masalah keamanan dan portabilitas tentu saja ada. Karena mereka untuk skrip shell atau apa pun yang menjalankan executable lain pada sistem. Dan Anda harus berhati-hati bahwa orang-orang menggunakan program Anda tidak bisa mendapatkan shell meta-charcaters ke string Anda masuk ke popenkarena string yang diberikan langsung ke shdengan sh -c <string from popen as a single argument>.

Tapi saya tidak berpikir mereka mengapa aneh melihat program C menggunakan popen. Alasannya aneh adalah karena C biasanya bahasa tingkat rendah, dan popenbukan tingkat rendah. Dan karena menggunakan popenbatasan desain tempat pada program Anda karena itu akan berinteraksi aneh dengan input dan output standar program Anda dan membuatnya susah untuk melakukan manajemen proses atau penanganan sinyal Anda sendiri. Dan karena program C biasanya tidak diharapkan memiliki ketergantungan pada executable eksternal.

Beraneka ragam
sumber
0

Program Anda mungkin mengalami peretasan, dll. Salah satu cara untuk melindungi diri Anda dari jenis aktivitas ini adalah dengan membuat salinan lingkungan mesin Anda saat ini dan menjalankan program Anda menggunakan sistem yang disebut chroot.

Dari sudut pandang program Anda, pengeksekusiannya dalam lingkungan normal, dari aspek keamanan, jika seseorang merusak program Anda, program itu hanya memiliki akses ke elemen-elemen yang Anda berikan ketika membuat salinan.

Pengaturan seperti itu disebut jail chroot untuk detail lebih lanjut lihat chroot jail .

Biasanya digunakan untuk menyiapkan server yang dapat diakses publik, dll.

Dave
sumber
3
OP sedang menulis sebuah program untuk dijalankan orang lain, bukan bermain dengan binari atau sumber yang tidak tepercaya di sistemnya sendiri.
Peter Cordes