严重等级 |
缺陷详解 |
测试用例 |
用例解释 |
修复后测试用例 |
正确测试用例解释 |
致命 |
如果内存在释放后又被使用或引用,或如果对其进行了两次释放,则可能导致无法预料的结果。内存引用问题可能导致使用无法预料的值,并因此导致程序崩溃或执行任意代码。通常,如果存在错误条件或出现异常、争用条件未解决或是程序的不同部分之间对内存的用途出现混乱,就会出现在释放后使用或重复释放的内存错误。 |
#include <stdlib.h>
#include <malloc.h>
char* copy(const char* x)
{
char* q=(char*)malloc(strlen(x)+1);
return strcpy(q,x);
}
void main()
{
char *p;
int t=1;
p=copy("hello world!");
free(p); //
*p=t; //not compliant
} |
第(14)行使用[free]释放了[p],在第(15)行使用了释放的[p],可能会导致程序崩溃。 |
#include <stdlib.h>
#include <malloc.h>
char* copy(const char* x)
{
char* q=(char*)malloc(strlen(x)+1);
return strcpy(q,x);
}
void main()
{
char *p;
int t=1;
p=copy("hello world!");
free(p); //
//*p=t; //not compliant
} |
不要访问已经释放的内存。 |
致命 |
c和c++语言中数组下标越界,即访问了超出数组定义的内容。编译器是不会检查出错误的,也并未内置任何保护功能来阻止访问或覆盖内存任何部分中的数据,也不会自动检查写入某一数组的数据是否在该数组的边界内。但是实际上后果可能会很严重,会造成程序崩溃等不可预知的错误。 |
#include <stdio.h>
void foobar(int x)
{
int local_array[7]; //
local_array[x]=0; //not compliant
}
int main()
{
foobar(15); //not
return 0;
} |
第(5)行定义大小为[7]的数组[local_array],在第(6)行的数组[local_array]索引[x]的值可能是[15],超出了其定义范围,造成了数组越界。 |
#include <stdio.h>
void foobar(int x)
{
int local_array[7];
if (x>=0&&x<sizeof(local_array)/sizeof(local_array[0]))
local_array[x]=0; // compliant
}
int main()
{
foobar(15);
return 0;
} |
数组元素操作前,加逻辑判断。 |
致命 |
如果内存在释放后又被使用或引用,或如果对其进行了两次释放,则可能导致无法预料的结果。内存引用问题可能导致使用无法预料的值,并因此导致程序崩溃或执行任意代码。
通常,如果存在错误条件或出现异常、争用条件未解决或是程序的不同部分之间对内存的用途出现混乱,就会出现在释放后使用或重复释放的内存错误 |
#include <stdlib.h>
int main()
{
char* a=malloc(sizeof(a));
free(a); //释放内存地址
free(a); //因为之前已经释放过了,所以这行代码属于重复释放
} |
第[6]行对a进行了重复释放 |
#include <stdlib.h>
int main()
{
char* a=malloc(sizeof(a));
free(a); //取消重复释放
} |
对同一内存地址,执行一次释放即可 |
致命 |
如果存在一条可达路径,在该路径上使用前面没有被赋初值的变量会导致未初始化使用的错误。使用未初始化的堆内存会导致严重的代码性能缺陷,因为分配到给定数据对象的值是从所分配的堆内存中随机选取的,并能反映先前所用对象或其他进程中的对象的状态。如果软件没有对内存进行正确的初始化,会发生意外结果,并可能存在安全隐患。 |
int uninit() {
int ret;
int flag;
scanf("%d", &flag);
if (flag > 0) {
ret = flag;
} else if (flag < 0) {
ret = -flag;
}
return ret * ret; //当flag=0时,ret未被初始化
} |
第[2]行声明的变量[ret]可能未初始化就在[10]行使用。 |
int uninit() {
int ret;
int flag;
scanf("%d", &flag);
if (flag > 0) {
ret = flag;
} else if (flag < 0) {
ret = -flag;
} else {
ret = 0;
} //任何条件下都给ret设置初始化的操作,排除了安全隐患。
return ret * ret;
} |
在使用前的每个分支中进行初始化 |
致命 |
有些函数如 malloc 分配出来的内存是没有初始化的,可以使用 memset 进行清零,或者使用 calloc 进行内存分配, calloc 分配的内存是清零的。 当然,如果后面需要对申请的内存进行全部赋值,就不要清零了,但要确保内存被引用前是被初始化的。 此外,分配内存初始化,可以消除之前可能存放在内存中的敏感信息,避免敏感信息的泄露。 |
int uninit() {
int ret;
int flag;
scanf("%d", &flag);
if (flag > 0) {
ret = flag;
} else if (flag < 0) {
ret = -flag;
}
return ret * ret; //当flag=0时,ret未被初始化
} |
第[2]行声明的变量[ret],由于两个分支都有可能不执行,那么在第[10]会发生未初始化使用的缺陷 |
int uninit_good() {
int ret;
int flag;
scanf("%d", &flag);
if (flag > 0) {
ret = flag;
} else if (flag < 0) {
ret = -flag;
} else {
ret = 0;
} //任何条件下都给ret设置初始化的操作,排除了安全隐患。
return ret * ret;
} |
在ret使用前考虑每个分支的情况,确保每个分支都有初始化 |
致命 |
访问已经释放的内存, 是很危险的行为,主要分为两种情况:
1)堆内存: 一块内存释放了,归还内存池以后,就不应该再访问。因为这块内存可能已经被其他部分代码申请走,内容可能已经被修改;直接修改释放的内存,可能会导致其他使用该内存的功能不正常;读也不能保证数据就是释放之前写入的值。在一定的情况下,可以被利用执行恶意的代码。即使是对空指针的解引用,也可能导致任意代码执行漏洞。如果黑客事先对内存 0 地址内容进行恶意的构造,解引用后会指向黑客指定的地址,执行任意代码。
2)栈内存:在函数执行时,函数内局部变量的存储单元都可以在栈上创建,函数执行完毕结束时这些存储单元自动释放。如果返回这些已释放的存储单元的地址(栈地址),可能导致程序崩溃或恶意代码被利用。 |
#include <stdlib.h>
typedef struct _tagNode
{
int value;
struct _tagNode * next;
}Node;
Node * Noncompliant(void)
{
Node* head = (Node *)malloc(sizeof(Node)*10);
if (head==NULL)
{
/* ...do something... */
return NULL;
}
/* ...do something... */
free(head);
/* ...do something... */
head->next = NULL; //【错误】解引用了已经释放的内存
return head;
} |
第[16]行释放结构体内存head,第[18]行访问结构体成员变量next |
#include <stdlib.h>
typedef struct _tagNode
{
int value;
struct _tagNode * next;
}Node;
Node * Noncompliant(void)
{
Node* head = (Node *)malloc(sizeof(Node)*10);
if (head==NULL)
{
/* ...do something... */
return NULL;
}
/* ...do something... */
head->next = NULL;
free(head);
/* ...do something... */
return head;
} |
修改代码后,在释放结构体内存head之前,访问结构体的成员变量 |
致命 |
重复释放内存( double-free)会导致内存管理器出现问题。重复释放内存在一定情况下,有可能导致“ 堆溢出” 漏洞,可以被用来执行恶意代码,具有很大的安全隐患。 |
#include <stdlib.h>
int main()
{
char* a=malloc(sizeof(a));
free(a); //释放内存地址
free(a); //因为之前已经释放过了,所以这行代码属于重复释放
} |
第[5]行释放内存后,第[6]行对a进行了重复释放 |
#include <stdlib.h>
int main()
{
char* a=malloc(sizeof(a));
free(a); //释放内存地址
} |
修改后的代码,确保a内存只被释放一次 |