-
-
[原创]DirtyFrag浅析及一条新的攻击路径
-
发表于: 2026-7-13 11:13 2348
-
2026 年 5 月 7 日,韩国安全研究员 Hyunwoo Kim(@v4bel)向 oss-security 邮件列表公开披露了一个名为 Dirty Frag 的 Linux 内核本地权限提升漏洞链,并附带了完整的技术分析和 PoC 代码。
这个名字精确地概括了漏洞的本质:攻击者通过 splice() 零拷贝机制,将只读文件的 page cache 页面引用植入网络发送缓冲区(skb)的 frag 槽位。当接收端内核在 esp4/esp6(IPsec ESP)或 RxRPC 模块中执行原地解密操作时,本应对密文的解密变成了直接写入只读页缓存的 STORE 原语。
当这两个独立子系统(xfrm-ESP 和 RxRPC)在 splice() 的帮助下与 Page Cache 交汇时,一个无特权的本地用户可以向系统中任意可读文件的页面缓存写入可控数据。
漏洞链由两个 CVE 组成:
漏洞影响 2017 年以来几乎所有主流 Linux 发行版(CVSS 评分 7.8 High),且无需竞争条件,成功率接近 100%。
ℹ️ 时间线
我们从历史发展的角度,来看漏洞的由来,这样可能更通俗易懂。
Dirty Cow
Linux高危漏洞Dirtycow整理
它发现了一个Linux内存管理的漏洞,可以越权修改文件内容, 比如修改 /etc/passwd 文件。
Dirty Pipe
[原创]CVE-2022-0847 dirty Pipe 漏洞原理深度分析 近年来最"干净"的内核提权漏洞之一
它发现了一种使用pipe + splice直接修改文件缓存的方法,比如修改 /usr/bin/su 文件。
Copy Fail
[原创]Copy Fail 深度研究:Linux 页缓存漏洞的根因、利用与检测
[原创]探索CVE-2026-31431的热补丁修复方案
在Dirty Pipe被修复后,它发现了 pipe + splice + socket + authencesn 也可以修改文件缓存。
Dirty Frag
在Copy Fail被修复后,它发现了 pipe + splice + socket + esp + authencesn 的方式来修改文件缓存。(RxRPC暂未研究,就此略过)
内核支持使用socket来操作authencesn, Copy Fail只需要构造特定网络包调用解密api就可以,相对简单;
而IPsec ESP模块是用来处理网络加密的模块,所以Dirty Frag就需要搭建一个能走到ESP模块esp_input函数的网络环境。
所以相对Copy Fail就是多了一个网络环境的搭建,其他部分大同小异。
Dirty Clone
Dissecting and Exploiting Linux LPE Variant: DirtyClone (CVE-2026-43503)
在Dirty Frag被修复后,它发现 pskb_copy 会丢失关键Flag,从而让补丁失效。
esp4部分的关键代码如下,主要目标是跳过 skb_cow_data 函数(它会给数据重新分配一段内存)。
这里的要点在[2]部分的检测上。
Linux中skb的分片分两种:
而 skb_has_frag_list 检测的是第二种情况。
所以Dirty Frag只要构造第一种情况的分片就可以了。
可能有同学要问了,为什么不能走[1]?
因为需要用 splice 来定位要攻击的文件缓存。
对 socket 做 splice 写时,会走 generic_splice_sendpage -> udp_sendpage 路径(udp socket);udp_sendpage 会将页缓存用 分片1 的方式加入到 skb 中。
而[1]线性的意思差不多是说,skb中不能有分片(实际含义是skb中的数据内存是连续的)。
最后,走到 crypto_aead_decrypt 时,只要前面设定好加密算法,就能直达 authencesn 模块。
在本地回环网络上搭建esp4网络环境,简单说只需要3步:
1. 配置xfrm state
网络数据包在进 esp4 之前,需要先经过 xfrm。
xfrm 负责转换报文,转换时会检查是否有匹配的状态。
其中比较关键的参数:
2. 创建socket接收端
接收端是不可或缺的,否则会被drop掉。
而且,需要配置上 UDP_ENCAP 选项,才能接收 esp4 包。
3. 创建socket发送端
发送端的关键之处在于要走pipe + splice。
4. 权限
实施过程中会发现,配置xfrm state 需要网络管理员权限。
怎么办呢?
Linux中可以建立 UID/GID 映射,可以让自己伪装成 root 用户。
映射写成功后,命名空间内的 UID 0 既能获得完整 capability,又能操作外部属于该用户自己的文件。
但映射操作有一个前提,只能写映射尚未写入的全新用户空间。
所以就需要使用 Linux 的命令空间 clone 技术,先 clone 用户和网络 命令空间才行。
当然还有一些其他细节,比如:
总体上前,Dirty Frag并没有太多新东西。
在看别人POC的时候,发现了一个之前一直忽略的问题。
Copy Fail明明用的 authencesn 模块,为什么官方要建议禁用 authenc 模块。
后来才明白,之所以公开的 POC一般用 authencesn,只有一个原因——简单方便,实现起来容易,能快速验证就可以了。
而 authenc 本身也存在攻击点——出入同源(这一点authencesn也存在),它的问题在于——只要知道点密码学知识,就可以控制解密结果。
拿 authenc 中的解密函数 crypto_authenc_decrypt 为例,其解密过程如下:
其中:
完整的 esp 数据包 = 【spi】+【seq】+【iv】+【目标文件内容】+【验证数据】
还有几个隐含的内容,在 xfrm state 中设置。
其中,【验证数据】= 验证算法(验证key, 【spi】+【seq】+【iv】+【目标文件内容】)
剩下的内容,只有 加密算法、加密key 和【iv】会影响解密结果。其中前两者在 xfrm state 中配置,修改难度较大。相对【iv】修改起来就方便很多。
所以,最终要做的就是,找到一个算法,算出【iv】, 使【目标数据】= 加密算法(加密key,【iv】,【目标文件内容】)
通常esp使用的加密算法都是 AES-CBC。
AES(Advanced Encryption Standard —— 高级加密标准),是国际上广泛采用的对称密钥加密算法。
其工作原理为:
AES的CBC模式
CBC(Cipher Block Chaining —— 密码块链接)是一个利用反馈加密块的模式 —— 每个明文块在加密前,都需要与前一个密文块进行异或(XOR)操作。这一过程产生了两个重要的特性:

