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 核心漏洞模式

  1. 特权进程第一次读取用户空间变量并验证(Check)
  2. 基于验证结果执行某些操作
  3. 特权进程第二次读取同一变量(Use)
  4. 攻击者在两次读取之间修改变量值
  5. 特权进程使用被篡改的值执行危险操作

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 漏洞变种

  1. 显式双重抓取:源代码中明显存在多次访问
  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 典型利用模式

  1. 攻击者准备特定内存布局
  2. 特权进程第一次读取并验证数据
  3. 攻击者快速修改内存内容
  4. 特权进程第二次读取被篡改的数据
  5. 导致缓冲区溢出、类型混淆等漏洞

5.2 常见目标

  • 内核与用户空间接口
  • 设备驱动程序
  • 虚拟化组件
  • 特权服务进程

6. 漏洞检测与防御

6.1 检测方法

  1. 代码审计:查找用户空间指针的多次解引用
  2. 静态分析:使用专用工具检测潜在模式
  3. 动态检测:通过模糊测试发现竞争条件

6.2 防御措施

  1. 单一获取原则:获取用户数据后立即复制到内核空间
  2. 锁定机制:使用适当的同步原语
  3. 引用计数:确保数据在使用期间不被修改
  4. 编译器屏障:防止编译器优化引入额外访问

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. 总结与最佳实践

双重抓取漏洞是系统安全中的严重威胁,特别是在内核和驱动开发中。开发人员应:

  1. 始终假设用户空间数据可能被随时修改
  2. 遵循最小权限原则处理用户数据
  3. 实现防御性编程,包括适当的验证和错误处理
  4. 对性能关键路径进行专门的安全审查
  5. 使用现代静态分析工具辅助检测

理解并防范双重抓取漏洞对于开发安全的系统软件至关重要,特别是在操作系统内核、设备驱动和特权服务等关键组件中。

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 典型代码模式 2.3 漏洞变种 显式双重抓取 :源代码中明显存在多次访问 编译器引入的双重抓取 :编译器优化导致隐式多次访问 共享对象/变量访问 :不仅限于内存获取,也包括共享对象访问 3. 漏洞风险等级分类 | 风险等级 | 描述 | 潜在影响 | |---------|------|---------| | 无危害 | 无法被利用 | 无实质性危害 | | 低级危害 | 可能导致内核/进程故障 | 性能下降、进程崩溃 | | 高级危害 | 可被积极利用 | 权限提升、任意读写、RCE | 4. 漏洞触发原因 4.1 编程错误 无效的程序员假设 共享内存访问保护不足 缺乏对用户空间变量的持续验证 4.2 编译器优化 编译器可能引入额外的内存访问,导致源代码中不可见的双重抓取: 5. 漏洞利用场景 5.1 典型利用模式 攻击者准备特定内存布局 特权进程第一次读取并验证数据 攻击者快速修改内存内容 特权进程第二次读取被篡改的数据 导致缓冲区溢出、类型混淆等漏洞 5.2 常见目标 内核与用户空间接口 设备驱动程序 虚拟化组件 特权服务进程 6. 漏洞检测与防御 6.1 检测方法 代码审计 :查找用户空间指针的多次解引用 静态分析 :使用专用工具检测潜在模式 动态检测 :通过模糊测试发现竞争条件 6.2 防御措施 单一获取原则 :获取用户数据后立即复制到内核空间 锁定机制 :使用适当的同步原语 引用计数 :确保数据在使用期间不被修改 编译器屏障 :防止编译器优化引入额外访问 6.3 修复示例 7. 相关CVE案例 MS08-061:Windows内核双重抓取漏洞 CVE-2005-2490:Linux内核2.6.9中的类似漏洞 CVE-2015-8550 (XSA-155):编译器引入的双重抓取 多个虚拟化组件中的类似漏洞 8. 总结与最佳实践 双重抓取漏洞是系统安全中的严重威胁,特别是在内核和驱动开发中。开发人员应: 始终假设用户空间数据可能被随时修改 遵循最小权限原则处理用户数据 实现防御性编程,包括适当的验证和错误处理 对性能关键路径进行专门的安全审查 使用现代静态分析工具辅助检测 理解并防范双重抓取漏洞对于开发安全的系统软件至关重要,特别是在操作系统内核、设备驱动和特权服务等关键组件中。