SECCOMP(2) | Руководство программиста Linux | SECCOMP(2) |
seccomp - переводит процесс в состояние безопасных вычислений
#include <linux/seccomp.h> #include <linux/filter.h> #include <linux/audit.h> #include <linux/signal.h> #include <sys/ptrace.h>
int seccomp(unsigned int operation, unsigned int flags, void *args);
Системный вызов 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);
При добавлении фильтров посредством 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.
Настоятельно рекомендуется использовать подход белого списка, когда это возможно, потому что такой подход более устойчив и прост. Черный список нужно будет обновлять каждый раз, когда добавляется потенциально опасный системный вызов (или опасный флаг или параметр, если они помещены в черный список), и это часто возможно изменит представление значения, не изменяя его смысла, что приведёт к обходу черного списка. Также смотрите ЗАМЕЧАНИЯ ниже.
Поле arch не уникально для всех соглашений о вызовах. В x86-64 ABI и x32 ABI в arch используется AUDIT_ARCH_X86_64, и они запускаются на одних и тех же процессорах. Чтобы отличать один ABI от другого используется маска __X32_SYSCALL_BIT с номером системного вызова.
Это означает, что для создания чёрного списка системных вызовов на основе seccomp, выполняемых через x86-64 ABI, необходимо не только проверять что arch равно AUDIT_ARCH_X86_64, но также явно отвергать все системные вызовы, которые содержат __X32_SYSCALL_BIT в nr.
В поле instruction_pointer содержится адрес инструкции машинного языка, который запускает системный вызов. Это может быть полезно вместе с /proc/[pid]/maps для выполнения проверок из какой области (отображение) программы делается системный вызов (вероятно, стоит блокировать системные вызовы mmap(2) и mprotect(2) для запрета программе удалять такие проверки).
При проверке значений из args по чёрному списку имейте в виду, что часто аргументы просто обрезаются до обработки, но после проверки seccomp. Например, это случается, если на ядре x86-64 используется i386 ABI: хотя ядро, обычно, не смотрит дальше 32 младших бит аргументов, в данные seccomp попадут значения полных 64-битных регистров. Менее удивительный пример: если для выполнения системного вызова с аргументом типа int используется x86-64 ABI, то старшая половина регистра аргумента игнорируется системным вызовом, но видима в данных seccomp.
Фильтр seccomp возвращает 32-битное значение, состоящее из двух частей: в старших 16 битах (соответствует маске, определяемой константой SECCOMP_RET_ACTION_FULL) содержится одно из значений «действие», перечисленных далее; в младших 16 битах (определяется константой SECCOMP_RET_DATA) содержатся «данные», связанные с возвращаемым значением.
Если существует несколько фильтров, то все они выполняются в обратном порядке их добавления в дерево фильтров — то есть последние добавленные выполняются первыми (заметим, что все фильтры будут вызваны даже, если ранее выполнявшиеся фильтры вернули SECCOMP_RET_KILL. Это сделано для простоты кода ядра и предоставления крошечного ускорения выполнения набора фильтров, так как не выполняется проверка этого редкого случая). Возвращаемое значение для вычисления данного системного вызова —первое встреченного значение действия наивысшего приоритета (вместе с сопутствующими ему данными), возвращаемое выполнением всех фильтров.
Значения действий, которые могут возвращаться фильтром 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). Правила следующие:
При успешном выполнении seccomp() возвращает 0. При ошибке, если был использован SECCOMP_FILTER_FLAG_TSYNC, то возвращается ID нити, которая была причиной ошибки синхронизации (данный ID — идентификатор нити ядра с типом, возвращаемом clone(2) и gettid(2)). При других ошибках возвращается -1 и в errno записывается причина ошибки.
Функция 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 <errno.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/audit.h> #include <linux/filter.h> #include <linux/seccomp.h> #include <sys/prctl.h> #define X32_SYSCALL_BIT 0x40000000 static int install_filter(int syscall_nr, int t_arch, int f_errno) { unsigned int upper_nr_limit = 0xffffffff; /* предполагается, что AUDIT_ARCH_X86_64 означает обычный x86-64 ABI (в x32 ABI у всех системных вызовов установлены 30 бит в поле «nr», то есть номера >= X32_SYSCALL_BIT) */ if (t_arch == AUDIT_ARCH_X86_64) upper_nr_limit = X32_SYSCALL_BIT - 1; struct sock_filter filter[] = { /* [0] загружаем архитектуру из буфера «seccomp_data» в аккумулятор */ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, arch))), /* [1] прыгаем вперёд на 5 инструкции, если архитектура не совпадает с «t_arch» */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, t_arch, 0, 4), /* [2] загружаем номер системного вызова из буфера «seccomp_data» в аккумулятор */ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))), /* [3] проверяем ABI — нужно только для чёрного списка на x86-64. Используем BPF_JGT вместо проверки битовой маски, чтобы избежать перезагрузки номера syscall. */ BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, upper_nr_limit, 3, 0), /* [4] прыгаем вперёд на 1 инструкцию, если номер системного вызова не совпадает с «syscall_nr» */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, syscall_nr, 0, 1), /* [5] совпала архитектура и системный вызов: не выполняем системный вызов и возвращаем «f_errno» в «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] не совпала архитектура: прерываем задачу */ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL), }; struct sock_fprog prog = { .len = (unsigned short) (sizeof(filter) / sizeof(filter[0])), .filter = filter, }; if (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), strtol(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), sigaction(2), proc(5), signal(7), socket(7)
Various pages from the libseccomp library, including: scmp_sys_resolver(1), seccomp_init(3), seccomp_load(3), seccomp_rule_add(3) и seccomp_export_bpf(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
2019-03-06 | Linux |