Запобігайте процесу відкриття нового дескриптора файлів у Linux, але дозволяйте отримувати дескриптори файлів через сокети


9

Зараз я працюю над проектом, де у мене є батьківський процес, який встановлює socketpair, forks, а потім використовує цей socketpair для спілкування. Якщо дитина хоче відкрити файл (або будь-який інший ресурс на основі дескриптора файлу), дитина повинна завжди перейти до батьків, запитувати ресурс і отримати fdвідправлене через socketpair. Крім того, я хочу завадити дитині відкривати будь-який дескриптор файлу сам.

Я наткнувся на те, setrlimitщо успішно заважає дитині відкривати нові дескриптори файлів, але, здається, це також визнає недійсним дескриптори файлів, надіслані через початкове з'єднання сокета. Чи існує якийсь метод в Linux, який дозволяє одному процесу відкривати будь-який файл, надсилати його дескриптор файлів іншим процесам і дозволяє використовувати їх, не дозволяючи цим іншим процесам відкривати будь-який дескриптор файлу самостійно?

Для мого використання це може бути будь-яка конфігурація ядра, системний виклик тощо, якщо це може бути застосовано після вилки та до тих пір, поки це стосується всіх дескрипторів файлів (не лише файлів, але й сокетів, socketpairs тощо).


1
Можливо, вас зацікавить seccomp.
користувач253751

Відповіді:


6

У вас тут є саме такий варіант використання seccomp .

Використовуючи seccomp, ви можете фільтрувати системні виклики різними способами. Те , що ви хочете зробити в цій ситуації, відразу ж після того fork(), щоб встановити seccompфільтр , який забороняє використання open(2), openat(2), socket(2)(і більше). Для цього можна зробити наступне:

  1. Спочатку створіть контекст seccomp, використовуючи seccomp_init(3)поведінку за замовчуванням SCMP_ACT_ALLOW.
  2. Потім додайте правило до контексту, використовуючи seccomp_rule_add(3)для кожного syscall, який ви хочете відхилити. Ви можете використовувати SCMP_ACT_KILLдля вбивства процесу при спробі системного виклику, SCMP_ACT_ERRNO(val)щоб змусити syscall повернути вказане errnoзначення або будь-яке інше actionзначення, визначене на сторінці керівництва.
  3. Завантажте контекст, seccomp_load(3)щоб зробити його ефективним.

Перш ніж продовжувати, зверніть увагу, що такий підхід у чорному списку, як цей, загалом слабший, ніж підхід до білого списку. Це дозволяє будь-який syscall, який явно не заборонений, і може призвести до обходу фільтра . Якщо ви вважаєте, що дочірній процес, який ви хочете виконати, може зловмисно намагатися уникати фільтра, або якщо ви вже знаєте, які систематичні дзвінки знадобляться дітям, підхід із білого списку краще, і вам слід зробити протилежне вищесказаному: створити фільтр із дією за замовчуванням SCMP_ACT_KILLта дозволити необхідні syscalls з SCMP_ACT_ALLOW. У коді різниця мінімальна (білий список, мабуть, довший, але кроки однакові).

Ось приклад вищесказаного (я роблю exit(-1)у випадку помилки просто для простоти):

#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);
}

Тепер у вашій програмі ви можете зателефонувати вищевказаній функції, щоб застосувати фільтр seccomp відразу після fork(), наприклад, такий:

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

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

    // Child code here...

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

Кілька важливих зауважень щодо seccomp:

  • Одноразовий фільтр, що застосовується, не може бути видалений або змінений процесом.
  • Якщо фільтр дозволений fork(2)або clone(2)дозволений, будь-які дочірні процеси будуть обмежені тим самим фільтром.
  • Якщо execve(2)це дозволено, існуючий фільтр зберігатиметься під час виклику до execve(2).
  • Якщо prctl(2)дозволено систематичне виклик, процес може застосувати додаткові фільтри.

2
Чорний список для пісочниці? Як правило, погана ідея, ви хочете, щоб білі списки замість цього.
Дедуплікатор

@Deduplicator Я знаю, але білий підхід не дуже добре застосовується до ситуації з ОП, оскільки вони просто хочуть заборонити відкривати нові дескриптори файлів. Я додам записку наприкінці.
Марко Бонеллі

Дякую за відповідь, ось що мені потрібно. Білий список справді кращий для програми, яку я спочатку задумав. Я просто не мав на увазі, що є більше речей, які слід обмежити, ніж просто відкривати дескриптори файлів.
jklmnn

@jklmnn так, саме так. Я дійсно просто забув, що socket(2)також можна створити fd, так що і його слід заблокувати. Якщо ви знаєте, що процес дитини доцільний, краще використовувати підхід із білого списку.
Марко Бонеллі

@MarcoBonelli Білий список, безумовно, кращий. Навскидку, creat(), dup()і dup2()всі системи Linux виклики, які повертають дескриптори файлів. Існує багато способів навколо чорного списку ...
Ендрю Генле
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.