导语:一段 2007 年写下的 SCTP 动态地址重配置代码,在 2026 年被一支多智能体 AI 流水线重新发现——不只是把漏洞重现出来,而是把它做成了一条从内核 UAF(释放后使用)一路走到主机 root 反弹 shell 的完整利用链。这个漏洞编号 CVE-2026-64564,在上游 commit
9b2854f86f0b中修复。
转载声明:本文为转载翻译,原文作者为腾讯朱雀实验室(Tencent Zhuque Lab)与 TencentOS 安全团队,原文标题 “SCTPhantom: An 18-Year-Old SCTP ASCONF Transport Use-After-Free”,原文地址:https://matrix.tencent.com/en/2026/08/06/sctphantom-CVE-2026-64564 。本文仅做翻译与必要的转载标注,不改动原文技术内容;图片均为原作者团队所有,本文按”压缩转载”形式保留原文配图。
TL;DR(原文摘要):SCTPhantom 是 Linux 内核 SCTP(流控制传输协议)动态地址重配置(Dynamic Address Reconfiguration)中的一处 use-after-free。一段精心排列的 ASCONF(地址配置变更)序列可以把一个传输对象(transport)从关联(association)中删除,然后让代码继续持有这个被删除对象的悬空指针,最终让关联里残留指向已释放路径的引用。Corvus AI 把这个初始发现做成可复现的漏洞,并在所测系统上演示了完整的本地提权与容器逃逸利用。该问题编号为 CVE-2026-64564,已由上游 commit 9b2854f86f0b 修复。
一、背景(Background)
在 SCTP 这类协议代码里挖洞,通常意味着要把状态机一层层扒开——单遍分析器(single-pass analyzer)很难跟上状态跳转。编码智能体(Coding Agent)已经可以承担大量机械工作:在庞大的内核源码树里导航、迭代概念验证(PoC)、编译并启动内核、收集 sanitizer 报告或 panic 日志,再根据这些结果设计下一轮实验。
编码智能体虽然能覆盖大部分执行循环,但要对内核漏洞做完整的研究,仍需要在”代码生成”层面之上的研究决策——这些决策和它们对应的证据,必须在多次任务和多模型之间被一致地保存下来。
Corvus AI(由 TencentOS 安全团队,即腾讯朱雀实验室联合开发)把这一过程变成一条持久化的、多智能体协作的漏洞研究流水线。
FIGURE 1 — Corvus AI:操作系统漏洞研究流水线。

SCTPhantom 就是这条流水线跑出来的一个具体案例。该漏洞位于 Linux SCTP 动态地址重配置子系统中,根源是:用于校验 DEL-IP 操作(删除 IP 操作)的”地址”和后续 ASCONF 处理所保留的”传输对象(transport)”之间存在不一致。
下文会展开说明这个漏洞是如何被发现与验证的,又是如何被做成一条完整的提权链,最后说明上游是如何修复的。
二、漏洞详情(Vulnerability)
2.1 发现过程(Discovery)
调查阶段使用一份结构化的 SCTP 研究计划来界定搜索空间,覆盖对端可控字段、ASCONF 参数顺序、多宿主(multihoming)状态以及传输对象的所有权关系等维度。Corvus AI 把这些维度拆成有边界的源代码分析、数据包生成、崩溃分诊(crash triage)和虚拟机复现等任务。
把 IPv4 数据包源地址和 ASCONF Address Parameter(地址参数)所选中的传输对象分开处理后,问题浮出水面:通过变换对端传输对象的状态,测试得到一个可复现的场景——后续某个 socket 操作访问到了已经被释放的传输对象。冷启动复现确认了这是一个 use-after-free。
2.2 SCTP 与 ASCONF(SCTP and ASCONF)
SCTP(Stream Control Transmission Protocol,流控制传输协议)由 RFC 4960 定义,是一种支持多宿主的面向消息的传输协议。一个 association(关联)可以包含多条到对端的路径。Linux 用 struct sctp_association 表示 association,用 struct sctp_transport 表示每条路径,多个 transport 通过 asoc->peer.transport_addr_list 串成链表。
这个 bug 的关键在于 association 上缓存的两个指针:
struct sctp_association
peer.transport_addr_list -> [ transport A ] -> [ transport L ] -> [ transport C ]
peer.primary_path -------------------------------^
peer.active_path -------------------------------^primary_path 和 active_path 应当指向归属于本 association 的、仍然存活的 transport。
RFC 5061 给 SCTP 加上了”动态地址重配置”扩展。一段 ASCONF chunk 包含一个 Address Parameter(地址参数)以及若干顺序排列的操作参数,常见的有 ADD-IP(添加 IP)、DEL-IP(删除 IP)和 SET-PRIMARY(设置主路径)。Linux 按消息顺序依次处理这些操作参数。
2.3 根因(Root Cause)
触发漏洞的 ASCONF chunk 同时使用两种不同的”身份”:IPv4 数据包源地址 S,以及通过 Address Parameter 选中 transport 用的地址 L。DEL-IP 的检查会把请求删除的地址和 S 做比对,而后续处理则依赖 L 选中的那个 transport。
这就允许以下面的顺序来构造请求:
[ Address Parameter L ] [ DEL-IP L ] [ DEL-IP 0.0.0.0 ]
由于 S 和 L 不同,DEL-IP L 这一步能通过源地址校验并把 transport(L) 删除掉;接下来用通配 DEL-IP 0.0.0.0 删”当前保留路径”时,代码复用的还是指向刚被删除的 transport 的那个缓存指针。结果就是:association 的 primary_path 和 active_path 里继续保留着已被删除的 transport,后续的 socket 操作会去解引用这个悬空指针。
上游修复通过拒绝”删除当前 ASCONF chunk 所保留的传输对象”这一做法,闭合了身份不一致的窗口。
三、利用链(Exploit Chain)
完整利用链如下:
幸存的 SCTP transport UAF
→ UAF #1 被 pg_vec 重新占用
→ 直通映射页(direct-map page)地址泄露
→ 可重复的 4 字节内核读
→ 基于 IDT 的 KASLR 恢复
→ UAF #2 被受控的 SCTP authentication-key 数据重新占用
→ 受控的内核对象图(object graph)
→ 面向数据的 commit_creds()
→ 拿到全局 root
→ usermode-helper 变体
→ 容器逃逸到宿主机
Corvus AI 把每一阶段的证据和约束都保存下来,使得这条链可以被一步步增量地开发和验证。
3.1 让 UAF 稳定存活(Surviving UAF)
利用要稳定成立,前提是同时满足三个条件:
- transport 已经完成 RCU(Read-Copy-Update,一种延迟回收机制)释放
- 与此同时,association 还活着
- 用户态仍然能触达这条”已经不合法”的路径
直接删 ACTIVE 状态的主路径会保留 association,但定时器和其他引用会让 transport 没法被回收。删 UNCONFIRMED 状态的从路径虽然能让 release 走起来,但后续协议工作会把整个 association 拆掉。最终稳定的方案是:删一个”已确认的、ACTIVE 状态”的从路径。
稳定的搭建步骤是:
创建一个多宿主 association
→ 反复触发心跳,直到目标从路径变为 ACTIVE
→ 关掉目标以及剩余路径上的心跳
→ 注入 [Address 目标] [DEL 目标] [通配 DEL]
→ 让 ASCONF 处理返回
→ 等待 RCU 释放
→ 通过仍然存活的 socket 访问那条"已经不合法"的路径
实现上把 ASCONF 数据包伪造的源地址做黑洞处理(blackhole),让 ASCONF-ACK 没有办法在刚刚被释放的那个槽位上再分配一个新的 transport。CPU 亲和性(affinity)把”收包处理、RCU 回调、回收分配”绑定在同一个 per-CPU slab(内核对象缓存)路径上,让可靠性敏感阶段始终落在同一个 CPU。
后面执行 getsockopt(SCTP_STATUS),活着的 socket 会一路摸到那条已被释放的主路径。KASAN(Kernel AddressSanitizer,内核内存错误检测器)确认了 slab 上的 UAF,并且能在 sctp_transport_new() 和 RCU 回调里看到完整的分配栈与释放栈。
3.2 pg_vec 泄露(pg_vec Leak)
在用作代表性目标的 6.6 内核上,struct sctp_transport 走 kmalloc-1024(内核通用 1024 字节内存分配)通道。一个块数为 128 的 TPACKET V1 发送环(transmit ring)会分配一个 1024 字节的 pg_vec 数组:
pg_vec allocation
+0x000 page pointer 0
+0x008 page pointer 1
+0x010 page pointer 2
+... ...
+0x3f8 page pointer 127
当 pg_vec 重新占用了 transport 那个槽位后,SCTP_STATUS 会把若干个页指针当成 transport 字段读出来。在目标内核版本上,返回的 srtt(平滑往返时间)和 cwnd(拥塞窗口)两个值拼起来正好能还原出一个 64 位的内核页地址。
更重要的是,pg_vec 里的每个条目都指向攻击者进程已经映射过的一页。因此这次泄露同时给出同一物理页的两个虚拟地址:
用户态映射 <---- 同一页 ----> 内核 direct-map 地址
攻击者可以通过用户态映射写入伪造的内核对象,然后通过泄露出来的 direct-map 地址去引用它们。后续所有伪造对象都被限制在这一页之内。这种做法避免了一开始”以为 TPACKET 各块在物理上连续”的错误假设。
只有当返回的值是规范(canonical)地址、页对齐,并且通过用户态映射写入的数据能够从预期内核指针读到时,这次泄露才算成功。回收失败的情况会被判定为重试一次受害 association。
3.3 任意读(Arbitrary Read)
SCTP_STATUS 会回传与所选 transport 相关联的 association id,等价操作大致为:
spinfo_assoc_id = sctp_assoc2id(transport->asoc);
只要能控制 transport->asoc,想读目标地址 T 上的一个字(word),只需要把 transport 设置成:
transport->asoc = T - offsetof(struct sctp_association, assoc_id)
这样返回的就是:
SCTP_STATUS.assoc_id = *(u32 *)T
从而得到一个可重复的 4 字节内核读。周围被解释的字段被填成受控值,让 status 通路保持合法。利用代码会用独立的”受害 association”专门做读,并丢弃那些没通过回收标记、规范性检查或已知值校验的尝试。
3.4 基于 IDT 的 KASLR 绕过(IDT KASLR)
读取原语从 CPU entry area(CPU 入口区)里那个固定、只读的 IDT(Interrupt Descriptor Table,中断描述符表)映射中读取 gate 0(0 号门)来恢复 text KASLR(Kernel Address Space Layout Randomization,内核地址空间布局随机化)偏移。在测试内核上 UMIP(User-Mode Instruction Prevention,用户态指令阻止)使得 sidt 在用户态拿到的东西没有利用价值,所以利用代码通过上面的内核读原语去读这个映射。
一次 32 位读能拿到门描述符中间那一段偏移位,结合已知的低位、规范的高位以及目标的对齐要求,就能还原 asm_exc_divide_error 的运行期地址:
runtime_handler = reconstruct(IDT gate 0)
KASLR_slide = runtime_handler - link_time_asm_exc_divide_error
这个偏移在套用到 commit_creds 等链接期符号前会被先验证一遍。
3.5 commit_creds(拿到 root)
第二条”受害 association”用来装一个完全可控的 transport 大小的对象。在所测的 6.6 内核上,msg_msg 走 GFP_KERNEL_ACCOUNT 分配,落进 kmalloc-cg-1024,而 SCTP transport 落在通用 kmalloc-1024。持续存在的 SCTP authentication-key 分配能在匹配的缓存里塞入受控字节。
利用代码先把 association 和目标 transport 的地址记下来。LIST_HARDENED 要求被回收的数据包的空 chunk_list 必须指向它自己的 list-head 地址,所以回收对象的构造是面向”那个 transport 槽位的具体地址”的,而不是写成与位置无关的数据。
受支撑的对象图正好落在 UAF #1 泄露出来的那张 direct-map 页里:
stale association active_path
|
v
[ reclaimed transport / embedded packet state ]
|
+---- packet->transport ------> [ fake transport ]
|
+---- asoc ------> [ fake association ]
| |
| +-- base.sk --> [ fake socket / cred ]
|
+---- af_specific -> [ fake SCTP address-family ops ]
|
+---- dst --------> NULL
伪造的 address-family 操作表里,get_dst 用一个普通的返回函数,get_saddr 用 commit_creds 的运行期地址。伪造的 association 让 base.sk 指向攻击者控制的一页内存,其中填上 SCTP 期望的 socket 字段和一份特权凭据(cred)字段。dst 置为 NULL,让凭据操作执行完之后就停止包构造。
在最后触发之前,SCTP_STATUS 必须能从伪造 association 返回一个像 0x1337beef 这样的标记值,证实:目标 authentication-key 分配确实抢占了那个悬空的 transport,并且整张受控对象图是连通的。
最终的触发点是异常关闭(abortive close):
setsockopt(SO_LINGER, { on = 1, linger = 0 })
→ close(victim_socket)
→ SCTP ABORT generation
→ output-queue flush
→ sctp_transport_route()
→ 受控的 af_specific->get_saddr(fake_sk, ...)
→ commit_creds(controlled_cred)
这条链路复用了内核已有的代码段,既不需要用户态 shellcode,也不需要传统的 ROP(Return-Oriented Programming,返回导向编程)链。是否真的拿到了全局 root,是通过比较触发前后能否访问 /etc/shadow、能否在 /root 下创建一个属主为 root 的文件来验证的,而不仅仅是看 UID 变了没。
FIGURE 2. 四个不同 Linux 发行版上的本地提权实测。

3.6 容器逃逸(Container Escape)
接下来用 Corvus AI 评估这个 SCTP 漏洞在容器环境下的影响。完整的容器链路从”在容器里执行 exploit 命令”开始,到”在宿主机的初始命名空间里完成一个动作”结束。
早期的 PoC 走的是开 net.sctp.addip_enable 和 net.sctp.addip_noauth_enable 这两个 sysctl(sysctl 是 Linux 内核运行时配置接口),看起来必须有 CAP_NET_ADMIN(网络管理权限)才能跑。Corvus AI 后来发现一条不需要改这两个 sysctl 的路:SCTP_ASCONF_SUPPORTED 和 SCTP_AUTH_SUPPORTED 可以在每个 socket 上单独打开这两个特性,触发包携带一个合法的 AUTH chunk。
前几阶段保持不变:
容器内的 SCTP 触发
→ 宿主机 root 文件系统操作
FIGURE 3. 容器逃逸。

最后一次验证保留默认 seccomp(Secure Computing,安全计算模式)配置,没有给容器 CAP_NET_ADMIN 也没有给 CAP_SYS_ADMIN。八次尝试中有六次成功打到宿主机 root;剩下两次以”指针走访问失败”的方式干净停下来,没有触发内核 panic。SCTP 是否可用、是否允许 raw socket 和 packet socket、user-namespace 策略、seccomp、能力(capabilities)以及 LSM(Linux Security Module,Linux 安全模块)策略,都会影响其他环境下的暴露面。
最终的受控回调通过 call_usermodehelper_exec() 触发,它把 subprocess_info 结构放在前面泄露出来的那张内核映射页上。内核 helper 在初始命名空间里执行,从而完成对宿主机的操作。
3.7 验证情况(Validation)
利用在多个内核和发行版目标的不同深度上做了评估。
| 目标 | 内核 | 结果 |
|---|---|---|
| 研究内核 | Linux 7.2-rc2 | Root |
| OpenCloudOS 系列目标 | 6.6.119 | Root |
| Debian 13 | 6.12.95+deb13-amd64 | Root |
| Rocky Linux 9 / RHEL 9 | 5.14 厂商内核 | Root(需加载 SCTP 模块) |
| Ubuntu 24.04 | 6.8.0-134-generic | Root |
这些结果同样为评估影响提供了依据。按 CVSS v4.0 基础指标打分:
CVSS v4.0 基础分(CVSS-B):8.5(高危)
向量:CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
四、上游修复与时间线(Upstream Fix and Timeline)
4.1 上游修复(Upstream Fix)
上游补丁拒绝删除 ASCONF chunk 自身所保留的 transport:

