| seccomp(2) | System Calls Manual | seccomp(2) |
seccomp - переводит процесс в состояние безопасных вычислений
Standard C library (libc, -lc)
#include <linux/seccomp.h> /* определения констант SECCOMP_* */ #include <linux/filter.h> /* определения struct sock_fprog */ #include <linux/audit.h> /* определения констант AUDIT_* */ #include <linux/signal.h> /* определения констант SIG* */ #include <sys/ptrace.h> /* определения констант PTRACE_* */ #include <sys/syscall.h> /* определения констант SYS_* */ #include <unistd.h>
int syscall(SYS_seccomp, unsigned int operation, unsigned int flags,
void *args);
Note: glibc provides no wrapper for seccomp(), necessitating the use of syscall(2).
Системный вызов seccomp() переводит вызвавший процесс в состояние безопасных вычислений (Secure Computing, seccomp).
В настоящее время в Linux поддерживаются следующие значения operation:
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
prctl(PR_SET_NO_NEW_PRIVS, 1);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, args);
struct seccomp_notif_sizes
__u16 seccomp_notif; /* Size of notification structure */
__u16 seccomp_notif_resp; /* Size of response structure */
__u16 seccomp_data; /* Size of 'struct seccomp_data' */
};
При добавлении фильтров посредством SECCOMP_SET_MODE_FILTER, значение args указывает на программу фильтрации:
struct sock_fprog {
unsigned short len; /* количество инструкций BPF */
struct sock_filter *filter; /* указатель на массив
инструкций BPF */
};
В каждой программе должно быть не менее одной инструкции BPF:
struct sock_filter { /* блок фильтрации */
__u16 code; /* действительный код фильтра */
__u8 jt; /* переход при совпадении */
__u8 jf; /* переход при несовпадении */
__u32 k; /* общее поле для различных целей */
};
При выполнении инструкций информация о системном вызове (когда используется режим адресации BPF_ABS) программе BPF доступна из буфера (только для чтения) в виде:
struct seccomp_data {
int nr; /* номер системного вызова */
__u32 arch; /* значение AUDIT_ARCH_*
(смотрите <linux/audit.h>) */
__u64 instruction_pointer; /* указатель на инструкцию ЦП */
__u64 args[6]; /* до 6 аргументов системного вызова */
};
Так как количество системных вызовов различно на разных архитектурах и некоторые архитектуры (например, x86-64) позволяют коду в пользовательском пространстве использовать соглашения о вызовах нескольких архитектур (и используемое соглашение может меняться на протяжении выполнения процесса, если он использует execve(2) для запуска выполняемых файлов, которые задействуют другие соглашения), то, обычно, необходимо проверять значение поля arch.
It is strongly recommended to use an allow-list approach whenever possible because such an approach is more robust and simple. A deny-list will have to be updated whenever a potentially dangerous system call is added (or a dangerous flag or option if those are deny-listed), and it is often possible to alter the representation of a value without altering its meaning, leading to a deny-list bypass. See also Caveats below.
Поле arch не уникально для всех соглашений о вызовах. В x86-64 ABI и x32 ABI в arch используется AUDIT_ARCH_X86_64, и они запускаются на одних и тех же процессорах. Чтобы отличать один ABI от другого используется маска __X32_SYSCALL_BIT с номером системного вызова.
This means that a policy must either deny all syscalls with __X32_SYSCALL_BIT or it must recognize syscalls with and without __X32_SYSCALL_BIT set. A list of system calls to be denied based on nr that does not also contain nr values with __X32_SYSCALL_BIT set can be bypassed by a malicious program that sets __X32_SYSCALL_BIT.
Additionally, kernels prior to Linux 5.4 incorrectly permitted nr in the ranges 512-547 as well as the corresponding non-x32 syscalls ORed with __X32_SYSCALL_BIT. For example, nr == 521 and nr == (101 | __X32_SYSCALL_BIT) would result in invocations of ptrace(2) with potentially confused x32-vs-x86_64 semantics in the kernel. Policies intended to work on kernels before Linux 5.4 must ensure that they deny or otherwise correctly handle these system calls. On Linux 5.4 and newer, such system calls will fail with the error ENOSYS, without doing anything.
В поле instruction_pointer содержится адрес инструкции машинного языка, который запускает системный вызов. Это может быть полезно вместе с /proc/pid/maps для выполнения проверок из какой области (отображение) программы делается системный вызов (вероятно, стоит блокировать системные вызовы mmap(2) и mprotect(2) для запрета программе удалять такие проверки).
When checking values from args, keep in mind that arguments are often silently truncated before being processed, but after the seccomp check. For example, this happens if the i386 ABI is used on an x86-64 kernel: although the kernel will normally not look beyond the 32 lowest bits of the arguments, the values of the full 64-bit registers will be present in the seccomp data. A less surprising example is that if the x86-64 ABI is used to perform a system call that takes an argument of type int, the more-significant half of the argument register is ignored by the system call, but visible in the seccomp data.
Фильтр seccomp возвращает 32-битное значение, состоящее из двух частей: в старших 16 битах (соответствует маске, определяемой константой SECCOMP_RET_ACTION_FULL) содержится одно из значений «действие», перечисленных далее; в младших 16 битах (определяется константой SECCOMP_RET_DATA) содержатся «данные», связанные с возвращаемым значением.
If multiple filters exist, they are all executed, in reverse order of their addition to the filter tree—that is, the most recently installed filter is executed first. (Note that all filters will be called even if one of the earlier filters returns SECCOMP_RET_KILL. This is done to simplify the kernel code and to provide a tiny speed-up in the execution of sets of filters by avoiding a check for this uncommon case.) The return value for the evaluation of a given system call is the first-seen action value of highest precedence (along with its accompanying data) returned by execution of all of the filters.
Значения действий, которые могут возвращаться фильтром seccomp (в порядке уменьшения приоритета):
Если значение действия ни одно из указанных выше, то действием фильтра считается или SECCOMP_RET_KILL_PROCESS (начиная с Linux 4.14), или SECCOMP_RET_KILL_THREAD (в Linux 4.13 и старее).
Файлы в каталоге /proc/sys/kernel/seccomp предоставляют дополнительную информацию seccomp и настройку:
Начиная с Linux 4.14 ядро позволяет протоколировать действия, возвращаемые фильтрами seccomp в журнал контроля (audit log). Ядро принимает решение о протоколировании действие основываясь на типе действия, имеется ли действие в файле actions_logged и включён ли контроль в ядре (например, посредством параметра загрузки ядра audit=1). Правила следующие:
On success, seccomp() returns 0. On error, if SECCOMP_FILTER_FLAG_TSYNC was used, the return value is the ID of the thread that caused the synchronization failure. (This ID is a kernel thread ID of the type returned by clone(2) and gettid(2).) On other errors, -1 is returned, and errno is set to indicate the error.
Функция seccomp() может завершиться с ошибкой по следующим причинам:
Системный вызов seccomp() впервые появился в Linux 3.17.
Системный вызов seccomp() является нестандартным расширением Linux.
Вместо ручного кодирования фильтров seccomp, как показано в примере ниже, вы можете воспользоваться библиотекой libseccomp, которая предоставляет клиентскую часть для генерации фильтров seccomp.
В поле Seccomp файла /proc/pid/status отображается метод просмотра режима seccomp в процессе; смотрите proc(5).
Вызов seccomp() предоставляет больше возможностей по сравнению с операцией PR_SET_SECCOMP prctl(2) (которая не поддерживает flags).
Начиная с Linux 4.4, вызов ptrace(2) с операцией PTRACE_SECCOMP_GET_FILTER можно использовать для получения дампа фильтров seccomp процесса.
Архитектурная поддержка фильтрации seccomp BPF доступна на следующих архитектурах:
Есть различные тонкости, которые нужно учитывать при применении фильтров seccomp к программе:
В следствии вышеупомянутого возможно понадобится фильтровать не тот системный вызов, который ожидался. В различных справочных страницах раздела 2 есть абзац Отличия между библиотекой C и ядром, в котором содержится полезная информация о различиях между оберточными функциями и нижележащими системными вызовами.
Кроме того, обратите внимание, что применение фильтров seccomp даже может привести к появлению дефектов приложений, когда фильтры вызывают неожиданные отказы для законных операций, которые приложение, возможно, должно было бы выполнить. Такие дефекты может оказаться нелегко обнаружить при тестировании фильтров seccomp, если дефекты возникают в редко используемых путях кода приложения.
Заметим, что следующие особенности BPF относятся только к фильтрам seccomp:
Программа, показанная далее, обрабатывает четыре и более аргументов. Первые три аргумента — номер системного вызова, числовой идентификатор архитектуры и номер ошибки. Программа использует эти значения для создания фильтра BPF, который используется во время работы для выполнения следующих проверок:
В оставшихся аргументах командной строки указываются путь и дополнительные аргументы программы, которую программа из примера должна попытаться выполнить с помощью execv(3) (библиотечной функции, которая использует системный вызов execve(2)). Несколько примеров запуска программы показаны далее.
Сначала мы выведем имя архитектуры, на которой работаем (x86-64), а затем создадим функцию оболочки, которая выдаёт список номеров системных вызовов этой архитектуры:
$ uname -m
x86_64
$ syscall_nr() { cat /usr/src/linux/arch/x86/syscalls/syscall_64.tbl | \ awk '$2 != "x32" && $3 == "'$1'" { print $1 }' }
Когда фильтр BPF отклоняет системный вызов (случай [2] выше), системный вызов завершается с номером ошибки, указанной в командной строке. В наших экспериментах используется номер ошибки 99:
$ errno 99 EADDRNOTAVAIL 99 Cannot assign requested address
В следующем примере мы пытаемся выполнить команду whoami(1), но фильтр BPF отклоняет системный вызов execve(2), и поэтому команда даже не начнёт выполняться:
$ syscall_nr execve
59
$ ./a.out
Использование: ./a.out <syscall_nr> <arch> <errno> <prog> [<args>]
Подсказка для <arch>: AUDIT_ARCH_I386: 0x40000003
AUDIT_ARCH_X86_64: 0xC000003E
$ ./a.out 59 0xC000003E 99 /bin/whoami
execv: Cannot assign requested address
В следующем примере фильтр BPF отклоняет системный вызов write(2), и хотя выполнение началось, команда whoami(1) не может записать в стандартный вывод:
$ syscall_nr write 1 $ ./a.out 1 0xC000003E 99 /bin/whoami
В последнем примере фильтр BPF отклоняет системный вызов, который не используется в команде whoami(1), и поэтому она выполняется без ошибок и выводит:
$ syscall_nr preadv 295 $ ./a.out 295 0xC000003E 99 /bin/whoami cecilia
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <unistd.h>
#define X32_SYSCALL_BIT 0x40000000
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
static int
install_filter(int syscall_nr, unsigned int t_arch, int f_errno)
{
unsigned int upper_nr_limit = 0xffffffff;
/* Assume that AUDIT_ARCH_X86_64 means the normal x86-64 ABI
(in the x32 ABI, all system calls have bit 30 set in the
'nr' field, meaning the numbers are >= X32_SYSCALL_BIT). */
if (t_arch == AUDIT_ARCH_X86_64)
upper_nr_limit = X32_SYSCALL_BIT - 1;
struct sock_filter filter[] = {
/* [0] Load architecture from 'seccomp_data' buffer into
accumulator. */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
(offsetof(struct seccomp_data, arch))),
/* [1] Jump forward 5 instructions if architecture does not
match 't_arch'. */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, t_arch, 0, 5),
/* [2] Load system call number from 'seccomp_data' buffer into
accumulator. */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
(offsetof(struct seccomp_data, nr))),
/* [3] Check ABI - only needed for x86-64 in deny-list use
cases. Use BPF_JGT instead of checking against the bit
mask to avoid having to reload the syscall number. */
BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, upper_nr_limit, 3, 0),
/* [4] Jump forward 1 instruction if system call number
does not match 'syscall_nr'. */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, syscall_nr, 0, 1),
/* [5] Matching architecture and system call: don't execute
the system call, and return 'f_errno' in 'errno'. */
BPF_STMT(BPF_RET | BPF_K,
SECCOMP_RET_ERRNO | (f_errno & SECCOMP_RET_DATA)),
/* [6] не совпал номер системного вызова: разрешаем
работу других системных вызовов */
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
/* [7] Destination of architecture mismatch: kill process. */
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
};
struct sock_fprog prog = {
.len = ARRAY_SIZE(filter),
.filter = filter,
};
if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog)) {
perror("seccomp");
return 1;
}
return 0;
}
int
main(int argc, char *argv[])
{
if (argc < 5) {
fprintf(stderr, "Использование: "
"%s <syscall_nr> <arch> <errno> <prog> [<args>]\n"
"Подсказка для <arch>: AUDIT_ARCH_I386: 0x%X\n"
" AUDIT_ARCH_X86_64: 0x%X\n"
"\n", argv[0], AUDIT_ARCH_I386, AUDIT_ARCH_X86_64);
exit(EXIT_FAILURE);
}
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("prctl");
exit(EXIT_FAILURE);
}
if (install_filter(strtol(argv[1], NULL, 0),
strtoul(argv[2], NULL, 0),
strtol(argv[3], NULL, 0)))
exit(EXIT_FAILURE);
execv(argv[4], &argv[4]);
perror("execv");
exit(EXIT_FAILURE);
}
bpfc(1), strace(1), bpf(2), prctl(2), ptrace(2), seccomp_unotify(2), sigaction(2), proc(5), signal(7), socket(7)
Various pages from the libseccomp library, including: scmp_sys_resolver(1), seccomp_export_bpf(3), seccomp_init(3), seccomp_load(3), and seccomp_rule_add(3).
Файлы исходного кода ядра Documentation/networking/filter.txt и Documentation/userspace-api/seccomp_filter.rst (до Linux 4.13 файл Documentation/prctl/seccomp_filter.txt).
McCanne, S. and Jacobson, V. (1992) The BSD Packet Filter: A New Architecture for User-level Packet Capture, Proceedings of the USENIX Winter 1993 Conference http://www.tcpdump.org/papers/bpf-usenix93.pdf
Русский перевод этой страницы руководства был сделан Alexander Golubev <fatzer2@gmail.com>, Azamat Hackimov <azamat.hackimov@gmail.com>, Hotellook, Nikita <zxcvbnm3230@mail.ru>, Spiros Georgaras <sng@hellug.gr>, Vladislav <ivladislavefimov@gmail.com>, Yuri Kozlov <yuray@komyakino.ru> и Иван Павлов <pavia00@gmail.com>
Этот перевод является бесплатной документацией; прочитайте Стандартную общественную лицензию GNU версии 3 или более позднюю, чтобы узнать об условиях авторского права. Мы не несем НИКАКОЙ ОТВЕТСТВЕННОСТИ.
Если вы обнаружите ошибки в переводе этой страницы руководства, пожалуйста, отправьте электронное письмо на man-pages-ru-talks@lists.sourceforge.net.
| 5 февраля 2023 г. | Linux man-pages 6.03 |