CVE-2026-31431(Copy Fail)是 Linux 内核 algif_aead 模块(AF_ALG 子系统)中的一个本地权限提升漏洞。2017 年 8 月引入的一个逻辑缺陷允许任何无特权的本地用户通过 AF_ALG + splice() 向任意可读文件的页缓存中写入 4 个受控字节,然后执行被篡改的 setuid 二进制文件以获得 root 权限。该漏洞利用是确定性的——无需竞态条件、无需内核偏移、不会导致系统崩溃。它影响所有搭载 4.14 至 7.0-rc 内核的主流 Linux 发行版。
CVSS:7.8 | 修复版本:内核 7.0、6.19.12、6.18.22 | 主线修复:commit a664bf3d603d
来源:copy.fail、The Hacker News、CloudLinux 公告
crates/
├── exp/ # 漏洞利用 PoC(Rust 重新实现)
├── copy_fail_guard/ # 用户空间 eBPF 加载器 — LSM 模式
├── copy_fail_guard-ebpf/ # eBPF LSM 程序(不在工作区中)
├── copy_fail_guard_kprobe/ # 用户空间 eBPF 加载器 — kprobe 模式
└── copy_fail_guard_kprobe-ebpf/ # eBPF kprobe 程序(不在工作区中)
scripts/
└── build_guard.sh # 一键构建 → 输出到 dist/
根本原因是三个独立的内核特性以不安全的方式交互:
AF_ALG 套接字 — 将内核加密 API 暴露给无特权的用户空间splice() — 以页缓存引用(而非副本)的形式将文件数据零拷贝传输到加密散列列表(scatterlist)中authencesn AEAD 模板 — 将调用者的输出缓冲区用作临时空间,在 dst[assoclen + cryptlen] 处写入 4 个字节2017 年,algif_aead.c(72548b093ee3)中的一项就地优化使 req->src == req->dst,从而将页缓存页面链接到可写的目标散列列表中。当 authencesn 写入其临时字节时,它会越过输出缓冲区进入链接的页缓存页面。攻击者可以控制:
被篡改的页面永远不会被标记为脏——磁盘上的文件不受影响,但 execve() 会从页缓存中读取。篡改 setuid 二进制文件 → 获得 root 权限。
公开的 732 字节 Python PoC 的 Rust 重新实现。目标是 /usr/bin/su,将其页缓存页面拼接(splice)到 AF_ALG AEAD 套接字中,并用压缩的 shell 载荷覆盖它们。
需要 Rust 1.85+(edition 2024)。
cargo build --release -p copy_fail
该二进制文件仅适用于 Linux。在其他平台上,它会以 Unsupported 错误退出。
警告:这会利用真实的内核缺陷。仅可在您拥有并控制的系统上运行,最好是可丢弃的虚拟机。
启动一台运行易受攻击内核的虚拟机(任何搭载内核 < 7.0 / < 6.19.12 / < 6.18.22 的主流发行版)。
将构建好的二进制文件复制到虚拟机中,并以非 root 用户身份运行:
./target/release/copy_fail
易受攻击:出现 root shell(#)。运行 whoami 确认是 root。
不易受攻击(已修补内核):AF_ALG 操作失败或 su 二进制文件行为正常。您会看到错误或正常的 su 密码提示。
一种运行时内核防御机制,无需升级内核或重启即可阻止 CVE-2026-31431。它使用 eBPF 拦截 AF_ALG 套接字的创建,从而切断漏洞利用的第一步。
提供两种模式。一键加载器会自动选择最佳可用模式:
┌──────────────────────────────────────────────────┐
│ run_guard.sh │
│ • 检测 BPF LSM 支持 │
│ • LSM 可用 → copy_fail_guard (EPERM) │
│ • LSM 不可用 → copy_fail_guard_kprobe (KILL) │
└──────────────┬───────────────────────────────────┘
│
┌──────────────▼───────────────────────────────────┐
│ eBPF 程序 │
│ 如果套接字族 == 38 (AF_ALG) │
│ → 阻止 (EPERM 或 SIGKILL) │
│ 否则 │
│ → 允许 │
└──────────────────────────────────────────────────┘
阻止 AF_ALG 对典型系统几乎没有影响:
AF_ALGAF_ALG 的应用程序(例如启用了 afalg 引擎的 OpenSSL、某些嵌入式加密卸载路径)socket(AF_ALG, ...) 的操作,零开销一键构建(推荐):
./scripts/build_guard.sh
这会自动安装缺失的工具链(nightly、bpf-linker),编译所有 eBPF 程序和用户空间加载器,并将所有内容输出到 dist/:
dist/
├── copy_fail_guard.bpf.o # eBPF LSM 程序
├── copy_fail_guard_kprobe.bpf.o # eBPF kprobe 程序
├── copy_fail_guard # 用户空间加载器(LSM)
├── copy_fail_guard_kprobe # 用户空间加载器(kprobe)
└── run_guard.sh # 自动选择:sudo ./run_guard.sh
将 dist/ 目录复制到任何目标机器,运行 sudo ./run_guard.sh 即可激活防护。
手动构建(逐步):
步骤 1:编译 eBPF 程序(必须在 Linux 上完成):
# LSM 变体
cd crates/copy_fail_guard-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
# kprobe 变体
cd crates/copy_fail_guard_kprobe-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
步骤 2:构建用户空间加载器:
cargo build --release -p copy_fail_guard -p copy_fail_guard_kprobe
推荐 — 自动选择最佳模式:
sudo ./dist/run_guard.sh
手动 — 运行特定模式:
# LSM 模式(需要 lsm=bpf)
sudo GUARD_BPF_OBJ=path/to/copy_fail_guard.bpf.o RUST_LOG=info ./copy_fail_guard
# kprobe 模式(随处可用)
sudo GUARD_BPF_OBJ=path/to/copy_fail_guard_kprobe.bpf.o RUST_LOG=info ./copy_fail_guard_kprobe
按 Ctrl-C 可分离 eBPF 程序并恢复正常行为。
在一个终端中运行防护程序:
# 在另一个终端中,尝试漏洞利用:
./target/release/copy_fail
# LSM 模式: "error: Operation not permitted"
# kprobe 模式:"已杀死" / "Killed"
# 或直接用 Python 测试:
python3 -c "import socket; socket.socket(38, 5, 0)"
# LSM 模式: PermissionError: [Errno 1] Operation not permitted
# kprobe 模式:Killed
AF_ALG 套接字创建。没有机制可以通过 PID、cgroup 或命令名豁免特定进程。这计划在未来的版本中实现(通过 eBPF HashMap 映射)。对于绝大多数系统来说这没有问题,因为几乎没有任何东西使用 AF_ALG。bpf-linker 单独编译(它们以 bpfel-unknown-none 为目标,不能作为普通的工作区成员)。大多数发行版默认不启用 BPF LSM。如果您想要更干净的 LSM 模式(EPERM 而非 SIGKILL):
# 检查当前的 LSM:
cat /sys/kernel/security/lsm
# 如果缺少 "bpf",请添加:
sudo sed -i 's/^GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="\1 lsm=lockdown,capability,yama,apparmor,bpf"/' /etc/default/grub
sudo update-grub && sudo reboot
kprobe 模式无需此步骤即可工作。
将内核修补到 ≥ 7.0 / ≥ 6.19.12 / ≥ 6.18.22 之后:
# 确认内核版本
uname -r
# 重新运行漏洞利用——不应再产生 root shell
./target/release/copy_fail
或者,确认易受攻击的模块已被中和:
modinfo algif_aead | grep filename
# 如果是内置的,则屏蔽 initcall(需要重启)
sudo grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init"
sudo reboot
| LSM 模式 | kprobe 模式 |
|---|
| 阻止方式 | 返回 -EPERM(拒绝套接字创建) | SIGKILL(终止进程) |
| 内核要求 | ≥ 5.7 且带有 lsm=bpf 启动参数 | ≥ 5.3,无需特殊参数 |
| 需要重启才能启用 | 可能需要(如果尚未设置 lsm=bpf) | 不需要 |
| 挂钩点 | socket_create LSM 挂钩 | __sys_socket kprobe |