Запретить процессу открывать новый файловый дескриптор на Linux, но разрешать получение файловых дескрипторов через сокеты - PullRequest
9 голосов
/ 14 января 2020

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

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

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

1 Ответ

6 голосов
/ 14 января 2020

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

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

  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) разрешено, процесс может применять другие фильтры.
...