Skip to content

필사 모드: GhostLock:潜伏在 Linux rtmutex 中长达 15 年的栈上 use-after-free(CVE-2026-43499)

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

引言 — 成因小得惊人

2026 年 7 月 7 日,nebusec.ai 上发表了一篇题为“IonStack part II”的分析文章,在 Hacker News 上引发热议。这篇文章讲的正是 GhostLock,也就是 CVE-2026-43499。它是 Linux 内核实时互斥锁(rtmutex)代码(kernel/locking/rtmutex.c)中的一个栈上 use-after-free;按研究团队的说法,它从 2011 年的 Linux 2.6.39-rc1 起就在主线里,直到 2026 年 4 月的 7.1-rc1 才被修好。大约 15 年。

有意思的不是 15 年这个数字,而是成因小得让人有点难堪。一个把“调用自己的线程是谁”搞错的辅助函数,竟然扛过了 15 年的评审、模糊测试和 lockdep 活了下来。本文谈三点:栈上 use-after-free 是什么、为什么微妙,这么小的 bug 怎么能藏这么久,以及刻意去掉夸张之后,真正利用 GhostLock 需要什么、我们又该做什么。

先说明一点。本文是该系列的第二部分,并不会替你概括第一部分。我只叙述第二部分讲到的内容,同时也会指出哪些数字并非经过独立核实,而只是基于这一篇文章。

什么是栈上 use-after-free

use-after-free 顾名思义,就是内存已经被释放,却还在继续使用那个指针。堆上的例子大家都熟悉,栈上的版本则更陌生。局部变量住在函数的栈帧里。函数返回后,那个帧就被“释放”了——所谓释放,是说这块空间可以立刻被下一次调用复用。如果指向局部变量的指针活得比拥有它的函数还久,后续代码就会读写这块如今已属于别的东西的内存。

在内核里,这比用户空间更棘手。既没有可供污染的分配器元数据,也没有显眼的崩溃。因为栈不过是一段 RAM,复用这块空间本就是正常的快速路径。一个指向已释放栈帧的悬空指针,在有人的数据恰好落到那个位置之前,什么都不会做。

这份沉默正是问题的全部。这个 bug 在普通测试里看不见,是因为大多数情况下,还没有人覆盖那个已释放的帧。只有当攻击者用自己控制的字节刻意把这块空间重新占回并填满时,它才成为武器——GhostLock 利用代码做的正是这件事。

GhostLock 的真面目

出问题的代码在实时互斥锁(rtmutex)子系统里,通过 PI-futex 系统调用到达。它是一套优先级继承(priority inheritance)机制,用来防止持锁的低优先级线程饿死高优先级线程。出问题的辅助函数是 remove_waiter()。它原本是为一种情形而写的——线程在锁上阻塞,然后自己收尾。于是它假定正在运行的线程(current)永远就是要移除的那个 waiter,并清掉 current->pi_blocked_on

requeue-PI 功能悄悄打破了这个假定。使用 FUTEX_WAIT_REQUEUE_PIFUTEX_CMP_REQUEUE_PI 时,一个线程的 rt_mutex_waiter(住在该线程的栈上)会被另一个线程通过 rt_mutex_start_proxy_lock() 代理注册到另一个 futex 上。当这条路径检测到死锁环并以 -EDEADLK 回滚时,会从错误的线程去调用 remove_waiter(),于是清掉的不是真正的 waiter,而是代理注册者的状态。结果,真正的 waiter 带着一个指向已被复用的自身栈帧的指针,仍然挂在 PI 链上返回用户空间,而下一次链遍历就会解引用这块已释放的内存。

/* rtmutex.c: 辅助函数假定正在运行的线程就是 waiter */
static void remove_waiter(struct rt_mutex *lock, struct rt_mutex_waiter *w)
{
        /* ... 从锁的 waiter 树上摘下 w ... */
        current->pi_blocked_on = NULL;   /* bug:清掉的是 current 而不是 w */
}

/* 修复,提交 3bfdc63936dd:以 waiter 自身的 task 为目标 */
raw_spin_lock(&w->task->pi_lock);
w->task->pi_blocked_on = NULL;

