Cegah proses membuka deskriptor file baru di Linux tetapi izinkan menerima deskriptor file melalui soket

9

Saat ini saya sedang mengerjakan sebuah proyek di mana saya memiliki proses induk yang mengatur socketpair, garpu dan kemudian menggunakan socketpair ini untuk berkomunikasi. Anak, jika ingin membuka file (atau sumber daya berbasis deskriptor file lainnya) harus selalu pergi ke orang tua, minta sumber dan dapatkan fddikirim melalui soketpair. Selanjutnya saya ingin mencegah anak dari membuka file descriptor dengan sendirinya.

Saya tersandung setrlimityang berhasil mencegah anak dari membuka deskriptor file baru, tetapi juga tampaknya membatalkan deskriptor file yang dikirim melalui koneksi socket awal. Apakah ada metode di Linux yang memungkinkan satu proses untuk membuka file apa pun, mengirim deskriptor file ke proses lain dan memungkinkan mereka menggunakannya tanpa mengizinkan proses lain ini untuk membuka file deskriptor sendiri?

Untuk kasus penggunaan saya yang dapat berupa konfigurasi kernel, panggilan sistem, dll. Asalkan dapat diterapkan setelah garpu dan selama itu berlaku untuk semua deskriptor file (bukan hanya file tetapi juga soket, soket, dll.).

jklmnn
sumber
1
Anda mungkin tertarik dengan seccomp.
user253751

Jawaban:

6

Apa yang Anda miliki di sini adalah kasus penggunaan seccomp .

Dengan menggunakan seccomp, Anda dapat memfilter panggilan sys dengan berbagai cara. Apa yang ingin Anda lakukan dalam situasi ini, tepat setelah fork(), untuk menginstal seccompfilter yang melarang penggunaan open(2), openat(2), socket(2)(dan lebih). Untuk mencapai ini, Anda dapat melakukan hal berikut:

  1. Pertama, buat konteks seccomp menggunakan seccomp_init(3)dengan perilaku default SCMP_ACT_ALLOW.
  2. Kemudian tambahkan aturan ke konteks menggunakan seccomp_rule_add(3)untuk setiap syscall yang ingin Anda tolak. Anda dapat menggunakan SCMP_ACT_KILLuntuk membunuh proses jika syscall dicoba, SCMP_ACT_ERRNO(val)untuk membuat syscall gagal mengembalikan nilai yang ditentukan errno, atau actionnilai lain yang ditentukan dalam halaman manual.
  3. Memuat konteks menggunakan seccomp_load(3)untuk membuatnya efektif.

Sebelum melanjutkan, CATATAN bahwa pendekatan daftar hitam seperti ini secara umum lebih lemah daripada pendekatan daftar putih. Ini memungkinkan syscall yang tidak dianulir secara eksplisit, dan dapat mengakibatkan bypass filter . Jika Anda yakin bahwa proses anak yang ingin Anda lakukan bisa berbahaya mencoba menghindari filter, atau jika Anda sudah tahu syscalls mana yang dibutuhkan oleh anak-anak, pendekatan daftar putih lebih baik, dan Anda harus melakukan kebalikan dari yang di atas: buat filter dengan aksi default SCMP_ACT_KILLdan izinkan syscall yang diperlukan SCMP_ACT_ALLOW. Dalam hal kode perbedaannya minimal (daftar putih mungkin lebih lama, tetapi langkah-langkahnya sama).

Berikut adalah contoh di atas (Saya melakukan exit(-1)jika ada kesalahan hanya karena kesederhanaan):

#include <stdlib.h>
#include <seccomp.h>

static void secure(void) {
    int err;
    scmp_filter_ctx ctx;

    int blacklist[] = {
        SCMP_SYS(open),
        SCMP_SYS(openat),
        SCMP_SYS(creat),
        SCMP_SYS(socket),
        SCMP_SYS(open_by_handle_at),
        // ... possibly more ...
    };

    // Create a new seccomp context, allowing every syscall by default.
    ctx = seccomp_init(SCMP_ACT_ALLOW);
    if (ctx == NULL)
        exit(-1);

    /* Now add a filter for each syscall that you want to disallow.
       In this case, we'll use SCMP_ACT_KILL to kill the process if it
       attempts to execute the specified syscall. */

    for (unsigned i = 0; i < sizeof(blacklist) / sizeof(blacklist[0]); i++) {
        err = seccomp_rule_add(ctx, SCMP_ACT_KILL, blacklist[i], 0);
        if (err)
            exit(-1);
    }

    // Load the context making it effective.
    err = seccomp_load(ctx);
    if (err)
        exit(-1);
}

Sekarang, di program Anda, Anda dapat memanggil fungsi di atas untuk menerapkan filter seccomp tepat setelah fork(), seperti ini:

child_pid = fork();
if (child_pid == -1)
    exit(-1);

if (child_pid == 0) {
    secure();

    // Child code here...

    exit(0);
} else {
    // Parent code here...
}

Beberapa catatan penting pada seccomp:

  • Filter seccomp, sekali diterapkan, tidak dapat dihapus atau diubah oleh proses.
  • Jika fork(2)atau clone(2)diizinkan oleh filter, setiap proses anak akan dibatasi oleh filter yang sama.
  • Jika execve(2)diizinkan, filter yang ada akan dipertahankan pada panggilan ke execve(2).
  • Jika prctl(2)syscall diizinkan, proses dapat menerapkan filter lebih lanjut.
Marco Bonelli
sumber
2
Daftar hitam untuk kotak pasir? Umumnya ide yang buruk, Anda ingin masuk daftar putih.
Deduplicator
@Deduplicator Saya tahu, tetapi pendekatan daftar putih tidak berlaku untuk situasi OP dengan sangat baik karena mereka hanya ingin melarang membuka deskriptor file baru. Saya akan menambahkan catatan di bagian akhir.
Marco Bonelli
Terima kasih atas jawabannya, itulah yang saya butuhkan. Daftar putih memang lebih baik untuk aplikasi yang saya maksudkan semula. Saya hanya tidak memiliki pikiran bahwa ada lebih banyak hal yang harus dibatasi daripada hanya membuka deskriptor file.
jklmnn
@ jklmnn ya, tepatnya. Saya memang hanya lupa yang socket(2)juga bisa buat fd, sehingga harus diblokir juga. Jika Anda tahu proses anak, pendekatan daftar putih lebih baik.
Marco Bonelli
@MarcoBonelli Daftar putih pasti lebih baik. Begitu saja, creat(), dup(), dan dup2()semua sistem Linux panggilan bahwa file kembali deskriptor. Ada banyak cara di sekitar daftar hitam ...
Andrew Henle