
Смягчение последствий для 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 по умолчанию не блокирует AF_ALG, и RuntimeDefault недостаточно —
протестированные кластеры показали, что поды, допущенные в рамках PSS Restricted,
всё ещё могли открывать сокеты AF_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.
Оба подхода внедряют профиль только когда под ещё не объявляет собственный, поэтому поды с явными профилями остаются нетронутыми.