Linux 又双叒叕出提权漏洞:Dirty Frag
是的,Copy Fail 的热度还没散,Linux 内核又出事了。
五一才刚过好吧,Copy Fail 的讨论还在洛谷文章区里刷屏,Linux 内核又出漏洞了……
不行,这次我一定要抢先发!不能像 Copy Fail 一样被别人抢先!
展示仓库:GitHub: V4bel/dirtyfrag。
截止今天写稿的时候有 2k star,317 fork。
号称是:Universal Linux LPE(本地提权)。
那我可就来兴趣了啊。
:::warning[注意]{open} 截至 2026-05-08,仅 xfrm 侧的补丁已合入主线(CVE-2026-43284 已分配并修复);RxRPC 侧(CVE-2026-43500)在任何发行版树上均没有补丁。
和 Copy Fail 最不一样的地方就是修复没 Copy Fail 及时……
本文 PoC 引用自官方公开仓库! :::
啥子玩意
Dirty Frag 是 Hyunwoo Kim (@v4bel) 发现的一类新漏洞,通过链式利用 xfrm-ESP Page-Cache Write(CVE-2026-43284)和 RxRPC Page-Cache Write(CVE-2026-43500)两个独立漏洞,在几乎所有主流 Linux 发行版上实现本地 root 提权。
似曾相识的描述,果然和 Copy Fail 是一类的!
这个确实和 Copy Fail 是同一类的。
都是用 splice() 把只读文件的页缓存弄进发送侧 skb 的 frag 槽,然后内核在那个 frag 上做就地运算,顺手就把只读文件的内存写了。修改留存在页缓存里,直到 drop_caches 或重启。
先看一下来龙去脉:
Dirty Pipe(CVE-2022-0847)发现了 splice() 的危险性。页缓存页直接进管道,如果某段代码路径没做写时复制,就能改原文件。
Copy Fail 把同样的思路搬到了 AF_ALG 的 AEAD 解密路径上,algif_aead 为了性能做就地操作,一个 4 字节的临时写就原地炸了被攻破了。
Dirty Frag 把这个又复现了一遍,这次是在非线性 skb 的 frag 上,触发路径换成了 xfrm ESP 解密和 RxRPC 数据包校验,两条路径互相覆盖盲区。
怎么做
xfrm-ESP Page-Cache Write
esp_input() 在做就地 AEAD 解密之前,碰到非线性的 skb 理应调用 skb_cow_data() 先把 frag 数据复制到私有缓冲区里再操作。但有一条路径把这步跳过了:
static int esp_input(struct xfrm_state *x, struct sk_buff *skb)
{
if (!skb_cloned(skb)) {
if (!skb_is_nonlinear(skb)) {
nfrags = 1;
goto skip_cow;
} else if (!skb_has_frag_list(skb)) {
nfrags = skb_shinfo(skb)->nr_frags;
nfrags++;
goto skip_cow; // byebye! 这不对吧……
}
}
err = skb_cow_data(skb, 0, &trailer);
// ...
只要 skb 有 frag 但没有 frag_list,就直接 skip_cow,跳过写时复制,对 frag 里的页做就地解密。如果那个 frag 是攻击者通过 splice 进来的页缓存页,问题就来了。
crypto_authenc_esn_decrypt() 在预处理时要把序列号高位挪到 src SGL 末尾:
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, /*out=*/1);
4 字节写在 assoclen + cryptlen 偏移处。攻击者控制载荷长度,让目标页缓存页 P 恰好落在这个偏移,写就落在文件对应位置上。
写入的值是 seq_hi,也就是 ESP 头里的序列号高 32 位。而 seq_hi 是攻击者注册 XFRM SA 时通过 XFRMA_REPLAY_ESN_VAL.seq_hi 自由填的。
AEAD 认证在 STORE 之后才跑——认证可以失败,STORE 已经发生了,页缓存修改永久留存。也就是说,攻击者对任意文件的任意偏移有精确的 4 字节任意写原语,且完全不需要知道 SA 的认证密钥。
利用目标是 /usr/bin/su(setuid-root)。把它页缓存的前 192 字节全部替换成一个迷你 root shell ELF——入口点 0x400078 直接跑 setgid(0); setuid(0); setgroups(0, NULL); execve("/bin/sh", ...),绕过 PAM,直接出 shell。
192 字节除以每次 4 字节等于 48 次写操作。每次注册一个 XFRM SA(SPI 各不同,seq_hi 填对应的 shellcode 片段),然后触发一次解密。由于注册 XFRM SA 需要 CAP_NET_ADMIN,先用 unshare(CLONE_NEWUSER | CLONE_NEWNET) 开一个用户命名空间,在里面拿到 root 再操作。
每次触发的流程:用 vmsplice 把伪造的 ESP 头(由 SPI、seq_no 和 IV 组成,共 24 字节)写进管道,再用 splice(file_fd, offset = i * 4, pipe) 把 /usr/bin/su 第 splice(pipe → sk_send) 发出去。接收端设置了 UDP_ENCAP_ESPINUDP,收到包后走 xfrm4_udp_encap_rcv → esp_input,触发漏洞,STORE 发生。
48 次之后,父进程 execve("/usr/bin/su") 收到的是被替换过的 ELF,直接出 root shell。
RxRPC Page-Cache Write
rxkad_verify_packet_1() 验证 RXKAD 安全级别的数据包时,对 skb 的前 8 字节做就地 pcbc(fcrypt) 解密:
sg_init_table(sg, ARRAY_SIZE(sg));
ret = skb_to_sgvec(skb, sg, sp->offset, 8);
// ...
skcipher_request_set_crypt(req, sg, sg, 8, iv.x); // src == dst,原地启动!
ret = crypto_skcipher_decrypt(req);
skb_to_sgvec() 把 frag 直接转成 SGL,所以通过 splice 钉进来的页缓存页 P 就成了 src/dst,8 字节 STORE 就这么落在文件上了。
这里和 xfrm 变体有个区别:写入的值不是攻击者直接控制的,而是 fcrypt_decrypt(C, K)——C 是文件里原本的那 8 字节,K 是攻击者通过 add_key("rxrpc", ...) 注册的会话密钥。想写什么,就要在用户态暴力搜 K 使解密结果等于目标。
fcrypt 是一个 56 位密钥的 AFS 专用分组密码,可以移植到用户态。约 18M/s,找一次 K 几毫秒到 1 秒,代价可以接受。
而且这个变体不需要创建用户命名空间,add_key()、socket(AF_RXRPC)、splice() 无特权用户都能用。
又是 Copy Fail 似曾相识的描述。
目标是 /etc/passwd 第 1 行,把密码字段改空:
root:x:0:0:root:/root:/bin/bash → root::0:0:GGGGGG:/root:/bin/bash
PAM 的 pam_unix.so nullok 遇到空密码直接放行。
每次 8 字节,覆盖 chars 4..15 需要三次 last-write-wins:
| splice 位置 | 写入范围 | 目标 |
|---|---|---|
| offset 4 | 4..11 | chars 4..5 = "::" |
| offset 6 | 6..13 | chars 6..7 = "0:" |
| offset 8 | 8..15 | chars 8..9 = "0:", char 15 = ":" |
每次 splice 之后文件内容会变,所以下一次暴搜 K 时用的 C 要用前一次写完之后的内容,不能拿原始文件来算。
对每个偏移,先搜出 K,然后用 add_key 注册,创建 AF_RXRPC 客户端套接字并绑上这个密钥,强制 RXRPC_SECURITY_AUTH 安全级别,用一个假的 UDP “服务端”发 CHALLENGE 让客户端完成握手,再用 vmsplice + splice 把伪造的 DATA 包头和 /etc/passwd 对应页钉进管道发给客户端,客户端 recvmsg 触发 rxkad_verify_packet_1,STORE 落下。
三次之后,su - 无密码直接 root shell。
又是 Copy Fail 的说法。
为什么要两个
xfrm-ESP 覆盖大多数发行版,但需要 unshare(CLONE_NEWUSER)。
据说 Ubuntu 有时会通过 AppArmor 策略把这个权限封掉,此时 xfrm 变体跑不起来。RxRPC 不需要命名空间特权,但 rxrpc.ko 在大多数发行版上默认不带,RHEL、CentOS 都没有,但是又据说 Ubuntu 默认加载它。
大多数发行版允许用户命名空间创建,用 ESP;Ubuntu 封掉了 unshare 但有 rxrpc,用 RxRPC。一个二进制先跑 ESP 变体,检测第一字节 shellcode 有没有写进去,失败就回退到 RxRPC 变体。
这下就能“真·通杀 Linux”了!
xfrm-ESP 漏洞自 2017 年的提交 cac2661c53f3 起就存在,接近 9 年。RxRPC 漏洞自 2023 年 6 月的提交 2dc334f1a63a 起存在。Ubuntu 24.04.4、RHEL 10.1、openSUSE Tumbleweed、CentOS Stream 10、Fedora 44 全部确认可利用。
目前没有任何发行版发布补丁(xfrm 的补丁仅合入主线,还没回移到各发行版)。暂时的缓解方法是卸载相关模块:
sh -c "printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' \
> /etc/modprobe.d/dirtyfrag.conf; \
rmmod esp4 esp6 rxrpc 2>/dev/null; \
echo 3 > /proc/sys/vm/drop_caches; true"
:::warning[注意]{open}
卸载 esp4 / esp6 会让本机 IPsec ESP 失效。普通桌面或竞赛环境可以接受这个代价。执行后清空页缓存(或直接重启),防止已污染的缓存继续留存。
:::
比赛呢?
显然大家都知道 NOI Linux 是 Ubuntu 魔改。
NOI Linux 2.0 的内核版本是 5.4.0-42-generic,也在范围内。
Ubuntu 20.04 默认允许无特权的用户命名空间创建(AppArmor 对此的限制是在更新的 Ubuntu 版本里才收紧的),所以 xfrm-ESP 变体理论上可以跑。Ubuntu 也默认带 rxrpc.ko,RxRPC 变体同样可以跑。两条路都通。
攻击程序是 C 写的,不会像 Copy Fail 那样出现 该文章中提到的无法提交 Python 代码的问题。
但是显然 CSP-J/S 和 NOIP 在评测机进行评测,你也不适合把它当 C++ solution 交上去。未必能绕过评测机的 sandbox,不仅不会有效果,还会被发现并禁赛。
但现有 NOI Linux 2.0 在本机上跑就不知道了,不过应该不会有什么太大的作用。
这个漏洞的披露过程也能当吃瓜群众了:
原计划把消息封锁 5 天,结果 2026-05-07 当天就有人在网上直接公开了 exploit。这下消息是锁不住了。
作者就只能当天全量公开了文档和 exploit。今天(2026-05-08),xfrm 的补丁合入主线,CVE 号下发。
xfrm 侧窗口期约 9 年,RxRPC 侧约 3 年。目前仍没有任何发行版打上补丁。
此时不应有 Wake Up.。
:::info[AI 使用说明]{open} 本文编写时使用了 AI 辅助分析,但本文非 AI 编写/AI 输出。 :::