seccomp как выйти на EXIT_SUCCESS?

Как перейти к EXIT_SUCCESS после того, как установлен строгий режим seccomp. Правильно ли вызывать syscall(SYS_exit, EXIT_SUCCESS); в конце main?

#include <stdlib.h>
#include <unistd.h> 
#include <sys/prctl.h>     
#include <linux/seccomp.h> 
#include <sys/syscall.h>

int main(int argc, char **argv) {
  prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);

  //return EXIT_SUCCESS; // does not work
  //_exit(EXIT_SUCCESS); // does not work
  // syscall(__NR_exit, EXIT_SUCCESS); // (EDIT) This works! Is this the ultimate answer and the right way to exit success from seccomp-ed programs?
  syscall(SYS_exit, EXIT_SUCCESS); // (EDIT) works; SYS_exit equals __NR_exit
}

// gcc seccomp.c -o seccomp && ./seccomp; echo "${?}" # I want 0

person MCH    schedule 15.10.2015    source источник
comment
Вы не можете просто вернуть EXIT_SUCCESS? (Упс: неважно - недостаточно внимательно посмотрел на ваш код.)   -  person Steven    schedule 15.10.2015
comment
У меня такая же проблема, мой процесс убивается.   -  person Lev    schedule 15.07.2016
comment
Очень странно, что _exit(EXIT_SUCCESS) не работает, так как на странице руководства четко указано, что в строгом режиме seccomp единственные системные вызовы, которые разрешено делать вызывающему потоку, это read(2), write(2), _exit(2) ( но не exit_group(2)), а sigreturn(2). (где числа в скобках, конечно же, являются ручными разделами).   -  person    schedule 06.11.2016
comment
@user263688 user263688 Я не думаю, что проблема была в том, что вы опубликовали в качестве ответа (а не в ответе), я опубликовал ответ, было бы неплохо, если бы вы взглянули! :)   -  person gsamaras    schedule 07.11.2016


Ответы (2)


Как описано в eigenstate.org и в SECCOMP (2):

Единственные системные вызовы, которые разрешено выполнять вызывающему потоку, это read(2), write(2), _exit(2) (но не exit_group(2)), и сигретерн(2). Другие системные вызовы приводят к доставке сигнала SIGKILL.

В результате можно было бы ожидать, что _exit() будет работать, но это функция-оболочка, которая вызывает exit_group(2), что не разрешено в строгом режиме ([1],[2]), поэтому процесс уничтожается.

Об этом даже сообщается в exit(2) - man-странице Linux:

В glibc до версии 2.3 функция-оболочка _exit() вызывала одноименный системный вызов ядра. Начиная с glibc 2.3, функция-оболочка вызывает exit_group(2), чтобы завершить все потоки в процессе.

То же самое происходит с оператором return, который должен закончиться прекращением вашего процесса, очень похожим образом с _exit().

Отслеживание процесса предоставит дополнительное подтверждение (чтобы это отображалось, вы должны не установить PR_SET_SECCOMP; просто прокомментируйте prctl()), и я получил одинаковый результат для обоих нерабочих случаев:

linux12:/home/users/grad1459>gcc seccomp.c -o seccomp
linux12:/home/users/grad1459>strace ./seccomp
execve("./seccomp", ["./seccomp"], [/* 24 vars */]) = 0
brk(0)                                  = 0x8784000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb775f000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=97472, ...}) = 0
mmap2(NULL, 97472, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7747000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220\226\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1730024, ...}) = 0
mmap2(NULL, 1739484, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xdd0000
mmap2(0xf73000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a3) = 0xf73000
mmap2(0xf76000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xf76000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7746000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7746900, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xf73000, 8192, PROT_READ)     = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0x16e000, 4096, PROT_READ)     = 0
munmap(0xb7747000, 97472)               = 0
exit_group(0)                           = ?
linux12:/home/users/grad1459>

Как видите, exit_group() называется, все объясняя!


Теперь, как вы правильно сказали, "SYS_exit equals __NR_exit"; например, он определен в mit.syscall .ч:

#define SYS_exit __NR_exit

поэтому последние два вызова эквивалентны, т.е. вы можете использовать тот, который вам нравится, и вывод должен быть таким:

linux12:/home/users/grad1459>gcc seccomp.c -o seccomp && ./seccomp ; echo "${?}" 
0

PS

Конечно, вы можете определить filter самостоятельно и использовать:

prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, filter);

как объяснено в ссылке на собственное состояние, разрешить _exit() (или, строго говоря, exit_group(2)), но делайте это только в том случае, если вам это действительно нужно и вы знаете, что делаете.

