【缺陷周话】第13期:二次释放
字数 1649 2025-08-18 11:38:28

二次释放(Double Free)漏洞详解与防护指南

1. 二次释放概述

二次释放(Double Free)是指对同一个指针指向的内存释放了两次的操作。这种内存管理错误主要出现在C/C++程序中,是常见的安全漏洞类型之一。

1.1 基本概念

  • C语言中的表现:对同一个指针进行两次free()操作
  • C++中的表现:通常由浅拷贝操作不当引起,当两个对象共享动态内存时,析构函数可能多次释放同一块内存

1.2 相关标准

  • CWE ID: 415 (Double Free)
  • 危害等级: 中等到严重,可导致程序崩溃或拒绝服务

2. 二次释放的危害

二次释放可能导致以下严重后果:

  1. 应用程序崩溃:破坏内存管理数据结构
  2. 拒绝服务攻击:使关键服务不可用
  3. 潜在的安全漏洞:可能被利用执行任意代码

2.1 实际漏洞案例

CVE编号 受影响软件/版本 漏洞位置 影响
CVE-2018-18751 GNU gettext 0.19.8 read-catalog.c 拒绝服务
CVE-2018-17097 SoundTouch 2.0 WavFile.cpp 拒绝服务
CVE-2018-16425 OpenSC <0.19.0-rc1 pkcs15-sc-hsm.c 应用程序崩溃
CVE-2018-16402 elfutils 0.173 elf_end.c 二次释放和崩溃

3. 代码示例分析

3.1 缺陷代码示例

// CWE415_Double_Free__malloc_free_char_17.c
void bad()
{
    char * data;
    data = (char *)malloc(100*sizeof(char)); // 第32行:内存分配
    if (data == NULL) {return;}
    free(data); // 第36行:第一次释放
    for(int i = 0; i < 1; i++)
    {
        free(data); // 第38行:第二次释放 - 缺陷点
    }
}

问题分析

  1. 第32行使用malloc()分配内存
  2. 第36行第一次释放内存
  3. 第38行在循环中再次释放同一块内存,导致二次释放

3.2 修复后的代码

void good()
{
    char * data;
    data = (char *)malloc(100*sizeof(char)); // 内存分配
    if (data == NULL) {return;}
    free(data); // 释放内存
    /* 不再对已释放的内存进行二次释放 */
}

修复要点

  1. 保持内存分配和释放的对称性
  2. 避免对已释放的指针再次调用free()

4. 二次释放的常见场景

4.1 C语言中的典型场景

  1. 显式多次调用free():直接对同一指针多次调用free
  2. 函数间传递已释放指针:一个函数释放后,另一个函数不知情再次释放
  3. 错误处理路径:在不同错误处理分支中都释放同一指针

4.2 C++中的特殊场景

  1. 浅拷贝问题

    • 两个对象共享动态内存
    • 析构时各自尝试释放同一块内存
  2. 赋值运算符重载不当

    • 未正确处理自我赋值
    • 未正确管理资源所有权
  3. 移动语义使用不当

    • 移动后源对象仍尝试释放资源

5. 防护措施与最佳实践

5.1 基本防护策略

  1. 设置指针为NULL

    free(ptr);
    ptr = NULL; // 防止后续误用
    
  2. 使用静态分析工具

    • 360代码卫士等工具可自动检测二次释放问题
  3. 代码审查重点

    • 检查所有free/delete调用
    • 跟踪指针的生命周期

5.2 C++特定防护

  1. 深拷贝替代浅拷贝

    class MyClass {
    public:
        // 深拷贝构造函数
        MyClass(const MyClass& other) {
            data = new int(*other.data);
        }
    
        // 深拷贝赋值运算符
        MyClass& operator=(const MyClass& other) {
            if (this != &other) {
                delete data;
                data = new int(*other.data);
            }
            return *this;
        }
    private:
        int* data;
    };
    
  2. 使用智能指针

    #include <memory>
    
    void safe_function() {
        std::shared_ptr<int> ptr(new int(42));
        // 无需手动释放,自动管理生命周期
    }
    
  3. 实现移动语义

    class Resource {
    public:
        // 移动构造函数
        Resource(Resource&& other) noexcept 
            : data(other.data) {
            other.data = nullptr; // 确保源对象不再拥有资源
        }
    
        // 移动赋值运算符
        Resource& operator=(Resource&& other) noexcept {
            if (this != &other) {
                delete data;
                data = other.data;
                other.data = nullptr;
            }
            return *this;
        }
    private:
        int* data;
    };
    

5.3 高级防护技术

  1. 自定义内存分配器

    • 跟踪所有分配和释放操作
    • 检测重复释放尝试
  2. 内存调试工具

    • Valgrind
    • AddressSanitizer (ASan)
  3. 防御性编程

    #define SAFE_FREE(p) do { if(p) { free(p); p = NULL; } } while(0)
    

