
Смягчение последствий для CVE-2026-31431 ('Copy Fail') в Docker. Также включает шаблоны Kubernetes.
Идемпотентный скрипт для блокировки создания сокетов AF_ALG для всех контейнеров Docker
на хосте в качестве меры смягчения для CVE-2026-31431 («Copy Fail»).
CVE-2026-31431 — это локальное повышение привилегий в криптографическом шаблоне
authencesn ядра Linux, присутствующем в ядрах, собранных с 2017 года и до
появления исправления в основной ветке (коммит a664bf3d603d). Непривилегированный
пользователь может объединить операцию сокета AF_ALG с splice(), чтобы
выполнить контролируемую запись 4 байт в page cache любого читаемого файла,
нацелившись на setuid-бинарник для получения root-оболочки. Python-эксплойт
размером 732 байта надёжно эксплуатирует эту уязвимость без гонок и
дистрибутив-специфичных смещений на каждом крупном дистрибутиве Linux,
поставляющем уязвимое ядро.
Обязательный первый шаг эксплойта — открытие сокета AF_ALG
(socket(AF_ALG, SOCK_SEQPACKET, 0)). Блокировка этого системного вызова через seccomp
предотвращает эксплуатацию даже на незапатченных ядрах. Встроенный профиль seccomp
Docker по умолчанию не блокирует , и недостаточно —
протестированные кластеры показали, что поды, допущенные в рамках PSS Restricted,
всё ещё могли открывать сокеты .
AF_ALGRuntimeDefaultAF_ALGСм. оригинальный отчёт исследователя на https://copy.fail и рекомендации CERT-EU на https://cert.europa.eu/publications/security-advisories/2026-005/ для полных технических деталей и доступности патчей по дистрибутивам.
Этот скрипт охватывает глобальную конфигурацию демона Docker Engine. Для
Kubernetes см. раздел Kubernetes ниже. Для bare-metal или
VM-нагрузок (не контейнеризированных) вместо этого отключите модуль ядра algif_aead:
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
Этот подход не влияет на dm-crypt/LUKS, kTLS, IPsec/XFRM, OpenSSL, GnuTLS, NSS или SSH.
Извлекает активный встроенный профиль seccomp Docker, проверяя
HostConfig.SecurityOpt кратковременного контейнера. Это исключает любую
зависимость от удалённого URL и гарантирует, что базовый профиль соответствует
фактически установленной версии Docker. Загрузка с GitHub из
moby/profiles используется как запасной
вариант только если проверка контейнера ничего не даёт.
Патчит профиль, удаляя socket из записи разрешённого списка Docker
и добавляя его обратно с фильтром аргументов, который разрешает все семейства
адресов, кроме AF_ALG (значение 38), используя SCMP_CMP_NE. Всё остальное
поведение seccomp Docker по умолчанию сохраняется.
Записывает пропатченный профиль атомарно в
/etc/seccomp/docker-block-af-alg.json (временный файл + переименование).
Пропускается, если содержимое на диске уже идентично.
Обновляет /etc/docker/daemon.json, устанавливая "seccomp-profile" на
путь пропатченного профиля. Оригинальный файл сохраняется в daemon.json.bak
при первом изменении. Пропускается, если уже настроен корректно.
Перезагружает dockerd через systemctl reload docker (SIGHUP — перезапуск
не требуется). Пропускается, если ни один файл не изменился.
Проверяет, что блокировка активна, запуская пробу внутри контейнера, независимо от того, были ли внесены какие-либо изменения на шагах выше.
Скрипт идемпотентен: многократный запуск даёт тот же результат и перезагружает Docker только когда что-то действительно изменилось.
Примечание: контейнеры
--privilegedобходят все профили seccomp независимо от этой конфигурации. Проверяйте ваши Compose-файлы и команды запуска привилегированных контейнеров отдельно.
docker в PATHcurl в PATH (только запасной вариант)systemctl (хост на systemd)/etc/seccomp и /etc/docker, а также для
systemctl reload docker# Применить меру смягчения и проверить (обычное использование)
sudo python3 harden-docker-seccomp.py
# Показать, что изменится, без записи чего-либо или перезагрузки Docker
sudo python3 harden-docker-seccomp.py --dry-run
# Повторно запустить только проверку контейнера (без изменений конфигурации)
python3 harden-docker-seccomp.py --verify-only
# Подробный вывод
sudo python3 harden-docker-seccomp.py --verbose
INFO Written: /etc/seccomp/docker-block-af-alg.json
INFO Updated: /etc/docker/daemon.json
INFO Reloading Docker daemon (SIGHUP)…
INFO Docker daemon reloaded.
INFO Verifying AF_ALG is blocked inside a container…
OK: AF_ALG blocked — [Errno 1] Operation not permitted
INFO All done. CVE-2026-31431 (Copy Fail) mitigation is active.
INFO Profile unchanged: /etc/seccomp/docker-block-af-alg.json
INFO daemon.json already configured correctly.
INFO No changes — Docker daemon reload not required.
INFO Verifying AF_ALG is blocked inside a container…
OK: AF_ALG blocked — [Errno 1] Operation not permitted
INFO All done. CVE-2026-31431 (Copy Fail) mitigation is active.
| Код | Значение |
|---|---|
0 | Успех — мера смягчения активна |
1 | Скрипт не запущен от root (при патчинге) или неисправимая ошибка |
2 | Проверка не удалась — AF_ALG не заблокирован |
Поды Kubernetes используют ядро хоста, поэтому тот же примитив сокета AF_ALG
доступен из любого пода на уязвимом узле. Seccomp RuntimeDefault недостаточно —
протестированные кластеры показали, что поды, допущенные в рамках PSS Restricted,
всё ещё могли открывать сокеты AF_ALG. Требуется профиль Localhost с явным
правилом запрета.
Мера смягчения требует двух вещей: JSON-профиля, присутствующего в файловой системе каждого узла, и ссылки на него в каждом спецификации пода. Разделы ниже охватывают оба аспекта, включая глобальное внедрение профиля без изменения отдельных спецификаций подов.
Kubelet разрешает профили seccomp Localhost относительно своего корня seccomp,
который по умолчанию находится в /var/lib/kubelet/seccomp. Профиль должен
существовать по этому пути на каждом узле, который может планировать рабочую нагрузку.
Примените ConfigMap и DaemonSet из этого репозитория:
kubectl apply -f templates/configmap-seccomp-profile.yaml
kubectl apply -f templates/daemonset-distribute-profile.yaml
DaemonSet запускает init-контейнер, который копирует профиль из ConfigMap в корень seccomp kubelet узла, а затем размещает минимальный pause-контейнер, чтобы под оставался видимым для мониторинга здоровья. Он переносит все taints, поэтому также запускается на control-plane узлах.
Нестандартный корень seccomp kubelet: RKE2 использует
/var/lib/rancher/rke2/agent/kubelet/seccomp. Переопределите путь, установивNODE_SECCOMP_ROOTв env init-контейнера DaemonSet перед применением.
Проверьте наличие файла на узле:
kubectl -n kube-system exec -it \
$(kubectl -n kube-system get pod -l app.kubernetes.io/name=distribute-af-alg-seccomp \
-o jsonpath='{.items[0].metadata.name}') -- \
cat /var/lib/kubelet/seccomp/block-af-alg.json
Вместо изменения отдельных спецификаций подов или Helm-чартов используйте
мутирующий admission webhook для автоматического внедрения seccompProfile во время
допуска. Предоставляются два варианта: Kyverno и OPA Gatekeeper.
Оба подхода внедряют профиль только когда под ещё не объявляет собственный, поэтому поды с явными профилями остаются нетронутыми.
Важно: Существующие запущенные поды не мутируются ретроактивно. После применения политики перезапустите ваши deployment'ы, чтобы подхватить внедрённый профиль:
kubectl rollout restart deployment -A
Этот шаблон использует API MutatingPolicy (policies.kyverno.io/v1),
который достиг GA в Kyverno 1.17. Устаревший API ClusterPolicy
(kyverno.io/v1) был объявлен устаревшим в Kyverno 1.17 (январь 2026) и
планируется к удалению в 1.20 (октябрь 2026); не используйте его для новых политик.
CEL-выражение matchConditions проверяет отсутствие seccompProfile
перед мутацией, поэтому поды, уже объявляющие профиль, остаются нетронутыми.
# Установка Kyverno (если ещё не установлен)
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm upgrade --install kyverno kyverno/kyverno -n kyverno --create-namespace
# Применение политики
kubectl apply -f templates/kyverno-mutate-seccomp.yaml
Проверьте, что новый под получает внедрённый профиль:
kubectl run test --image=busybox --restart=Never -- sleep 30
kubectl get pod test -o jsonpath='{.spec.securityContext.seccompProfile}'
# Ожидается: {"localhostProfile":"block-af-alg.json","type":"Localhost"}
kubectl delete pod test
CRD мутации Assign Gatekeeper использует условие pathTests для внедрения
профиля только когда spec.securityContext.seccompProfile ещё не существует.
Мутация стабильна с Gatekeeper 3.10+; флаг функции не требуется.
# Установка Gatekeeper (если ещё не установлен)
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install -n gatekeeper-system gatekeeper gatekeeper/gatekeeper \
--create-namespace
# Применение мутации
kubectl apply -f templates/gatekeeper-assign-seccomp.yaml
Проверка:
kubectl run test --image=busybox --restart=Never -- sleep 30
kubectl get pod test -o jsonpath='{.spec.securityContext.seccompProfile}'
# Ожидается: {"localhostProfile":"block-af-alg.json","type":"Localhost"}
kubectl delete pod test
Gatekeeper против Kyverno: Подход
Assignработает на уровне полей и требует включения мутационного webhook Gatekeeper.MutatingPolicyKyverno с CELmatchConditionобрабатывает условие встроенно. Любой из них даёт тот же результат — предпочитайте тот, который уже развёрнут в вашем кластере.
Поды с hostPID: true, hostNetwork: true или
securityContext.privileged: true имеют повышенный доступ, который seccomp сам по себе
полностью не сдерживает. Проверяйте такие рабочие нагрузки отдельно и удаляйте
привилегии, где это возможно.
ConfigMap как источник истины. JSON-профиль находится в
configmap-seccomp-profile.yaml, а не встроен в DaemonSet
или продублирован в файлах. DaemonSet монтирует его и копирует на узел.
Обновление профиля означает редактирование одного ConfigMap и перезапуск подов
DaemonSet — никакие другие файлы не меняются.
DaemonSet использует приоритет system-node-critical. Это гарантирует, что
под распространения не будет вытеснен до планирования защищаемых рабочих нагрузок,
что оставило бы узлы без файла профиля и поды в состоянии
CreateContainerError.
Gatekeeper исключает kube-system и gatekeeper-system. Внедрение
профиля Localhost в системные поды, которые могут предшествовать установке
DaemonSet, рискует сломать ссылку на профиль, если файл ещё не присутствует на
узле. Политика Kyverno не требует этого исключения, потому что Kyverno
более корректно обрабатывает порядок webhook'ов, но правила exclude могут быть
добавлены и там при необходимости.
Условное внедрение, а не переопределение. И CEL matchCondition
MutatingPolicy Kyverno, и проверка пути MustNotExist Gatekeeper означают, что
политика допуска действует только когда под не имеет существующего seccompProfile.
Рабочие нагрузки, которые уже объявляют собственный профиль — включая те, которым
легитимно нужен AF_ALG через собственный разрешённый список — остаются нетронутыми.