一个概念验证,演示完全无特权容器如何通过共享容器镜像层利用 CVE-2026-31431 Linux 内核页缓存损坏漏洞,在 Kubernetes 上实现节点级代码执行。
免责声明: 本仓库仅用于教育和防御目的。请仅在您拥有或已获得明确授权测试的系统上使用。
CVE-2026-31431("Copy Fail")是 Linux 内核页缓存写时复制(CoW)路径中的一个漏洞。AF_ALG splice 竞态条件允许无特权进程损坏只读文件的页缓存页面。该损坏会持续存在于内核页缓存中,并对随后读取或执行该文件的每个进程可见——包括其他容器或宿主机上的进程。
有关原始漏洞的完整详情,请参阅 copy.fail。
攻击链分为三个阶段:页缓存损坏、跨容器传播和特权执行。
内核的 AF_ALG(加密)子系统为用户空间加密操作提供了基于套接字的接口。该漏洞利用内核处理从文件到 AF_ALG 套接字的 splice() 时的竞态条件:
/usr/sbin/ipset)。authencesn(hmac(sha256),cbc(aes)) 的 AF_ALG AEAD 套接字。MSG_MORE 通过 AF_ALG 套接字发送一小块载荷,告知内核预期更多数据。splice() 目标文件的内容。该漏洞对每个 4 字节窗口重复此过程,直到目标二进制文件的整个缓存页面被自定义载荷覆盖。
无需对文件具有写权限。磁盘上的文件保持不变——只有内存中的页缓存被损坏。
容器运行时(containerd、CRI-O)使用 overlay 文件系统。当两个容器共享同一镜像层时,内核从相同的页缓存页面提供它们的文件读取。
此 PoC 镜像基于 FROM registry.k8s.io/kube-proxy:v1.35.2 构建。每个 Kubernetes 节点上的 kube-proxy DaemonSet 使用完全相同的基础层。因此,两个容器中的 /usr/sbin/ipset 映射到相同的页缓存页面集合。
当无特权的 PoC 容器损坏 ipset 的页缓存时,该损坏会立即对同一节点上特权的 kube-proxy 容器可见——无需任何跨容器通信。
kube-proxy 以特权 DaemonSet 运行,并带有 hostNetwork: true。它定期调用 /usr/sbin/ipset 来管理 iptables/ipset 规则。当它下次执行 ipset 时,内核会加载损坏的页缓存页面,以 kube-proxy 的完整权限执行攻击者的载荷:
此 PoC 中的载荷(payload/payload.c)仅挂载主机根文件系统并写入标记文件到 /root/res,作为节点级代码执行的证明。
┌──────────────────────────┐ ┌──────────────────────────┐
│ PoC 容器 │ │ kube-proxy 容器 │
│ (无特权) │ │ (特权) │
│ │ │ │
│ 1. 打开 /usr/sbin/ipset │ │ │
│ (只读) │ │ │
│ │ │ │
│ 2. AF_ALG splice 竞态 │ │ │
│ 损坏页缓存 │ │ │
│ │ │ │ │
└──────────┼───────────────┘ └──────────────────────────┘
│ │
▼ │
┌─────────────────────┐ │
│ 内核页缓存 │ │
│ /usr/sbin/ipset │◄────────────────────┘
│ (已损坏) │ 3. kube-proxy 执行 ipset
│ 包含攻击者的 │ → 加载损坏的页面
│ 载荷字节 │ → 载荷以 root 权限
└─────────────────────┘ 在宿主机上运行
.
├── cmd/copyfail/main.go # 入口点;嵌入编译后的载荷
├── internal/
│ ├── exploit/
│ │ ├── exploit.go # 核心漏洞利用:AF_ALG splice 竞态循环
│ │ └── patch.go # 将载荷拆分为 4 字节补丁窗口
│ └── alg/
│ └── alg.go # AF_ALG AEAD 套接字抽象
├── payload/
│ ├── payload.c # 验证载荷(挂载主机文件系统,写入标记)
│ └── nolibc/ # 内核的微型 libc,用于静态、无依赖的载荷
├── deploy/
│ └── poc.yaml # Kubernetes Deployment 清单
├── Dockerfile # 基于 kube-proxy 构建以共享镜像层
├── Makefile # 构建编排
└── docs/ # 来自 ACK(阿里云)的验证证据
x86_64-linux-gnu-gcc)imagePullPolicy: IfNotPresent(默认值)的 Kubernetes 集群# 构建载荷 + Go 二进制文件
make build
# 构建 Docker 镜像
make docker-build
# 构建并推送到 GHCR
make docker-push IMAGE=ghcr.io/<you>/copy-fail-poc TAG=latest
对于 arm64 目标:
make build CC=aarch64-linux-gnu-gcc GOARCH=arm64
kubectl apply -f deploy/poc.yaml
该 Deployment 创建一个无特权的 pod。它会:
/bin/copyfail -target /usr/sbin/ipset 来损坏页缓存。在 kube-proxy 下次执行 ipset 后(由于其协调循环,这通常会在几秒内发生,或在其下次重启时),检查节点:
# SSH 进入节点,或使用特权调试 pod
cat /root/res
# 预期输出:[*] success
宿主机文件系统上存在 /root/res 证明攻击者提供的代码以节点级权限执行——从 kube-proxy 特权容器上下文内部写入。
kubectl delete -f deploy/poc.yaml
# 在受影响的节点上,移除标记并重启 kube-proxy:
rm -f /root/res
systemctl restart kubelet # 或删除 kube-proxy pod 以强制重新拉取
kube-proxy 是理想的目标,因为它:
privileged: true,hostNetwork: true。imagePullPolicy: IfNotPresent — 一旦攻击者的镜像被拉取并共享相同的基础层,overlay lower-dir 页面就会被共享。任何镜像中包含可预测二进制文件的特权 DaemonSet 都可能以相同方式被攻击。
默认载荷(payload/payload.c)是一个仅用于验证的程序,用于写入标记文件。要构建自定义载荷:
payload/payload.c。该程序基于 nolibc(内核的最小 C 库)构建,生成静态、无依赖的二进制文件。make payload 进行交叉编译。//go:embed 嵌入到 Go 二进制文件中。tools/include/nolibc/)本仓库中的 Go 漏洞利用代码按原样提供,仅供研究目的使用。
载荷(payload/payload.c)衍生自 copy-fail-c,采用 LGPL-2.1-or-later OR MIT 双重许可。请参阅 LICENSE-LGPL 和 LICENSE-MIT。