通过上面信息,可以明确一点,【iv】在 AES-CBC 的作用: 与 第一个数据块的解密结果 进行异或。
用代码表示:
本文是在分析DirtyClone(CVE-2026-43503)时写的,因手上的资源只有DirtyFrag的漏洞,就拿DirtyFrag作为了素材。
漏洞本身只是一个引子,authenc的这一块攻击路径,网上资料不多,同时也是我一直忽略的部分,所以就写了这篇文章,加深自己的认识。
以上内容仅为个人理解,如有错误,欢迎指正。
V4bel/dirtyfrag —— github.com
nu11secur1ty/CVE-mitre —— github.com
Dissecting and Exploiting Linux LPE Variant: DirtyClone (CVE-2026-43503) —— research.jfrog.com
AES加密技术的CBC模式详解 —— csdn.net
AES加解密-CBC ECB —— cnblogs.com
| 日期 | 事件 |
|---|---|
| 2017-01 | xfrm-ESP 漏洞随 commit cac2661c53f3 引入内核(潜伏 9 年) |
| 2023-06 | RxRPC 漏洞随 commit 2dc334f1a63a 引入内核 |
| 2026-04-29 | Hyunwoo Kim 向 security@kernel.org 报告漏洞及完整 exploit |
| 2026-05-07 | 漏洞详情提交至 linux-distros 邮件列表,约定 5 天 embargo |
| 2026-05-07 | 同日,第三方公开发布了 ESP(xfrm) 漏洞详情与 exploit,embargo 破裂 |
| 2026-05-07 | 完整 Dirty Frag 文档被迫提前公开,此时无 CVE、无官方补丁 |
| 2026-05-08 | CVE-2026-43284 补丁合并入主线内核 |
static int esp_input(struct xfrm_state *x, struct sk_buff *skb)
{
//.....
if (!skb_cloned(skb)) { // <== 非clone
if (!skb_is_nonlinear(skb)) { // <== [1]线性
nfrags = 1;
goto skip_cow;
} else if (!skb_has_frag_list(skb)) { // <== [2]无分片
nfrags = skb_shinfo(skb)->nr_frags;
nfrags++;
goto skip_cow;
}
}
err = skb_cow_data(skb, 0, &trailer);
if (err < 0)
goto out;
nfrags = err;
skip_cow:
//.....
err = crypto_aead_decrypt(req);
//.....
}