2026 年 7 月 7 日,安全公司 Nebula Security 公开了一个被命名为 GhostLock 的 Linux 内核漏洞(CVE-2026-43499)。这个漏洞从 Linux 2.6.39(2011 年发布)一直存活到 2026 年 4 月才被修复,覆盖了几乎所有主流 Linux 发行版长达 15 年的时间窗口。触发漏洞不需要特殊内核配置,也不需要任何特权——一个普通用户进程只用常规的系统调用就能拿到悬空的内核指针。
Google 在 kernelCTF 中为这个漏洞的利用链支付了 92,337 美元的奖金,因为 Nebula Security 的研究团队把 GhostLock 变成了一条 97% 成功率的本地提权加容器逃逸链路。从 FUTEX_CMP_REQUEUE_PI 系统调用中的一个指针清理错误出发,最终可以直取 root 权限。
漏洞根源:一个被误用的清理函数
GhostLock 的出问题的地方在 kernel/locking/rtmutex.c 的 remove_waiter() 函数里。这个函数的核心职责是从 rtmutex 的等待队列中移除一个 waiter 结构体,并清理对应任务的 pi_blocked_on 指针。
在正常的慢速加锁路径上,remove_waiter() 的行为是正确的:调用者是加锁失败后正在自我清理的线程,current 指向的就是 waiter 的拥有者。函数执行 current->pi_blocked_on = NULL 合情合理。
问题出在代理路径上。rt_mutex_start_proxy_lock() 函数会在 FUTEX_CMP_REQUEUE_PI 操作中,把一个线程 A 的 waiter 代理到线程 B 持有的 PI futex 上。当 rtmutex 的优先级继承链遍历检测到死锁时,代理操作会回滚——此时调用 remove_waiter() 清理 waiter。但此时的 current 是发起 requeue 操作的线程 C,不是 waiter 的实际拥有者线程 A。
于是 current->pi_blocked_on 被错误地清零了。而线程 A 的 pi_blocked_on 仍然指向它自己栈上的 rt_mutex_waiter 结构体——这个结构体在线程 A 从 FUTEX_WAIT_REQUEUE_PI 返回用户空间后就已经被释放了。留下的,是一个悬空的内核栈指针。
修复的核心思路也很直接:把 current->pi_blocked_on 改成 waiter->task->pi_blocked_on,即始终清理 waiter 真正所属任务的指针。Nebula Security 在 4 月 18 日向 [email protected] 提交了漏洞和补丁草案,两天后内核主线就合并了修复。
触发:三线程、三 futex 的精确编排
触发 GhostLock 不需要竞态条件中的毫秒级运气。只要搭建好一个优先级继承的依赖环,漏洞窗口就稳定地敞开。
编排需要三个 futex 和三个线程:
f_pi_chain:一个 PI futex,由 waiter 线程先持有f_pi_target:另一个 PI futex,由 owner 线程先持有,也是 requeue 的目标f_wait:一个普通 futex,waiter 线程在其上调用FUTEX_WAIT_REQUEUE_PI
执行序列如下:waiter 线程拿到 f_pi_chain,然后阻塞在 FUTEX_WAIT_REQUEUE_PI(f_wait → f_pi_target) 上,此时 rt_mutex_waiter 分配在它的内核栈上。owner 线程拿到 f_pi_target,然后尝试获取 f_pi_chain 并阻塞——因为 f_pi_chain 被 waiter 持有。主线程调用 FUTEX_CMP_REQUEUE_PI(f_wait → f_pi_target)。
requeue 操作试图把 waiter 代理到 f_pi_target 上,但 f_pi_target 的持有者 owner 正阻塞在 waiter 持有的 f_pi_chain 上。优先级继承链检测到这个依赖环(waiter → f_pi_target → owner → f_pi_chain → waiter),返回 -EDEADLK 并触发有问题的回滚路径。
回滚完成后,waiter 线程返回用户空间,但 pi_blocked_on 依然指向已释放的栈帧。而此时已经没有任何时间压力——waiter 可以悠闲地在用户空间停留,后续通过 sched_setattr() 触发 PI 链遍历时,内核就会沿着这个悬空指针一路走下去。

