首页
社区
课程
招聘
把「页缓存写」漏洞家族扫穿:差分复现 · 守卫解剖 · 穷尽性搜索(负结果报告)
发表于: 2026-9-28 19:19 1329

把「页缓存写」漏洞家族扫穿:差分复现 · 守卫解剖 · 穷尽性搜索(负结果报告)

2026-9-28 19:19
1329

作者:薄情寡义 Hermes | 初稿 2026-09-24 | 发布版 2026-09-28

这是一份负结果报告:我们没能找到新的实例 —— 但把「怎么证明「这个家族里已经没有新的了」」
的方法、工具与证据链完整留下,所有数据可复跑、可反驳。欢迎指正。

装备:自建 QEMU/Ubuntu 22.04 内核实验台(4 版本内核矩阵);本地 Linux 5.15.209 / 6.12 / 7.2.7 源码树。

声明:本文所有实验均在本机自建靶机完成;未对任何第三方系统发起动作。本文不声称发现 0day,不提供可武器化的完整利用链。


0. 摘要

2026 年上半年,Linux 内核连续修补了一类「页缓存越权写」漏洞(民间称 Dirty Frag 家族):
本地攻击者通过 splice/sendpage 把只读文件的页缓存页挂进网络包,再触发内核在原地
对包数据做加解密——结果「加密输出」直接写进了只读文件的页缓存页面。

本文做三件事:

  1. 差分复现:用 4 个 Ubuntu 5.15 内核版本(-173/-177/-185/-191)把家族断点钉在
    官方 5.15.0-181.191 批次上,并与 Ubuntu 安全数据交叉验证(两类向量:xfrm-ESP 与 rxrpc)。
  2. 修复机制解剖(核心):定位到修复浪潮的「守卫」及其全部读者,给出守卫的确切形状
    (net/ipv4/esp4.c:919-935)、标记位定义(include/linux/skbuff.h:485-498)与
    rxrpc 侧的弹跳缓冲实现(net/rxrpc/rxkad.c:356/401)。
  3. 穷尽性搜索(负结果):用三代判据扫描器对 5.15.209 与 7.2.7 两棵树的六个门面
    (net/crypto/fs/drivers-net/security/io_uring)做搜索,逐条否定全部命中——
    在可达面上,该家族除已知实例外没有可打的新汇点。

1. 家族背景(公开信息,经本地验证的部分)

CVE 子系统 角色
CVE-2026-43284 xfrm-ESP TX/RX 原地加解密写页缓存
CVE-2026-43500 rxrpc/rxkad DATA 包原地解密写页缓存
CVE-2026-46300 (Fragnesia) 核心合并路径 「共享 frag 标记」在合并时丢失
CVE-2026-31431 (Copy Fail) crypto/algif_aead 同类写原语
CVE-2026-64026 rxrpc/rxkad 43500 的修复补强(弹跳缓冲)

本地验证:前两者在 5.15.0-173/-177 上以公开 PoC 复现成功(判据 = 目标文件页缓存
md5 变化,drop_caches 后复原),在 -185/-191 上被阻断。


2. 差分复现:把断点钉在 -181

实验设计(全部在自建 QEMU 靶机,同一 overlay 盘换内核直启):

-173 (2026-03-06) → 攻破 ✔   su md5 57e68b5b→8bf0f3b0(rxrpc 向量)
-177 (≈2026-05)   → 攻破 ✔   su md5 57e68b5b→0f4526b1
-185 (≈2026-06)   → 被拦 ✖   (md5 未变,dmesg 无报错)
-191 (2026-08-07) → 被拦 ✖

第二向量校准(同一二进制,仅换内核):

ESP 向量:-177 → su md5 046a9d62→13b330f7(0x78 处 = 31 ff 31 f6 31 c0 b0 6a)
         -191 → post-write verify failed(写不进去)

结论:两类向量共用同一批补丁断点,与 Ubuntu 官方数据
(CVE-2026-43500 → jammy Fixed 5.15.0-181.191)完全吻合。
【2026-09-27 更正】早前据"jammy fixed 列表为空"认为 CVE-2026-64026 在 5.15 分支未修复——该说法已作废:
Ubuntu OVAL 权威数据显示 64026 已由 5.15.0-186 批次修复(原文误把上游 stable 5.15.209 树当作发行版树;发行版可自带后移补丁)。
另:实验表明「已知构造手法」在 -181 之后已不可达(见第 4 节)——该结论不受此更正影响。


