GhostLock(CVE-2026-43499)完整利用链深度解析:一个潜伏15年的Stack-UAF如何通向Root 一、漏洞概述1.1 影响范围漏洞影响Linux内核v2.6.39至v7.1之间的所有版本具体引入commit为8161239a8bcce9ad6b537c04a1fa3b5c68bae6932.6.39-rc1修复commit为3bfdc63936dd4773109b7b8c280c0f3b5ae7d349。各分支修复版本6.1分支≥ 6.1.1756.6分支≥ 6.6.1406.12分支≥ 6.12.866.18分支≥ 6.18.277.0分支≥ 7.0.4目标内核需启用CONFIG_FUTEX_PIy默认开启。受影响的发行版包括Ubuntu14.04至26.04 LTS全部受影响、Debian11至14、RHEL、CentOS等主流发行版。1.2 漏洞本质remove_waiter()中的任务上下文混淆漏洞存在于kernel/locking/rtmutex.c的remove_waiter()函数中。该函数承担双重职责慢速锁路径slowlock paths当前线程等待锁失败后进行清理代理锁回滚proxy-lock rollback在futex_requeue()调用rt_mutex_start_proxy_lock()时的回滚路径问题出在第二种场景waiter::task并非current当前执行线程而是另一个正在睡眠的等待线程。但remove_waiter()错误地使用current进行出队操作。具体而言c// 错误实现漏洞版本 static void remove_waiter(struct rt_mutex *lock, struct rt_mutex_waiter *waiter) { // ... rt_mutex_dequeue(lock, waiter); // 对current进行出队 current-pi_blocked_on NULL; // 清除current的pi_blocked_on // ... }在代理锁回滚场景中waiter属于另一个睡眠线程但remove_waiter()却操作了current的rbtree节点和pi_blocked_on状态。这导致三个并发问题rbtree出队在未持有waiter::task::pi_lock的情况下发生—— 竞态条件实际等待者waiter-task的pi_blocked_on未被清除—— 悬垂指针rt_mutex_adjust_prio_chain()操作了错误的最高优先级等待者任务—— 优先级链混乱二、根本原因Root Cause深入分析2.1 触发路径FUTEX_CMP_REQUEUE_PI的代理锁回滚漏洞的触发依赖于Futex PIPriority Inheritance的FUTEX_CMP_REQUEUE_PI操作。该操作允许一个线程代表另一个正在睡眠的线程设置或回滚PI等待。关键调用链textfutex_requeue() └── rt_mutex_start_proxy_lock() └── __rt_mutex_start_proxy_lock() └── 若检测到死锁-EDEADLK └── remove_waiter() // 回滚路径当__rt_mutex_start_proxy_lock()因死锁检测返回-EDEADLK时remove_waiter()被调用于回滚。此时current 发起futex_requeue()系统调用的线程代理线程waiter-task 正在FUTEX_WAIT_REQUEUE_PI中睡眠的实际等待线程2.2 悬垂指针的形成死锁循环构造攻击者通过构造三线程PI依赖循环来触发这一竞态条件。设线程A、B、C线程A持有锁L1等待锁L2线程B持有锁L2等待锁L3线程C代理线程发起FUTEX_CMP_REQUEUE_PI代表线程A进行代理锁操作当内核检测到死锁并回滚时remove_waiter()错误地执行crt_mutex_dequeue(lock, waiter); // 操作current线程C而非waiter-task线程A current-pi_blocked_on NULL; // 清除线程C的pi_blocked_on而实际等待者线程A的pi_blocked_on指针未被清除仍然指向其自身内核栈上的rt_mutex_waiter对象。当线程A从futex_wait_requeue_pi()系统调用返回用户空间时其内核栈帧已被销毁但task_struct-pi_blocked_on仍悬空指向该已释放栈内存中的rt_mutex_waiter对象。这是一个典型的栈上UAFStack UAF—— 悬垂指针指向的对象位于已释放的内核栈上而非堆内存。2.3 为何是栈上而非堆上rt_mutex_waiter对象在futex_wait_requeue_pi()路径中被分配在等待线程的内核栈上作为局部变量或通过ALLOC_WAITER宏在栈上分配。当系统调用返回时该栈帧被销毁但悬垂指针依然存在。这一定位至关重要它决定了后续的利用策略——不是堆风水的UAF而是栈帧复用。三、利用原语与栈回收3.1 栈帧复用策略PR_SET_MM_MAP与user_auxv喷洒由于悬垂指针指向的是已释放的内核栈内存攻击者需要在该栈位置被重新使用前精确控制其内容。Nebula团队采用的核心技术是prctl(PR_SET_MM, PR_SET_MM_MAP, ...)系统调用。prctl_set_mm_map()允许调用者修改进程的mm_struct边界和saved_auxv向量。在实现中内核会将用户提供的auxv数据复制到一个固定大小的栈缓冲区user_auxv[AT_VECTOR_SIZE]中。关键洞察user_auxv缓冲区恰好位于与刚释放的rt_mutex_waiter大致相同的栈深度。通过精确控制prctl(PR_SET_MM_MAP)的参数攻击者可以在悬垂指针指向的栈位置放置伪造的rt_mutex_waiter对象。c// 伪代码通过PR_SET_MM_MAP在目标栈位置喷洒受控数据 struct prctl_mm_map map { .auxv (unsigned long)controlled_auxv_data, .auxv_size sizeof(controlled_auxv_data), // ... 其他字段 }; prctl(PR_SET_MM, PR_SET_MM_MAP, (unsigned long)map, sizeof(map), 0);user_auxv的大小为AT_VECTOR_SIZE通常为44个条目足以容纳一个完整的伪造rt_mutex_waiter结构。3.2 时间窗口的规避Stack UAF的利用面临一个核心挑战时间窗口。从悬垂指针产生到栈内存被复用存在一个竞争窗口。攻击者通过以下方式规避或利用这一时间差精确的线程调度控制利用futex自身的同步原语精确控制各线程的睡眠与唤醒时机CPU亲和性CPU affinity绑定将相关线程绑定到同一CPU核心确保栈复用发生在同一CPU的同一深度prctl(PR_SET_MM_MAP)的即时性该调用在悬垂指针产生后立即执行最小化竞争窗口四、红黑树擦除引发的受限写入4.1 rt_mutex_dequeue写入原语当攻击者通过PR_SET_MM_MAP在悬垂指针位置放置了伪造的rt_mutex_waiter对象后内核在某些代码路径中会再次调用rt_mutex_dequeue()对该伪造对象进行操作。rt_mutex_dequeue()的核心操作是从红黑树中移除一个节点cstatic void rt_mutex_dequeue(struct rt_mutex *lock, struct rt_mutex_waiter *waiter) { // 从lock的等待者红黑树中移除waiter节点 rb_erase(waiter-tree_entry, lock-waiters); // 更新lock的树结构 // ... }关键在于攻击者控制的伪造rt_mutex_waiter中的tree_entry红黑树节点的rb_left和rb_right指针可以被精确控制。当rb_erase()执行时它会将红黑树父节点的相应子指针更新为被删除节点的子节点——这本质上是一个受控的指针写入原语。具体来说攻击者可以实现text*(target_address offset) controlled_value其中target_address和controlled_value均可通过伪造的rt_mutex_waiter中的红黑树节点指针来控制。4.2 写入约束自旋锁状态与地址对齐这一写入原语并非任意写入存在多重约束目标地址必须位于红黑树节点的父指针附近写入的目标是红黑树父节点的rb_left或rb_right字段因此目标地址必须是某个内核结构体中恰好位于红黑树根节点指针偏移处的位置自旋锁状态检测在rt_mutex_dequeue()执行期间相关自旋锁wait_lock等必须处于正确的锁定状态。若锁状态不匹配写入不会发生或触发内核panic值约束写入的值是伪造rt_mutex_waiter中tree_entry的子节点指针因此攻击者需要精确控制该指针的值地址对齐红黑树节点要求对齐写入目标地址必须满足相应的对齐要求尽管受限这一原语已经足够覆盖关键内核结构中的函数指针——这正是后续控制流劫持的基石。五、绕过与劫持5.1 KASLR绕过prefetch时序侧信道在拥有受限写入原语之前攻击者必须先解决KASLR内核地址空间布局随机化。Nebula团队采用的技术是基于prefetch指令的时序侧信道来推断内核内存布局。prefetch指令会将指定地址的数据加载到CPU缓存中通过测量后续内存访问的延迟差异攻击者可以推断某个内核地址是否有效、是否映射了特定数据。具体方法攻击者枚举可能的KASLR偏移量对每个候选偏移量使用prefetch预取对应的内核符号地址通过时序测量判断该地址是否命中缓存从而验证偏移量的正确性这一技术允许攻击者在不依赖任何信息泄露漏洞的情况下以较高的成功率确定内核基址。5.2 物理映射区基址与CPU Entry AreaCEA定位获得KASLR基址后攻击者需要定位两个关键内存区域物理映射区physmap基址用于计算内核数据结构的物理地址CPU Entry AreaCEA一个可预测的内核内存区域用于存储攻击者构造的特制数据结构CEA是x86-64架构中用于CPU入口代码的固定映射区域其布局相对可预测。攻击者利用受限写入原语将伪造的函数指针表或ROP链放置到CEA中。5.3 控制流劫持覆盖inet6_protos[IPPROTO_UDP]有了受限写入原语和目标地址CEA 已知偏移攻击者下一步是劫持内核的控制流。目标inet6_protos[IPPROTO_UDP]函数表。inet6_protos是IPv6协议族中各个传输层协议的操作函数表数组每个条目包含该协议的handler、err_handler等函数指针。当内核处理一个IPv6 UDP数据包时它会通过inet6_protos[IPPROTO_UDP]中的函数指针调用相应的处理函数。攻击流程利用受限写入原语将inet6_protos[IPPROTO_UDP]的条目覆盖为指向CEA中攻击者控制的伪造函数表的指针在CEA中构造伪造的函数表其中的函数指针指向ROP链的起始地址通过回环IPv6 UDP流量::1触发内核处理一个UDP数据包内核在协议处理过程中通过被篡改的函数表跳转到攻击者控制的ROP链5.4 ROP链与栈转换控制流被劫持后攻击者执行精心构造的ROPReturn-Oriented Programming链。由于内核栈布局和寄存器状态的复杂性攻击者通常需要栈转换Stack Pivoting将栈指针rsp转移到攻击者控制的CEA区域JOP/ROP链执行利用内核现有的gadget序列完成提权操作六、DirtyMode提权阶段6.1 一次写入翻转core_pattern权限完整的ROP链可以很长且复杂为了缩短攻击面并提高可靠性Nebula团队采用了“DirtyMode”技术。目标coredump_sysctls[core_pattern].mode—— 控制core dump模式的内核sysctl权限位。core_pattern是Linux的core dump处理器配置它决定了当进程崩溃时内核执行哪个用户空间程序来处理core dump。正常情况下只有root用户才能修改core_pattern。攻击者通过受限写入原语执行单次内核内存写入将coredump_sysctls[core_pattern].mode的权限位从0666翻转为可写的状态。6.2 用户空间提权恶意coredump处理器一旦core_pattern的权限位被篡改攻击者在用户空间写入一个恶意core dump处理器脚本例如/tmp/exploit.sh通过sysctl或echo写入/proc/sys/kernel/core_pattern将其指向恶意脚本触发一个会崩溃的进程如kill -SEGV $$内核以root权限执行恶意core dump处理器至此攻击者以root身份执行任意代码完成提权。这种“一次写入”策略极大缩短了ROP链的长度将复杂的提权逻辑转移到用户空间显著提高了利用的可靠性。七、修复与缓解7.1 官方补丁分析修复commit3bfdc63936dd的核心改动diffdiff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c --- a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c -remove_waiter, ... static void remove_waiter(struct rt_mutex *lock, struct rt_mutex_waiter *waiter) { - rt_mutex_dequeue(lock, waiter); - current-pi_blocked_on NULL; struct task_struct *task waiter-task; rt_mutex_dequeue(lock, waiter); task-pi_blocked_on NULL; }关键改动使用waiter-task替代current进行pi_blocked_on的清除确保pi_lock在正确的任务上下文下持有修复了rt_mutex_adjust_prio_chain()操作错误任务上下文的问题7.2 补丁的完整性讨论NPD隐患Nebula Security在分析中发现初始补丁可能引入空指针解引用NPD漏洞。具体而言在remove_waiter()被调用的某些边缘路径中waiter-task可能为NULL例如在死锁检测早期返回的场景。这一问题导致了后续的CVE-2026-53163补丁textlocking/rtmutex: Skip remove_waiter() when waiter is not enqueued该补丁在调用remove_waiter()之前增加了对waiter是否已入队的检查。因此生产环境不应仅应用第一个补丁而应更新到包含完整修复链的最新稳定版本。7.3 缓解措施评估RANDOMIZE_KSTACK_OFFSET该选项在每次系统调用时随机化内核栈偏移。理论上可以增加栈帧复用的难度——攻击者需要猜测正确的栈偏移才能将PR_SET_MM_MAP的user_auxv精确喷洒到悬垂指针位置。评估增加利用难度但并非彻底防御。攻击者可以通过多次尝试或改进的 spraying 技术绕过。STATIC_USERMODE_HELPER该选项限制内核只能执行白名单中的用户模式辅助程序。在DirtyMode阶段如果core_pattern指向的程序不在白名单中内核将拒绝执行。评估有效阻断DirtyMode提权路径但不影响控制流劫持本身。攻击者可能采用其他提权方式如直接修改cred结构。总体结论两者均为缓解措施mitigations而非修复fixes。唯一彻底的解决方案是更新到包含完整补丁链的内核版本。八、总结GhostLock展示了经典的内核并发漏洞如何在15年后依然致命API误用remove_waiter()在代理锁回滚路径中被错误复用假设了错误的执行上下文栈上对象UAF对象位于内核栈上使得传统的堆风水无效需要创新的栈帧复用技术受限写入的巧妙扩展一个红黑树擦除操作产生的受限写入通过精确的目标选择inet6_protos表扩展为完整的控制流劫持最小化ROPDirtyMode技术将复杂的内核ROP简化为单次写入再转移到用户空间完成提权该漏洞的发现归功于AI驱动的漏洞挖掘工具VEGA这预示着未来内核安全研究范式的转变——自动化工具将不断发现那些“人类已遗忘的代码”中的深层漏洞。最终建议所有运行Linux内核v2.6.39至v7.0的系统应立即更新至修复版本6.1.175、6.6.140、6.12.86、6.18.27、7.0.4并确保应用了完整的补丁链包括CVE-2026-53163的修复。容器化环境和多租户基础设施应作为最高优先级进行修补。