Запретить процессу открывать новый дескриптор файла в Linux, но разрешать получение файловых дескрипторов через сокеты

9

В настоящее время я работаю над проектом, в котором у меня есть родительский процесс, который устанавливает пару сокетов, разветвляется, а затем использует эту пару сокетов для связи. Дочерний, если он хочет открыть файл (или любой другой ресурс, основанный на дескрипторе файла), должен всегда идти к родителю, запрашивать ресурс и получать fdотправленный через socketpair. Кроме того, я хочу запретить ребенку самостоятельно открывать любой дескриптор файла.

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

Для моего варианта использования это может быть любая конфигурация ядра, системный вызов и т. Д., Если это применимо после fork и при условии, что оно применяется ко всем файловым дескрипторам (не только файлам, но также сокетам, парам сокетов и т. Д.).

jklmnn
источник
1
Вы можете быть заинтересованы в seccomp.
user253751

Ответы:

6

Здесь у вас есть именно тот случай использования seccomp .

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

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

Прежде чем продолжить, ОБРАТИТЕ ВНИМАНИЕ, что такой черный список, как этот, в целом слабее, чем белый. Это позволяет любой системный вызов, который явно не запрещен, и может привести к обходу фильтра . Если вы считаете, что дочерний процесс, который вы хотите выполнить, мог злонамеренно пытаться избежать фильтра, или если вы уже знаете, какие системные вызовы понадобятся детям, лучше использовать белый список, и вы должны сделать обратное: создать фильтр с действием по умолчанию SCMP_ACT_KILLи разрешить необходимые системные вызовы с 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:

  • После применения фильтра seccomp процесс не может быть удален или изменен.
  • Если fork(2)илиclone(2) фильтр разрешен разрешен, любые дочерние процессы будут ограничены тем же фильтром.
  • Если execve(2)это разрешено, существующий фильтр будет сохранен при вызовеexecve(2) .
  • Если prctl(2)системный вызов разрешен, процесс может применить дополнительные фильтры.
Марко Бонелли
источник
2
Черный список для песочницы? Как правило, плохая идея, вы хотите вместо этого белый список.
дедупликатор
@Deduplicator Я знаю, но подход белого списка не очень хорошо подходит для ситуации OP, так как они просто хотят запретить открывать новые файловые дескрипторы. Я добавлю записку в конце.
Марко Бонелли
Спасибо за ответ, это то, что мне нужно. Белый список действительно лучше для приложения, которое я изначально намеревался. Я просто не имел в виду, что нужно ограничивать больше вещей, чем просто открывать файловые дескрипторы.
jklmnn
@jklmnn да, именно так. Я действительно просто забыл, что socket(2)также может создать fd, так что это тоже должно быть заблокировано. Если вы знаете, что ребенок обрабатывает, лучше использовать белый список.
Марко Бонелли
@MarcoBonelli Белый список определенно лучше. Навскидку,creat() , dup()и dup2()все системы Linux вызовы, возвращающие дескрипторы файлов. Есть много способов обойти черный список ...
Эндрю Хенле