一句话定调 :这是近几年 Linux 内核最干净利落、最让人后背发凉的本地提权漏洞之一------732 字节纯 Python 标准库脚本,无需编译,无需竞态,跨所有主流发行版,确定性拿到 root。
1. 基本档案卡
| 项目 | 内容 |
|---|---|
| CVE | CVE-2026-31431 |
| 代号 | Copy Fail |
| 发现者 | Taeyang Lee(Theori / Xint Code),2026 年 3 月 23 日报送,4 月 29 日公开披露 |
| CVSS | **7.8(High)** --- AV:L / AC:L / PR:L / UI:N |
| 漏洞类型 | 本地权限提升(LPE)/ 容器逃逸原语 |
| 触发前提 | 你只需要一个非特权本地用户 shell(或容器内代码执行) |
| **远程?** | ❌ 不远程直接利用,但凡是有"先有个低权限执行点"的场景------Web RCE、SSH 弱口令、CI Runner、恶意包、容器 foothold------立刻变成 root |
| PoC 状态 | ✅ 已公开,约 732 字节 Python 脚本,无需第三方库 |
| 修复提交 | 主线 a664bf3d603d(2026 年 4 月 1 日合入),回退了 2017 年的 in-place 优化 |
2. 漏洞到底藏在哪?------三个"各自合理"的改动,凑出了灾难
Copy Fail 最精妙(也最可怕)的地方在于:它不是一段明显傻掉的代码,而是三个来自不同时期、各自看似合理的设计决策,在特定交汇点产生的逻辑缺陷。
🧩 三块拼图
| # | 组件 | 引入时间 | 单独看没问题,但...... |
|---|---|---|---|
| ① | authencesn (内核 crypto 的 IPsec ESN 认证模板)在解密时为处理认证数据的序列号字节,会向输出缓冲区偏移 assoclen + cryptlen处写入 4 字节临时数据 (相当于一个"scratch 写"),且不恢复原值 |
2011 年 | 它假设输出缓冲是自己专属的,不应该被别人共享 |
| ② | AF_ALG 接口获得 splice()支持 (commit 突约 2015),允许零拷贝地把文件数据喂入内核 crypto 管线------数据不经过用户态拷贝,直接以 **page cache 的 struct page*** 引用链入 scatterlist |
2015 年 | splice()的核心承诺就是"零拷贝传引用",但这里传的是可读文件(如 /usr/bin/su)的页缓存页 |
| ③ | algif_aead的 in-place 优化 (commit 72548b093ee3,2017 年 8 月 )把源散列表和目标散列表合并/复用为同一链,省掉一次内存拷贝 |
2017 年 | 这意味着:从 splice 来的 page cache 页 被放进了可写输出的 scatterlist 中 |
致命交汇 = ② 把 su 的 page cache 页以引用方式喂入 → ③ 让它进可写 scatterlist → ① 对着它做了 4 字节越界 scratch 写
结果:无特权用户精准地向 /usr/bin/su的 in-memory 页缓存写了 4 个自己控制的字节------而磁盘上的文件纹丝不动。
🔑 为什么是"恰好 4 字节"就够了?
你可能会想:"才 4 个字节能干什么?"
答案是------足够把 setuid(0)的机器码 shellcode(或其等效跳转/gadget)写进 su的内存映像的关键偏移,或者更常见的利用手法:
-
通过多次迭代(约 ~40 次 splice→触发→写入循环),逐步逐页覆写目标 setuid 二进制的关键字节
-
最终让
execve("/usr/bin/su")时内核映射的已被污染的页缓存执行你注入的代码路径 -
由于页缓存在宿主机与所有容器间共享 ,容器内也能污染宿主机的
su页缓存 → 容器逃逸
3. 攻击链全景图(概念级)
攻击者(普通用户 alice)
│
├─ 1. 打开 /usr/bin/su(只读 fd)← 这是 setuid(root) 二进制
│
├─ 2. 创建 AF_ALG 套接字(socket(AF_ALG, ...))
│ 绑定 aead / authencesn 算法
│
├─ 3. 用 pipe + splice(),把 su 的 fd 的页缓存页
│ 零拷贝喂入 AF_ALG 的 scatterlist
│ ── 此时 su 的 page cache 页被链入"可写"输出散列表
│
├─ 4. 触发 authencesn 解密路径
│ → 4 字节受控数据写入 su 的 page cache 页
│
├─ 5. 重复若干轮,完成 payload 植入
│
└─ 6. execve("/usr/bin/su") → 内核加载被污染的页缓存页
→ 以 root 身份执行注入代码 → 🎉 get root shell
关键属性:
-
确定性:不靠 race condition,每次都能打中
-
隐蔽 :只改内存页缓存,不改磁盘 →
sha256sum /usr/bin/su看着完全正常 -
便携:纯 Python 标准库,732 字节,同脚本通吃 Ubuntu / RHEL / SUSE / Amazon Linux / Arch......
-
容器可见 :页缓存跨容器共享 → 容器内打宿主机
su
4. 影响范围------"几乎一切"
| 维度 | 详情 |
|---|---|
| 内核 commit 区间 | 引入:72548b093ee3(2017 年 8 月)→ 修复:a664bf3d603d(2026 年 4 月 1 日合入) |
| 不受影响的版本 | 主线 ≥ 7.0 / 稳定 ≥ 6.18.22 / 6.19.12+;Ubuntu **26.04 LTS(Resolute)** 不受影响;RHEL 6/7 因内核基线不同不受影响 |
| 仍在影响的长线 | 6.12 所有 、6.6 所有 、6.1 所有 、5.15 所有 、5.10 所有------意味着大量 LTS / 云定制内核在 2026 年 4-5 月仍裸奔 |
| 发行版 | Ubuntu 18.04/20.04/22.04/24.04、RHEL 8/9/10、Debian 10/11/12、Amazon Linux 2/2023、SUSE 12~16、Rocky/Alma/Oracle/openEuler......几乎全覆盖 |
| 高危场景 | 多租户共享主机、CI/CD Runner(GitHub Actions / GitLab Runner 沙boxes)、容器宿主机/K8s Node、Serverless 执行环境 |
5. 怎么排查?(两分钟内知道你中没中)
Step 1 --- 内核版本
bash
uname -r
uname -a
对照原则:如果你的内核 git log 落在 72548b093ee3之后、a664bf3d603d之前 → 理论上有风险。快速经验法则:
-
内核版本号在
4.14以上且未确认包含修复提交 → 假定受影响,继续查 -
已升级到发行版标注"修复"的 kernel pkg → 相对安全
Step 2 --- 模块是否加载 / 可加载
bash
# 是否已加载
lsmod | grep algif
# 或者看有没有 AF_ALG 活跃 fd
lsof 2>/dev/null | grep -i AF_ALG
# 看你能否打开 AF_ALG(普通用户就能试)
python3 -c "
import socket
try:
s = socket.socket(38, socket.SOCK_SEQPACKET, 0) # AF_ALG=38
print('AF_ALG 可用 → 潜在触发路径存在')
except Exception as e:
print('AF_ALG 不可用:', e)
"
如果
algif_aead压根没加载 + AF_ALG socket 创建被 seccomp 拦了 → 攻击面显著收窄(但仍建议按修复方案处理)
6. 修复 & 临时缓解(优先级:升级 > 缓解 > 忽视)
✅ 首选:升级内核(需重启)
bash
# Ubuntu / Debian
apt update && apt upgrade linux-image-$(uname -r)
# 或直接升级所有内核相关
apt full-upgrade
# RHEL / Rocky / Alma
dnf update kernel
reboot
# Amazon Linux
yum update kernel
reboot
⚠️ 截至 2026 年 4 月 30 日左右,Debian 和 Ubuntu 多数在支版本尚未推完修复包 ,SUSE 也有部分版本待更------所以如果
apt/dnf告诉你 kernel 已经是最新但uname -r还没跳进安全区间,你需要用下面的临时缓解先顶住。
🛡️ 临时缓解方案 A:禁用 algif_aead模块(推荐)
bash
# 阻止未来加载
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif-aead.conf
# 尝试卸载(如果已加载)
rmmod algif_aead 2>/dev/null || echo "(algif_aead 未加载或 builtin,跳过)"
# 清页缓存(把可能被篡改的脏缓存刷掉------虽然 Copy Fail 的写入不标脏,但稳妥起见)
sync && echo 3 > /proc/sys/vm/drop_caches
注意:某些发行版(如 Rocky 8)把
algif_aead编进了内核本体(builtin),不能用rmmod卸------此时需要用方案 B。
🛡️ 临时缓解方案 B:Grub 内核参数(builtin 场景)
bash
# 编辑 /etc/default/grub
# 在 GRUB_CMDLINE_LINUX 里追加:
# initcall_blacklist=algif_aead_init
# 然后重新生成 grub cfg
grub2-mkconfig -o /boot/grub2/grub.cfg # 或 /boot/efi/EFI/... 视引导方式而定
reboot
🛡️ 容器环境:Seccomp 封 AF_ALG
不管宿主机修没修,**给容器加 Seccomp Profile 直接拦 socket(AF_ALG, ...)** 是最有效的额外防线:
// 在你的 seccomp profile 的 "syscalls" 里加:
{
"names": ["socket"],
"action": "SCMP_ACT_ERRNO",
"args": [
{ "index": 0, "value": 38, "op": "SCMP_CMP_EQ" }
]
}
Docker 默认 profile 没有拦 AF_ALG ------ 这就是很多人在评估时踩坑的点。
7. 检测思路(蓝队 / SOC 角度)
| 层次 | 做法 |
|---|---|
| AuditD | 监控非 root 用户创建 address family 38(AF_ALG)socket:auditctl -a always,exit -F arch=b64 -S socket -F a0=38 -F uid!=0 -k af_alg_exploit |
| 行为异常 | 普通用户短时间内反复 socket(AF_ALG)+ splice()是极强信号------正常业务几乎不可能这么做 |
| 后验猎疑 | 找 ruid≠0 但进程 uid=0 的异常 shell、/tmp 或 /dev/shm 中新出现的可执行、非预期 ssh key 写入 |
| 文件完整性 | ⚠️ Copy Fail 不改磁盘 → 传统 HIDS 文件哈希检查看不到 → 必须用行为检测补位 |
8. 为什么这个漏洞值得你在意(不只是又一个 CVE)
Copy Fail 之所以在 2026 年 4 月底炸出这么大的回响,是因为它击穿了一条所有人默认可信的安全假设:
"我只是读一个 setuid 文件(只读 fd),就算把它 splice 到一个内核接口去'处理',也不应该让它的内容在内存里被改写。"
而 Linux 的页缓存共享模型 + 零拷贝的"高效"哲学 + 加密子系统的 in-place 优化------三个优秀工程的叠加------恰好让这条假设崩塌了。
这也是为什么它被拿来和历史上的 Dirty COW(CVE-2016-5195) 类比,但更恶劣 :Dirty COW 还要抢竞态窗口、可能 crash;Copy Fail 是直线逻辑路径,稳定、静默、可脚本化。
⚠️ 最后再强调一遍
本文对 CVE-2026-31431 的分析仅供安全研究、防御加固、授权渗透测试场景使用 。如果你在自己的服务器上复现------请确保那是你自己的机器或你有书面授权。这个东西的利用门槛实在太低了(一个 Python 脚本、一个普通账号、几秒出 root),拿它碰不属于你的系统 = 刑事风险。