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/sui \times 4 字节所在的页缓存钉进管道下一个槽,然后 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 输出。 :::