C/C++中容易造成内存溢出的函数
1.strcpy()
strcpy()函数将源字符串复制到缓冲区。没有指定要复制字符的具体数目。复制字符的数目直接取决于源字符串中的数目。如果源字符串碰巧来自用户输入,且没有专门限制其大小,则有可能会陷入大的麻烦中!
建议使用strncpy.
2.strcat()
strcat()函数非常类似于 strcpy(),除了它可以将一个字符串合并到缓冲区末尾。它也有一个类似的、更安全的替代方法 strncat()。如果可能,使用 strncat() 而不要使用 strcat()。
3. gets
void main()
{
char buffer[5];
/* DON'T DO THIS */
while ((buffer[i++] = getchar()) != '\n')
{
};
}
void main()
{
char buffer[5];
/* DON'T DO THIS */
while ((buffer[i++] = getchar()) != '\n')
{
};
}
建议使用 fgets
4.sprintf() vsprintf()
函数 sprintf()和 vsprintf()是用来格式化文本和将其存入缓冲区的通用函数。它们可以用直接的方式模仿 strcpy() 行为。换句话说,使用 sprintf() 和 vsprintf() 与使用 strcpy() 一样,都很容易对程序造成缓冲区溢出。
5.scanf() 系列
scanf() sscanf() fscanf() vfscanf() vscanf() vsscanf()
scanf系列的函数也设计得很差。在这种情况下,目的地缓冲区会发生溢出。考虑以下代码:
void main(int argc, char **argv)
{
char buf[256];
sscanf(argv[0], "%s", &buf);
}
void main(int argc, char **argv)
{
char buf[256];
sscanf(argv[0], "%s", &buf);
}
如果输入的字大于 buf 的大小,则有溢出的情况.
另还有几种情况:
a) 使用"%x"或"%d",但最后一个参数是char,也可能导致溢出,因"%x"或"%d"是读取4个字节,char只有一个字节,因此有可能会覆盖后面的内容。
b) 使用"d%"读取64位的数字也可能导致溢出
c) 使用为int定义的bool型时,若赋值为char型时,亦会出现溢出的现象
6. strdup()
strdup()函数是复制输入字符串,返回新申请内存的字符串。它是调用malloc,因此调用strdup后,需free来释放申请的内存。
#include <string.h>
#include <stdio.h>
void main( void )
{
char buffer[] = "This is the buffer text";
char *newstring;
printf( "Original: %s\n", buffer );
newstring = strdup( buffer );
free( newstring );
}
内存溢出处理
使用new可以动态分配内存,但是当内存不足以分配或者内存溢出时,new会如何处理呢?
如果new运算符不能分配所需的内存,它会激活一个函数指针,该函数一般会输出一条错误消息并退出程序(exit),或是释放某些内存然后返回,一旦返回,new会再次尝试分配所需要的内存,这种情况会持续下去,直到有可分配的内存或者退出程序。如果待激活的函数指针为空(默认情况),也就是当内存耗尽而new不能分配时没有要调用的函数,这种情况与编译器的版本有关,一般new会终止程序。要设定上述这个函数指针要使用函数set_new_handler(vc++下用_set_new_handler ),这个函数包含在new库中,原型是:
typedef void (*new_handler)();
new_handler set_new_handler(new_hanlder) throw();
输入是新的函数指针,输出是旧有的函数指针,而且函数指针所指函数必须不带参数也不带返回值。故下面程序的就很好理解了:
#include <new>
void (*my_own)(){/*code*/}//这是我自己的内存溢出处理函数
int main(){
void (*previous_handler)();//用来存储旧有的函数指针
previous_handler=set_new_handler(my_own);//设置内存溢出处理函数为my_own
previous_handler=set_new_handler(previous_handler);
//设置内存溢出处理函数为原来的函数,但现在的previous_handler存储的函数指针是my_own
previous_handler=set_new_handler(previous_handler);//重新设置内存处理函数为my_own
}
例:
#include <iostream>
#include <new>
#include <string>
#include <exception>int main(){
void (*my_new_handler){
throw "There is no memory to use.";
}
set_new_handler(my_new_handler);
try {
while(true){
new int[50000];
}
}catch(const char *str){
cout<<str;
}
}
内存溢出 内存溢出已经是软件开发历史上存在了近40年的“老大难”问题,象在“红色代码”病毒事件中表现的那样,它已经成为黑客攻击企业网络的“罪魁祸首”。 如在一个域中输入的数据超过了它的要求就会引发数据溢出问题,多余的数据就可以作为指令在计算机上运行。据有关安全小组称,操作系统中超过50%的安全漏洞都是由内存溢出引起的,其中大多数与微软的技术有关。
为了便于理解,我们不妨打个比方。缓冲区溢出好比是将十磅的糖放进一个只能装五磅的容器里。一旦该容器放满了,余下的部分就溢出在柜台和地板上,弄得一团糟。由于计算机程序的编写者写了一些编码,但是这些编码没有对目的区域或缓冲区——五磅的容器——做适当的检查,看它们是否够大,能否完全装入新的内容——十磅的糖,结果可能造成缓冲区溢出的产生。如果打算被放进新地方的数据不适合,溢得到处都是,该数据也会制造很多麻烦。但是,如果缓冲区仅仅溢出,这只是一个问题。到此时为止,它还没有破坏性。当糖溢出时,柜台被盖住。可以把糖擦掉或用吸尘器吸走,还柜台本来面貌。与之相对的是,当缓冲区溢出时,过剩的信息覆盖的是计算机内存中以前的内容。除非这些被覆盖的内容被保存或能够恢复,否则就会永远丢失。
在丢失的信息里有能够被程序调用的子程序的列表信息,直到缓冲区溢出发生。另外,给那些子程序的信息——参数——也丢失了。这意味着程序不能得到足够的信息从子程序返回,以完成它的任务。就像一个人步行穿过沙漠。如果他依赖于他的足迹走回头路,当沙暴来袭抹去了这些痕迹时,他将迷失在沙漠中。这个问题比程序仅仅迷失方向严重多了。入侵者用精心编写的入侵代码(一种恶意程序)使缓冲区溢出,然后告诉程序依据预设的方法处理缓冲区,并且执行。此时的程序已经完全被入侵者操纵了。
入侵者经常改编现有的应用程序运行不同的程序。例如,一个入侵者能启动一个新的程序,发送秘密文件(支票本记录,口令文件,或财产清单)给入侵者的电子邮件。这就好像不仅仅是沙暴吹了脚印,而且后来者也会踩出新的脚印,将我们的迷路者领向不同的地方,他自己一无所知的地方。
缓冲区溢出的处理
你屋子里的门和窗户越少,入侵者进入的方式就越少……
由于缓冲区溢出是一个编程问题,所以只能通过修复被破坏的程序的代码而解决问题。如果你没有源代码,从上面“堆栈溢出攻击”的原理可以看出,要防止此类攻击,我们可以:
1、开放程序时仔细检查溢出情况,不允许数据溢出缓冲区。由于编程和编程语言的原因,这非常困难,而且不适合大量已经在使用的程序;
2、使用检查堆栈溢出的编译器或者在程序中加入某些记号,以便程序运行时确认禁止黑客有意造成的溢出。问题是无法针对已有程序,对新程序来讲,需要修改编译器;
3、经常检查你的操作系统和应用程序提供商的站点,一旦发现他们提供的补丁程序,就马上下载并且应用在系统上,这是最好的方法。但是系统管理员总要比攻击者慢一步,如果这个有问题的软件是可选的,甚至是临时的,把它从你的系统中删除。举另外一个例子,你屋子里的门和窗户越少,入侵者进入的方式就越少。
如果在申请动态内存时找不到足够大的内存块,malloc 和 new 将返回 NULL 指针,
宣告内存申请失败。通常有三种方式处理“内存耗尽”问题。
(1)判断指针是否为 NULL,如果是则马上用 return 语句终止本函数。例如:
void Func(void)
{
A *a = new A;
if(a == NULL)
{
return;
}
...
}
(2)判断指针是否为 NULL,如果是则马上用 exit(1)终止整个程序的运行。例如:
void Func(void)
{
A *a = new A;
if(a == NULL)
{
cout << “Memory Exhausted” << endl;
exit(1);
}
...
}
(3)为 new 和 malloc 设置异常处理函数。例如 Visual C++可以用_set_new_hander 函数
为 new 设置用户自己定义的异常处理函数,也可以让 malloc 享用与 new 相同的异常处理函数。
上述(1)、(2)方式使用最普遍。如果一个函数内有多处需要申请动态内存,那么方
式(1)就显得力不从心(释放内存很麻烦),应该用方式(2)来处理。
很多人不忍心用 exit(1), 问:“不编写出错处理程序,让操作系统自己解决行不行?”
不行。如果发生“内存耗尽”这样的事情,一般说来应用程序已经无药可救。如果
不用 exit(1) 把坏程序杀死,它可能会害死操作系统。道理如同:如果不把歹徒击毙,歹
徒在老死之前会犯下更多的罪。