6. 测试与验证

6.1 测试用例设计

  1. 基本测试

    • 对同一指针连续调用free两次
    • 验证程序行为
  2. 边界条件测试

    • 释放NULL指针
    • 释放未分配内存的指针
  3. 多线程测试

    • 并发释放同一指针
    • 验证线程安全性

6.2 静态分析工具使用

使用360代码卫士等工具进行检测:

  1. 检测流程

    • 扫描整个代码库
    • 识别所有内存管理操作
    • 分析指针生命周期
  2. 典型输出

    • 缺陷位置定位
    • 风险等级评估
    • 修复建议

7. 总结

二次释放是C/C++程序中常见且危险的内存管理错误,开发者应当:

  1. 遵循"谁分配谁释放"原则
  2. 释放后立即置空指针
  3. 在C++中使用RAII和智能指针
  4. 定期使用静态分析工具检查代码
  5. 在关键代码路径添加额外保护

通过系统性的防护措施和严格的代码规范,可以有效地预防二次释放漏洞,提高软件的安全性和稳定性。

二次释放(Double Free)漏洞详解与防护指南 1. 二次释放概述 二次释放(Double Free)是指对同一个指针指向的内存释放了两次的操作。这种内存管理错误主要出现在C/C++程序中,是常见的安全漏洞类型之一。 1.1 基本概念 C语言中的表现 :对同一个指针进行两次 free() 操作 C++中的表现 :通常由浅拷贝操作不当引起,当两个对象共享动态内存时,析构函数可能多次释放同一块内存 1.2 相关标准 CWE ID : 415 (Double Free) 危害等级 : 中等到严重,可导致程序崩溃或拒绝服务 2. 二次释放的危害 二次释放可能导致以下严重后果: 应用程序崩溃 :破坏内存管理数据结构 拒绝服务攻击 :使关键服务不可用 潜在的安全漏洞 :可能被利用执行任意代码 2.1 实际漏洞案例 | CVE编号 | 受影响软件/版本 | 漏洞位置 | 影响 | |--------------|----------------|---------|------| | CVE-2018-18751 | GNU gettext 0.19.8 | read-catalog.c | 拒绝服务 | | CVE-2018-17097 | SoundTouch 2.0 | WavFile.cpp | 拒绝服务 | | CVE-2018-16425 | OpenSC <0.19.0-rc1 | pkcs15-sc-hsm.c | 应用程序崩溃 | | CVE-2018-16402 | elfutils 0.173 | elf_ end.c | 二次释放和崩溃 | 3. 代码示例分析 3.1 缺陷代码示例 问题分析 : 第32行使用 malloc() 分配内存 第36行第一次释放内存 第38行在循环中再次释放同一块内存,导致二次释放 3.2 修复后的代码 修复要点 : 保持内存分配和释放的对称性 避免对已释放的指针再次调用 free() 4. 二次释放的常见场景 4.1 C语言中的典型场景 显式多次调用free() :直接对同一指针多次调用free 函数间传递已释放指针 :一个函数释放后,另一个函数不知情再次释放 错误处理路径 :在不同错误处理分支中都释放同一指针 4.2 C++中的特殊场景 浅拷贝问题 : 两个对象共享动态内存 析构时各自尝试释放同一块内存 赋值运算符重载不当 : 未正确处理自我赋值 未正确管理资源所有权 移动语义使用不当 : 移动后源对象仍尝试释放资源 5. 防护措施与最佳实践 5.1 基本防护策略 设置指针为NULL : 使用静态分析工具 : 360代码卫士等工具可自动检测二次释放问题 代码审查重点 : 检查所有free/delete调用 跟踪指针的生命周期 5.2 C++特定防护 深拷贝替代浅拷贝 : 使用智能指针 : 实现移动语义 : 5.3 高级防护技术 自定义内存分配器 : 跟踪所有分配和释放操作 检测重复释放尝试 内存调试工具 : Valgrind AddressSanitizer (ASan) 防御性编程 : 6. 测试与验证 6.1 测试用例设计 基本测试 : 对同一指针连续调用free两次 验证程序行为 边界条件测试 : 释放NULL指针 释放未分配内存的指针 多线程测试 : 并发释放同一指针 验证线程安全性 6.2 静态分析工具使用 使用360代码卫士等工具进行检测: 检测流程 : 扫描整个代码库 识别所有内存管理操作 分析指针生命周期 典型输出 : 缺陷位置定位 风险等级评估 修复建议 7. 总结 二次释放是C/C++程序中常见且危险的内存管理错误,开发者应当: 遵循"谁分配谁释放"原则 释放后立即置空指针 在C++中使用RAII和智能指针 定期使用静态分析工具检查代码 在关键代码路径添加额外保护 通过系统性的防护措施和严格的代码规范,可以有效地预防二次释放漏洞,提高软件的安全性和稳定性。