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:

Вызвавшей нити доступны только системные вызовы read(2), write(2), _exit(2) (но не exit_group(2)) и sigreturn(2). При запуске других системных вызовов генерируется сигнал SIGKILL. Строгий режим безопасных вычислений полезен для вычислительных приложений, которым может потребоваться выполнить недоверительный байт-код, возможно полученный при чтении из канала или сокета.
Заметим, что хотя вызывающая нить больше не вызывает 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);
Разрешённые системные вызовы определяются указателем на 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 все нити вызывающего процесса. «Дерево фильтров» — упорядоченный список фильтров, присоединённых к нити (присоединённые одинаковые фильтры отдельными вызовами seccomp() считаются разными фильтрами, с этой точки зрения).
Если в какой-то нити невозможна синхронизация с единым деревом фильтров, то вызов не присоединит новый фильтр seccomp, и завершится с ошибкой, вернув ID первой обнаруженной нити, для которой синхронизация невозможна. Синхронизации не получится, если другая нить того же процесса находится в SECCOMP_MODE_STRICT, или если она присоединила новые фильтры seccomp к самой себе, отличающиеся от дерева фильтров вызывающей нити.
Все фильтры, возвращающие действия, кроме SECCOMP_RET_ALLOW, должны протоколироваться. Администратор может заменить этот флаг фильтров, предварительно запретив протоколировать определённые действия через файл /proc/sys/kernel/seccomp/actions_logged.
Выключить недопущение Speculative Store Bypass.
Проверить, поддерживается ли действие ядром. Данная операция помогает убедиться, что ядро знает о самых последних добавленных фильтрах, возвращающих действие, так как ядро считает все неизвестные действия как SECCOMP_RET_KILL_PROCESS.
Значение flags должно быть равно 0, а args должно быть указателем на беззнаковый 32-битный фильтр, возвращающих действие.

Фильтры

При добавлении фильтров посредством 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_THREAD, описанном далее, завершаются все нити в группе нитей (группы нитей представлены в описании CLONE_THREAD в clone(2)).
Процесс завершается думая, что убит сигналом SIGSYS. Даже если обработчик сигнала SIGSYS был зарегистрирован, в этом случае он будет проигнорирован и процесс всегда прекращает выполнение. Родительскому процессу, который ждёт этот процесс (с помощью waitpid(2) или подобного вызова) возвращается wstatus, который будет показывать, что потомок завершился по сигналу SIGSYS.
Это значение возвращается при немедленном завершении нити, сделавшей системный вызов. Системный вызов не выполняется. Другие нити в той же группе нитей продолжат выполнение.
Нить завершается думая, что убита сигналом 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, для более ясного различения двух этих действий.
Это значение приводит к отправке ядром направленного в нить сигнала 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_DATA возвращаемого значения фильтра передаётся в пространство пользователя в виде значения errno без выполнения системного вызова.
При возврате это значение заставит ядро попытаться уведомить трассировщик на основе ptrace(2) до выполнения системного вызова. Если трассировщика нет, то системный вызов не выполняется и возвращается состояние ошибки со значением errno равным ENOSYS.
Трассировщик будет уведомлён, если он запросил PTRACE_O_TRACESECCOMP посредством ptrace(PTRACE_SETOPTIONS). Трассировщик будет уведомлён о PTRACE_EVENT_SECCOMP, а часть SECCOMP_RET_DATA возвращаемого значения фильтра будет доступна через PTRACE_GETEVENTMSG.
Трассировщик может пропустить системный вызов, изменив номер системного вызова на -1. Или же он может изменить запрашиваемый системный вызов на системный вызов с другим номером. Если трассировщик просит пропустить системный вызов, то системный вызов появится в возвращаемом значении, которое трассировщик помещает в регистр возвращаемого значения.
До ядра 4.8 проверка seccomp не будет запущена ещё раз после уведомления трассировщика (для старых ядер это означает, что ограниченные окружения (sandbox) на основе seccomp не должны позволять использовать ptrace(2) — даже другим процессам в окружении — без максимальной предосторожности; ptracer-ы могут использовать этот механизм для выхода из окружения seccomp).
Это значение приводит к выполнению системного вызова после протоколирования фильтра, возвращающего действие. Администратор может заменить протоколирование этого действия в файле /proc/sys/kernel/seccomp/actions_logged.
Это значение приводит к выполнению системного вызова.

Если значение действия ни одно из указанных выше, то действием фильтра считается или SECCOMP_RET_KILL_PROCESS (начиная с Linux 4.14), или SECCOMP_RET_KILL_THREAD (в Linux 4.13 и старее).

Интерфейсы /proc

Файлы в каталоге /proc/sys/kernel/seccomp предоставляют дополнительную информацию seccomp и настройку:

Доступный только для чтения упорядоченный список возвращаемых действий фильтром seccomp в виде строки. Список упорядочен слева направо в порядке уменьшения приоритета. Представляет собой набор возвращаемых фильтром seccomp действий, поддерживаемых ядром.
Доступный для чтения-записи упорядоченный список возвращаемых действий фильтром 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)), то действие протоколируется.
  • В противном случае действие не протоколируется.

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

При успешном выполнении seccomp() возвращает 0. При ошибке, если был использован SECCOMP_FILTER_FLAG_TSYNC, то возвращается ID нити, которая была причиной ошибки синхронизации (данный ID — идентификатор нити ядра с типом, возвращаемом clone(2) и gettid(2)). При других ошибках возвращается -1 и в errno записывается причина ошибки.

ОШИБКИ

Функция seccomp() может завершиться с ошибкой по следующим причинам:

У вызывающего нет мандата CAP_SYS_ADMIN в своём пространстве имён пользователя или не установлен no_new_privs до использования SECCOMP_SET_MODE_FILTER.
Аргумент args не содержит допустимого адреса.
Аргумент operation неизвестен или не поддерживается этой версией ядра или из-за настроек.
Указанное значение flags некорректно для заданного значения operation.
Значение operation включает BPF_ABS, но указанное смещение не выровнено по 32-битной границе или превышает sizeof(struct seccomp_data).
Режим безопасных вычислений уже включён, и значение operation отличается от существующей настройки.
В operation указано SECCOMP_SET_MODE_FILTER, но фильтрующая программа, задаваемая в args, некорректна или её длина равна 0 или превышает BPF_MAXINSNS (4096) инструкций.
Не хватает памяти.
Общая длина всех фильтрующих программ, присоединённых к вызывающей нити, превысила бы MAX_INSNS_PER_PATH (32768) инструкций. Заметим, что для вычисления этого предела на каждую уже существующую фильтрующую программу прибавляются ещё 4 инструкции.
В operation указано SECCOMP_GET_ACTION_AVAIL, но ядро не поддерживает фильтр, возвращающий действие, указанное в args.
Во время синхронизации нити произошла ошибка в другой нити, но её 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, который используется во время работы для выполнения следующих проверок:

[1]
Если программа не запущена на определённой архитектуре, то фильтр BPF заставляет системные вызовы завершаться с ошибкой ENOSYS.
[2]
Если программа попытается выполнить системный вызов с заданным номером, то фильтр 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 <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