一个自包含、无需特权的 CVE-2026-74586 PoC,该漏洞是 Linux 内核 SCTP ASCONF(动态地址重配置)处理中的释放后使用问题。漏洞本身的功劳归于 Qing Ming,他向上游报告了该问题;修复提交为 beb33f8ee1ca(“sctp: clear new_transport when removing a peer”),于 2026-08-12 合并并带有 Cc: stable 标记——Red Hat 将其评级为重要,第三方跟踪器将其评为 CVSS 9.8。
本仓库在公告之外额外提供的内容:
sctp_process_asconf_param() 将 ADD-IP 参数创建的传输对象存储在 asoc->new_transport 中。同一 ASCONF 块中对同一地址的 DEL-IP 会通过 sctp_assoc_rm_peer() 释放该传输对象,而(在修复之前)该函数不会清除 new_transport。当 sctp_process_asconf() 返回时,sctp_sf_do_asconf() 会对悬空指针进行操作——在发行版内核上,可见效果是向刚释放的传输对象地址发出一个 HEARTBEAT;在 KASAN 下,则会产生 slab 释放后使用报告。
一个 ASCONF 块就足够了:
[Address Parameter L] [ADD-IP G] [DEL-IP G]
gcc -O2 -Wall -o poc poc.c
./poc
无需 root。该 PoC 会取消共享用户和网络命名空间,在其中设置 SCTP sysctl,通过回环接口建立多宿主关联,然后使用原始套接字注入精心构造的 ASCONF。
在存在漏洞的内核上的预期输出:
[+] Association established (no AUTH)
[+] server_vtag=0x... captured_tsn=0x...
[*] serial=0x... (= initial_tsn)
[+] Injected 60 bytes
[9222->9111 vt=...] ASCONF-ACK(0x80) len=8
[+] ASCONF-ACK -> server processed ADD-IP + DEL-IP
[9222->9111 vt=...] HB(0x04) len=60 <- kernel using freed transport
[9111->9222 vt=...] ABORT(0x06) len=8
[+] GhostTransport UAF TRIGGERED (ASCONF-ACK received)
[+] HEARTBEAT to ghost address observed (dangling transport USED)
ACK 之后的 HEARTBEAT 是值得关注的部分:该数据包之所以存在,仅仅是因为内核遍历了悬空的 new_transport。客户端随后立即发出 ABORT,因为心跳包是凭空返回的。
这些都不是新颖的研究,只是文档化不足,足以耗费一个下午的时间,所以在此记录下来供下一个人参考:
sctp_rcv() 会校验校验和并在不匹配时丢弃。在将校验和字段清零的情况下,对整个 SCTP 数据包计算 CRC32C(多项式 0x82F63B78,反射式)。sctp_auth_recv_cid() 在输入路径中、状态机之前运行,即使 addip_noauth_enable=1 也会丢弃未认证的 ASCONF——该 sysctl 仅放宽 sctp_sf_do_asconf() 内部的检查。解决办法是首先在套接字层面完全不协商 AUTH(不设置 SCTP_AUTH_SUPPORTED),这样 peer.auth_capable 保持为 false,两项检查都能通过。上游提交上的 Fixes: 标签是 6af29ccc223b(“sctp: Bundle HEARTBEAT into ASCONF_ACK”),因此该代码历史悠久。已在主线中修复并回溯到稳定系列(Debian 跟踪器:在 sid 中自 7.1.9-1 起修复,在 trixie-security 中自 6.12.107 起修复)。截至 2026-09-09,Kali rolling 在所有套件中均提供 7.1.5-1kali1,这早于所有修复版本——这就是本 PoC 的主要实际意义。
供进一步研究的人参考——针对 QEMU 中的 7.1.5 实测,偏移量来自随附 sctp.ko 的 objdump -d:
| 字段 | 偏移量 |
|---|---|
| flowi (88 bytes) | 0x30 |
| ipaddr | 0x88 |
| af_specific | 0xa8 |
| asoc | 0xb0 |
| dst | 0xe0 |
| state | 0x15c |
| rcu | 0x2b0 |
sctp_association->new_transport 位于 0x6b0。该表来自运行内核上的 pahole -C sctp_transport /sys/kernel/btf/vmlinux——该表的早期修订版带有错误的 ipaddr 偏移量(0x68,因误算 flowi 所致);BTF 才是真实依据。
sctp_transport_destroy_rcu),此路径上不存在双重释放。如果你希望关闭套接字并获得两次释放——sctp_association_free() 只遍历传输列表,而 rm_peer() 已经将幽灵对象从链表中移除。不要在这上面浪费一个晚上。sctp_outq_select_transport() 读取块中 +0xe8 处 chunk->transport 的 +0x15c 偏移处的 state。一个被回收的对象若被喷洒为 state=1(ACTIVE),会在释放后改变内核行为——该对象正在被查询。sctp_outq_select_transport() 在注入时恰好从悬空传输指针读取 state=0xFFFF(slab 垃圾数据)。结论:喷洒需要在触发之前预先整理缓存,而非之后——在此路径上,对象在宽限期到期之前就被读取了。*(p+0x288) 与 p+0x288 进行比较。这是一个自指针。在不知道 slab 地址的情况下,你无法通过喷洒绕过它,也就是说,在 af_specific 控制转化为指令指针控制之前,此漏洞需要一个信息泄露。af_specific 本身是一个间接调用点(*(af_specific+0x18),rdi = transport),因此一旦存在泄露,这就是目标。setsockopt(SCTP_AUTH_KEY) 会落入同一个 kmalloc-1k 缓存。请记住头部是 struct sctp_authkey { assoc_id; keynumber; keylength; key[] },并且需要先通过带有 sctp_assoc_value 的 SCTP_AUTH_SUPPORTED 在每个套接字上启用 AUTH——普通的 int 会得到 EINVAL。这是一个触发/崩溃 PoC。它不是权限提升,本仓库也不声称提供权限提升。请在虚拟机中针对你自己的内核运行。
已在以下环境测试:7.1.5+kali-amd64(Kali rolling)和 7.0.12+kali-amd64。
逻辑中源自内核的部分采用 GPL-2.0;其余部分可随意使用。仅限授权的安全测试和研究用途。