person gsamaras    schedule 06.11.2016
comment
Кроме того, причина, по которой return EXIT_SUCCESS; также терпит неудачу, та же самая: библиотека GNU C делает exit_group() и в этом случае. У меня есть отдельный C для x86-64 SYSV ABI, который доказывает, что системный вызов exit отлично работает после вызова prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT), если вам интересно. - person Nominal Animal; 07.11.2016
comment
Однако запуска бинарного файла под strace (то есть strace ./example) достаточно, чтобы доказать, что библиотека C использует системный вызов exit_group вместо exit. - person Nominal Animal; 07.11.2016
comment
Привет, @NominalAnimal! :) Да, хорошая идея, в одной из ссылок тоже упоминалось, но не отобразилось, обновил! Нравится ли вам ответ сейчас? - person gsamaras; 07.11.2016
comment
О, я был достаточно доволен ответом как есть, но добавление strace устраняет любые сомнения, позволяя другим убедиться в этом самостоятельно. Мне это нравится. :) - person Nominal Animal; 07.11.2016
comment
Что касается заданного вопроса OP, я бы сказал, что библиотека GNU C выполняет системный вызов exit_group вместо exit, когда в процессе есть только один поток ... это ошибка. Мне не нравится идея обхода очистки библиотеки путем прямого вызова системного вызова exit. Другими словами, OP должен иметь возможность просто return EXIT_SUCCESS; или exit(EXIT_SUCCESS); без того, чтобы быть убитым сигналом. Только изменение внутренней части библиотеки C изменит это. Я бы сказал, пришло время сообщить об ошибке glibc. - person Nominal Animal; 07.11.2016
comment
Хорошая идея @NominalAnimal! - person gsamaras; 07.11.2016
comment
Я некоторое время думал об этом и решил, что создание пользовательского фильтра (который разрешает системный вызов exit_group) имеет больше смысла. Во-первых, код работает прямо сейчас. Надеюсь, вы не возражаете, если я добавлю отдельный ответ, следуя этой касательной? - person Nominal Animal; 09.11.2016
comment
Конечно, не @NominalAnimal, в конце концов, я один упомянул фильтр, рад, что у вас было время расширить его, хороший ответ! - person gsamaras; 09.11.2016
comment
Хорошо! Видите ли, я думал, как настраиваемый фильтр seccomp может быть полезен для плагинов, работающих в отдельном потоке или процессе, и хотел посмотреть, насколько это будет сложно. Не за что, оказывается. Эффективная генерация BPF нетривиальна, но определенно не сложна; просто вопрос экспериментов и испытаний. Самый простой генератор фильтров будет просто строить тесты в обратном порядке, наименее вероятно, что они будут тестироваться последними, но будут ограничены 255 тестами и только сравнением номеров системных вызовов; нетривиальная часть состоит в том, чтобы избежать этих ограничений, но оставаться эффективным. - person Nominal Animal; 09.11.2016

Проблема возникает из-за того, что библиотека GNU C использует системный вызов exit_group, если он доступен, в Linux вместо exit для функции _exit() (см. sysdeps/unix/sysv/linux/_exit.c для проверки), и как описано в man 2 prctl, системный вызов exit_group не разрешен строгим фильтром seccomp.

Поскольку вызов функции _exit() происходит внутри библиотеки C, мы не можем вставить его в нашу собственную версию (это просто вызовет системный вызов exit). (Обычная очистка процесса выполняется в другом месте; в Linux функция _exit() выполняет только последний системный вызов, завершающий процесс.)

Мы могли бы попросить разработчиков библиотеки GNU C использовать системный вызов exit_group в Linux только тогда, когда в текущем процессе более одного потока, но, к сожалению, это было бы непросто, и даже если бы его добавили прямо сейчас, потребовалось бы довольно много времени для эта функция будет доступна в большинстве дистрибутивов Linux.

К счастью, мы можем отказаться от строгого фильтра по умолчанию и вместо этого определить свой собственный. Есть небольшая разница в поведении: видимый сигнал, убивающий процесс, изменится с SIGKILL на SIGSYS. (Сигнал на самом деле не доставляется, так как ядро ​​​​убивает процесс; изменяется только кажущийся номер сигнала, вызвавший смерть процесса.)

Более того, это даже не так сложно. Я потратил немного времени на изучение некоторых трюков с макросами GCC, которые упростили бы управление списком разрешенных системных вызовов, но я решил, что это не будет хорошим подходом: список разрешенных системных вызовов должен быть тщательно продуман — мы только добавить exit_group() по сравнению со строгим фильтром, здесь! -- так что если сделать его немного сложным, это нормально.

