ИМЯ
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:
- SECCOMP_SET_MODE_STRICT
- The only system calls that the calling thread is permitted to make are
read(2), write(2), _exit(2) (but not
exit_group(2)), and sigreturn(2). Other system calls result
in the termination of the calling thread, or termination of the entire
process with the SIGKILL signal when there is only one thread.
Strict secure computing mode is useful for number-crunching applications
that may need to execute untrusted byte code, perhaps obtained by reading
from a pipe or socket.
- Заметим,
что хотя
вызывающая
нить
больше не
вызывает
sigprocmask(2), она
может
использовать
sigreturn(2) для
блокировки
всех
сигналов
(кроме SIGKILL и
SIGSTOP). Это
означает,
что alarm(2)
(например)
недостаточно
для
ограничения
времени
выполнения
процесса.
Вместо
него для
надёжного
завершения
процесса
нужно
использовать
SIGKILL. Это
можно
сделать с
помощью
timer_create(2) с SIGEV_SIGNAL и
sigev_signo равным
SIGKILL, или
используя
setrlimit(2) для
задания
жёсткого
ограничения
по RLIMIT_CPU.
- Эта
операция
доступна
только,
если в ядре
включён
параметр
CONFIG_SECCOMP.
- Значение
flags должно
быть равно
0, а args — NULL.
- Эта
операция
функционально
идентична
вызову:
-
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
- SECCOMP_SET_MODE_FILTER
- Разрешённые
системные
вызовы
определяются
указателем
на Berkeley Packet Filter (BPF),
передаваемый
через args.
Данный
аргумент
является
указателем
на struct sock_fprog; эту
структуру
можно
использовать
для отбора
произвольных
системных
вызовов и
их
аргументов.
Если
фильтр
некорректен,
то seccomp()
завершается
с ошибкой
EINVAL в errno.
- Если
фильтром
разрешён
fork(2) или clone(2), то
все
потомки
будут
ограничены
тем же
фильтром
системных
вызовов
что и
родитель.
Если
разрешён
execve(2), то
существующий
фильтр
сохраняется
и после
вызова execve(2).
- Чтобы
использовать
операцию
SECCOMP_SET_MODE_FILTER
вызывающая
нить
должна
иметь
мандат CAP_SYS_ADMIN
в своём
пространстве
имён
пользователя
или у нити
уже должен
быть
установлен
бит no_new_privs.
Если этот
бит не
установлен
предком
этой нити,
то в нити
нужно
сделать
следующий
вызов:
-
prctl(PR_SET_NO_NEW_PRIVS, 1);
- В
противном
случае
операция
SECCOMP_SET_MODE_FILTER
завершается
ошибкой и
возвращает
EACCES в errno.
Данное
требование
гарантирует,
что
непривилегированный
процесс не
сможет
применить
вредоносный
фильтр и
вызвать
программу
с set-user-ID или
другую
привилегированную
программу
с помощью
execve(2), то есть
потенциально
подвергнуть
эту
программу
опасности
(такой
вредоносный
фильтр
может,
например,
заставить
попытаться
использовать
setuid(2) для
установки
ID
вызывающего
пользователя
в
ненулевые
значения
вместо
возврата 0
без
действительного
запуска
системного
вызова.
Таким
образом,
программа
может быть
обманута и
остаться с
правами
суперпользователя
в
окружении,
где
возможно
заставить
её сделать
что-то
опасное,
так как в
действительности
она не
отказалась
от своих
прав).
- Если prctl(2) или
seccomp()
разрешены
присоединённым
фильтром,
то могут
быть
добавлены
дополнительные
фильтры.
Это
увеличит
время
вычисления,
но в
дальнейшем
позволит
сократить
область
атаки при
выполнении
нити.
- Операция
SECCOMP_SET_MODE_FILTER
доступна
только,
если в ядре
включён
параметр
CONFIG_SECCOMP_FILTER.
- Если
значение
flags равно 0, то
эта
операция
функционально
идентична
вызову:
-
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, args);
- Возможные
значения
flags:
- SECCOMP_FILTER_FLAG_LOG
(начиная с Linux
4.14)
- Все
фильтры,
возвращающие
действия,
кроме SECCOMP_RET_ALLOW,
должны
протоколироваться.
Администратор
может
заменить
этот флаг
фильтров,
предварительно
запретив
протоколировать
определённые
действия
через файл
/proc/sys/kernel/seccomp/actions_logged.
- SECCOMP_FILTER_FLAG_NEW_LISTENER
(начиная с Linux
5.0)
- After successfully installing the filter program, return a new user-space
notification file descriptor. (The close-on-exec flag is set for the file
descriptor.) When the filter returns SECCOMP_RET_USER_NOTIF a
notification will be sent to this file descriptor.
- At most one seccomp filter using the
SECCOMP_FILTER_FLAG_NEW_LISTENER flag can be installed for a
thread.
- See seccomp_unotify(2) for further details.
- SECCOMP_FILTER_FLAG_SPEC_ALLOW
(начиная с Linux
4.17)
- Выключить
недопущение
Speculative Store Bypass.
- SECCOMP_FILTER_FLAG_TSYNC
- При
добавлении
нового
фильтра,
выполнять
синхронизацию
с одним
деревом
фильтров seccomp
все нити
вызывающего
процесса.
«Дерево
фильтров»
—
упорядоченный
список
фильтров,
присоединённых
к нити
(присоединённые
одинаковые
фильтры
отдельными
вызовами
seccomp()
считаются
разными
фильтрами,
с этой
точки
зрения).
- Если в
какой-то
нити
невозможна
синхронизация
с единым
деревом
фильтров,
то вызов не
присоединит
новый
фильтр seccomp, и
завершится
с ошибкой,
вернув ID
первой
обнаруженной
нити, для
которой
синхронизация
невозможна.
Синхронизации
не
получится,
если
другая
нить того
же
процесса
находится
в SECCOMP_MODE_STRICT, или
если она
присоединила
новые
фильтры seccomp к
самой себе,
отличающиеся
от дерева
фильтров
вызывающей
нити.
- SECCOMP_GET_ACTION_AVAIL
(начиная с Linux
4.14)
- Проверить,
поддерживается
ли
действие
ядром.
Данная
операция
помогает
убедиться,
что ядро
знает о
самых
последних
добавленных
фильтрах,
возвращающих
действие,
так как
ядро
считает
все
неизвестные
действия
как SECCOMP_RET_KILL_PROCESS.
- Значение
flags должно
быть равно
0, а args должно
быть
указателем
на
беззнаковый
32-битный
фильтр,
возвращающих
действие.
- SECCOMP_GET_NOTIF_SIZES
(начиная с Linux
5.0)
- Get the sizes of the seccomp user-space notification structures. Since
these structures may evolve and grow over time, this command can be used
to determine how much memory to allocate for sending and receiving
notifications.
- The value of flags must be 0, and args must be a pointer to
a struct seccomp_notif_sizes, which has the following form:
-
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' */
};
- See seccomp_unotify(2) for further details.
Фильтры
При
добавлении
фильтров
посредством
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,
описанном
далее,
завершаются
все нити в
группе
нитей
(группы
нитей
представлены
в описании
CLONE_THREAD в clone(2)).
- Процесс
завершается
думая, что
убит
сигналом
SIGSYS. Даже
если
обработчик
сигнала SIGSYS
был
зарегистрирован,
в этом
случае он
будет
проигнорирован
и процесс
всегда
прекращает
выполнение.
Родительскому
процессу,
который
ждёт этот
процесс (с
помощью waitpid(2)
или
подобного
вызова)
возвращается
wstatus, который
будет
показывать,
что
потомок
завершился
по сигналу
SIGSYS.
- SECCOMP_RET_KILL_THREAD
(или SECCOMP_RET_KILL)
- Это
значение
возвращается
при
немедленном
завершении
нити,
сделавшей
системный
вызов.
Системный
вызов не
выполняется.
Другие
нити в той
же группе
нитей
продолжат
выполнение.
- Нить
завершается
думая, что
убита
сигналом
SIGSYS.
Смотрите
описание
SECCOMP_RET_KILL_PROCESS выше.
- До Linux 4.11 любой
процесс,
завершавшийся
таким
образом, не
вызывал
образование
дампа
(несмотря
на то, что
описание
SIGSYS в signal(7)
сообщает,
что по
умолчанию
завершение
приводит к
дампу).
Начиная с Linux
4.11 для
процесса с
единственной
нитью
будет
сделан
дамп, если
он
завершается
при таких
обстоятельствах.
- В
дополнении
к SECCOMP_RET_KILL_PROCESS в Linux 4.14
как
синоним
SECCOMP_RET_KILL
добавлено
значение
SECCOMP_RET_KILL_THREAD, для
более
ясного
различения
двух этих
действий.
- Note: the use of SECCOMP_RET_KILL_THREAD to kill a single
thread in a multithreaded process is likely to leave the process in a
permanently inconsistent and possibly corrupt state.
- SECCOMP_RET_TRAP
- Это
значение
приводит к
отправке
ядром
направленного
в нить
сигнала SIGSYS
возбудившей
нити
(системный
вызов не
выполняется).
Заполняются
некоторые
поля
структуры
siginfo_t
(смотрите
sigaction(2)),
связанные
с
сигналом:
- •
- В si_signo будет
содержаться
значение
SIGSYS.
- •
- В si_call_addr будет
показан
адрес
инструкции
системного
вызова.
- •
- В si_syscall и si_arch
будет
указываться
какой
системный
вызов была
попытка
запустить.
- •
- В si_code будет
содержаться
значение
SYS_SECCOMP.
- •
- В si_errno будет
содержаться
часть SECCOMP_RET_DATA
из
возвращаемого
значения
фильтра.
- Программный
счётчик
будет
таким же
как при
системном
вызове (т. е.,
программный
счётчик не
будет
указывать
на
инструкцию
системного
вызова). В
регистре
возвращаемого
значения
будет
содержаться
значение,
зависящее
от
архитектуры;
если
выполнение
продолжится,
оно равно
чему-нибудь
подходящему
для
системного
вызова
(зависимость
от
архитектуры
возникает
из-за того,
что при
замене его
на ENOSYS может
перезаписаться
какая-нибудь
полезная
информация).
- SECCOMP_RET_ERRNO
- Это
значение
приводит к
тому, что
часть SECCOMP_RET_DATA
возвращаемого
значения
фильтра
передаётся
в
пространство
пользователя
в виде
значения
errno без
выполнения
системного
вызова.
- SECCOMP_RET_USER_NOTIF
(начиная с Linux
5.0)
- Forward the system call to an attached user-space supervisor process to
allow that process to decide what to do with the system call. If there is
no attached supervisor (either because the filter was not installed with
the SECCOMP_FILTER_FLAG_NEW_LISTENER flag or because the file
descriptor was closed), the filter returns ENOSYS (similar to what
happens when a filter returns SECCOMP_RET_TRACE and there is no
tracer). See seccomp_unotify(2) for further details.
- Note that the supervisor process will not be notified if another filter
returns an action value with a precedence greater than
SECCOMP_RET_USER_NOTIF.
- SECCOMP_RET_TRACE
- При
возврате
это
значение
заставит
ядро
попытаться
уведомить
трассировщик
на основе
ptrace(2) до
выполнения
системного
вызова.
Если
трассировщика
нет, то
системный
вызов не
выполняется
и
возвращается
состояние
ошибки со
значением
errno равным
ENOSYS.
- Трассировщик
будет
уведомлён,
если он
запросил
PTRACE_O_TRACESECCOMP
посредством
ptrace(PTRACE_SETOPTIONS).
Трассировщик
будет
уведомлён
о PTRACE_EVENT_SECCOMP, а
часть SECCOMP_RET_DATA
возвращаемого
значения
фильтра
будет
доступна
через PTRACE_GETEVENTMSG.
- Трассировщик
может
пропустить
системный
вызов,
изменив
номер
системного
вызова на -1.
Или же он
может
изменить
запрашиваемый
системный
вызов на
системный
вызов с
другим
номером.
Если
трассировщик
просит
пропустить
системный
вызов, то
системный
вызов
появится в
возвращаемом
значении,
которое
трассировщик
помещает в
регистр
возвращаемого
значения.
- Before Linux 4.8, the seccomp check will not be run again after the tracer
is notified. (This means that, on older kernels, seccomp-based sandboxes
must not allow use of ptrace(2)—even of other
sandboxed processes—without extreme care; ptracers can use this
mechanism to escape from the seccomp sandbox.)
- Note that a tracer process will not be notified if another filter returns
an action value with a precedence greater than
SECCOMP_RET_TRACE.
- SECCOMP_RET_LOG
(начиная с Linux
4.14)
- Это
значение
приводит к
выполнению
системного
вызова
после
протоколирования
фильтра,
возвращающего
действие.
Администратор
может
заменить
протоколирование
этого
действия в
файле
/proc/sys/kernel/seccomp/actions_logged.
- SECCOMP_RET_ALLOW
- Это
значение
приводит к
выполнению
системного
вызова.
Если
значение
действия
ни одно из
указанных
выше, то
действием
фильтра
считается
или SECCOMP_RET_KILL_PROCESS
(начиная с Linux
4.14), или SECCOMP_RET_KILL_THREAD (в
Linux 4.13 и старее).
Интерфейс
/proc
Файлы в
каталоге
/proc/sys/kernel/seccomp
предоставляют
дополнительную
информацию
seccomp и
настройку:
- actions_avail
(начиная с Linux
4.14)
- Доступный
только для
чтения
упорядоченный
список
возвращаемых
действий
фильтром seccomp
в виде
строки.
Список
упорядочен
слева
направо в
порядке
уменьшения
приоритета.
Представляет
собой
набор
возвращаемых
фильтром seccomp
действий,
поддерживаемых
ядром.
- actions_logged
(начиная с Linux
4.14)
- Доступный
для
чтения-записи
упорядоченный
список
возвращаемых
действий
фильтром seccomp,
которые
разрешено
протоколировать.
Записи в
файл не
нужно
упорядочивать,
но
прочитанные
данные
будут
упорядочены
также как в
файле actions_avail.
- Важно
отметить,
что
значение
actions_logged не
останавливает
от
протоколирования
определённого
фильтра
возвращаемых
действий,
если
подсистема
аудита
настроена
на аудит
задачи.
Если
действие
не найдено
в файле actions_logged,
то
конечное
решение об
аудите
действия
для этой
задачи, в
конечном
итоге,
основывается
на
действие
подсистемы
аудита для
всех
фильтров
возвращающих
действия,
кроме SECCOMP_RET_ALLOW.
- Строка «allow»
недопустима
в файле actions_logged,
так как
невозможно
протоколирование
действий
SECCOMP_RET_ALLOW.
Попытка
записи «allow»
в файле
завершится
ошибкой
EINVAL.
Ведение
журнала
контроля
действий seccomp
Начиная с
Linux 4.14 ядро
позволяет
протоколировать
действия,
возвращаемые
фильтрами
seccomp в журнал
контроля (audit
log). Ядро
принимает
решение о
протоколировании
действие
основываясь
на типе
действия,
имеется ли
действие в
файле actions_logged и
включён ли
контроль в
ядре
(например,
посредством
параметра
загрузки
ядра audit=1).
Правила
следующие:
- •
- Если
действие —
SECCOMP_RET_ALLOW, то оно
не
протоколируется.
- •
- В
противном
случае,
если
действие
SECCOMP_RET_KILL_PROCESS или
SECCOMP_RET_KILL_THREAD, и это
действие
есть в
файле actions_logged,
то
действие
протоколируется.
- •
- В
противном
случае,
если для
фильтра
запрошено
протоколирование
(флаг SECCOMP_FILTER_FLAG_LOG) и
действие
есть в
файле actions_logged,
то
действие
протоколируется.
- •
- В
противном
случае,
если
включён
контроль в
ядре и
процесс
контролируется
(autrace(8)), то
действие
протоколируется.
- •
- В
противном
случае
действие
не
протоколируется.
ВОЗВРАЩАЕМОЕ
ЗНАЧЕНИЕ
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() может
завершиться
с ошибкой
по
следующим
причинам:
- EACCES
- У
вызывающего
нет
мандата
CAP_SYS_ADMIN в своём
пространстве
имён
пользователя
или не
установлен
no_new_privs до
использования
SECCOMP_SET_MODE_FILTER.
- EBUSY
- While installing a new filter, the SECCOMP_FILTER_FLAG_NEW_LISTENER
flag was specified, but a previous filter had already been installed with
that flag.
- EFAULT
- Аргумент
args не
содержит
допустимого
адреса.
- EINVAL
- Аргумент
operation
неизвестен
или не
поддерживается
этой
версией
ядра или
из-за
настроек.
- EINVAL
- Указанное
значение
flags
некорректно
для
заданного
значения
operation.
- EINVAL
- Значение
operation
включает
BPF_ABS, но
указанное
смещение
не
выровнено
по 32-битной
границе
или
превышает
sizeof(struct seccomp_data).
- EINVAL
- Режим
безопасных
вычислений
уже
включён, и
значение
operation
отличается
от
существующей
настройки.
- EINVAL
- В operation
указано
SECCOMP_SET_MODE_FILTER, но
фильтрующая
программа,
задаваемая
в args,
некорректна
или её
длина
равна 0 или
превышает
BPF_MAXINSNS (4096)
инструкций.
- ENOMEM
- Не хватает
памяти.
- ENOMEM
- Общая
длина всех
фильтрующих
программ,
присоединённых
к
вызывающей
нити,
превысила
бы MAX_INSNS_PER_PATH (32768)
инструкций.
Заметим,
что для
вычисления
этого
предела на
каждую уже
существующую
фильтрующую
программу
прибавляются
ещё 4
инструкции.
- EOPNOTSUPP
- В operation
указано
SECCOMP_GET_ACTION_AVAIL, но
ядро не
поддерживает
фильтр,
возвращающий
действие,
указанное
в args.
- ESRCH
- Во время
синхронизации
нити
произошла
ошибка в
другой
нити, но её ID
невозможно
определить.
ВЕРСИИ
Системный
вызов 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 BPF доступна
на
следующих
архитектурах:
- •
- x86-64, i386, x32
(начиная с Linux
3.5)
- •
- ARM (начиная с
Linux 3.8)
- •
- s390 (начиная с
Linux 3.8)
- •
- MIPS (начиная с
Linux 3.16)
- •
- ARM-64 (начиная с
Linux 3.19)
- •
- PowerPC (начиная с
Linux 4.3)
- •
- Tile (начиная с
Linux 4.3)
- •
- PA-RISC (начиная с
Linux 4.6)
Предостережения
Есть
различные
тонкости,
которые
нужно
учитывать
при
применении
фильтров seccomp
к
программе:
- •
- На многих
архитектурах
некоторые
обычные
системные
вызовы
реализованы
в
пользовательском
пространстве
в vdso(7).
Заметными
примерами
можно
считать
clock_gettime(2), gettimeofday(2) и time(2).
На таких
архитектурах
фильтрация
seccomp данных
системных
вызовов не
действует
(однако,
есть
случаи где
реализации
vdso(7) могут
вызвать
реальный
системный
вызов и
фильтры seccomp filters
увидят
такое
обращение).
- •
- Фильтрация
seccomp работает
по номерам
системных
вызовов.
Однако,
обычно, в
приложениях
системные
вызовы
вызываются
не
напрямую, а
через
обёрточные
функции
библиотеки
C, которые, в
свою
очередь,
вызывают
системные
вызовы.
Следовательно,
нужно
учитывать
следующее:
- •
- Обёртки glibc
некоторых
обычных
системных
вызовов
могут
использовать
системные
вызовы
ядра с
другими
именами.
Например,
обёрточная
функция exit(2)
использует
системный
вызов exit_group(2), а
обёрточная
функция fork(2)
в
действительности
вызывает
clone(2).
- •
- Поведение
обёрточных
функций
могут быть
различных
на разных
архитектурах,
с учётом
диапазона
системных
вызовов,
предоставляемых
архитектурой.
Другими
словами,
одна
обёрточная
функция на
разных
архитектурах
может
вызывать
разные
системные
вызовы.
- •
- И наконец,
поведение
обёрточных
функций
может
различаться
в разных
версиях glibc.
Например, в
старых
версиях
обёрточная
функция glibc
для open(2)
вызывала
системный
вызов с тем
же именем,
но начиная
с glibc 2.26, в
реализации
начал
вызываться
openat(2) на всех
архитектурах.
В
следствии
вышеупомянутого
возможно
понадобится
фильтровать
не тот
системный
вызов,
который
ожидался. В
различных
справочных
страницах
раздела 2
есть абзац
Отличия
между
библиотекой
C и ядром, в
котором
содержится
полезная
информация
о
различиях
между
оберточными
функциями
и
нижележащими
системными
вызовами.
Кроме
того,
обратите
внимание,
что
применение
фильтров seccomp
даже может
привести к
появлению
дефектов
приложений,
когда
фильтры
вызывают
неожиданные
отказы для
законных
операций,
которые
приложение,
возможно,
должно
было бы
выполнить.
Такие
дефекты
может
оказаться
нелегко
обнаружить
при
тестировании
фильтров seccomp,
если
дефекты
возникают
в редко
используемых
путях кода
приложения.
Особенности
seccomp в BPF
Заметим,
что
следующие
особенности
BPF относятся
только к
фильтрам
seccomp:
- •
- Модификаторы
размера BPF_H
и BPF_B не
поддерживаются:
все
операции
должны
загружать
и
сохранять
слова
(4-байта) (BPF_W).
- •
- Для
доступа к
содержимому
буфера seccomp_data
используйте
модификатор
режима
адресации
BPF_ABS.
- •
- Модификатор
режима
адресации
BPF_LEN выдаёт
непосредственный
операнд
режима, чьё
значение
равно
размеру
буфера seccomp_data.
ПРИМЕРЫ
Программа,
показанная
далее,
обрабатывает
четыре и
более
аргументов.
Первые три
аргумента
— номер
системного
вызова,
числовой
идентификатор
архитектуры
и номер
ошибки.
Программа
использует
эти
значения
для
создания
фильтра BPF,
который
используется
во время
работы для
выполнения
следующих
проверок:
- •
- Если
программа
не
запущена
на
определённой
архитектуре,
то фильтр BPF
заставляет
системные
вызовы
завершаться
с ошибкой
ENOSYS.
- •
- Если
программа
попытается
выполнить
системный
вызов с
заданным
номером, то
фильтр BPF
заставит
системный
вызов
завершиться
с ошибкой,
а в errno будет
записан
указанный
номер
ошибки.
В
оставшихся
аргументах
командной
строки
указываются
путь и
дополнительные
аргументы
программы,
которую
программа
из примера
должна
попытаться
выполнить
с помощью
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.