C 和 C++ 中的双重抓取漏洞的研究(一):原理篇
字数 1459 2025-08-06 21:48:53
C/C++中的双重抓取漏洞深度解析与防御
1. 双重抓取漏洞概述
双重抓取漏洞(Double Fetch)是一种特定类型的TOCTOU(Time-of-Check to Time-of-Use)漏洞,主要发生在共享内存接口中。当特权进程(如内核或设备驱动)多次访问不受信任的用户空间变量,且在第二次访问时未重新验证变量值,就可能产生此漏洞。
1.1 基本特征
- 共享内存交互:发生在特权进程与用户空间共享内存的交互过程中
- 双重访问模式:对同一变量进行两次或多次访问
- 缺乏二次验证:第二次及后续访问时未重新验证变量有效性
- 权限提升潜力:可能导致权限提升或远程代码执行(RCE)
1.2 历史背景
该术语最早出现在微软MS08-061安全公告中,描述了一个允许本地权限提升的Windows内核漏洞。虽然2008年才被正式命名,但类似漏洞在此之前就已存在。
2. 漏洞原理与机制
2.1 核心漏洞模式
- 特权进程第一次读取用户空间变量并验证(Check)
- 基于验证结果执行某些操作
- 特权进程第二次读取同一变量(Use)
- 攻击者在两次读取之间修改变量值
- 特权进程使用被篡改的值执行危险操作
2.2 典型代码模式
// 攻击者控制lParam
void win32k_entry_point(...) {
[...]
my_struct = (PMY_STRUCT)lParam; // 第一次获取
if (my_struct->lpData) {
cbCapture = sizeof(MY_STRUCT) + my_struct->cbData; // [1]第一次获取
[...]
if ((my_allocation = UserAllocPoolWithQuota(cbCapture, TAG_SMS_CAPTURE)) != NULL) {
RtlCopyMemory(my_allocation, my_struct->lpData, my_struct->cbData); // [2]第二次获取
}
}
[...]
}
2.3 漏洞变种
- 显式双重抓取:源代码中明显存在多次访问
- 编译器引入的双重抓取:编译器优化导致隐式多次访问
- 共享对象/变量访问:不仅限于内存获取,也包括共享对象访问
3. 漏洞风险等级分类
| 风险等级 | 描述 | 潜在影响 |
|---|---|---|
| 无危害 | 无法被利用 | 无实质性危害 |
| 低级危害 | 可能导致内核/进程故障 | 性能下降、进程崩溃 |
| 高级危害 | 可被积极利用 | 权限提升、任意读写、RCE |
4. 漏洞触发原因
4.1 编程错误
- 无效的程序员假设
- 共享内存访问保护不足
- 缺乏对用户空间变量的持续验证
4.2 编译器优化
编译器可能引入额外的内存访问,导致源代码中不可见的双重抓取:
// 改编自CVE-2015-8550 (XSA-155)
int cmsghdr_from_user_compat_to_kern(struct msghdr *kmsg, ...) {
[...]
while (ucmsg != NULL) {
if (get_user(ucmlen, &ucmsg->cmsg_len)) // 第一次获取
return -EFAULT;
[...]
}
[...]
while (ucmsg != NULL) {
__get_user(ucmlen, &ucmsg->cmsg_len); // 第二次获取
[...]
if (copy_from_user(..., ucmlen - ...)) // 使用未经验证的长度
}
}
5. 漏洞利用场景
5.1 典型利用模式
- 攻击者准备特定内存布局
- 特权进程第一次读取并验证数据
- 攻击者快速修改内存内容
- 特权进程第二次读取被篡改的数据
- 导致缓冲区溢出、类型混淆等漏洞
5.2 常见目标
- 内核与用户空间接口
- 设备驱动程序
- 虚拟化组件
- 特权服务进程
6. 漏洞检测与防御
6.1 检测方法
- 代码审计:查找用户空间指针的多次解引用
- 静态分析:使用专用工具检测潜在模式
- 动态检测:通过模糊测试发现竞争条件
6.2 防御措施
- 单一获取原则:获取用户数据后立即复制到内核空间
- 锁定机制:使用适当的同步原语
- 引用计数:确保数据在使用期间不被修改
- 编译器屏障:防止编译器优化引入额外访问
6.3 修复示例
// 修复后的代码示例
void safe_win32k_entry_point(...) {
[...]
// 一次性获取所有必要数据
MY_STRUCT local_copy;
if (copy_from_user(&local_copy, lParam, sizeof(MY_STRUCT)))
return ERROR;
if (local_copy.lpData) {
size_t cbCapture = sizeof(MY_STRUCT) + local_copy.cbData;
if ((my_allocation = UserAllocPoolWithQuota(cbCapture, TAG_SMS_CAPTURE)) != NULL) {
// 直接从用户空间拷贝到目标位置,避免中间变量
if (copy_from_user(my_allocation, local_copy.lpData, local_copy.cbData))
return ERROR;
}
}
[...]
}
7. 相关CVE案例
- MS08-061:Windows内核双重抓取漏洞
- CVE-2005-2490:Linux内核2.6.9中的类似漏洞
- CVE-2015-8550 (XSA-155):编译器引入的双重抓取
- 多个虚拟化组件中的类似漏洞
8. 总结与最佳实践
双重抓取漏洞是系统安全中的严重威胁,特别是在内核和驱动开发中。开发人员应:
- 始终假设用户空间数据可能被随时修改
- 遵循最小权限原则处理用户数据
- 实现防御性编程,包括适当的验证和错误处理
- 对性能关键路径进行专门的安全审查
- 使用现代静态分析工具辅助检测
理解并防范双重抓取漏洞对于开发安全的系统软件至关重要,特别是在操作系统内核、设备驱动和特权服务等关键组件中。