修复——提交 3bfdc63936dd(“rtmutex: Use waiter::task instead of current in remove_waiter()”)——本质上就是一行。它不用 current,而是取 waiter 自身的 task,锁住 waiter->task->pi_lock 并清掉 waiter->task->pi_blocked_on。15 年的暴露,仅仅靠指向正确的结构体就被合上了——一个让人安心、又有点发凉的事实。

为何能明目张胆藏身 15 年 — 以及利用的真实难度

文章对它为何存活这么久的解释平平无奇,却很有说服力:一次越过了假定边界的函数复用。remove_waiter() 对原来的调用者是正确的,在那里也一直正确。requeue-PI 加进了第二个调用者,而它对“current 是谁”的理解并不相同,却没有人重新核对那个假定。而且要触发这个 bug,需要一种特定的、非随机的布置——为了强制触发 -EDEADLK 回滚,必须构造出横跨三个 futex 和三个线程的优先级继承依赖环。这是模糊测试器或日常工作负载极少会造出的状态。

接下来是关于影响的诚实部分。GhostLock 是本地权限提升,而不是远程代码执行。这一切要成立,你必须已经能在那台机器上执行代码——无论是通过 shell,还是容器里的某个进程。利用也不是一行就能搞定的。公开的利用链先用 prctl(PR_SET_MM_MAP) 修整已释放的帧、伪造一个假 waiter,再用受约束的 rb-tree 写入覆盖位于 CPU entry area 的 inet6_protos[IPPROTO_UDP],最后翻转 /proc/sys/kernel/core_pattern 的权限位拿到 root。

这是研究级别的工作。作者们在关闭加固选项的 kernelCTF 条件下报告了 97% 的稳定性,Google 的 kernelCTF 为此支付了 92,337 美元。公正地看,它可靠、真实存在,但界限也很清楚——需要本地访问和精巧的利用链。最该担心的场景,是本地权限提升借由容器逃逸,把一个被攻陷的工作负载变成对整个节点的掌控。

披露过程也很快,而这一点和修复本身同样重要。据文章所述,这个 bug 于 2026 年 4 月 18 日报告给 security@kernel.org,4 月 20 日在主线被修好,5 月 4 日 backport 到 stable。Google 在 6 月 30 日确认了 kernelCTF 提交,公开分析在 7 月 7 日发布。从悄悄修好到公开披露细节之间,间隔大约两个月——也就是说,stable 用户在这套机制登上头条之前,有时间完成更新。

结语 — 打补丁,以及如何确认是否受影响

务实的应对无聊却准确:打补丁。修复以提交 3bfdc63936dd(“rtmutex: Use waiter::task instead of current in remove_waiter()”)进入主线,并于 2026 年 5 月 4 日 backport 到 stable 树,所以只要是更新到最新的发行版内核,就已经包含了它。受影响范围原则上很广——凡是启用了 CONFIG_FUTEX_PI=y 的 2.6.39-rc1 之后的所有内核,几乎覆盖所有发行版的构建。所以真正的问题不是“是否受影响”,而是“是否已打补丁”。

uname -r                      # 正在运行的内核
# 然后查看你所用发行版的 CVE-2026-43499 公告。
# 若是 2026-05-04 之后更新的 stable 内核,即已打补丁。

文章还提到两项纵深防御——但两者都替代不了补丁。RANDOMIZE_KSTACK_OFFSET 会在每次系统调用时随机化栈偏移,打破利用所依赖的确定性帧重叠。它只有大约 5 比特、约 1/32 的概率,但聊胜于无。STATIC_USERMODE_HELPER 能堵住这里用到的那条特定 /proc/sys 路径,却堵不住根本的 bug 本身。最后是关于数字的诚实提醒:15 年的寿命和版本细节,都来自属于该系列第二部分的一篇厂商文章。机制是具体的、可以对照内核树核实,所以我信它;但周边那些数字,更适合当作出处的叙述来看待,而非经过独立确认。

参考资料

현재 단락 (1/28)

2026 年 7 月 7 日,nebusec.ai 上发表了一篇题为“IonStack part II”的分析文章,在 Hacker News 上引发热议。这篇文章讲的正是 GhostLock,也就是...

작성 글자: 0원문 글자: 3,702작성 단락: 0/28