1、二次释放
二次释放简单理解就是对同一个指针指向的内存释放了两次,针对C语言源代码,对同一个指针进行两次 free() 操作,可能导致二次释放,本文3.1章节的缺陷代码就是对这类情况的描述。在C++语言中,浅拷贝操作不当是导致二次释放常见原因之一。如:调用一次赋值运算符或拷贝构造函数将会导致两个对象的数据成员指向相同的动态内存。此时引用计数机制变得非常重要,当引用计数不当时,一个对象超出作用域时,析构函数将会释放这两个对象共享的内存。另一个对象中对应的数据成员将会指向已经释放的内存地址,而当这个对象也超出作用域时,它的析构函数试图再次释放这块内存,导致二次释放问题。详细请参见CWE ID 415: Double Free。
2、 二次释放的危害
二次释放内存可能导致应用程序崩溃、拒绝服务攻击等问题,是 C/C++ 中常见的漏洞之一。2018年1月至11月,CVE中共有38条漏洞信息与其相关。部分漏洞如下:
CVE 编号 | 概述 |
---|---|
CVE-2018-18751 | GNU gettext 0.19.8 版本中的 read-catalog.c 文件的 ‘defaultaddmessage’ 函数存在二次释放漏洞。 |
CVE-2018-17097 | Olli Parviainen SoundTouch 2.0 版本中的 WavFile.cpp 文件的 WavFileBase 类存在安全漏洞,远程攻击者可利用该漏洞造成拒绝服务(二次释放)。 |
CVE-2018-16425 | OpenSC 0.19.0-rc1 之前版本中的 libopensc/pkcs15-sc-hsm.c 文件的 ‘scpkcs15emuschsminit’ 函数存在二次释放漏洞。攻击者可借助特制的智能卡利用该漏洞造成拒绝服务(应用程序崩溃)。 |
CVE-2018-16402 | elfutils 0.173 版本中的 libelf/elf_end.c 文件存在安全问题,远程攻击者可利用该漏洞造成拒绝服务(二次释放和应用程序崩溃)。 |
3、示例代码
示例源于Samate Juliet Test Suite for C/C++ v1.3 (https://samate.nist.gov/SARD/testsuite.php),源文件名:CWE415_Double_Free__malloc_free_char_17.c。
3.1缺陷代码
在上述示例代码中,在第32行使用 malloc() 进行内存分配,并在第36行使用 free() 对分配的内存进行了释放,在第38行 for 循环语句中,又对已经释放的内存 data 进行了一次释放,导致二次释放问题。
使用360代码卫士对上述示例代码进行检测,可以检出“二次释放”缺陷,显示等级为中。如图1所示:
图1:二次释放检测示例
3.2 修复代码
在上述修复代码中,Samate 给出的修复方式为: 在第32行使用 malloc() 进行内存分配,并在第36行处使用 free() 进行释放,释放后不在对该内存进行释放操作。
使用360代码卫士对修复后的代码进行检测,可以看到已不存在“二次释放”缺陷。如图2:
图2:修复后检测结果
4 、如何避免二次释放
要避免二次释放,需要注意以下几点:
(1)野指针是导致二次释放和释放后使用的重要原因之一,消除野指针的有效方式是在释放指针之后立即把它设置为 NULL 或者设置为指向另一个合法的对象。
(2)针对 C++ 浅拷贝导致的二次释放问题,始终执行深拷贝是不错的解决方案。
(3)使用源代码静态分析工具,可以自动化的发现程序中可能存在的二次释放问题。