CVE-2026-46300 — “Fragnesia“ 深度拆解:当修复补丁亲手唤醒了另一只恶魔

CVE-2026-46300 — “Fragnesia“ 深度拆解:当修复补丁亲手唤醒了另一只恶魔 一句话定调这是 2026 年 5 月最尴尬的剧本——安全研究员修好了Dirty FragCVE-2026-43284结果修复改动把skb_try_coalesce()里一个潜伏多年的 flag-propagation 缺陷推到了可达路径上。修一个 page-cache 写漏洞引入另一个 page-cache 写漏洞。​ 安全圈管这叫regression-induced vulnerability黑客管这叫Christmas。0 · 档案卡项目内容CVE​CVE-2026-46300代号​Fragnesiafrag 分片/amnesia 遗忘——skb 把SHARED_FRAG标记忘掉了发现者​William BowlingV12 Security / Zellic借助 AI 辅助审计工具发现公开日期​2026 年5 月 13 日PoC 补丁同日发布到 netdev 列表CVSS 3.1​7.8High​ — AV:L / AC:L / PR:L / UI:N漏洞类​本地权限提升LPE—— page-cache 写入原语确定性​✅ 无竞态、无偏移泄露、无崩溃——单次 syscall 序列一发入魂​PoC 状态​✅ 已公开GitHub:theori-io/fragnesia/ V12 的 PoC最讽刺的事实​Dirty Frag 的修复激活了它​ —— Hyunwoo Kim 的 CVE-2026-43284 补丁改了 coalescing 路径的控制流让一个之前不可达的SKBFL_SHARED_FRAG丢失路径变得可达1 · 先搞懂SKBFL_SHARED_FRAG 到底是干嘛的1.1 sk_buff 的碎片frags不是拷贝是引用Linux 网络栈的核心数据结构sk_buff简称skb承载数据包。一个 skb 的数据可以分为线性区skb-head ~ skb-tail实际分配的内存非线性 paged fragsskb_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() → 做 COWCopy-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 场景unshare(CLONE_NEWUSER \| CLONE_NEWNET)可用​在 user namespace 里你能拿到伪CAP_NET_ADMIN足以创建 XFRM SA、attach ULPCONFIG_INET_ESPINTCPy/mESP-in-TCPXFRM ESP over TCP编译进内核esp4/esp6 模块可用大多数发行版默认满足Ubuntu 的默认 AppArmor 会限制非特权 user namespacekernel.apparmor_restrict_unprivileged_userns1这构成了部分缓解——但需要sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns0就能撤掉。2.2 六步攻击链概念级完整版① 创建 user netns → 获得伪 CAP_NET_ADMIN ─────────────────────────────────────────── unshare(CLONE_NEWUSER | CLONE_NEWNET) ② 打开目标只读文件 /usr/bin/suO_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 → ★ BUGSKBFL_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 datagramsplice()路径漏设​SKBFL_SHARED_FRAGTCPskb_try_coalesce()丢掉已有的SKBFL_SHARED_FRAG写原语​ESP 原地解密 → 4 字节受控 STORE / 轮ESP 原地解密 →192 字节 XOR/轮更快、更灵活需要哪个模块​esp4/esp6或​ rxrpc仅​ esp4/esp6espintcp 路径与 Dirty Frag 修复关系​原始 bug被修复改动激活的 latent bug​本质段位​忘了锁门门其实锁了但搬家具时把已锁牌子碰掉了最值得品味的句子来自 Hyunwoo KimDirty 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 causeflag 不传播但藏得更深。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 均有 errataCloudLinux​✅ 测试中 KernelCare livepatch 验证中Fedora​✅ 7.0.6 含修复Amazon Linux​❌ 不受影响CONFIG_INET_ESPINTCP未编译Alibaba Cloud Linux​❌ 不受影响同上ESPINTCP未编译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 VPNstrongSwan/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。在未授权系统上跑它 刑事入侵行为。