Seccomp ограничивает системные вызовы, которые процесс может отправлять ядру Linux. Фильтр проверяет номер syscall, архитектуру ABI и числовые аргументы, после чего разрешает вызов, возвращает ошибку, отправляет сигнал, записывает событие в журнал или передаёт решение пользовательскому процессу-наблюдателю.
Главная польза seccomp состоит в сокращении доступной поверхности ядра. Если атакующий захватит веб-сервер, обработчик документов или другой процесс, эксплойт не сможет обратиться к запрещённым интерфейсам ядра. Поведение фильтров и ограничения BPF подробно описывает документация ядра Linux.
Seccomp не является полноценной песочницей. Фильтр не управляет путями к файлам, сетевыми адресами внутри структур, пользователями, каталогами и объектами ядра. Seccomp нужно сочетать с Linux capabilities, AppArmor или SELinux, пространствами имён, непривилегированным пользователем и ограничением файловой системы.
Самая частая ошибка при настройке seccomp выглядит так. Администратор запускает strace на несколько минут, копирует увиденные syscall в allowlist и блокирует всё остальное. Профиль проходит один тест, но позже ломает обновление сертификата, ротацию журнала, перезагрузку конфигурации, DNS, обработку ошибки или запуск нового рабочего процесса.
Содержание
- Как работает seccomp
- Как выбрать действие фильтра
- Что проверить перед настройкой
- Как правильно собрать syscall приложения
- Как настроить seccomp в Docker
- Как настроить seccomp для systemd-сервиса
- Как включить seccomp в Kubernetes
- Как найти заблокированный syscall
- Неочевидные ограничения и ошибки
- Как откатить настройки
Как работает seccomp
Linux поддерживает Strict Mode и Filter Mode.
| Режим | Разрешённые операции | Практическое применение |
|---|---|---|
SECCOMP_MODE_STRICT |
read, write, _exit и sigreturn |
Небольшой процесс, который заранее открыл все дескрипторы и больше не выделяет память, не создаёт потоки и не открывает файлы |
SECCOMP_MODE_FILTER |
Набор операций, заданный classic BPF-фильтром | Контейнеры, серверы, службы, браузеры и большинство современных песочниц |
Strict Mode разрешает именно _exit, а не библиотечную функцию exit. Современная libc часто завершает многопоточный процесс через exit_group, которого в строгом режиме нет. Динамически связанные программы также требуют openat, mmap, mprotect, close и других вызовов. Поэтому Strict Mode подходит только для специально спроектированного кода.
Filter Mode получает структуру с номером syscall, идентификатором архитектуры, адресом инструкции и шестью аргументами. Фильтр может проверять числовые значения и битовые маски. Например, правило способно запретить определённое семейство сокетов в прямом вызове socket или разрешить clone только без флагов создания новых пространств имён.
BPF-фильтр не разыменовывает указатели из памяти процесса. Поэтому seccomp не может прочитать путь, переданный в openat, содержимое структуры sockaddr или массив аргументов старого вызова socketcall. Для проверки файлов и сетевых объектов применяют LSM-механизмы, включая AppArmor, SELinux и Landlock.
Фильтр нельзя ослабить после установки
После включения seccomp процесс не может удалить действующий фильтр или заменить его менее строгим. Новые фильтры добавляются поверх старых. Ядро выполняет все применимые фильтры и выбирает действие с наибольшим приоритетом.
Дочерние процессы наследуют фильтр через fork и clone, если фильтр разрешает такие вызовы. Ограничения сохраняются после execve. Поэтому один неверный профиль может сломать не только основной бинарный файл, но и запускаемые им утилиты.
Зачем нужен No New Privileges
Непривилегированный процесс может самостоятельно установить seccomp-фильтр только после включения флага no_new_privs. Альтернативный вариант требует возможности CAP_SYS_ADMIN в пользовательском пространстве имён.
Флаг no_new_privs запрещает получать новые привилегии через последующий execve. Биты setuid, setgid и файловые capabilities больше не повышают права такого процесса. Флаг наследуется дочерними процессами и не отключается.
Как выбрать действие фильтра
| Действие libseccomp | Результат | Когда применять |
|---|---|---|
SCMP_ACT_ALLOW |
Ядро выполняет syscall | Разрешённые операции рабочего профиля |
SCMP_ACT_ERRNO |
Ядро не выполняет syscall и возвращает заданный errno | Основной вариант на этапе внедрения |
SCMP_ACT_LOG |
Ядро записывает событие и выполняет syscall | Краткая диагностика на тестовом узле |
SCMP_ACT_KILL_PROCESS |
Ядро завершает весь процесс | Строгий профиль после полного тестирования |
SCMP_ACT_KILL_THREAD |
Ядро завершает только вызвавший поток | Почти никогда не подходит для многопоточных программ |
SCMP_ACT_TRAP |
Ядро отправляет потоку SIGSYS |
Специализированная обработка нарушений внутри приложения |
SCMP_ACT_NOTIFY |
Решение принимает пользовательский процесс-наблюдатель | Контейнерные среды и сложные песочницы с отдельным супервизором |
Для первого блокирующего профиля обычно выбирают SCMP_ACT_ERRNO. Приложение получает ошибку, а администратор может найти проблемную операцию без внезапного завершения процесса.
EPERM подходит не для каждого syscall
Большинство профилей возвращает EPERM, когда политика запрещает операцию. Однако некоторые библиотеки проверяют наличие нового syscall и переходят на старый интерфейс только после получения ENOSYS.
Известный пример связан с clone3. Старые seccomp-профили возвращали EPERM, после чего новые версии glibc не всегда переходили на clone. Приложение сообщало об ошибке создания потока, хотя старый syscall был разрешён. Docker использует для заблокированного clone3 значение ENOSYS, когда контейнер не получил нужные capabilities.
Не назначайте ENOSYS всем запрещённым вызовам. Программа может воспринять ошибку как отсутствие функции ядра и выбрать менее безопасный обходной путь. Выбирайте errno отдельно для syscall, который действительно поддерживает совместимый fallback.
Почему LOG не заменяет тестирование
SCMP_ACT_LOG разрешает операцию. Профиль с таким действием не проверяет поведение приложения при реальном запрете. Журналирование также может быстро заполнить журнал ядра, если фильтр регистрирует read, write, futex или другие частые вызовы.
Поддержка LOG зависит от версии ядра, libseccomp и контейнерной среды. Доступные действия показывает файл /proc/sys/kernel/seccomp/actions_avail.
Что проверить перед настройкой
Проверьте конфигурацию ядра
config="/boot/config-$(uname -r)"
if [ -r "$config" ]; then
grep -E '^CONFIG_SECCOMP=y$|^CONFIG_SECCOMP_FILTER=y$' "$config"
elif [ -r /proc/config.gz ]; then
zgrep -E '^CONFIG_SECCOMP=y$|^CONFIG_SECCOMP_FILTER=y$' /proc/config.gz
else
echo "Конфигурация текущего ядра недоступна"
fi
Для Filter Mode нужны обе строки.
CONFIG_SECCOMP=y
CONFIG_SECCOMP_FILTER=y
Некоторые дистрибутивы не публикуют конфигурацию ядра в /boot или /proc/config.gz. Отсутствие файла не доказывает отсутствие seccomp.
Посмотрите доступные действия
cat /proc/sys/kernel/seccomp/actions_avail 2>/dev/null
Современное ядро обычно выводит несколько значений, включая kill_process, kill_thread, trap, errno, user_notif, trace, log и allow. Конкретный список зависит от версии и конфигурации ядра.
Проверьте состояние процесса
grep -E '^(NoNewPrivs|Seccomp|Seccomp_filters):' /proc/$$/status
| Значение Seccomp | Значение |
|---|---|
0 |
Фильтр не включён |
1 |
Работает Strict Mode |
2 |
Работает Filter Mode |
Поле Seccomp_filters показывает количество фильтров и присутствует не во всех ядрах. Значение больше единицы может появиться, когда фильтры последовательно установили systemd, контейнерный runtime и само приложение.
Зафиксируйте среду тестирования
Запишите версии компонентов, которые влияют на набор системных вызовов.
uname -a
ldd --version | head -n 1
strace --version
systemd --version | head -n 1
docker version
runc --version
systemd-detect-virt
Один и тот же исходный код может использовать разные syscall после обновления glibc, musl, Go, Java, Python, OpenSSL, контейнерного образа или ядра. Профиль, созданный на x86_64, нельзя считать проверенным для arm64.
Как правильно собрать syscall приложения
Профилирование показывает наблюдавшееся поведение, но не доказывает, что список полный. Редкая ветка кода может сработать через неделю или только при аварии.
Запустите приложение под strace с самого начала
sudo strace -ff -tt -T -yy -s 256
-o /var/tmp/myapp.trace
-- /usr/local/bin/myapp --config /etc/myapp/config.yml
-fотслеживает создаваемые процессы и потоки.-ffзаписывает каждый процесс в отдельный файл с PID в имени.-ttдобавляет точное время.-Tпоказывает длительность syscall.-yyдополняет сведения о файловых дескрипторах.-s 256увеличивает длину выводимых строк.
-ff несовместим со сводным режимом -c. Для статистики запустите отдельный тест.
sudo strace -f -c -S calls
-o /var/tmp/myapp-syscalls.txt
-- /usr/local/bin/myapp --config /etc/myapp/config.yml
Файл покажет количество вызовов, ошибок и затраченное время. Сводка удобна для первого списка, но не содержит достаточного контекста для правил по аргументам.
Не ограничивайтесь штатным трафиком
Во время трассировки выполните все доступные сценарии.
- Запустите и штатно остановите приложение.
- Подайте обычную и пиковую нагрузку.
- Перезагрузите конфигурацию без остановки процесса.
- Создайте новые рабочие процессы и потоки.
- Проверьте ротацию журналов.
- Обновите или перечитайте TLS-сертификаты.
- Проверьте DNS, IPv4, IPv6 и Unix-сокеты.
- Выполните healthcheck, резервное копирование и административные команды.
- Отключите внешнюю базу данных или API.
- Смоделируйте тайм-аут, обрыв соединения и нехватку места.
- Проверьте завершение по
SIGTERMи перезагрузку поSIGHUP. - Запустите все подключаемые модули, плагины и обработчики файлов.
Подключение к работающему процессу
Подключение через -p помогает исследовать уже запущенную службу, но не показывает загрузку библиотек и раннюю инициализацию.
pids=$(pgrep -x nginx)
sudo strace -f
$(printf '%s
' "$pids" | sed 's/^/-p /')
-o /var/tmp/nginx-attached.trace
Команда подключается к найденным процессам Nginx. Запуск вида strace -p $(pgrep nginx) ненадёжен, поскольку strace требует отдельный параметр -p перед каждым PID.
Учитывайте влияние strace
Запускайте трассировку на тестовом узле. Strace работает через ptrace, замедляет приложение и меняет временные характеристики. Журнал может содержать пути, имена файлов, адреса, фрагменты передаваемых данных и другую чувствительную информацию.
Профилирование контейнера может потребовать CAP_SYS_PTRACE, изменения политики Yama или временного диагностического профиля. Не переносите такие послабления в рабочую конфигурацию.
Почему нельзя слепо копировать список strace
Библиотечные функции и syscall не совпадают один к одному. Например, функция exit может вызвать exit_group, функция fork может использовать clone, а функция open в современной glibc обычно обращается к openat.
Выбор syscall зависит от архитектуры, версии libc и доступности новых интерфейсов ядра. Java, Go и другие среды выполнения также меняют системные вызовы после обновлений.
Как настроить seccomp в Docker
Docker применяет встроенный seccomp-профиль к обычному контейнеру, если администратор не указал пользовательский профиль и не отключил фильтрацию. Текущий принцип работы и список значимых ограничений описывает документация Docker.
Не начинайте с пользовательского профиля без причины. Встроенный профиль обновляется вместе с Docker Engine и может получить новые ограничения после обнаружения уязвимости ядра. Старая локальная копия останется без таких исправлений.
Проверьте встроенный профиль
docker info --format '{{json .SecurityOptions}}'
В выводе должен присутствовать seccomp. Затем проверьте состояние процесса внутри контейнера.
docker run --rm alpine
sh -c 'grep -E "^(NoNewPrivs|Seccomp|Seccomp_filters):" /proc/1/status'
Значение Seccomp: 2 подтверждает Filter Mode.
Проверьте, какой профиль назначен контейнеру
docker inspect
--format '{{json .HostConfig.SecurityOpt}}'
nginx-seccomp-test
Пустой список не обязательно означает отсутствие seccomp. Docker может применять встроенный профиль без явной записи в параметрах контейнера.
Когда нужен пользовательский профиль
Пользовательский профиль оправдан в нескольких случаях.
- Контейнер обрабатывает недоверенные данные и требует более строгого allowlist.
- Приложение не работает со встроенным профилем, а анализ подтвердил необходимость конкретного syscall.
- Политика организации требует одинакового проверенного профиля на всех узлах.
- Команда фиксирует профиль вместе с версией приложения и проверяет его в CI.
Не заменяйте встроенный профиль минимальным JSON, собранным по одному запуску Nginx. Текущий профиль Docker содержит архитектурные правила, проверки capabilities, аргументов syscall и специальные errno для совместимости.
Возьмите профиль из закреплённой версии
Не скачивайте файл из ветки main при каждом развёртывании. Закрепите тег или commit репозитория Moby Profiles и сохраните контрольную сумму. В июле 2026 года при подготовке статьи использовался релиз seccomp-профиля seccomp/v0.2.3.
После загрузки сохраните исходную копию.
install -d -m 0750 ./seccomp
cp docker-default.json ./seccomp/docker-default.original.json
cp docker-default.json ./seccomp/myapp.json
sha256sum ./seccomp/docker-default.original.json
> ./seccomp/docker-default.original.sha256
Проверьте синтаксис JSON.
jq empty ./seccomp/myapp.json
Команда должна завершиться без вывода и вернуть код 0.
Не удаляйте карту архитектур
Профиль Docker обычно содержит archMap. Карта связывает основную архитектуру с совместимыми ABI, включая x86 и x32 на системе x86_64. Удаление карты создаёт риск обхода правил через альтернативный ABI или ломает 32-битные приложения.
Оставьте defaultAction, defaultErrnoRet и archMap из базового профиля. Меняйте только проверенные правила и фиксируйте разницу в системе контроля версий.
git diff --no-index
./seccomp/docker-default.original.json
./seccomp/myapp.json
Примените профиль к тестовому контейнеру
docker run --rm
--name nginx-seccomp-test
--security-opt seccomp="$PWD/seccomp/myapp.json"
-p 127.0.0.1:8080:80
nginx:alpine
В другом терминале проверьте ответ.
curl -fsS http://127.0.0.1:8080/ >/dev/null
Одного запроса недостаточно. Проверьте reload, завершение, TLS, журналирование, обработку ошибок и весь набор функций контейнера.
Подключите профиль через Docker Compose
services:
web:
image: nginx:alpine
ports:
- "127.0.0.1:8080:80"
security_opt:
- seccomp=./seccomp/myapp.json
Проверьте итоговую конфигурацию.
docker compose config
После изменения профиля пересоздайте контейнер.
docker compose up -d --force-recreate
docker restart не меняет параметры уже созданного контейнера.
Не используйте unconfined как постоянное решение
docker run
--security-opt seccomp=unconfined
myimage
Параметр полностью отключает seccomp для контейнера. Допустим только краткий диагностический запуск на изолированном тестовом узле. Если приложение работает лишь с unconfined, найдите конкретный заблокированный syscall и исправьте профиль.
Кейс CVE-2026-31431 и ограничение socketcall
Уязвимость Linux CVE-2026-31431, известная как Copy Fail, показала, почему seccomp нельзя рассматривать отдельно от AppArmor и SELinux. Эксплуатация использовала семейство сокетов AF_ALG, которое открывает пользовательским процессам доступ к криптографическому API ядра.
Прямой вызов socket передаёт семейство сокета числовым аргументом, поэтому seccomp может заблокировать AF_ALG. Старый 32-битный интерфейс socketcall передаёт аргументы через указатель. BPF не разыменовывает такой указатель и не видит семейство сокета.
Первая попытка закрыть обход через полный запрет socketcall сломала часть 32-битных программ. Позже Moby убрал общий запрет и перенёс защиту старого пути в AppArmor и SELinux, сохранив проверку аргумента прямого socket.
Патч ядра остаётся основной защитой. Seccomp и LSM могут уменьшить доступную поверхность, но не заменяют обновление уязвимого ядра. Пользовательский профиль и отключённый AppArmor способны убрать защиту, которую добавил контейнерный runtime.
Как настроить seccomp для systemd-сервиса
Systemd создаёт seccomp-фильтр через директиву SystemCallFilter. Группы syscall позволяют начать с подготовленного набора, а затем добавить операции, которые действительно нужны службе.
Проверьте версию systemd
systemd --version | head -n 1
SystemCallFilter доступен в старых выпусках systemd, но SystemCallLog появился только в systemd 247. Состав групп syscall также меняется между версиями и архитектурами.
Посмотрите состав группы system-service
systemd-analyze syscall-filter @system-service
Группа @system-service содержит вызовы, которые обычно нужны системным службам, но исключает специализированные интерфейсы для монтирования, перезагрузки, swap и изменения часов.
Список формируется во время сборки systemd. Новое ядро может содержать syscall, которого установленная версия systemd ещё не знает. Проверяйте фактический вывод на целевом сервере.
Проверьте архитектуру бинарных файлов
file /usr/sbin/nginx
file /usr/lib/nginx/modules/*.so 2>/dev/null
Директива SystemCallArchitectures=native запрещает альтернативные ABI. Такое ограничение полезно против обходов, но сломает 32-битный бинарный файл или helper на 64-битном сервере.
Создайте drop-in-файл
sudo systemctl edit nginx.service
Добавьте базовый профиль.
[Service]
NoNewPrivileges=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
SystemCallErrorNumber=EPERM заставляет ядро вернуть ошибку вместо немедленного завершения процесса через SIGSYS. Такой режим удобнее во время внедрения.
Systemd не требует явно перечислять некоторые вызовы, необходимые для запуска и завершения процесса. В список не нужно вручную добавлять execve, exit, exit_group, getrlimit, sigreturn, rt_sigreturn и базовые операции сна и чтения времени.
Проверьте конфигурацию до перезапуска
sudo systemd-analyze verify nginx.service
Затем примените настройки.
sudo systemctl daemon-reload
sudo systemctl restart nginx.service
sudo systemctl is-active nginx.service
Ожидаемый вывод последней команды выглядит как active.
Проверьте активные параметры
systemctl show nginx.service
-p NoNewPrivileges
-p SystemCallFilter
-p SystemCallArchitectures
-p SystemCallErrorNumber
Найдите PID основного процесса и проверьте состояние ядра.
pid=$(systemctl show
--property MainPID
--value nginx.service)
sudo grep -E '^(NoNewPrivs|Seccomp|Seccomp_filters):'
"/proc/$pid/status"
Ожидаемый результат содержит Seccomp: 2.
Добавьте только подтверждённый syscall
Допустим, приложение использует memfd_create, а трассировка и документация приложения подтверждают необходимость вызова. Дополните строку.
[Service]
NoNewPrivileges=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service memfd_create
SystemCallErrorNumber=EPERM
Не добавляйте syscall только потому, что strace показал попытку. Приложение может проверять необязательную функцию и нормально работать после отказа.
Проверьте общий уровень изоляции
systemd-analyze security nginx.service
Команда оценивает доступные механизмы systemd, но не проводит аудит приложения и не доказывает безопасность. Низкий балл риска не гарантирует, что allowlist покрывает все реальные сценарии.
Дополните seccomp другими ограничениями
[Service]
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
LockPersonality=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@mount @reboot @swap
SystemCallErrorNumber=EPERM
Не копируйте блок без проверки путей записи и функций службы. Например, ProtectSystem=strict потребует явно открыть каталоги журналов, кэша или состояния через ReadWritePaths, StateDirectory, CacheDirectory и LogsDirectory.
Как включить seccomp в Kubernetes
Kubernetes поддерживает три типа профиля.
| Тип | Поведение |
|---|---|
RuntimeDefault |
Используется стандартный профиль containerd, CRI-O или другого runtime |
Localhost |
Используется локальный JSON-профиль, установленный на узле |
Unconfined |
Seccomp отключается |
Включите RuntimeDefault на уровне Pod
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginx:alpine
Профиль уровня контейнера может переопределить настройку Pod. Проверьте init containers и ephemeral containers отдельно, если манифест задаёт им собственный securityContext.
Подключите локальный профиль
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/nginx.json
containers:
- name: nginx
image: nginx:alpine
Файл должен находиться на каждом подходящем узле относительно каталога seccomp kubelet. При стандартном root-dir полный путь будет выглядеть как /var/lib/kubelet/seccomp/profiles/nginx.json.
Обычный Kubernetes не распространяет Localhost-профиль по узлам автоматически. Используйте систему управления конфигурациями, DaemonSet с контролируемой установкой или возможности конкретного runtime.
Проверьте применённую политику
kubectl get pod web
-o jsonpath='{.spec.securityContext.seccompProfile.type}{"
"}'
Ожидаемый результат выглядит как RuntimeDefault или Localhost.
Привилегированный контейнер несовместим с реальной seccomp-изоляцией. Не рассчитывайте на профиль, если контейнер запущен с privileged: true. Сначала устраните привилегированный режим.
Как найти заблокированный syscall
Начните с журнала приложения
sudo journalctl
-u nginx.service
--since "15 minutes ago"
Сообщения Operation not permitted, Permission denied, pthread_create failed или завершение с status=31/SYS могут указывать на seccomp. Такие же ошибки способны вызвать права файлов, capabilities, SELinux, AppArmor и пространства имён.
Проверьте журнал ядра
sudo journalctl -k
--since "15 minutes ago"
| grep -Ei 'seccomp|sigsys|audit'
На системе с auditd используйте отдельный поиск.
sudo ausearch -m SECCOMP -ts recent -i
Полезные поля записи включают syscall, arch, comm, exe, pid и code.
Преобразуйте номер syscall в имя
ausyscall 435
Пакет libseccomp может предоставлять другую утилиту.
scmp_sys_resolver 435
Номер зависит от ABI. Сначала проверьте поле arch в записи аудита. Номер 59 на x86_64 и номер 59 на другой архитектуре могут обозначать разные операции.
Повторите ошибку под strace
sudo strace -f
-e trace=all
-o /var/tmp/nginx-debug.trace
nginx -t
Найдите отказы.
grep -E 'EPERM|EACCES|ENOSYS'
/var/tmp/nginx-debug.trace
Следите за последовательностью. Первый отказ может вызвать десятки последующих ошибок, которые не требуют новых разрешений.
Проверьте LSM отдельно
sudo journalctl -k
--since "15 minutes ago"
| grep -Ei 'apparmor|avc|selinux'
Если журнал содержит AppArmor DENIED или SELinux AVC, изменение seccomp-профиля не исправит причину.
Сравните запуск с базовым профилем
Для Docker сначала верните встроенный профиль, а не unconfined. Если приложение заработало, проблема находится в пользовательском профиле. Если ошибка сохранилась, проверьте capabilities, LSM, файловую систему и конфигурацию приложения.
Неочевидные ограничения и ошибки
| Симптом | Причина | Исправление |
|---|---|---|
| Программа не запускается | Профиль блокирует динамический загрузчик, libc или раннюю инициализацию | Трассировать процесс с самого старта и проверить openat, mmap, mprotect, arch_prctl, сигналы и управление памятью |
| Приложение запускается, но не создаёт потоки | Заблокирован clone или clone3, возвращается неподходящий errno |
Проверить ожидаемый fallback библиотеки и правила Docker для clone3 |
| Reload ломается, хотя обычные запросы работают | Профилирование не охватило сигналы, fork, exec или открытие новой конфигурации | Проверить kill, pidfd_send_signal, clone, wait4, openat и связанные операции |
| Правило работает на x86_64 и ломается на arm64 | Архитектуры используют разные syscall и соглашения вызова | Тестировать профиль на каждой целевой архитектуре |
| Запрет kill не мешает отправлять сигналы | Ядро предоставляет похожие операции через tgkill, tkill и pidfd_send_signal |
Использовать allowlist вместо неполного blacklist |
| Seccomp не может ограничить конкретный файл | Путь передаётся через указатель, который BPF не читает | Применить AppArmor, SELinux, Landlock или изолированную файловую систему |
| Старый профиль стал слабее нового встроенного | Пользовательский JSON не получил исправления из обновления runtime | Сравнивать профиль после каждого обновления Docker, containerd, CRI-O или runc |
| Журнал seccomp пуст | Действие LOG не поддерживается, отключён аудит или отказ вернул другой механизм | Проверить actions_avail, auditd, AppArmor, SELinux и повторить ошибку под strace |
| Контейнер работает только с unconfined | Профиль запрещает нужный syscall или аргумент | Найти точный отказ и добавить минимальное правило вместо полного отключения seccomp |
| SystemCallArchitectures=native ломает службу | Служба запускает 32-битный helper или библиотеку | Проверить все бинарные файлы через file и разрешить нужную архитектуру только при обоснованной необходимости |
Blacklist быстро устаревает
Ядро регулярно получает новые системные вызовы. Запрет одного старого интерфейса не блокирует новый syscall с похожими возможностями. Например, pidfd_send_signal частично дублирует операции kill, а новые mount API дополняют традиционный mount.
Allowlist автоматически блокирует неизвестные новые вызовы, пока администратор не добавит их после тестирования. Поэтому allowlist лучше подходит для долгоживущих служб.
Слишком строгий профиль может ослабить надёжность
Процесс иногда использует редкий syscall только для корректного завершения, сброса данных или записи диагностического сообщения. Блокировка такой операции не создаёт прямую уязвимость, но может привести к потере данных, зависанию или повреждению состояния.
Тестируйте не только основной успешный сценарий, но и обработку сбоев.
Интерпретируемые языки требуют более широкого покрытия
Python, PHP, Node.js и Java загружают модули, создают потоки, используют JIT, DNS, локали и файловые кэши. Набор syscall зависит от подключённых библиотек и конкретного запроса. Профиль для одного скрипта нельзя автоматически переносить на другой проект с той же средой выполнения.
Как откатить настройки
Откат Docker
Удалите пользовательский параметр seccomp и пересоздайте контейнер. Docker вернётся к встроенному профилю.
docker rm -f nginx-seccomp-test
docker run --rm
--name nginx-seccomp-test
-p 127.0.0.1:8080:80
nginx:alpine
Для Compose удалите строку seccomp=... из security_opt.
docker compose up -d --force-recreate
Не используйте seccomp=unconfined как постоянный откат.
Откат systemd
Просмотрите созданные drop-in-файлы.
systemctl cat nginx.service
Удалите только файл, в котором настроен seccomp.
sudo rm
/etc/systemd/system/nginx.service.d/override.conf
sudo systemctl daemon-reload
sudo systemctl restart nginx.service
sudo systemctl is-active nginx.service
Не удаляйте весь каталог drop-in. В каталоге могут находиться параметры окружения, лимиты ресурсов и другие рабочие настройки.
Откат Kubernetes
Для пользовательского Localhost-профиля верните RuntimeDefault и обновите workload.
spec:
template:
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
После изменения проверьте rollout.
kubectl rollout status deployment/web
Не переключайте рабочий Pod на Unconfined, кроме краткой диагностики в контролируемой среде.
Как сопровождать seccomp-профиль
- Храните профиль рядом с кодом или манифестами приложения.
- Закрепляйте версию базового профиля и его контрольную сумму.
- Проверяйте JSON в CI.
- Запускайте интеграционные тесты с включённым фильтром.
- Тестируйте запуск, остановку, reload и аварийные сценарии.
- Проверяйте каждую поддерживаемую архитектуру отдельно.
- Сравнивайте пользовательский профиль с новым профилем runtime после обновлений.
- Пересматривайте правила после обновления libc, языка, контейнерного образа и ядра.
- Фиксируйте причину каждого нестандартного разрешения.
- Удаляйте syscall, который больше не нужен приложению.
Полезный комментарий к правилу должен объяснять не название syscall, а функцию приложения, которая без него не работает. Например, «нужен для перезагрузки конфигурации через создание нового worker», а не «разрешён clone».
Контрольный список перед включением
- Ядро поддерживает
CONFIG_SECCOMPиCONFIG_SECCOMP_FILTER. - Профиль соответствует архитектуре и ABI приложения.
- Профилирование охватило запуск, остановку, reload и обработку ошибок.
- Проверены дочерние процессы, потоки, плагины и административные команды.
- Для необязательных новых syscall выбран корректный errno.
- Профиль протестирован с реальной версией libc и контейнерного runtime.
- Проверены журналы seccomp, AppArmor и SELinux.
- Подготовлена команда отката.
- Пользовательский профиль не потерял свежие ограничения встроенного профиля.
- Seccomp дополнен capabilities, LSM и файловой изоляцией.
FAQ
Нужно ли создавать свой seccomp-профиль для каждого Docker-контейнера?
Нет. Начните со встроенного профиля Docker. Создавайте пользовательский allowlist только для контейнеров с повышенным риском или специальными требованиями.
Можно ли построить готовый allowlist по одному запуску strace?
Нет. Strace показывает только выполненные ветки кода. Профиль должен пройти тесты запуска, остановки, reload, ошибок, TLS, DNS, ротации журналов и обновлений.
Что лучше возвращать при блокировке, EPERM или ENOSYS?
Обычно подходит EPERM. ENOSYS применяют для конкретных syscall, когда библиотека ожидает такой ответ и переходит на совместимый старый интерфейс.
Почему seccomp не может разрешить openat только для одного каталога?
Путь передаётся указателем на строку в памяти процесса. BPF-фильтр не разыменовывает указатели. Ограничьте файлы через AppArmor, SELinux, Landlock или mount namespace.
Можно ли использовать один профиль на x86_64 и arm64?
Можно хранить общий OCI-профиль с картой архитектур, но профиль нужно отдельно тестировать на каждой платформе. Наборы syscall и поведение libc различаются.
Почему приложение сломалось после обновления базового образа?
Новая libc, библиотека или среда выполнения могла перейти на другой syscall. Повторите интеграционные тесты и обновите профиль только после анализа нового вызова.
Достаточно ли SCMP_ACT_LOG для построения профиля?
Нет. LOG показывает вызовы и разрешает их, но не проверяет реакцию приложения на отказ. Журналирование также не гарантирует покрытие редких веток.
Защищает ли seccomp от всех побегов из контейнера?
Нет. Seccomp сокращает поверхность ядра, но разрешённый syscall тоже может содержать уязвимость. Обновляйте ядро и runtime, убирайте capabilities, включайте AppArmor или SELinux и не запускайте контейнер как privileged.
Как понять, что systemd действительно применил фильтр?
Найдите MainPID службы и проверьте /proc/PID/status. Значение Seccomp: 2 означает, что процесс работает в Filter Mode.
Можно ли временно отключить seccomp для диагностики?
Можно только на изолированном тестовом узле. Сначала лучше вернуть встроенный профиль runtime. Режим unconfined полностью убирает защиту и не подходит для постоянной эксплуатации.
