CVE-2025-22126漏洞分析与复现
字数 2068 2025-08-30 06:50:12
CVE-2025-22126漏洞分析与复现教学文档
1. 漏洞概述
CVE-2025-22126是一个位于Linux内核md驱动(md.c)中的UAF(Use-After-Free)漏洞,影响内核版本待确认。该漏洞存在于md_notify_reboot函数中,由于不正确地使用list_for_each_entry_safe宏导致条件竞争,可能被利用进行权限提升或系统崩溃。
2. 漏洞位置
漏洞位于内核源码的drivers/md/md.c文件,具体位置在9676行的md_notify_reboot函数。
3. 漏洞原理分析
3.1 关键代码问题
md_notify_reboot函数在对每个mddev(多磁盘设备)进行操作时使用了list_for_each_entry_safe宏遍历链表:
list_for_each_entry_safe(mddev, n, &all_mddevs, all_mddevs) {
mddev_get(mddev); // 获取mddev引用
// 解锁all_mddevs_lock
// ...其他操作...
}
3.2 问题根源
-
锁机制问题:
- 在持有
all_mddevs_lock锁时,其他异步函数不能访问all_mddevs链表 - 但在
mddev_get之后会解锁,允许其他操作访问链表
- 在持有
-
宏使用不当:
list_for_each_entry_safe宏会预先保存next指针到变量n中- 这意味着mddev1和mddev2(下一个元素)都被"引用"了
- 但只对当前mddev1进行了
get操作,没有保护mddev2
-
竞争窗口:
- 在解锁后到访问下一个元素前的时间窗口内
- mddev2可以被异步释放(没有get保护)
- 后续访问已释放的mddev2导致UAF
3.3 图解漏洞时间线
时间线:
1. 获取all_mddevs_lock
2. list_for_each_entry_safe保存mddev1和mddev2(n)
3. mddev_get(mddev1)
4. 解锁all_mddevs_lock
[竞争窗口开始]
5. 其他线程可以释放mddev2
6. 循环尝试访问mddev2(n) -> UAF
[竞争窗口结束]
4. 漏洞复现
4.1 环境准备
-
编译带有漏洞的内核版本
- 开启x2apic选项(防止调试时中断在apic函数)
-
创建必要的设备文件:
mknod /dev/mddev b <设备号> 0 # 通过cat /proc/devices查看md设备号 -
使用qemu运行bzImage并创建多个虚拟磁盘
4.2 复现步骤
-
触发probe函数创建设备文件:
- 向/dev/mddev写入内容会触发probe探测函数
- 创建/dev/md0设备文件
- open该文件会触发md_open
-
创建多个MD设备:
mdadm --create /dev/md0 --level=linear --raid-devices=2 /dev/sda1 /dev/sda2 mdadm --create /dev/md1 --level=1 --raid-devices=2 /dev/sdb1 /dev/sdb2 mdadm --create /dev/md2 --level=5 --raid-devices=3 /dev/sdc1 /dev/sdc2 /dev/sdc3 -
构造POC程序:
- 在后台运行一个循环尝试释放md设备的程序
- 使用
nohup让程序在后台运行
-
触发reboot:
exit # 或直接poweroff触发reboot -
在
md_notify_reboot执行期间:- POC程序调用
mdadm --stop /dev/md0释放mddev - 导致后续访问已释放的mddev触发UAF
- POC程序调用
4.3 调试技巧
-
修改内核源码增加竞争窗口:
// 在md_notify_reboot中添加延迟循环 for (int i = 0; i < 1000000; i++) { // 空循环增加竞争可能性 } -
使用slub调试工具观察堆块状态:
slub-dump -
崩溃时观察:
- 获取的mddev和已释放的mddev相同
- next指针被修改为0
- 内核崩溃日志
5. MD(多磁盘)设备基础知识
5.1 MD设备概述
MD块设备核心作用是将多个小磁盘统一管理,方便操作磁盘资源。
5.2 RAID级别
| RAID级别 | 功能描述 | 典型用途 |
|---|---|---|
| Linear | 简单串联磁盘,无冗余或条带化 | 合并多个小磁盘为大容量设备 |
| RAID 0 | 条带化(Stripe),提升读写性能 | 高性能存储,无冗余需求 |
| RAID 1 | 镜像(Mirror),数据完全复制 | 高可用性,容忍单盘故障 |
| RAID 5 | 分布式奇偶校验,兼顾性能与冗余 | 平衡存储效率与容错能力 |
| RAID 6 | 双奇偶校验,容忍双盘故障 | 对数据安全性要求极高的场景 |
| RAID 10 | RAID 1 + RAID 0的组合(先镜像再条带) | 高性能+高冗余 |
5.3 MD设备管理工具
主要使用mdadm工具创建并管理MD设备:
mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/sda1 /dev/sdb1
6. 漏洞利用分析
6.1 利用思路
- 通过条件竞争使内核访问已释放的mddev结构体
- 通过堆喷射等技术控制释放后的内存内容
- 篡改mddev结构体中的函数指针等关键数据
- 实现任意代码执行或权限提升
6.2 利用难点
- 竞争窗口较小,需要精确控制时间
- 需要了解内核内存管理机制
- 需要绕过SMEP/SMAP等保护机制
7. 漏洞修复建议
7.1 修复方案
-
使用
list_for_each_entry替代list_for_each_entry_safe- 确保在整个操作期间保持锁或引用计数
-
或者在解锁前对所有需要访问的元素都增加引用计数
7.2 修复代码示例
// 方案1: 使用list_for_each_entry保持锁
list_for_each_entry(mddev, &all_mddevs, all_mddevs) {
mddev_get(mddev);
// 保持锁直到操作完成
}
// 方案2: 提前获取所有需要的引用
list_for_each_entry_safe(mddev, n, &all_mddevs, all_mddevs) {
mddev_get(mddev);
mddev_get(n); // 也保护下一个元素
// 解锁...
}
8. 总结与启示
-
关键教训:
list_for_each_entry_safe宏会同时获取current和next指针- 在循环中只对current指针加引用是不够的
- 解锁后next指针可能被异步释放
-
通用模式:
- 类似取双指针的操作都容易出现UAF漏洞
- 锁与引用计数的配合需要谨慎设计
- 链表遍历时要考虑所有可能被访问的元素
-
防御建议:
- 仔细审查所有链表遍历代码
- 确保在解锁时所有可能访问的元素都有保护
- 考虑使用更安全的遍历方式或增加保护范围
该漏洞展示了内核开发中链表操作和锁机制的复杂性,提醒开发者在设计类似功能时需要全面考虑所有可能的竞争条件和访问路径。