针对 CVE-2026-31431(“Copy Fail”)及 Dirty Frag 的 RxRPC 变体的 BPF-LSM 缓解方案,同时适用于依赖用户态访问内核侧就地加密路径(可通过 AF_ALG 或 AF_RXRPC 套接字族访问)的类似权限提升漏洞。
一个小型 DaemonSet 在每个节点上将一个 BPF-LSM 程序附加到 socket_create 钩子。该程序对任何用户态 socket(AF_ALG, ...) 或 socket(AF_RXRPC, ...) 调用返回 -EPERM,无论进程能力、命名空间或 seccomp 配置文件如何。内核内部的 sock_create_kern() 调用者(例如 fs/afs、IPsec 协议栈)被放行,因此合法的内核内用户可继续正常工作。
已在 Talos Linux 上测试(自 v1.10 起默认启用 CONFIG_BPF_LSM=y 并在默认 LSM 栈中包含 bpf),适用于任何具有相同内核配置的发行版。
Copy Fail(CVE-2026-31431)是 algif_aead 中的一个逻辑缺陷,允许非特权本地用户对任意 setuid 二进制文件执行 4 字节页缓存写入,仅需一个 732 字节的 Python 脚本即可获得 root 权限。该漏洞利用仅需 AF_ALG + splice(),两者默认情况下均可从任何非特权进程访问。主线修复为 a664bf3d603d。
Dirty Frag 是同一研究团队于 2026 年 5 月披露的后续漏洞类别。它串联了两个“污染” sk_buff 中 frag 成员的漏洞——xfrm-ESP Page-Cache Write 和 RxRPC Page-Cache Write。RxRPC 变体在 rxkad_verify_packet_1() 中对 splice() 固定的页缓存页面执行就地 pcbc(fcrypt) 解密,无需创建用户命名空间即可获得 root 权限,这使其成为加固发行版上该漏洞链中更普遍可利用的一半。xfrm-ESP 修复已作为 f4c50a4034e6(2026-05-07)合入 netdev;截至撰写本文时,各发行版仍在向后移植,而 RxRPC 尚无公开修复——披露时间线请参阅上游文档。
这两个漏洞利用都依赖于在受影响套接字族中打开套接字。在内核修复到达您的发行版之前,可以通过阻止用户态创建 AF_ALG 或 AF_RXRPC 套接字来消除攻击面。与替代方案相比:
本项目是免重启方案。在集群范围内运行它,然后按正常补丁节奏规划永久内核修复。
关于 Dirty Frag 的 ESP 变体的说明。 Dirty Frag 的
xfrm-ESP Page-Cache Write一半并未被此 DaemonSet 关闭——它通过 XFRM netlink +UDP_ENCAP_ESPINUDP触发,而非通过专用套接字族,针对它的干净 BPF-LSM 过滤器要么会破坏主机上的合法 IPsec,要么需要感知用户命名空间的逻辑。目前未在此跟踪——欢迎贡献。在阻止非特权用户命名空间的加固发行版上(例如 Ubuntu 的默认 AppArmor 策略),ESP 变体本身就无法触达,此处的 RxRPC 阻止已足够。
bpf/blocker.c 是一个简短的 BPF-LSM 程序:
SEC("lsm/socket_create")
int BPF_PROG(block_socket_family, int family, int type, int protocol,
int kern, int ret)
{
if (ret)
return ret;
/* kern != 0 表示 sock_create_kern() — 放行内核内调用者。 */
if (!kern && (family == AF_ALG || family == AF_RXRPC)) // 38, 33
return -EPERM;
return 0;
}
Go 加载器(main.go,约 40 行)加载程序并通过 bpf(BPF_LINK_CREATE) 附加。该链接在 Pod 生命周期内保持。收到 SIGTERM 时,链接关闭,钩子分离。
需要内核以 CONFIG_BPF_LSM=y 构建,且活动 LSM 栈中包含 bpf(内核命令行中的 lsm=...,bpf)。Talos Linux 自 v1.10 起默认启用两者。
kubectl apply -f https://raw.githubusercontent.com/cozystack/copy-fail-blocker/v0.3.0/manifests/copy-fail-blocker.yaml
对于 main 分支的最新提交(可能包含未发布更改):
kubectl apply -f https://raw.githubusercontent.com/cozystack/copy-fail-blocker/main/manifests/copy-fail-blocker.yaml
该 chart 未发布为 OCI 制品(注册表路径与容器镜像共享)。从带标签的检出安装:
git clone --branch v0.3.0 https://github.com/cozystack/copy-fail-blocker
cd copy-fail-blocker
helm upgrade --install copy-fail-blocker charts/copy-fail-blocker \
--namespace kube-system
或通过 Makefile 快捷方式:
make apply # helm upgrade --install 到 kube-system
make diff # 预览对集群的更改
make delete # 卸载
make manifest # 重新生成 manifests/copy-fail-blocker.yaml
DaemonSet 必须以特权模式运行(它加载 BPF 程序并写入 bpffs)。将其放置在具有特权 Pod 安全标准的命名空间中,或放在默认特权的 kube-system 中。
从被覆盖节点上的任意 Pod 执行:
python3 -c '
import errno, socket
# 为每个族传入族特定 create() 实际支持的类型(AF_ALG → SOCK_SEQPACKET,AF_RXRPC → SOCK_DGRAM),
# 以便在没有此钩子的节点上,调用要么成功(FAIL:套接字已创建),要么以非 EPERM 的 errno 失败——
# 两者在下方均显示为 FAIL。
# 钩子激活时,security_socket_create() 在 pf->create() 运行之前返回 -EPERM,因此类型无关紧要;
# 我们仍传入正确的类型以保持 FAIL 诊断无歧义。
for name, family, stype in [("AF_ALG", 38, socket.SOCK_SEQPACKET),
("AF_RXRPC", 33, socket.SOCK_DGRAM)]:
try:
socket.socket(family, stype, 0)
print(f"FAIL: {name} socket created")
except OSError as e:
if e.errno == errno.EPERM:
print(f"OK ({name}): blocked with EPERM")
else:
print(f"FAIL: {name} got {e.errno} ({e.strerror}), expected EPERM")'
预期输出:
OK (AF_ALG): blocked with EPERM
OK (AF_RXRPC): blocked with EPERM
任何其他 errno(例如 ESOCKTNOSUPPORT 94、EAFNOSUPPORT 97)意味着该节点上钩子未激活——在假设您已受保护之前请先调查。
make image # docker buildx build + push
make image REGISTRY=ghcr.io/myorg TAG=v0.3.0 # 自定义标签
make image PUSH=0 LOAD=1 # 本地构建而不推送
make image 使用解析后的镜像摘要更新 charts/copy-fail-blocker/values.yaml,以便 chart 始终按摘要固定版本。
构建依赖项位于 Containerfile 中(clang、libbpf-dev、Go)。本地主机仅需 docker buildx、helm、yq(mikefarah)、kubectl 和 helm-diff。
charts/copy-fail-blocker/values.yaml:
AF_ALG 和 AF_RXRPC 再次可访问。对大多数威胁模型而言这是可接受的;如果不是,请考虑将 BPF 链接固定到 bpffs(目前未实现——欢迎贡献)。CAP_BPF 和 CAP_SYS_ADMIN 的人都可以分离该钩子。这不能替代集群范围的权限限制。algif_skcipher / algif_hash 等。 该程序拒绝整个 AF_ALG 族,但目前已知可利用的只有 algif_aead。如果未来的 CVE 需要更精细的过滤器(例如钩住 bind() 并检查 salg_type),添加起来很简单。AF_ALG 或 AF_RXRPC 套接字的进程无效。 现有套接字在关闭前继续工作。UDP_ENCAP_ESPINUDP 到达,而非通过专用套接字族——目前未跟踪,欢迎贡献。参见中的说明。Apache License 2.0 — 参见 LICENSE。
| 缓解措施 | 覆盖范围 | 需重启? | 持久? |
|---|
内核命令行 module_blacklist=af_alg,rxrpc(族处理程序,而不仅仅是 algif_aead) | 主机级 | 是 | 是 |
/etc/modprobe.d/*.conf 配合 install af_alg /bin/false + install rxrpc /bin/false 并对已加载模块执行 rmmod(与上游 Dirty Frag 指南一致——请注意,普通的 blacklist 并不能阻止内核内 request_module() 自动加载,只有 install … /bin/false 可以) | 主机级 | 否 | 是(文件存在期间) |
不含 CRYPTO_USER_API / AF_RXRPC 的自定义内核 | 主机级 | 是 | 是 |
| 每 Pod 自定义 seccomp 配置文件 | 仅限标记的工作负载 | 否 | 是 |
| copy-fail-blocker(本项目) | 主机级用户态 | 否 | DS 运行期间 |
| 键 | 默认值 | 说明 |
|---|
image.repository | ghcr.io/cozystack/copy-fail-blocker | 由 make image 自动更新 |
image.tag | vX.Y.Z@sha256:... | 按摘要固定,当前值见 values.yaml |
priorityClassName | system-node-critical | 确保守护进程在驱逐中存活 |
tolerations | [{operator: Exists}] | 在每个节点上运行,包括带污点的节点 |
resources.requests | 5m CPU / 16Mi memory | 附加后的空闲占用 |
AF_RXRPC 客户端被阻止。 RxRPC 是 AFS 网络协议。!kern 守卫意味着树内 fs/afs(kAFS)模块继续正常工作,因为它通过 sock_create_kern() 打开套接字。直接通过 socket(2) 打开 AF_RXRPC 套接字的用户态 AFS 工具(例如 OpenAFS 用户态守护进程)将被拒绝——请勿在运行此类工具的节点上部署此 DaemonSet。