Следующий код, например example.c, проверен для работы с ядром 4.4 (должен работать с ядром 3.5 или более поздней версии) на платформе x86-64 (как для x86, так и для x86-64, т. е. 32-разрядные и 64-битные двоичные файлы). Однако он должен работать на всех архитектурах Linux и не требует и не использует библиотеку libseccomp.

#define  _GNU_SOURCE
#include <stdlib.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <stdio.h>

static const struct sock_filter  strict_filter[] = {
    BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof (struct seccomp_data, nr))),

    BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_rt_sigreturn, 5, 0),
    BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_read,         4, 0),
    BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_write,        3, 0),
    BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_exit,         2, 0),
    BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_exit_group,   1, 0),

    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
};

static const struct sock_fprog  strict = {
    .len = (unsigned short)( sizeof strict_filter / sizeof strict_filter[0] ),
    .filter = (struct sock_filter *)strict_filter
};

int main(void)
{
    /* To be able to set a custom filter, we need to set the "no new privs" flag.
       The Documentation/prctl/no_new_privs.txt file in the Linux kernel
       recommends this exact form: */
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        fprintf(stderr, "Cannot set no_new_privs: %m.\n");
        return EXIT_FAILURE;
    }
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &strict)) {
        fprintf(stderr, "Cannot install seccomp filter: %m.\n");
        return EXIT_FAILURE;
    }

    /* The seccomp filter is now active.
       It differs from SECCOMP_SET_MODE_STRICT in two ways:
         1. exit_group syscall is allowed; it just terminates the
            process
         2. Parent/reaper sees SIGSYS as the killing signal instead of
            SIGKILL, if the process tries to do a syscall not in the
            explicitly allowed list
    */

    return EXIT_SUCCESS;
}

Скомпилируйте, используя, например.

gcc -Wall -O2 example.c -o example

и запустить с помощью

./example

или под strace, чтобы увидеть выполненные системные и библиотечные вызовы;

strace ./example

Программа strict_filter BPF действительно тривиальна. Первый код операции загружает номер системного вызова в аккумулятор. Следующие пять кодов операций сравнивают его с приемлемым номером системного вызова и, если он найден, выполняют переход к последнему коду операции, разрешающему системный вызов. В противном случае предпоследний код операции убивает процесс.

Обратите внимание, что хотя в документации указано, что sigreturn является разрешенным системным вызовом, фактическое имя системного вызова в Linux — rt_sigreturn. (sigreturn было объявлено устаревшим в пользу rt_sigreturn веков назад.)

Кроме того, при установке фильтра коды операций копируются в память ядра (см. kernel/seccomp.c в исходных кодах ядра Linux), поэтому он никак не повлияет на фильтр, если данные будут изменены позже. Другими словами, наличие структур static const не оказывает никакого влияния на безопасность.

Я использовал static, поскольку нет необходимости, чтобы символы были видны за пределами этой единицы компиляции (или в удаленном двоичном файле), и const, чтобы поместить данные в раздел данных только для чтения двоичного файла ELF.

Форма BPF_JUMP(BPF_JMP | BPF_JEQ, nr, equals, differs) проста: аккумулятор (номер системного вызова) сравнивается с nr. Если они равны, то следующие equals опкодов пропускаются. В противном случае следующие коды операций differs пропускаются.

Поскольку случаи равенства переходят к самому последнему коду операции, вы можете добавлять новые коды операций вверху (то есть сразу после начального кода операции), увеличивая счетчик пропусков равных для каждого из них.

Обратите внимание, что printf() не будет работать после установки фильтра seccomp, поскольку внутри библиотеки C требуется выполнить системный вызов fstat (на стандартный вывод) и системный вызов brk, чтобы выделить часть памяти для буфера.

person Nominal Animal    schedule 08.11.2016
comment
Это один из тех моментов, когда я хотел бы разделить награду. - person ; 11.11.2016
comment
@Rhymoid награда похожа на атом! :) Но не волнуйся, Номимал классный парень! - person gsamaras; 12.11.2016
comment
Это НЕ БЕЗОПАСНО, необходимо проверить номер архитектуры. Если вы скомпилируете это для x86-64, то в дополнение к системному вызову x86-64 15 (rt_sigreturn) вы также разрешите системный вызов x86-32 15, который является chmod. Хотели бы вы, чтобы ваш домашний каталог был доступен для записи во всем мире, а кеш веб-браузера setuid? - person Timothy Baldwin; 10.09.2019