利用:栈复用、CEA 布局与函数表劫持
拿到悬空指针只是第一步。要让内核在解引用时读到攻击者控制的数据,需要把可控字节放回 waiter 线程内核栈的同一位置。这叫栈复用。
Nebula Security 选择的手段是 prctl(PR_SET_MM, PR_SET_MM_MAP, ...)。这个系统调用会在内核栈上分配一个 unsigned long user_auxv[AT_VECTOR_SIZE] 数组,大小和位置恰好与已释放的 waiter 结构体重叠。攻击者通过精心排列 auxv 的内容,就能在栈上锻造出一个假的 rt_mutex_waiter。
假 waiter 的字段被设计为:task 指向 &init_task(一个永远有效的 task_struct),lock 指向内核数据段中 inet6_protos[IPPROTO_UDP] 减去 8 字节的地址。当 PI 链遍历触发 rt_mutex_dequeue() 时,红黑树的删除操作会执行一次受约束的指针写入。
这个写入原语的限制很严格:目标地址的前 8 字节必须看起来像一个未上锁的 raw_spinlock(低 4 字节为零),后面若干字节也必须满足结构体校验。但 inet6_protos 数组恰好满足这些布局条件——inet6_protos[16] 是 NULL(模拟未上锁),inet6_protos[17] 是 &udpv6_protocol(写入目标),inet6_protos[18] 和 inet6_protos[19] 都是 NULL。
写入完成后,inet6_protos[IPPROTO_UDP] 不再指向真正的 udpv6_protocol,而是指向攻击者在 CPU Entry Area (CEA) 中布局的伪造 inet6_protocol 结构体。
CEA 是 x86 架构上每个 CPU 用于处理异常和系统调用的内存区域。在 6.2 之前的内核中,CEA 位于完全固定的虚拟地址;6.2 之后虽然虚拟地址被随机化了,但 CEA 的物理偏移是固定的,可以通过 prefetch 侧信道泄露 physmap 基地址后,用直接映射别名访问。
攻击者在 CEA 中同时放置了伪造的 inet6_protocol(handler 指向 pivot gadget)、JOP 跳板、以及一个简短的 ROP 链。发送一个到 ::1 的 IPv6 UDP 回环数据包,内核就会通过被劫持的函数指针跳入攻击者控制的执行流。

DirtyMode:一行写入拿 root
ROP 链的目标是修改 coredump_sysctls 表中 core_pattern 条目的 mode 字段。这个字段决定了 /proc/sys/kernel/core_pattern 的写权限——默认是 0644(只有 root 可写)。ROP 链执行一次 mov [reg], reg 操作,把 mode 改成包含写权限位的任意值。
此后,/proc/sys/kernel/core_pattern 对任何用户可写。攻击者写入 |/proc/%P/fd/666 %P,然后让一个 setuid 程序崩溃。内核调用 core_pattern 中指定的管道处理程序——攻击者提前准备好的 /proc/%P/fd/666 指向一个以 root 身份运行的二进制文件。
Nebula Security 把这一步叫做 DirtyMode——只用一次内核写入配合一个几乎任意的值,就能把后续提权完全交给用户空间。整个利用链从触发漏洞到拿到 root shell,在 kernelCTF 环境中用时约 5 秒。
15 年未被发现,意味着什么
GhostLock 的引入 commit 是 8161239a8bcc(rtmutex: Simplify PI algorithm and make highest prio task get lock),提交于 2011 年。在此后的 15 年里,这段代码被几乎所有主流发行版的内核继承,包括 RHEL、Ubuntu、Debian、Android 和各类云厂商定制内核。
更有意思的是,当前的 KASAN(内核地址消毒器)检测不到这种栈 UAF。2019 年的 commit 7771bdb 从 KASAN 中移除了栈上对象作用域检测的代码,原因是实现困难且收益有限。这意味着传统的 fuzzing 基础设施对这类漏洞存在盲区。
Nebula Security 的研究人员是用他们自研的 VEGA 自动化漏洞挖掘工具发现这个漏洞的。在 oss-security 邮件列表的讨论中,他们指出:随着 LLM 辅助的漏洞挖掘规模化,这类传统 fuzzing oracles 覆盖不到的盲点可能会更多地浮出水面。
从防御角度看,RANDOMIZE_KSTACK_OFFSET=y 可以将栈复用的理论成功率压低到 2% 以下——对个人 PC 和服务器来说是一个有效的缓解措施。STATIC_USERMODE_HELPER 可以阻断 DirtyMode 的具体利用路径,但类似的攻击思想可以泛化到任意权限位受 ctl_table::mode 控制且位于可预测内核数据段的 sysctl 条目上。
GhostLock 的公开也引发了对内核 ASLR 强度的再次讨论。漏洞本身不提供信息泄露,利用链严重依赖 prefetch 计时攻击绕过 KASLR。Nebula Security 在邮件列表中直言:内核 ASLR 在 x86 上的随机化位数太少(默认内核镜像文本基址约 9 位熵),加上 CEA 物理地址固定,使得绕过并不困难。
参考链接:
- Nebula Security 研究报告:GhostLock(ionstack part 2)
- Hacker News 讨论帖
本文的素材来自公开信息和社区讨论。如果你对这个话题有更深入的一手经验,欢迎指出文中的不足。