幂等脚本,用于阻止宿主机上所有 Docker 容器创建 AF_ALG 套接字,以缓解 CVE-2026-31431(“Copy Fail”)。
CVE-2026-31431 是 Linux 内核 authencesn 加密模板中的一个本地权限提升漏洞,影响 2017 年至上游修复可用(主线提交 a664bf3d603d)期间构建的内核。非特权用户可以将 AF_ALG 套接字操作与 splice() 链接起来,对任何可读文件的页缓存执行受控的 4 字节写入,以 setuid 二进制文件为目标获取 root shell。一个 732 字节的 Python 概念验证代码可在所有受影响的 Linux 发行版上可靠地利用此漏洞,无需竞争条件或针对各发行版的偏移量。
该漏洞利用的强制第一步是打开 AF_ALG 套接字(socket(AF_ALG, SOCK_SEQPACKET, 0))。通过 seccomp 阻止该系统调用,即使在未打补丁的内核上也能防止利用。Docker 内置的默认 seccomp 配置文件不会阻止 AF_ALG,而 RuntimeDefault 也不够——测试集群显示,在 PSS Restricted 下准入的 Pod 仍然可以打开 AF_ALG 套接字。
有关完整技术细节和各发行版的补丁可用性,请参阅原始研究人员公告 https://copy.fail 和 CERT-EU 公告 https://cert.europa.eu/publications/security-advisories/2026-005/。
此脚本涵盖 Docker Engine 全局守护进程配置。对于 Kubernetes,请参阅下面的 Kubernetes 部分。对于裸机或虚拟机工作负载(非容器化),请改为禁用 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 没有影响。
提取 Docker 当前内置的 seccomp 配置文件,通过检查一个短暂容器的 HostConfig.SecurityOpt。这避免了对远程 URL 的任何依赖,并确保基础配置文件与实际安装的 Docker 版本匹配。仅当容器检查无结果时,才使用来自 moby/profiles 的 GitHub 获取作为后备方案。
修补配置文件,从 Docker 的允许列表条目中移除 socket,并使用 SCMP_CMP_NE 重新添加,附带一个参数过滤器,允许除 AF_ALG(值 38)之外的所有地址族。Docker 的其他默认 seccomp 行为均保持不变。
原子写入修补后的配置文件 到 /etc/seccomp/docker-block-af-alg.json(临时文件 + 重命名)。如果磁盘上的内容已相同,则跳过。
更新 /etc/docker/daemon.json,将 "seccomp-profile" 设置为修补后配置文件的路径。首次修改时,原始文件备份为 daemon.json.bak。如果已正确配置,则跳过。
通过 systemctl reload docker 重新加载 dockerd(SIGHUP——无需重启)。如果两个文件均未更改,则跳过。
该脚本是幂等的:多次运行会产生相同的结果,并且仅在发生实际更改时才重新加载 Docker。
注意:
--privileged容器会绕过所有 seccomp 配置文件,无论此配置如何。请单独审计您的 Compose 文件和特权容器的运行命令。
PATH 中的 docker CLIPATH 中的 curl(仅后备)systemctl(systemd 宿主机)/etc/seccomp 和 /etc/docker 的写入以及 systemctl reload docker 需要 root / sudo# 应用缓解措施并验证(常规用法)
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 Pod 共享宿主内核,因此受影响的节点上的任何 Pod 都可以访问相同的 AF_ALG 套接字原语。RuntimeDefault seccomp 不够——测试集群显示,在 PSS Restricted 下准入的 Pod 仍然可以打开 AF_ALG 套接字。需要使用带有显式拒绝规则的 Localhost 配置文件。
缓解措施需要两件事:配置文件 JSON 存在于每个节点的文件系统上,并且每个 Pod 规范都引用它。以下各节涵盖两者,包括如何在不修改单个 Pod 规范的情况下全局注入配置文件。
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 复制到节点的 kubelet seccomp 根目录,然后停放一个最小的 pause 容器,使 Pod 保持可见以进行健康监控。它容忍所有污点,因此也会在控制平面节点上运行。
非标准 kubelet seccomp 根目录: RKE2 使用
/var/lib/rancher/rke2/agent/kubelet/seccomp。在应用前,通过设置 DaemonSet init 容器环境中的NODE_SECCOMP_ROOT来覆盖该路径。
验证文件是否存在于节点上:
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
与其修改单个 Pod 规范或 Helm chart,不如使用变更准入 Webhook 在准入时自动注入 seccompProfile。提供两个选项:Kyverno 和 OPA Gatekeeper。
两种方法仅在 Pod 尚未声明配置文件时才注入,因此具有显式配置文件的 Pod 保持不变。
重要: 现有运行中的 Pod 不会被追溯变更。应用策略后,滚动重启您的部署以获取注入的配置文件:
kubectl rollout restart deployment -A
此模板使用 MutatingPolicy API(policies.kyverno.io/v1),该 API 在 Kyverno 1.17 中达到 GA。旧版 ClusterPolicy API(kyverno.io/v1)已在 Kyverno 1.17(2026 年 1 月)中弃用,并计划在 1.20(2026 年 10 月)中移除;请勿将其用于新策略。
matchConditions CEL 表达式在变更前检查 seccompProfile 是否不存在,因此已声明配置文件的 Pod 保持不变。
# 安装 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
验证新 Pod 是否收到注入的配置文件:
kubectl run test --image=busybox --restart=Never -- sleep 30
kubectl get pod test -o jsonpath='{.spec.securityContext.seccompProfile}'
# Expected: {"localhostProfile":"block-af-alg.json","type":"Localhost"}
kubectl delete pod test
参见 templates/kyverno-mutate-seccomp.yaml。
Gatekeeper 的 Assign 变更 CRD 使用 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}'
# Expected: {"localhostProfile":"block-af-alg.json","type":"Localhost"}
kubectl delete pod test
参见 templates/gatekeeper-assign-seccomp.yaml。
Gatekeeper 与 Kyverno:
Assign方法在字段级别操作,需要启用 Gatekeeper 变更 Webhook。Kyverno 的MutatingPolicy配合 CELmatchCondition可内联处理条件。两者达到相同的结果——优先选择集群中已部署的方案。
具有 hostPID: true、hostNetwork: true 或 securityContext.privileged: true 的 Pod 具有 seccomp 单独无法完全控制的提升访问权限。请单独审计这些工作负载,并尽可能移除特权。
ConfigMap 作为事实来源。 配置文件 JSON 位于 configmap-seccomp-profile.yaml 中,而不是嵌入 DaemonSet 或在多个文件中重复。DaemonSet 挂载它并将其复制到节点。更新配置文件意味着编辑一个 ConfigMap 并重启 DaemonSet Pod——无需更改其他文件。
DaemonSet 使用 system-node-critical 优先级。 这确保分发 Pod 不会在其保护的工作负载被调度之前被驱逐,否则会导致节点缺少配置文件,Pod 卡在 CreateContainerError 状态。
Gatekeeper 排除 kube-system 和 gatekeeper-system。 将 Localhost 配置文件注入可能早于 DaemonSet 安装的系统 Pod,如果文件尚未存在于节点上,则存在配置文件引用损坏的风险。Kyverno 策略不需要此排除,因为 Kyverno 更优雅地处理 Webhook 排序,但如有需要,也可以在那里添加 exclude 规则。
条件注入,而非覆盖。 Kyverno MutatingPolicy 的 CEL matchCondition 和 Gatekeeper 的 MustNotExist 路径测试都意味着准入策略仅在 Pod 没有现有 seccompProfile 时生效。已声明自己配置文件的工作负载——包括那些通过自定义允许列表合法需要 AF_ALG 的工作负载——保持不变。
https://gist.github.com/devstuff/3ff3ca0139e2a2da7f1ee5875802f76f
验证阻止是否生效,在容器内运行探测,无论上述步骤是否进行了任何更改。