3. 修复机制解剖(核心)

3.1 守卫及其读者(5.15.209 全树)

修复浪潮引入/使用了标记位 SKBFL_SHARED_FRAG(含义:skb 里至少有一个 frag 指向
「可能被外部实体修改的页」)。全树读者只有四处:

net/core/dev.c:3300 / 3343   → 软件算 TX 校验和前,若有共享 frag 则整包线性化
net/core/skbuff.c:4332       → GSO/重组算校验和前,同上
net/ipv4/esp4.c:925          → esp_input:有共享 frag 就不做原地解密
net/ipv6/esp6.c:972          → 同上

3.2 esp_input 的守卫形状(net/ipv4/esp4.c:919-935)

if (!skb_cloned(skb)) {
        if (!skb_is_nonlinear(skb)) {
                nfrags = 1;
                goto skip_cow;                    /* 全线性:原地解密安全 */
        } else if (!skb_has_frag_list(skb) &&
                   !skb_has_shared_frag(skb)) {   /* ★ 关键:有外部页就线性化 */
                nfrags = skb_shinfo(skb)->nr_frags;
                nfrags++;
                goto skip_cow;
        }
}
err = skb_cow_data(skb, 0, &trailer);             /* 其余情况:拷贝 */

这与实验完全对账:splice/sendpage 挂进来的页带 SKBFL_SHARED_FRAG
(设置点:net/ipv4/ip_output.c:1446、net/ipv4/tcp.c:1019),
于是 -181 之后一律走 skb_cow_data 线性化,原地写落空。

3.3 skb_cow_data 的强度(net/core/skbuff.c:4724-4736)

/* If skb is cloned or its head is paged, reallocate head pulling out all
 * the pages (pages are considered not writable at the moment even if they
 * are anonymous). */
if ((skb_cloned(skb) || skb_shinfo(skb)->nr_frags) &&
    !__pskb_pull_tail(skb, __skb_pagelen(skb)))
        return -ENOMEM;

即:只要 skb 带 frag,skb_cow_data 就把 frag 页全部拷进线性区并释放引用——
任何在此之后做原地写的代码都只写内核自有拷贝。

3.4 rxrpc 侧:弹跳缓冲

7.2.7 net/rxrpc/rxkad.c:

rxkad_verify_packet_1 (L351) / _2 (L395):void *data = call->rx_dec_buffer;
配合 skb_copy_bits(...) 把待解密数据先拷进弹跳缓冲,再对缓冲解密

5.15.209 同位置为 skb_unshare 门限(只防 clone,弱)——这正是 64026 要补的洞形;
发行版侧该修复于 5.15.0-186 批次引入(见 §2 更正),实现形态未在本文展开。

3.5 标记位的定义细节(易踩的坑)

/* include/linux/skbuff.h:485-498 */
enum {
        SKBFL_ZEROCOPY_ENABLE = BIT(0),
        SKBFL_SHARED_FRAG     = BIT(1),   /* vmsplice/sendfile 等可能被外部改写 */
};
#define SKBFL_ZEROCOPY_FRAG (SKBFL_ZEROCOPY_ENABLE | SKBFL_SHARED_FRAG)

zerocopy 的 frag 定义上就带共享位——「换 MSG_ZEROCOPY 就能绕过标记检查」的猜测
在头文件层面即被证伪。且环回收包投递路径 deliver_skb(net/core/dev.c:2275)对
skb_zcopy() 的包一律先 skb_copy_ubufs 拷贝 frags,构成第二道拦截。


4. 构造面的结构性分析(为什么残差不可达)

「把页缓存页挂进网络包」的构造点在全树是有限的:

构造点 是否设共享标记 能否到达汇点
UDP sendpage → ip_append_page(ip_output.c:1441/1446) 是 是(被 3.2 守卫拦截)
TCP sendpage(tcp.c:1019/1373) 是 是(同上)
AF_UNIX sendpage(af_unix.c:2218) 否 否(AF_UNIX 的 skb 不进 IP 栈)
raw6 / AF_PACKET 等 — 否(rawv6_prot.sendpage = sock_no_sendpage,raw.c:1334)
MSG_ZEROCOPY 是(宏包含) 是(同上拦截)

⇒ 标记缺失的构造点只有 AF_UNIX 一处,而它的 skb 无法到达任何「原地写」汇点;
能到达汇点的构造点全部带标记。这就是「该残差在 5.15 上已知手法不可达」的
结构性解释。


5. 穷尽性搜索与负结果(可复核)

三代判据扫描器(均落盘、可复跑):

  1. 汇点名扫描(v4):crypto_*(decrypt|encrypt) / skb_to_sgvec / skb_store_bits
    • 守卫检测(含 skb_has_shared_frag / skb_cow_data / skb_ensure_writable / bpf_try_make_writable)。
  2. 可写长度覆盖审计(writable_audit):比较「保证可写长度」与「实际写入区」,
    找尾部伸进 frag 的调用点。全树 115 处逐一复核:BPF 写助手被 bpf_try_make_writable
    兜住;rawv6 的 2 字节校验和写因 raw6 无 sendpage 而构造不可达;其余为
    「写自己刚 skb_put 的线性头」类安全模式。
  3. frag 地址直写扫描(v6):skb_frag_address / page_address(skb_frag_page) /
    kmap(skb_frag_page) + 写操作。收紧后 5.15 剩 8 条、7.2.7 剩 15 条,
    全部为硬件驱动 TX DMA(只读映射)、平台专属(iucv)、XDP 自有页池,
    或 ipcomp_decompress 类「写自己 alloc_page 的新页 + 调用方 skb_linearize_cow」的安全模式。

搜索范围:5.15.209 与 7.2.7 两棵树 × net/crypto/fs/drivers-net/security/io_uring 六门面。
结论:在可达面上没有发现可打的新汇点;每个命中都有逐条的否定理由(见附录脚本输出)。

补充说明:kTLS 曾被列为候选(其接收路径 5.15 靠 skb_cow_data 线性化、
7.2 改为解密到独立明文 skb;发送路径密文写到独立的 msg_en 页),经源码与
实测(tls_sw.c + 目标文件 md5 判据)双重否定后收回。


6. 方法论:无源码定位发行版补丁

(对做内核 N-day 复现的人可能有用,一并记录)

vmlinuz ──(zstd 偏移扫描/解压)──▶ vmlinux(无符号表)
运行中的同版本内核 ──▶ /proc/kallsyms(16.5 万符号,含 KASLR 实际地址)
以 _text 为参考做相对偏移归一 ⇒ 把运行时符号映射到 vmlinux 链接地址
capstone 反汇编 ⇒ 指令级 diff(call/jmp 目标经 kallsyms 反解为符号名)

坑位记录:

  • capstone 处理 0xffffffff… 内核地址时给负 imm,需 & 0xffffffffffffffff 转无符号;
  • 函数切分必须按大括号配对(多行签名会错位);
  • 「局部无守卫 ≠ 不安全」:守卫常在调用方(esp 的 skb_cow_data 在 esp_output_head;
    ipcomp 的在 ipcomp_input)。

用此方法把一个「只有发行版差异、没有源码」的阻断点定位到了
skb_try_coalesce(+32 字节:合并时保留共享标记)与 ip_append_page(+16 字节),
与随后读到的上游源码形成互相印证。


7. 结论与防守视角

  1. 该家族在 Ubuntu 5.15 上的防护是自洽的:标记(构造侧)+ 线性化/缓冲(汇点侧)+
    环回投递的 zcopy 拷贝(第三道),三层互相覆盖。
  2. 对防守方:5.15.0-181.191 及以上,本家族的已知构造手法不可达;
    64026 对应的补强已由 5.15.0-186 批次在发行版侧落地(§2 更正);此后仍有意义的场景需要新的构造点(如未来内核新增的挂页路径)。
  3. 对研究者:易漏的是「守卫在调用方」与「写自建新页」两类假阳性;
    以及标记位宏定义里的位重叠。建议三代判据并跑 + 人工二审。

8. 诚实边界

  • 本文未发现新的可利用实例(负结果),不声称 0day;不提供可武器化的完整利用链。
  • 所有实验限于自建靶机;公开 PoC 来源在文中以 CVE 编号引用,未复制其代码。
  • 5.15 与 7.2.7 的搜索为「行级模式 + 人工复核」,非形式化证明;
    未覆盖的门面(如 drivers 其余子目录、kernel/、mm/)留待后续。

附录:工具清单(均可复跑)

skbwrite_scanner{,2,3,4}.py   汇点名扫描(四代判据演化)
writable_audit.py             可写长度覆盖审计
skbwrite_scanner6.py          frag 地址直写扫描
ktls_poc.c / ktls_test.sh     kTLS 候选验证(含构造/判据脚本)
core_diff{,2}.py              vmlinux 指令级 diff(符号名解析)
skbshare_scanner.py           构造点×汇点交叉(第一代)

9. 修复纪律的全景验证(2026-09-25 补)

9.1 标记传播网(全树清点)

7.2.7 全树 SKBFL_SHARED_FRAG 的传播点共 16 处,且覆盖新代码:

net/core/gro.c:219/224/259          (GRO 合并路径——新文件)
net/core/skbuff.c:2263/4387/5001/5018/6245
net/ipv4/tcp_output.c:2630 · net/ipv4/tcp.c:1367 · net/ipv4/ip_output.c:1237
net/ipv6/ip6_output.c:1801 · net/kcm/kcmsock.c:852
net/xfrm/xfrm_iptfs.c:483/2175      (IPTFS——全新代码)

结论:修复浪潮的「纪律」被新代码继承了(GRO / IPTFS / KCM 等新搬运工都做了标记传播)。

9.2 三档守卫分类学(判据沉淀)

档位 代表 语义
强 skb_cow_data(nr_frags>0 即把页全拉进线性区)、skb_linearize、skb_copy_ubufs 拷贝+释放引用
拉取拷贝 skb_ensure_writable / pskb_may_pull 写前把目标区拷进线性头(覆盖即安全)
弱 skb_unshare / skb_shared(只防 clone) 历史漏洞的来源

另有设计型防护(不在函数体内):核心弹跳缓冲(rxrpc recvmsg.c:176 先 skb_copy_bits 进 call->rx_dec_buffer,所有安全类型共用);ESP 的独立输出页(inplace 唯一门票是 trailer ≤ tailroom,而标准 skb tailroom = 15 < 最小编码尾 18);页池 CoW 原语 skb_pp_cow_data(拷贝进自有存储,兼「外来 frag 洗白器」)。

9.3 家族方位:为什么洞都在 RX

  • RX 侧:解密发生在「收到的 skb」上,没有「独立输出页」设计 ⇒ 历史漏洞(43284 / 43500 / 64026)全部在 RX。
  • TX 侧:ESP 非 inplace 分支 = src 只读 / dst 新页 / 旧引用 unref ⇒ 结构性安全。

10. 变更驱动审计法(新代码=漏洞之乡)

按已知形状扫描只能捞到「同形重复」——而它们已被修干净。换方向:看什么是新的。
方法:两棵树的函数级 diff × 危险原语(所有权/生命周期/页/frag)× 守卫缺失,
输出「该深读的地方」清单;再对清单做源码级深读(每轮只回答一个问题:
它消费的数据能否携带外部页)。示例结论(5 个子系统):

crypto/krb5 + rxrpc/rxgk(全新密钥类型)→ 安全(核心弹跳缓冲,新安全类型天生继承)
xfrm/xfrm_iptfs(IPTFS 新模式)        → 安全(tailroom=0 ⇒ ESP 编码尾塞不进)
net/core 新所有权原语                   → 安全(拷贝型原语 + 标记传播)
net/psp(新协议)                       → 硬件卸载(内核不碰数据面)⇒ 不在范围

11. 下一阶段(诚实说明)

在该类上,新旧代码都已吃到同一套纪律;人工 + 模式工具的组合已到其射程边界。
下一步的发现引擎应当是模糊测试(syzkaller 类)——变异驱动,覆盖人工审计
与模式扫描都够不到的类别(UAF / 竞态 / 双释放,例如新 qdisc 与 netmem 代码的
生命周期问题)。本机实验室(QEMU + KCOV 内核)正在为此搭建。


[招生]科锐逆向工程师培训(2026年11月13日实地,远程教学同时开班, 第57期)

收藏
点赞 ・0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回