CVE-2026-98432 — Linux IPv4/TCP ireq\_opt use-after-free PoC
Ots安全
2026年11月9日 19:56
广东
去阅读
一、这份 PoC 能证明到哪一步
源仓库之外,另取 kernel.org 的 CVE 记录、GitHub Advisory 数据库条目 GHSA-qp6h-4hvh-qqqc、Anthropic 10 月 27 日的研究文章、Red Hat 安全公告 RHSB-2026-016、Ubuntu 与 Debian 的漏洞跟踪页,以及多家发行版状态汇总做交叉核对。可确认的事实.
开放问题:Anthropic 未说明为何演示环境需要 MPTCP 监听器,未公开完整利用链,53 分钟为自报且测试台架未披露;11 月 2 日起出现的 MPTCP 443 端口探测与该 CVE 的对应关系属推测;CVE-2026-31669 是另一个 MPTCP IPv6 子流 UAF,与本洞不是同一缺陷。以上均写成公开信息未说明,不猜。
二、判定范围:内核版本与监听器配置
2.1 运行中的内核是否含修复
排查起点是 uname -r,它给出的是当前正在运行的内核,不是已安装的新内核包。发行版普遍做回溯而不改上游版本串,因此必须拿包版本去比对本发行版 tracker 的修复包名,不能拿 6.8.0-147 这类数字去套上游 6.6 到 6.12 之间的大小关系。装了新内核而没重启,等于没修。
2.2 是否存在 IPv4 TCP 监听
触发条件是 IPv4 首部带 options 的 SYN 打到一个会 accept() 的 TCP 监听端口,无需凭据、无需用户交互、无需登录会话。Red Hat 公告明确 MPTCP 不是前置条件,默认关闭 MPTCP 的 RHEL 系统同样在受影响范围内。IPv6 主竞态路径不受影响,因为 tcp_v6_syn_recv_sock() 复制而非移交选项,但 IPv6 请求套接字迁移那条路径仍待单独补丁,纯 IPv6 监听不构成跳过更新的理由。
2.3 MPTCP 与 Go 服务的隐性暴露面
sysctl net.mptcp.enabled 为 1 时内核允许创建 MPTCP 套接字,多数发行版默认即 1。Go 1.24 起 ListenConfig 在支持 MPTCP 的 Linux 上默认对监听套接字启用 MPTCP,因此用 Go 1.24 及以上版本编译的服务,运维并未主动开过 MPTCP,也可能已经具备被演示路径所需的监听器。用 ss -Mlnp 或支持 MPTCP 的新版 ss -tlm 枚举,再用 go version /path/to/binary 核对二进制的构建版本。空输出只说明此刻没有 MPTCP 监听,不说明内核不含该缺陷。
2.4 容器、云内核与打包设备
容器共享宿主内核,升级镜像不会修宿主,Kubernetes 节点要单独排期并重启。云厂商 flavour 内核、HWE 内核与 linux-azure 这类变体各有自己的发布时间线,通用包的状态表不能替它们下结论。Veeam 已就 Software Appliance 13.1、Infrastructure Appliance 13.1 以及 Backup & Replication 13.1 与 12.3 中的 Linux 辅助主机和启动介质发布修复内核,这类把内核打包进产品的设备容易被资产表漏掉。
三、原因导致:所有权移交与 RCU 宽限期
3.1 一个 72 字节对象的两处引用
带 IPv4 options 的 SYN 会为请求套接字建出一个 ip_options_rcu 对象,README 记为 72 字节、落在 kmalloc-96。握手完成时,tcp_v4_syn_recv_sock() 把 ireq->ireq_opt 的所有权交给子套接字的 newinet->inet_opt,没有复制,随后清掉请求套接字里的指针。移交而非复制本身不是错,错在旧持有者一侧的读操作没有同步退出。
3.2 两颗 CPU 的时间窗口
自 2015 年那个把请求套接字装进 ehash 表的 commit 起,syn\_recv 请求的处理不再持有 listener lock。一个重传的 SYN 可以在另一颗 CPU 上进入 tcp_check_req(),经 tcp_v4_send_synack() 与 inet_csk_route_req() 在 rcu_read_lock() 保护下读到同一份 ireq_opt,再由 ip_build_and_send_pkt() 交给 ip_options_build() 组装 SYN-ACK,后者把 opt->optlen 读了两次。RCU 读锁只保证读者不被抢占后误判,并不延后对象释放;子套接字此时可能已被拆掉。
图 1:双 CPU 竞态窗口与 RCU 宽限期修复点。本文绘制的机制示意图,非原文章配图,不作为证据引用。
官方记录里有一句容易被二手文章漏掉的澄清:SYN-ACK 定时器本身不是问题,它持有对请求套接字的引用,且 inet_csk_reqsk_queue_drop() 会在子套接字被释放之前调用 timer_delete_sync()。把锅记到定时器上,会顺着错误的路径去找补丁。
3.3 释放方式才是关键
TCP 子套接字不带 SOCK_RCU_FREE,inet_sock_destruct() 用普通 kfree() 直接释放 inet_opt。于是窗口里出现一个读者仍在 dereference、写者已经把内存交还 slab 的局面,UAF 落在 ip_options_build()。官方还指出,内核里替换 inet_opt 的其他路径,例如 do_ip_setsockopt() 与 cipso_v4_sock_setattr(),早就用的是 kfree_rcu();两处析构函数漏了这一步。修复因此只改两行释放调用,让已经进入 RCU 读侧的读者先跑完,再回收对象。
3.4 第二条路径出现在请求套接字迁移
当 net.ipv4.tcp_migrate_req=1 或有 BPF_SK_REUSEPORT_SELECT_OR_MIGRATE 程序时,reqsk_timer_handler() 会用 inet_reqsk_clone() 克隆请求套接字,新旧两份共享同一个 ireq_opt,reqsk_migrate_reset() 再把旧指针清掉。另一颗 CPU 若仍持有旧请求套接字的引用,在克隆体被释放时可能还在使用这些选项,而 tcp_v4_reqsk_destructor() 同样用了普通 kfree()。上游把这一处一并改成 kfree_rcu(),说明两条路径要一起修才算闭环。
3.5 补丁的边界
修复 commit e3f33b0a1d89 由 Eric Dumazet 提交,记录里对报告者的致谢指向 Anthropic 的 Xinyang Ge。IPv6 主竞态不受影响,原因是 tcp_v6_syn_recv_sock() 复制选项;但 tcp_v6_reqsk_destructor() 对 ipv6_opt 存在同类的迁移问题,该字段仅由 CALIPSO 设置,官方写明将由单独补丁处理。修的是释放时机,不是握手逻辑,这也解释了为什么补丁小而影响面大。
四、仓库解剖:Oracle 怎样把 UAF 变成可观测
4.1 每次尝试的四个动作
README 把一次尝试拆成四步:发一个带 40 字节 IPv4 Timestamp(或 Record Route)选项的 SYN,让目标建出带 ireq_opt 的请求套接字并在 SYN-ACK 里回显该选项;在握手 ACK 之前补一个重复纯 SYN,让第二颗 CPU 也走一遍 SYN-ACK 路径;发 ACK 完成握手,子套接字继承 inet_opt,请求套接字丢掉指针;发一个带 ACK 的 RST 拆掉子套接字,带 ACK 是为了穿过本地 RST 过滤规则。对象在读者可能仍在使用的窗口里被释放,这一窗口是全部设计的落点。
4.2 为什么用 magic 加 tag 做判定
每次尝试在选项偏移 16 至 23 处埋一个各次互不相同的 32 位 magic,再配一个由 tag == magic ^ 0x5a5a5a5a 关系算出的校验 tag。若 SYN-ACK 回显的是另一次分配的 magic,且 tag 与该 magic 满足异或关系,就说明内核是从一个已释放并被复用的对象里拷贝的字节。README 的表述是,正确的内核不可能回显另一个分配的标记。这个判定的价值在于它把\"内存被读错了\"变成了一个可计数、可复核的布尔事件,不必等到崩溃或拿到 root 才下结论。
4.3 三段重写与 marker 的存活位置
用户态看到回显选项之前,内核会三次改写选项里的指针与地址槽位:服务端在 SYN 上跑 ip_options_compile()、在 SYN-ACK 上跑 ip_options_build()、接收端在 SYN-ACK 上再跑一次 ip_options_compile()。marker 被放在三段槽位之后,因此不会被这些合法改写污染。
| 改写阶段 | Timestamp | Record Route |
|---|---|---|
服务端 ip_options_compile()(SYN) |
4…7 | 3…6 |
服务端 ip_options_build()(SYN-ACK) |
8…11 | 7…10 |
接收端 ip_options_compile()(SYN-ACK) |
12…15 | 11…14 |
| marker 与 tag | 16…23 | 16…23 |
图 2:40 字节 IPv4 选项里三段合法改写与 marker 存活区。本文绘制的机制示意图,非原文章配图,不作为证据引用。
把 marker 挪到槽位之前,任何一次命中都可能只是编译选项时的正常写回,判定式会失去意义。这类\"避开合法改写\"的设计,是这份 PoC 比单纯的崩溃触发更值得读的部分。
4.4 证明与利用的分界
仓库明确写了不创建用户、不落文件、不实现代码执行链,--proof 只在 127.0.0.1 起一个临时监听器、加一条临时防火墙规则、跑完竞态后收尾清理,主机上只留下短暂的 TCP 状态。它回答的是\"内核有没有读到已释放内存\",不是\"能不能拿到 root\"。把这两问分开,PoC 数量再多也不会自动抬高漏洞的已证实危害等级。
五、复现与判读:命令、字段与退出码
5.1 环境与前置
脚本用 Python 3.8 及以上(作者在 3.12 上测试),仅标准库;需要 root 或 CAP_NET_RAW 发原始包,自动 RST 过滤规则另需 CAP_NET_ADMIN;目标是一台未修补 Linux 上会 accept() 的 IPv4 TCP 监听器;本机需有 iptables 或 nftables。--proof 在单机回环上自洽,适合拿来确认脚本与判定式是否工作。
5.2 五条入口命令
# 离线自检,不建套接字,任何操作系统都能跑python3 poc_cve_2026_98432.py--selftest
# 在待测主机上打印运行内核、修复版本表与相关 sysctlsudo python3 poc_cve_2026_98432.py--check
# 单机回环一键证明,起停监听器与临时规则由脚本自理sudo python3 poc_cve_2026_98432.py--proof9000 \\ --duration180--workers6--spray2--spray-rate1000 \\ --pcap /tmp/proof.pcap
# 单个 SYN 探测,看目标是否回显选项、是否为 MPTCP 监听器sudo python3 poc_cve_2026_98432.py--probe TARGET:PORT
# 实验室内网对自有靶标跑竞态,公网地址默认拒绝sudo python3 poc_cve_2026_98432.py TARGET:PORT \\ --workers16--spray4--duration600--pcap hits.pcap
--probe 只发一个 SYN,用来确认 oracle 的前提成立;目标不回显 IPv4 选项时,后面的计数没有判读意义。远程竞态命令默认拒绝公网地址,放开需要显式 --allow-public,这道闸是作者自己设的,不要在自有资产以外动它。
5.3 输出字段怎么读
| 字段 | 含义 |
|---|---|
attempts |
发起的握手次数 |
synacks |
观察到的 SYN-ACK 数量,约为尝试数的两倍 |
hits |
SYN-ACK 带回另一次分配的标记,UAF 确认 |
anomalies |
解析到标记但 tag 不匹配,属部分损坏 |
leaks |
回显字节里出现内核指针形态的值 |
noopt |
SYN-ACK 未回显选项,常见于 syncookie、队列满或中间设备剥离 |
stale |
迟到重传的 SYN-ACK,已过滤,不计入命中 |
malformed |
ihl < 5 的 SYN-ACK,对应 stale optlen 下溢特征 |
timeouts |
超时时间内没有 SYN-ACK |
spray |
发出的堆喷 SYN 数量 |
退出码是给自动化用的:0 观察到证据,3 未观察到,2 环境准备出错,1 用法或运行时出错。命中样例里,SYN-ACK echoed magic d4bcdcc7 tag 8ee6869d 与 0x8ee6869d == 0xd4bcdcc7 ^ 0x5a5a5a5a 的异或关系,是判定成立的那一行;只看到 leaks 或 anomalies 时不能算命中。把 3 读成\"这台机器安全\",是这套输出最容易出现的误用。
六、概率与调优:短跑零命中不等于安全
6.1 两组实测与一组反例
| 运行 | 时长 | 尝试数 | 命中数 | 按 README 数据复算的命中率 |
|---|---|---|---|---|
回环,--workers 6 --spray 2 --spray-rate 1000 |
180 秒 | 320,829 | 21 | 约 0.0065% |
| 回环,同组参数 | 300 秒 | 616,911 | 22 | 约 0.0036% |
回环,--workers 4 --spray 1 --spray-rate 500 |
60 秒 | 未给出 | 0 | 不适用 |
同一台 Ubuntu 24.04.5 配 6.8.0-147-generic 的主机,短参数跑 60 秒得到零命中,长参数跑满三分钟得到 21 次。两组命中率并不相同,说明这是概率性竞态而非稳定触发;README 据此提醒不要用短跑的零结果推断\"不受影响\"。这两组数字与 Anthropic 报告的 48 分钟泄漏内核指针、再 5 分钟达到命令执行,属于不同证据层级,都是自报且都未经第三方复现,彼此不能互相印证。
6.2 参数各管什么
| 参数 | 作用 |
|---|---|
--duration |
增加尝试总数,公开研究里的复现耗时以数十分钟计 |
--workers |
提高并发尝试数,实验室内网建议 8 至 16 |
--spray / --spray-rate |
保持 kmalloc-96 的分配churn,让被释放的选项对象更快被复用 |
--lead / --rst-delta / --jitter |
微秒级时序扫描,README 建议从 20/40/10 起,lead 扫 0 至 40、delta 扫 0 至 80 |
链路顺序也有影响:回环优于内网,内网优于公网。距离越远,抖动越大,重复 SYN 与 ACK、RST 之间的相对位置越难压进那个窗口。
6.3 实验室边界
README 第 7 节给出在一次性虚拟机里搭建脆弱内核的思路,做法是把 6.12.112 源码中两处 kfree_rcu() 逆回 kfree() 再编译,本文不转载具体构造命令。两点纪律要跟着走:脚本发的是真实数据包,长跑偶发会让内核不稳,动手前给主机做快照;调参放在可丢弃的虚拟机里,生产资产上只跑 --check 这类只读检查。作者把这段标成开发用靶标,不是给运维的检测清单。
参考
- Alucard0x1(2026-10-30):CVE-2026-98432 Linux IPv4/TCP ireq\_opt use-after-free PoC,https://github.com/Alucard0x1/cve-2026-98432
- 同上 README 原始文本,https://raw.githubusercontent.com/Alucard0x1/cve-2026-98432/main/README.md
- kernel.org CNA:CVE Record CVE-2026-98432(Published 2026-10-15,Updated 2026-10-24),https://www.cve.org/CVERecord?id=CVE-2026-98432
- NVD / GitHub Advisory Database:GHSA-qp6h-4hvh-qqqc,https://github.com/advisories/GHSA-qp6h-4hvh-qqqc
- Linux 内核上游修复 commit:e3f33b0a1d89(ipv4: free inet\_opt and ireq\_opt after an RCU grace period),https://git.kernel.org/stable/c/e3f33b0a1d89d738e2b913dab519226eaf76e15b
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论