一句话定调:这是 2026 年 5 月最尴尬的剧本——安全研究员修好了Dirty Frag(CVE-2026-43284),结果修复改动把
skb_try_coalesce()里一个潜伏多年的 flag-propagation 缺陷推到了可达路径上。修一个 page-cache 写漏洞,引入另一个 page-cache 写漏洞。 安全圈管这叫regression-induced vulnerability,黑客管这叫Christmas。
0 · 档案卡
项目 | 内容 |
|---|---|
CVE | CVE-2026-46300 |
代号 | Fragnesia("frag" = 分片/"amnesia" = 遗忘——skb 把 |
发现者 | William Bowling(V12 Security / Zellic),借助 AI 辅助审计工具发现 |
公开日期 | 2026 年5 月 13 日(PoC + 补丁同日发布到 netdev 列表) |
CVSS 3.1 | 7.8(High) — AV:L / AC:L / PR:L / UI:N |
漏洞类 | 本地权限提升(LPE)—— page-cache 写入原语 |
确定性? | ✅ 无竞态、无偏移泄露、无崩溃——单次 syscall 序列,一发入魂 |
PoC 状态 | ✅ 已公开(GitHub: |
最讽刺的事实 | Dirty Frag 的修复激活了它 —— Hyunwoo Kim 的 CVE-2026-43284 补丁改了 coalescing 路径的控制流,让一个之前不可达的 |
1 · 先搞懂:SKBFL_SHARED_FRAG 到底是干嘛的?
1.1 sk_buff 的碎片(frags)不是拷贝,是引用
Linux 网络栈的核心数据结构sk_buff(简称skb)承载数据包。一个 skb 的数据可以分为:
线性区(
skb->head ~ skb->tail):实际分配的内存非线性 paged frags(
skb_shinfo(skb)->frags[]):每个skb_frag_t就是一个(struct page *, offset, size)三元组——指向某个页面的引用,不一定有自己的内存
当你用splice()把一个文件(比如/usr/bin/su)喂进 socket 时,内核走的是零拷贝路径:文件的page cache 页被直接挂到frags[]里作为一个引用。磁盘内容没被拷贝,只是 page 指针被链进去了。
1.2 问题来了:后续代码想"写"怎么办?
如果后面有人(比如 ESP 解密)想对这个 skb 的数据做in-place 修改(AES-GCM 解密就是 XOR 到原地),它必须先搞清楚:
"这个 frag 指向的 page……是不是别人也在用?"
如果是 page-cache 页,别人当然在用(文件系统本身就在用)。所以内核有一套机制来标记这个事实——
SKBFL_SHARED_FRAG ← 标记位,意思是"这个 skb 的 frags 里有外部共享页(尤其是 page cache 页),别原地写!"带了这个标记的路径,应该走:
skb_cow_data() → 做 COW(Copy-On-Write)私有拷贝 → 再写私有副本如果不带这个标记,下游就以为 "这是我的私有非线性 skb,可以原地写" →直接往 page cache 页上 XOR。
1.3 那么标记怎么会丢?
这就是 Fragnesia 的 bug:当skb_try_coalesce()把@from的 paged frags 挂到@to上时,如果@from带着SKBFL_SHARED_FRAG,合并后的@to应该继承这个标记——但它没有。
skb_try_coalesce(to, from): to.frags += from.frags ← 把 page 引用搬过去了 ✓ to.flags |= SKBFL_SHARED_FRAG (if from had it) ← ❌ 丢了!下游 ESP input 路径看到的skb_has_shared_frag(skb)==false → 跳过skb_cow_data()→原地解密写到 page cache 页。
2 · 攻击链:怎么把这块零件拧成一把 root 扳手
2.1 前置条件(不苛刻)
条件 | 为什么 |
|---|---|
本地低权限 shell | 永远是 "already-in" 场景 |
| 在 user namespace 里你能拿到伪 |
| ESP-in-TCP(XFRM ESP over TCP)编译进内核 |
esp4/esp6 模块可用 | 大多数发行版默认满足 |
Ubuntu 的默认 AppArmor 会限制非特权 user namespace(
kernel.apparmor_restrict_unprivileged_userns=1),这构成了部分缓解——但需要sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0就能撤掉。
2.2 六步攻击链(概念级完整版)
① 创建 user netns → 获得伪 CAP_NET_ADMIN ─────────────────────────────────────────── unshare(CLONE_NEWUSER | CLONE_NEWNET) ② 打开目标只读文件 /usr/bin/su(O_RDONLY) 用 splice() 把它的一部分页缓存页喂入一个 TCP socket 的接收队列 ─────────────────────────────────────────── 此刻:su 的 page cache 页被挂到 skb->frags[] 里 内核标记了 SKBFL_SHARED_FRAG ✓ (初始标记是对的) ③ 在这个 TCP socket 上 attach ESP-in-TCP ULP (Upper Layer Protocol 切换——把该 TCP 连接的接收路径交给 ESP 解码器) ─────────────────────────────────────────── setsockopt(sock, SOL_TCP, TCP_ULP, "espintcp", ...) ④ TCP 接收路径做 skb_try_coalesce() 合并 frags → ★ BUG:SKBFL_SHARED_FRAG 在此丢失 ★ → ESP input 路径检查 skb_has_shared_frag() → false → 不 COW ⑤ ESP 原地 AES-GCM 解密 → XOR keystream 直接写到 su 的 page cache 页上 → 攻击者通过控制 IV/nonce(明文已知、ciphertext 可控) 逐块(192 字节/trigger)覆写 su 的关键字节为 shellcode stub ─────────────────────────────────────────── 要改的字节数不多:只需把 su 的前几条指令覆盖为 setuid(0) → execve("/bin/sh") 的 machine code stub ⑥ 执行 /usr/bin/su(仍是磁盘上的 setuid-root 二进制) → 内核映射已被污染的页缓存页 → 以 root 身份跑你的代码 → 🎉关键美学:磁盘上的
/usr/bin/su完全没有被修改。sha256sum /usr/bin/su结果不变。改的只是 RAM 里的 page cache——echo 3 > /proc/sys/vm/drop_caches一刷就恢复。
3 · 为什么 Fragnesia 比 Dirty Frag 更"优雅"(对攻击者而言)
维度 | Dirty Frag (CVE-2026-43284) | Fragnesia (CVE-2026-46300) |
|---|---|---|
入口 | UDP datagram | TCP |
写原语 | ESP 原地解密 → 4 字节受控 STORE / 轮 | ESP 原地解密 →192 字节 XOR/轮(更快、更灵活) |
需要哪个模块 | esp4/esp6或 rxrpc | 仅 esp4/esp6(espintcp 路径) |
与 Dirty Frag 修复关系 | 原始 bug | 被修复改动激活的 latent bug |
本质段位 | "忘了锁门" | "门其实锁了,但搬家具时把'已锁'牌子碰掉了" |
最值得品味的句子来自 Hyunwoo Kim(Dirty Frag 原作者)本人:
"Fragnesia wasactivated by the Dirty Frag fix — not a missed variant, but aregression introduced by the remediation."
这在安全工程史上有一个经典名字:Incomplete Fix / Regression-Induced Vulnerability。修 A 的时候改变了控制流形状,让原本不可达的 B 变成可达,而 B 也有同样的 root cause(flag 不传播)但藏得更深。
4 · 上游修复:两行补丁,四两拨千斤
上游修复提交信息(kernel.org / netdev 列表,2026-05-13)核心内容
c
// net/core/skbuff.c — skb_try_coalesce() bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from, ...) { // ... 原有逻辑把 from->frags 挂到 to->frags ... // ★ 新增:传播共享标记 ★ + if (skb_shinfo(from)->flags & SKBFL_SHARED_FRAG) + skb_shinfo(to)->flags |= SKBFL_SHARED_FRAG; return true; }以及更完整的修复还覆盖了兄弟路径(__pskb_copy_fclone()、skb_shift()、skb_gro_receive()等同样漏传播的 helper),因为 TLCTC 分析和 NVD 变更记录表明这是一类模式bug而非单点:
c
// __pskb_copy_fclone() 和 skb_shift() 也需要: + if (skb_shinfo(from)->flags & SKBFL_SHARED_FRAG) + skb_shinfo(new)->flags |= SKBFL_SHARED_FRAG;这就是全部——让 invariant 恢复:只要 frags 里有外部共享页,合并后的 skb必须继续保持 shared-frag 标记,下游才能正确走 COW。
5 · 影响范围速判
版本维度
范围 | 状态 |
|---|---|
< 4.11 | 大概率不受影响(espintcp ULP 还没存在 / coalescing 路径形态不同) |
4.11 ~ 修复日(2026-05-13) | ⚠️ 受影响,尤其 5.x / 6.x 全系 LTS |
已修复的 stable | 5.10.206+、5.15.206+、6.1.172+、6.6.138+、6.12.87+、6.18.28+、≥ 7.0.5 等(各发行版 pkg 名不同) |
发行版快照(公开状态)
发行版 | 状态(截至 5月中) |
|---|---|
AlmaLinux | ✅ 已发修复内核(8/9/10 均有 errata) |
CloudLinux | ✅ 测试中 + KernelCare livepatch 验证中 |
Fedora | ✅ 7.0.6 含修复 |
Amazon Linux | ❌ 不受影响( |
Alibaba Cloud Linux | ❌ 不受影响(同上, |
Ubuntu / Debian / RHEL / CentOS Stream / openSUSE | ⚠️ 多数在 "needs evaluation / pending" 状态——需要手动确认你的内核是否含 5月13日后的 patch |
一键自查
# 1. 你在哪个内核? uname -r # 2. espintcp 编译进去了吗? grep -E "CONFIG_INET_ESPINTCP|CONFIG_INET6_ESPINTCP" /boot/config-$(uname -r) 2>/dev/null # =y 或 =m → 攻击面存在 # 3. 模块活着吗? lsmod | grep -E "esp4|esp6" # 4. 非特权 userns 开了吗?(攻击前置条件) cat /proc/sys/kernel/unprivileged_userns_clone 2>/dev/null sysctl kernel.unprivileged_userns_clone 2>/dev/null # 或 AppArmor 限制: cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns 2>/dev/null6 · 修复 & 缓解(行动清单)
✅ 最终解:升内核(必须重启)
# Ubuntu / Debian sudo apt update && sudo apt full-upgrade sudo reboot # RHEL / Alma / Rocky sudo dnf update kernel sudo reboot验证新内核起来后uname -r确认版本跳进安全区间。
🛡️ 临时缓解(不能立刻重启时的标准操作)
# 1. 黑名单 + 尝试卸载 —— 斩断 esp4/esp6/rxrpc 的加载路径 sudo sh -c 'printf "install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n" > /etc/modprobe.d/fragnesia.conf' sudo rmmod esp4 esp6 rxrpc 2>/dev/null || true # 2. 刷掉可能已被污染的页缓存(不能"治愈"已污染的,但能强制从磁盘重新读) sync && echo 3 | sudo tee /proc/sys/vm/drop_caches > /dev/null⚠️ 如果你这台机真跑 IPsec VPN(strongSwan/libreswan 的 kernel-mode ESP),禁用 esp4/esp6 会断隧道——先评估业务。
🐳 容器场景
页缓存在宿主机全局共享,所以容器内的 exploit 也能污染宿主机的su。你需要的不是容器内的缓解,而是:
宿主机内核升级——唯一根治
容器 runtime 层限制
CAP_NET_ADMIN(但 unprivileged userns 可以伪造它,所以不够)Seccomp 拦
socket(AF_ALG, ...)和 XFRM netlink msg(辅助缩小面)
7 · 这个故事的真正教训
Fragnesia 不是某个程序员"粗心少写一行"那么简单。它暴露的是一个系统性难题:
零拷贝的承诺 = "我用引用,不拷贝,所以快" 安全的前提 = "每个下游消费者都必须尊重引用所有者语义" 现实 = 下游太多、路径太曲折、invariant 靠人的纪律维护 → 必漏Dirty Pipe、Copy Fail、Dirty Frag、Fragnesia——这四个的名字不同,根因是同一个:
内核用引用传递 page-cache 页到能做 in-place-write 的路径,然后 ownership tracking 在某个拐弯处脱落。
修一次不难,难的是让这个 invariant机器可验证(Rust-for-Linux 的类型系统、kCFI/Clang 的 stricter analysis、或者形式化验证级别的 skb API redesign)。在那之前,这个家族还会出续集。
⚠️ 收尾声明
CVE-2026-46300 的 PoC 已公开且极低门槛(不需要编译、不需要竞态调参、不需要内核地址泄露)。本文仅限合法授权环境(自有设备 / 授权渗透测试 / 企业红队 / CTF)。在未授权系统上跑它 = 刑事入侵行为。