设为首页 加入收藏

TOP

建议7:时刻提防内存溢出
2013-10-07 14:47:36 来源: 作者: 【 】 浏览:57
Tags:建议 时刻 提防 内存 溢出

建议7:时刻提防内存溢出

作为一个程序员,对内存溢出问题肯定不陌生,它已经是软件开发历史上存在了近40年的大难题。在内存空间中,当要表示的数据超出了计算机为该数据分配的空间范围时,就产生了溢出,而溢出的多余数据则可以作为指令在计算机中大摇大摆地运行。不幸的是,一不小心这就成了黑客们可利用的秘密后门,“红色代码”病毒事件就是黑客利用内存溢出攻击企业网络的“经典案例”。甚至有人称,操作系统中超过50%的安全漏洞都是由内存溢出引起的。

众所周知,C/C++(www.cppentry.com)语言虽然是一种高级语言,但是其程序的目标代码却非常接近机器内核,它能够直接访问内存和寄存器,这种特性大大提升了C/C++(www.cppentry.com)语言代码的性能,同时也提高了内存溢出问题出现的可能性。内存溢出问题可以说是C/C++(www.cppentry.com)语言所固有的缺陷,因为它们既不检查数组边界,也不检查类型可靠性。

假设代码申请了X字节大小的内存缓冲区,随后又向其中复制超过X字节的数据,那么多出来的字节会溢出原本的分配区。最重要的是,C/C++(www.cppentry.com)编译器开辟的内存缓冲区常常邻近重要的数据结构。如果恶意攻击者用“别有用心”的东西刻意地覆盖原本安全可信的数据,那么后果就是此机器将会成为他们肆意攻击的“肉鸡”。下面将介绍常见的缓冲区溢出,以及预防措施。

C语言中的字符串库没有采用相应的安全保护措施,所以在使用时要特别小心。例如,在执行strcpy、strcat等函数操作时没有检查缓冲区大小,就会很容易引起安全问题。

现在分析下面的代码片段:

  1. const int MAX_DATA_LENGTH = 32;  
  2. void DataCopy (char *szSrcData)  
  3.  {  
  4. char szDestData[MAX_DATA_LENGTH];  
  5. strcpy(cDest,szData);  
  6. // processing codes  
  7. ...  
  8.  } 

似乎这段代码不存在什么问题,但是细心的读者还是会发其中的危险。如果数据源szSrcDatar的长度不超过规定的长度,那么这段代码确实没什么问题。strcpy()不会在乎数据来源,也不会检查字符串长度,唯一能让它停下来的只有字符串结束符'\0'。不过,如果没有遇到这个结束符,它就会一个字节一个字节地复制szSrcData的内容,在填满32字节的预设空间后,溢出的字符就会取代缓冲区后面的数据。如果这些溢出的数据恰好覆盖了后面DataCopy函数的返回地址,在该函数调用完毕后,程序就会转入攻击者设定的“返回地址”中,乖乖地进入预先设定好的陷阱。

为了避免落入这样的圈套,给作恶者留下可乘之机,当C/C++(www.cppentry.com)代码处理来自用户的数据时,应该处处留意。如果一个函数的数据来源不可靠,又要用到内存缓冲区,那么必须提高警惕,必须知道内存缓冲区的总长度,并检验内存缓冲区。

  1. const int MAX_DATA_LENGTH = 32;  
  2. void DataCopy (char *szSrcData, DWORD nDataLen)  
  3.  {  
  4. char szDestData[MAX_DATA_LENGTH];  
  5.    if(nDataLen < MAX_DATA_LENGTH)  
  6.        strcpy(cDest,szData);  
  7.     szDestData[nDataLen] = '\0'; // 0x42;  
  8. // processing code  
  9. ...  
  10.  } 

首先,要获得szSrcData的长度,保证数据长度不大于最大缓冲区长度MAX_DATA_LENGTH;其次,要保证参数传来的数据长度真实有效,方法就是向内存缓冲区的末尾写入数据。因为,当缓冲区溢出时,一旦向其中写入常量值,代码就会出错,终止运行。与其落入阴谋家的陷阱,还不如及时终止程序运行。

虽然上述方法能够有效地降低内存溢出的危害,却不能从根本上避免对内存溢出的攻击。所以在调用 strcpy、strcat、gets 等经典函数时,你要从源代码开始就提高警惕,尽量追踪传入数据的流向,向代码中的每一个假设提出质疑,包括对那些所谓相对安全可靠的改良版N-Versions(strncpy 或 strncat)也不可轻信。

访问边界数据同样可能引起缓冲区溢出。在这种情况下的内存溢出不会像第一种那么危险,但同样令人讨厌。就如下面的代码片段:

  1. const int DATA_LENGTH = 16;  
  2. Int data[16] = {1,9,8,4,0,9,1,7,1,9,8,7,0,3,0,9};  
  3. void PrintData()  
  4. {  
  5.     for(int i=0; a[i]!=0&&i<DATA_LENGTH; i++)  
  6.     {  
  7.          cout<<data[i])<<endl;  
  8.      }  

这也是一个隐藏很深、难以发现的问题:当i==16的时候,在判断i< DATA_LENGTH的同时需要判断data[16]。而data[16]已经访问到了非法区域,可能引起缓冲区溢出。正确的方式应该是不要将索引号i与数据本身data[i]的判断放在一起,而是将判断条件分成两句:
  1. const int DATA_LENGTH = 16;  
  2. int data[16] = {1,9,8,4,0,9,1,7,1,9,8,7,0,3,0,9};  
  3. void PrintData()  
  4. {  
  5.     for(int i=0; i<DATA_LENGTH; i++)  
  6.     {  
  7.          if(a[i]!=0)  
  8.            cout<<data[i])<<endl;  
  9.     }  

类似的问题还有可能发生在访问未初始化指针或失效指针时。未初始化的指针和失效后未置NULL的指针指向的是未知的内存空间,所以对这样的指针进行操作很有可能访问或改写未知的内存区域,也就可能引起缓冲区溢出的问题了。

请记住:

因为内存溢出潜在的危害很大,所以必须注意和面对这个问题,特别是在网络相关的应用程序中。在调用C语言字符串经典函数(如strcpy、strcat、gets 等)时,要从源代码开始就提高警惕,尽量追踪传入数据的流向,向代码中的每一个假设提出质疑。在访问数据时,注意对于边界数据要特殊情况特殊处理,还要对杜绝使用未初始化指针和失效后未置NULL的“野指针”。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇建议9:防止重复包含头文件 下一篇建议14:小心typedef使用中的陷阱

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: