Linux SCTP CVE-2026-52924 深度分析
2026 年 6 月,Linux 内核合入了一个只有一行的补丁。它修掉的问题,躺在 SCTP 协议栈里将近九年。
补丁本身平淡无奇,把一条"重传"命令换成一条"清空"命令。但它背后是一个典型的生命周期错误:内核在某条状态回滚路径上释放了一块内存,却忘了通知那个还指着它的调度器缓存指针。等到调度器下次出队时,指针已经指向了被回收的 slab。
这个漏洞编号 CVE-2026-52924,官方定级 7.0,Red Hat 的定性是远程拒绝服务。而 2026 年 8 月之后,GitHub 上出现了一份完整的利用代码,把它从"内核崩溃"做成了"本地提权到 root"。
https://github.com/NebuSec/CyberMeowfia/tree/main/security-research/Linux-CVE-2026-52924-ubuntu-7.0.0-28/
本文要处理的是这条信息链上最容易被混淆的部分:官方口径说它是远程 DoS,第三方 PoC 证明它能本地提权,这两件事同时成立,却不是同一件事。本文先把影响范围逐层钉死,再拆根因与利用链,收尾给可执行的自查命令。

一,1行补丁与九年潜伏
1.1 补丁长什么样
修复补丁改动 net/sctp/sm_statefuns.c 一个文件,净减 5 行、净增 1 行,落在 sctp_sf_do_5_2_6_stale() 函数内:
@@ -2598,11 +2598,7 @@ staticenum sctp_disposition sctp_sf_do_5_2_6_stale(
*/
sctp_add_cmd_sf(commands, SCTP_CMD_DEL_NON_PRIMARY, SCTP_NULL());
- /* If we've sent any data bundled with COOKIE-ECHO we will need to
- * resend
- */
- sctp_add_cmd_sf(commands, SCTP_CMD_T1_RETRAN,
- SCTP_TRANSPORT(asoc->peer.primary_path));
+ sctp_add_cmd_sf(commands, SCTP_CMD_PURGE_OUTQUEUE, SCTP_NULL());
/* Cast away the const modifier, as we want to just
* rerun it through as a sideffect.两条命令的语义差异是整个漏洞的核心。SCTP_CMD_T1_RETRAN 要求协议栈把与 COOKIE-ECHO 捆绑发出的数据重新排队等待重传,这些重传数据仍然持有旧流状态(stream state)的引用。SCTP_CMD_PURGE_OUTQUEUE 则直接拆除出队结构,丢弃全部待发与重传状态。
命令的实现在 net/sctp/sm_sideeffect.c,只有一行:
case SCTP_CMD_PURGE_OUTQUEUE:
sctp_outq_teardown(&asoc->outqueue);
break;补丁作者没有选择"把悬垂指针置空"这种就地修补,而是选择了"清空整个出队"。原因写在提交信息里:只修 stream->out_curr 是不够的,因为已入队和可重传的数据仍然会引用旧流状态,在后续出队路径上继续触发释放后使用。这是一个正确的判断,局部置空只能堵住一条解引用路径,堵不住数据面的引用泄漏。
1.2 时间跨度:从 4.14 到 2026
补丁提交信息里带着 Fixes: 标签,指向引入问题的原始提交:
Fixes:5bbbbe32a431 ("sctp: introduce stream scheduler foundations")2017 年 10 月进入 net-next,对应 Linux 4.14 的合入窗口。修复提交由 Xin Long 签署于 2026 年 6 月 3 日,Greg Kroah-Hartman 于 2026 年 6 月 19 日完成 stable 回移,主线提交为 e374b22e9b07b72a25909621464ff74096151bfb,对应 stable 分支提交 1d4652f677906a64487c13f9ace54b0eb263b5d0,共产生了 8 个 stable 分支回移提交。
从 2017 年 10 月到 2026 年 6 月,这个生命周期错误在内核里存活了约八年八个月。
九年潜伏期的价值判断要克制。它说明这条状态回滚路径在真实流量中极少被走到,也说明 SCTP 子系统的代码审计密度长期偏低。它并不能直接推导出"危害极大",因为可利用性取决于路径能否被稳定触发,这一点由后续的 AC:H 评分体现。
1.3 报告者与发现方式
补丁提交信息列出了 7 位报告者,覆盖 Yuan Tan、Yifan Wu、Juefei Pu、Zhengchuan Liang、Xin Liu、Yuqi Xu、Ren Wei。KASAN 报告中 Comm: mini_poc、UID: 1001 的字段说明触发它的是用户态程序而非真实网络流量,运行环境是打开 KASAN 的调试内核 7.1.0-rc1。
七人共同署名、触发程序命名为 mini_poc、运行在 KASAN 调试内核上,这组特征指向一次系统性的代码审计或模糊测试工作,而不是偶遇崩溃。这类发现方式通常意味着漏洞路径已经被完全掌握,后续出现稳定利用只是时间问题。
二、影响范围:三个开关决定真实暴露面
影响范围是本次核实中最需要澄清的部分。大量二手报道把 CVSS 的 AV:N(攻击向量为网络)直接读成"可以被远程打穿",这是不成立的。本文按三个相互独立的开关来判定。
2.1 开关一:内核版本
漏洞存在于 Linux 4.14 以来的几乎所有主线版本,直到各 stable 分支的修复版本。Ubuntu 官方给出的修复状态如下(节选主内核包):
云与场景内核(aws、azure、gcp、kvm、fips、hwe 系列)绝大多数已有对应修复包。截至本次核实时,仍标记为"修复中"的只有两项:linux-azure-nvidia(24.04 LTS)与 linux-aws-fips(20.04 LTS FIPS Updates)。
这里有一个值得注意的对照。公开 PoC 的目录名是 Linux-CVE-2026-52924-ubuntu-7.0.0-28,而 Ubuntu 26.04 的修复版本是 7.0.0-29.29。也就是说 PoC 瞄准的正是修复前的末个版本,作者对环境边界的标注是准确的。
判定是否受影响不能只看 uname -r 的数字大小。发行版会把安全补丁回移到旧内核分支,一个版本号更低的厂商内核可能已经包含修复。判定依据是发行版的安全公告与包变更日志,不是上游版本号。
2.2 开关二:SCTP 协议栈是否可用
漏洞代码位于 SCTP 子系统,SCTP 不可用时该路径根本不存在。判定方式有三种结果:
# 模块当前是否已加载
lsmod | grep -w sctp
grep -w sctp /proc/modules
# 内核编译配置
grep CONFIG_IP_SCTP /boot/config-$(uname -r)
# =m 编译为可加载模块
# =y 直接编译进内核,无法卸载
# 无输出 内核不支持
# 模块文件是否存在(存在即可能被按需加载)
find /lib/modules/$(uname -r) -name 'sctp.ko*' 2>/dev/null需要区分的是,Ubuntu 与 Red Hat 系在这一个开关上的默认状态不同。Red Hat 公开说明其 sctp 内核模块在 RHEL 8、9、10 上默认是禁用状态,通过 kernel-modules-extra 提供并配合黑名单阻止非特权自动加载。Ubuntu 通用内核通常编译为 =m,模块文件存在即可被加载。
这一差异使得同等内核代码状态下,Red Hat 系的真实暴露面显著小于 Ubuntu。
同样是"内核版本受影响",能否走到漏洞代码取决于功能是否启用。这一类判定在漏洞运营中最常被跳过,也最常造成修复资源的错配。把"版本受影响"当成"可被利用",会让大量实际无风险的主机排在修复队列前列。
2.3 SCTP 到底用在哪
判断暴露面需要先知道 SCTP 在真实环境中出现在什么地方。SCTP(流控制传输协议,Stream Control Transmission Protocol)是一个面向消息的传输层协议,兼具 TCP 的可靠性与 UDP 的消息边界,还额外提供多宿主(multi-homing)与多流(multi-streaming)能力。它的主要落地场景集中在两类:
一、电信与信令网络。SIGTRAN 协议族在 SCTP 上承载 SS7 信令,涉及 M3UA、SUA、H.248 等适配层。移动通信核心网的多个接口同样依赖 SCTP,包括 LTE 的 S1-MME、5G 的 NGAP,以及 Diameter 与部分 GTP-C 场景的承载。这类系统一旦运行 SCTP,往往是持续存在的长连接关联,与本次漏洞要求的"进行中的 SCTP 关联"高度吻合。
二、实时通信与部分集群组件。WebRTC 的数据通道在协议上基于 SCTP,但服务端实现多为用户态协议栈,不经过内核 SCTP 模块。部分分布式存储与集群中间件在特定配置下会启用 SCTP。
与之相对,常见的 Web 服务器、数据库、缓存、办公桌面与大多数通用云主机几乎不会用到 SCTP。对这些资产而言,漏洞代码虽然存在于内核中,但处于不可达状态。
这解释了为什么一个 CVSS 7.0 且标记为网络可达的内核漏洞,实际需要紧急处置的资产范围并不大。真正的风险集中在电信核心网、信令网关、承载网元以及运行相应容器化网元的云节点上。资产分类时应当按业务类型而非按操作系统版本来划分优先级。
2.4 开关三:用户命名空间是否可创建
公开 PoC 走的是本地提权路径,它的第一个动作是创建用户命名空间与网络命名空间:
if (unshare(CLONE_NEWUSER) < 0)
die("unshare user");
/* 写入 setgroups / uid_map / gid_map */
if (unshare(CLONE_NEWNET) < 0)
die("unshare net");如果 unshare(CLONE_NEWUSER) 被拒绝,整条利用链在此终止,漏洞退化为一个需要特权才能触发的崩溃。
这里要特别说明 Ubuntu 的情况。自 24.04 LTS 起,Ubuntu 内核配合 AppArmor 默认启用了非特权用户命名空间限制,sysctl 值为 1:
sysctl kernel.apparmor_restrict_unprivileged_userns
# 默认输出 1该机制会在非特权非受限进程调用 unshare(CLONE_NEWUSER) 时,把新任务强制置入 unprivileged_userns 配置,其中的 audit deny capability 规则会剥夺命名空间内的一切 capability 检查。理论上这应当挡住"进 userns 拿 CAP_NET_ADMIN 再打内核网络路径"这一整类本地提权。
但 PoC 在 unshare 之前做了两件事:
change_profile("/usr/lib/snapd/snap-confine");
change_profile("plasmashell");这两行的作用是把自身切换到 AppArmor 的 flags=(unconfined) 配置。带 unconfined 标志的配置对策略引擎是空操作,任务在此类配置下创建用户命名空间时,LSM 不会把它改写为 unprivileged_userns,capability 也就不被剥夺。任何非特权进程都可以通过向 /proc/self/attr/exec 写入 exec 后调用 execv 自行切换到已加载的配置,配置所附的路径只是策略匹配用,不构成前置条件。
Ubuntu 基础 apparmor 包中自带多个 flags=(unconfined) 且授予 userns 权限的配置,这类配置在 Server、Desktop、Cloud 各类镜像上都会落地。Qualys 曾就这一类绕过向 Ubuntu 安全团队报告,Ubuntu 的公开立场是这些属于加固措施的已知局限而非安全漏洞。
这是本次分析中运维最应当记住的一点。Ubuntu 的 userns 加固确实挡住了朴素形态的本地提权,但它被同一发行版基础包自带的配置所绕过。启用加固不等于关闭攻击面,评估时要看加固机制是否存在默认可达的旁路,而不是只看 sysctl 的开关值。
2.5 AV:N 的真实含义
CVE 的攻击向量记为 Network,这在技术上是正确的:触发路径由网卡收到 SCTP 报文后进入协议栈处理,sctp_rcv 位于接收侧,不需要本地交互。
但 AV:N 不等于"互联网上任意主机都能打"。要走到 sctp_sf_do_5_2_6_stale(),目标主机上必须存在一个处于 COOKIE_ECHOED 状态的 SCTP 关联(association)。建立关联要求目标侧有 SCTP 监听端点,或者至少内核正在处理发往该关联的包。向一个没有 SCTP 监听的端口发 INIT,得到的只会是 ABORT。
攻击复杂度记为 High(AC:H)也印证了这一点。Red Hat 的说明写得很清楚:AC:H 是因为触发依赖特定的关联建立过程与 stale cookie 回滚序列,不是随便发一个包就能命中。
把 AV:N 翻译成"可被远程攻击"是漏洞传播中最常见的失真。Network 只描述攻击者的位置,不描述可达性前提。真正决定风险的是"有多少资产同时满足三个开关",这个数字通常远小于"版本受影响的资产数"。
三、原因:是一次状态回滚留下的悬垂指针
3.1 SCTP 握手与 Stale Cookie
SCTP 建立关联使用四次握手,其中 COOKIE 机制用于防御资源耗尽型攻击。服务端收到 INIT 后不立即分配状态,而是把参数编码成状态 cookie 回给客户端,自身停留在 COOKIE_WAIT 状态。客户端回 COOKIE-ECHO,服务端验证后才进入 ESTABLISHED。
COOKIE-ECHOED 是客户端发出 COOKIE-ECHO 之后的中间状态。此时客户端可能已经把用户数据与 COOKIE-ECHO 捆绑发出,这是 SCTP 允许的捆绑传输。
Stale Cookie ERROR 表示对端认为这个 cookie 已经过期。收到它之后,内核要把关联从 COOKIE_ECHOED 回滚到 COOKIE_WAIT,重新走一遍握手。处理这个回滚的函数就是 sctp_sf_do_5_2_6_stale(),函数名中的 5.2.6 对应 RFC 4960 的相应章节。
3.2 sctp_stream_update 的契约与破窗
回滚过程中会调用 sctp_stream_update() 重建流状态。该函数释放旧的流表并安装新表,其原始实现(2017 年引入)骨架如下:
void sctp_stream_update(struct sctp_stream *stream, struct sctp_stream *new)
{
struct sctp_sched_ops *sched = sctp_sched_ops_from_stream(stream);
sched->unsched_all(stream);
sctp_stream_outq_migrate(stream, new, new->outcnt);
sctp_stream_free(stream); /* 旧流表在此释放 */
stream->out = new->out;
stream->outcnt = new->outcnt;
stream->incnt = new->incnt;
sched->sched_all(stream);
new->out = NULL;
new->in = NULL;
}问题出在 stream->out_curr 这个字段。它是出队调度器的缓存指针,指向当前正在服务的 sctp_stream_out 条目。sctp_stream_free(stream) 释放了旧的 stream->out 数组,但整个函数从未触碰 out_curr。
设计上这不算疏忽。sctp_stream_update() 只在关联进入 COOKIE_WAIT 时被调用,此时按状态机的语义不应有用户数据被发出,out_curr 理应是干净的。补丁提交信息把这个前提写得很明确:outbound stream scheduler state 被期望是 clean 的。
Stale Cookie 这条路径破坏了这个前提。在 COOKIE_ECHOED 状态下回滚时,用户数据可能已经入队,甚至已经与 COOKIE-ECHO 捆绑发出,out_curr 不再是 NULL,而是指向旧流状态中的一个真实条目。旧流状态随后被 sctp_stream_free() 释放,out_curr 就此悬垂。
这是一个典型的隐式契约被边界路径破坏的案例。函数正确性依赖"调用时 out_curr 必为 NULL"这一未被断言、未被文档化的前提,而引入新的调用路径时没有人重新检查这个前提。这类缺陷在状态机密集的协议栈中尤其常见,也更难通过局部代码审查发现。
3.3 悬垂指针在哪里被解引用
同一个 2017 年提交引入了 FCFS(先到先服务,First Come First Serve)调度器,其出队函数如下:
static struct sctp_chunk *sctp_sched_fcfs_dequeue(struct sctp_outq *q)
{
struct sctp_stream *stream = &q->asoc->stream;
struct sctp_chunk *ch = NULL;
struct list_head *entry;
if (list_empty(&q->out_chunk_list))
goto out;
if (stream->out_curr) {
ch = list_entry(stream->out_curr->ext->outq.next,
struct sctp_chunk, stream_list);
} else {
entry = q->out_chunk_list.next;
ch = list_entry(entry, struct sctp_chunk, list);
}
sctp_sched_dequeue_common(q, ch);
out:
return ch;
}关键在 stream->out_curr->ext->outq.next 这一串解引用。它做了两级解引用:第一级取 out_curr 指向的 sctp_stream_out,第二级取其 ext 成员指向的 sctp_stream_out_ext 结构中的链表头。悬垂的 out_curr 让第一级读到已释放内存,第二级读到的地址完全由释放后该内存的内容决定。
这段解引用从 2017 年起就存在于内核中,当时它是正确的。它变成缺陷,是在 2026 年被证明存在一条能让 out_curr 悬垂的调用路径。
漏洞代码与缺陷代码在时间上相隔九年、在空间上分属两个文件,这正是此类漏洞难以被发现的原因。sctp_sched_fcfs_dequeue 本身没有错,它有权假设 out_curr 有效;错的是另一处代码破坏了这个假设却没有同步清理缓存。排查时如果只盯 KASAN 报告的崩溃点,会误判为调度器的边界检查缺失。
3.4 补丁为什么选择清空而非置空
补丁提交信息里有一段关键说明:
Updating only stream->out_curr would be insufficient, since queued and retransmittable data would still reference the old stream state and trigger later use-after-free in dequeue paths.
这句话点出了修复方案的选择依据。假设只把 out_curr 置为 NULL,出队函数会走 else 分支从 out_chunk_list 取数据,但这个链表上的 sctp_chunk 仍然持有旧流状态的引用,后续处理同样会解引用到已释放内存。
清空出队(SCTP_CMD_PURGE_OUTQUEUE)的做法是让整个出队结构连同其上所有待发与可重传数据一起丢弃。代价是回滚时不再尝试重传与 COOKIE-ECHO 捆绑的数据,语义上这是一次重新握手,丢弃未完成数据是可接受的。
这里能看到修复者的取舍判断。补丁用"放弃重传来换取状态一致性",牺牲的是边缘场景下的传输效率(Stale Cookie 本就罕见),换来的是不留残余引用的确定性。相比之下,精细地逐个修正引用链会引入更多分支与新的出错可能。安全补丁的正确性优先于功能完备性,这个取舍是对的。
3.5 调度器不止一个
提交信息中提到出队路径依赖 stream->out_curr->ext 的调度器包括 FCFS、RR(轮询,Round Robin)、PRIO(优先级,Priority)等。KASAN 报告落在 FCFS 上,是因为 FCFS 是默认调度器。通过 socket 选项切换调度器后,其余调度器的出队函数同样会解引用 out_curr。
SCTP 的调度器选择通过 SCTP_STREAM_SCHEDULER socket 选项设置,这是 2017 年同一系列补丁引入的用户态接口。
影响面不止默认配置。默认调度器只是最容易被触发的一条路径,切换调度器不能规避漏洞,反而会改变崩溃点。这一点对排查有帮助:崩溃栈中的函数名会随调度器不同而变化,但根因一致,不要因为函数名不匹配就排除本 CVE。
四、触发:从状态机到 slab 释放
4.1 崩溃调用链
KASAN 报告给出的栈回溯自顶向下如下:
BUG: KASAN: slab-use-after-free in sctp_sched_fcfs_dequeue+0x13a/0x140
Readofsize8at addr ff1100004d4d3208 by task mini_poc/9312
CPU: 1 UID: 1001 PID: 9312 Comm: mini_poc Not tainted
7.1.0-rc1-00305-gbd3a4795d574 #5 PREEMPT(full)
sctp_sched_fcfs_dequeue+0x13a/0x140
sctp_outq_flush+0x1603/0x33e0
sctp_do_sm+0x31c9/0x5d30
sctp_assoc_bh_rcv+0x392/0x6f0
sctp_inq_push+0x1db/0x270
sctp_rcv+0x138d/0x3c10逐层解读这条链:
sctp_rcv是 SCTP 的报文入口,由 IP 层在处理 IPPROTO_SCTP(132)协议号时调用。
sctp_inq_push把报文推入关联的入队。
sctp_assoc_bh_rcv在下半部(bottom half)处理入队中的报文。
sctp_do_sm是状态机主分发函数,根据当前状态与事件类型选择处理函数。
sctp_outq_flush刷新出队,把待发数据组装成报文。
sctp_sched_fcfs_dequeue从出队中取出下一个待发块,崩溃在此发生。
读取大小为 8 字节,对应 64 位指针的读取,与 stream->out_curr->ext 中 ext 成员的宽度一致。
栈回溯里最值得注意的不是崩溃点,而是崩溃发生的上下文为接收侧。整条链由收到报文驱动,出队刷新是状态机处理的副产物。这说明触发者不需要主动调用发送接口,只要能让目标进入正确的状态序列,出队动作会由协议栈自行完成。这也是攻击向量能记为 Network 的技术依据。
4.2 触发需要满足的时序
要让 out_curr 悬垂并被解引用,需要同时满足几个条件:
一、关联进入 COOKIE_ECHOED 状态。
二、在 COOKIE_ECHOED 状态下有用户数据入队,或与 COOKIE-ECHO 捆绑发出。
三、收到 Stale Cookie ERROR,触发回滚到 COOKIE_WAIT。
四、回滚后重新触发出队刷新,让调度器读到悬垂的 out_curr。
条件二到条件四之间存在时序窗口。数据必须在回滚前入队,出队必须在释放后发生。这个窗口由内核的定时器与对端的报文节奏共同决定,攻击者需要一定的控制能力才能稳定命中。
这组条件解释了 AC:H 的由来。它不是指配置复杂,而是指状态机时序难以稳定复现。对防御方而言这是好消息,它意味着随机扫描式的攻击难以成功;对攻击者而言,一旦掌握了稳定的时序控制手段,成功率就不依赖运气。PoC 的存在说明这个时序是可以被控制的。
4.3 为什么调试内核上才报 KASAN
KASAN(Kernel Address Sanitizer)是内核的内存错误检测机制,生产内核通常不开启。报告中的内核版本 7.1.0-rc1-00305-gbd3a4795d574 带有 PREEMPT(full) 与提交号后缀,是研究者自行编译的调试内核。
在没有 KASAN 的生产内核上,同样的访问不会打印报告,而是直接读取已释放 slab 的内容。结果取决于该内存被谁重新占用,可能是崩溃,也可能是读到无害数据后继续运行。
生产环境中此类漏洞的表现往往不是干净的内核崩溃,而是随机性故障。如果一台运行 SCTP 业务的主机出现难以解释的 sctp 相关 Oops 或 hung task,在未打补丁的情况下应当把本 CVE 纳入排查范围,不要因为没有 KASAN 报告就排除。
五、利用链:从悬垂指针到 root
本章只描述技术链条的结构关系,不提供可直接组装的利用代码与偏移数值。
5.1 链条总览
公开 PoC 的目录为 Linux-CVE-2026-52924-ubuntu-7.0.0-28,由 exploit.c、anchor.c、trigger.c、leak_fast.c 与 kernelsnitch 工具库组成,使用 musl 静态链接编译。Makefile 中的 check 目标会验证产物无 INTERP、无 DYNAMIC 段、无 NEEDED 依赖,即完全静态二进制。
程序有两种运行模式。--vuln-trigger 只制造崩溃,对应前文的远程崩溃路径。默认模式执行完整提权,对应本地提权路径。
主流程的顺序是:进入用户与网络命名空间、泄漏内核地址布局、准备受控内存页、构造 SCTP 状态序列制造悬垂指针、抢占释放后的内存、触发出队解引用、获得 root 执行。
5.2 前置:命名空间与 profile 切换
程序启动后先调用 enter_user_net_namespace()。如果当前是 root,它会先降到 nobody(uid 65534)再创建命名空间,这是为了验证漏洞在无特权条件下同样可达。随后按 2.3 节所述切换 AppArmor 配置,再 unshare(CLONE_NEWUSER) 与 unshare(CLONE_NEWNET)。
需要网络命名空间的原因是触发需要构造精确的 SCTP 报文序列。程序通过 netlink 建立一对 veth 虚拟网卡,在同一台机器上同时充当客户端与服务端,用 AF_PACKET 原始套接字自行组包,完全控制时序。
双命名空间加自造流量的组合,说明这条利用链的目标不是攻击远程主机,而是在本地构造出一个可控的协议环境。攻击向量的 Network 属性来自内核代码的接收侧位置,而 PoC 利用了"本地也能走同一条接收路径"这个事实。这就是为什么同一缺陷能同时支撑远程 DoS 与本地提权两种定性。
5.3 信息泄漏:不依赖任何特权 oracle
提权需要知道内核镜像基址(KASLR,内核地址空间布局随机化)与直接映射区基址。常规做法是读 /proc/kallsyms 或 dmesg,但这两处在 kernel.kptr_restrict=1 与 kernel.dmesg_restrict=1 下对非特权用户关闭。
PoC 的 leak_fast.c 没有使用任何特权信息源,源码注释明确写着"the exploit itself uses no privileged oracle"。它使用的是 PREFETCH 指令配合 RDTSC 计时器的时序侧信道:通过测量对不同虚拟地址执行预取指令的耗时差异,判断该地址对应的页表层级是否被拆分,从而定位内核镜像在虚拟地址空间中的位置以及直接映射区的基址。
源码中还处理了两个工程细节。一是内核镜像被 mark_rodata_ro() 划分为不同的物理别名,造成额外页表层级,这个层级同样能被 PREFETCH 观测到。二是单次测量存在抖动,代码对多个窗口长度与多次试验取多数表决来抑制误判。
这是整条链里最容易被低估的一环。很多加固指南把 kptr_restrict 与 dmesg_restrict 当作 KASLR 的配套保护,但硬件侧信道提供了不依赖软件接口的替代路径。只要攻击者能在目标上执行代码,符号泄漏类加固就无法阻止地址布局推断。真正的缓解应当落在减少攻击者的代码执行机会上,而不是隐藏地址。
5.4 内存占位:受控页的准备
anchor.c 负责准备一块内核可见、用户可控的内存页。它使用 memfd 创建匿名文件,通过 mmap 映射后在用户态反复写入内容,配合缺页与回收机制让目标物理页稳定落在一个内核会以特定方式使用的位置。
代码把它抽象为 struct page_ring,包含文件描述符、映射地址、总长度与块大小四个字段。提权阶段使用的块大小为 0x8000,共 128 块。
目标准确性直接决定利用稳定性。这里用 memfd 而非堆喷射,是因为漏洞对象是 slab 对象但伪造数据需要较大连续空间,用文件页承载可以使内容在用户态持续可读写,让内核读到的每一个字段都由攻击者决定。这类"用户态可控页被内核当结构体用"的模式是当代内核利用的主流形态。
5.5 抢占悬垂指针:伪造流状态扩展结构
触发漏洞释放旧流状态后,攻击者需要让这块内存被自己准备的页占用,使得 out_curr 解引用时读到伪造数据。
PoC 在这一步构造的是一个伪造的 sctp_stream_out_ext 结构。该结构中有一个链表头字段,正是 sctp_sched_fcfs_dequeue 中 outq.next 所指向的目标。攻击者把这个链表头设置为指向同一页上另一个伪造的 sctp_chunk 结构,从而让调度器出队时取出攻击者完全控制的块对象。
伪造数据还需要满足内核的完整性检查。代码中对链表做了自洽处理,使 CONFIG_LIST_HARDENED 下的双向链表校验能够通过;对消息块与分片链表的关系做了特殊安排,绕过出队完成路径中的数据头解引用;对关联结构的能力标志位做了设置,使后续处理走销毁分支。
利用链中工程密度最高的部分。UAF 本身只给了一个"读攻击者可控内存"的入口,要把它变成稳定的任意读写,必须让伪造结构通过内核每一层的检查。这些检查(链表加固、数据头校验、能力标志)都各自拦住过历史上的利用手法,但攻击者通过构造自洽数据逐一绕过。防护与绕过的军备竞赛在这一层持续进行。
5.6 对象叠合:从块对象到用户态辅助程序
伪造的块对象之所以有用,是因为内核会把它的某个字段当作待释放对象处理,触发一次释放。攻击者安排这次释放落在一个同时具有两种语义的对象上:对 SCTP 层它是 sk_buff(套接字缓冲区),对用户态辅助程序层它是 subprocess_info(子过程信息结构)。
subprocess_info 是内核 call_usermodehelper 机制的描述符,包含要执行的程序路径、参数数组、环境变量数组与清理函数指针。攻击者在这个位置上填入:
程序路径为
/proc/<自身 pid>/exe,即重新执行自己参数为内部约定的辅助模式开关与输出路径
环境变量为最小可用的 HOME 与 PATH
工作函数与执行函数指向内核中
call_usermodehelper的真实地址清理函数指向
do_exit,用于避免释放后的无效页释放导致崩溃
随后内核的工作队列(workqueue)消费这个对象,以 root 身份执行指定程序。被重新执行的程序检测到自身 uid 为 0,转入辅助模式读取目标文件并写入约定的输出路径。
用 call_usermodehelper 作为提权落点是一个成熟选择。它的优势是不需要构造 ROP 链或改写内核代码,只需要填对几个字段,规避了现代内核上的控制流完整性保护。代价是需要知道相关内核函数的地址,这正是 5.3 节侧信道泄漏要解决的问题。整条链的设计呈现出清晰的依赖顺序:先解决地址,再解决内存可控,末了才谈提权。
5.7 这段链条的边界
必须明确说明这份 PoC 的适用边界:
一、运行环境限定。程序启动即校验 uname 返回的 release 字段,与目标字符串不一致直接退出。它只在特定 Ubuntu 内核版本上工作。
二、地址与偏移写死。所有内核函数偏移、内核链接基址、发行版镜像偏置都以常量形式写在源码中。换一个内核版本,这些常量全部失效。
三、形态上是 CTF 解法。辅助模式检查 uid 为 0 后读取的是 /flag,这是典型的夺旗赛目标文件,不是真实攻击中的持久化或横向移动动作。
四、无在野利用证据。CISA KEV 未见收录,EPSS 约为 0.34%,属于低位区间。
三与四共同决定了处置节奏。这份代码证明了缺陷可被转化为可靠提权,从而抬高了补丁的必要性;但它不是武器化的在野样本,尚未构成需要紧急响应的事件。合理的做法是按常规补丁窗口排期,同时把暴露面收敛工作前置,而不是启动应急流程。
结束语
CVE-2026-52924 的技术形态并不复杂,一个状态回滚路径上被遗忘的缓存指针,一行命令替换即可修复。真正值得记的是它暴露出的三个判断方法问题。
第一,版本受影响不等于可被利用。本文给出的三个开关中,任何一个关闭都会让风险大幅下降。按 CVSS 数字排优先级,会把大量实际无风险的主机推到队列前列,同时让真正暴露的资产被淹没。
第二,官方定性与 PoC 能力是两件事。远程拒绝服务与本地提权共用同一个缺陷,但威胁模型、前提条件与处置优先级完全不同。混淆它们会造成两个方向的误判,本文第二章的对照表就是为了把这条界限标清楚。
第三,加固措施要看旁路,不能只看开关值。Ubuntu 的 userns 限制默认开启,却被同一发行版基础包自带的配置绕过。这类"加固被自身默认配置抵消"的情况在评估中极易被忽略,因为它不会体现在任何配置项的状态里。
从 2017 年到 2026 年,这段缺陷代码安静地躺在 SCTP 协议栈里,等着某个人把状态机的时序走对。补丁已经合入,各发行版的修复包也已就位。