peer = sctp_assoc_lookup_paddr(asoc, &addr);
这段对比保护的是后续通配分支真正要使用的那个对象,而不仅仅校验数据包源地址。该修复改动的是 net/sctp/sm_make_chunk.c。主线 commit 是 9b2854f86f0b;引入这段脆弱序列的是 Linux 2.6.25 的 commit 42e30bf3463c。
各厂商内核可能会在 backport(向旧版本回移植补丁)这条修复的同时保留一个较老的基础版本。版本字符串本身不足以判断是否受影响;具体修复状态请以厂商公告或源码包为准。
五、原文结语
本案例展示了一条持久化、证据驱动的研究工作流的价值。Corvus AI 把协议研究模板转成有边界的实验,保留负面结果(negative results),让同一份证据链从”源码分析 → 冷启动复现 → 利用开发 → 上游修复”一路贯穿。
最后,这个发现本身带有一份可复用的”假设、约束、验证步骤”说明——只要按这份说明去复现和评估,结论就成立。整个结论建立在这种”完整记录”之上,而不是某一次成功运行的偶然结果。
版权声明:本文为转载翻译,原文版权归腾讯朱雀实验室(Tencent Zhuque Lab)与 TencentOS 安全团队所有,原文地址:https://matrix.tencent.com/en/2026/08/06/sctphantom-CVE-2026-64564 。图片均按”压缩转载”形式保留原作者团队原图,特此致谢。
所有研究日期均使用中国标准时间(UTC+8)。














暂无评论内容