
2.6.4 Visual Studio中编译器生成的运行时检查
微软的Visual Studio C++编译器提供了几个选项来在运行时启用特定的检查。这些选项可以使用特定的编译器标志来启用。尤其是,/RTCs编译器标志打开了以下错误检查:
·局部变量溢出,如数组(除了在一个内部填充的结构中使用时)
·使用未初始化的变量
·栈指针损坏,这可能是由于调用约定不匹配造成的
这些标志可以在代码的各区域中调整或关闭。例如,下面的编译指示:
#pragma runtime_checks("s", off)
对代码中的任何后续函数关闭/RTCs标志检查。这个检查可以用如下编译指示恢复:
#pragma runtime_checks("s", restore)
运行时边界检查。虽然未向公众公开,但一些现有的C语言编译器和运行时系统确实执行数组边界检查。
Libsafe和Libverify。Libsafe是Avaya Labs Research 提供的一个动态库,它能限制栈的缓冲区溢出的影响。这个库可以拦截容易产生缓冲区溢出的C库函数并检查其参数范围。这个库确保帧指针和返回地址不能被拦截函数覆盖。同样也是由Baratloo和同事提供的[Baratloo 2000]Libverify库则实现了与Libsafe 类似的返回地址验证方案,但不需要重新编译源代码,这使得它能够用于现有的二进制程序文件。
CRED。Richard Jones和Paul Kelley[Jones 1997]提出了一个利用引用对象进行边界检测的方法。这种方法基于以下原则:一个根据边界内指针计算出来的地址必定与原始指针指向相同的对象。遗憾的是,数目惊人的现有程序生成并存储边界外地址,然后在计算中又取得这些地址处的值,而这些操作并未造成缓冲区溢出,这就使得这些程序不适宜采用前述的边界检测方式。这种运行时边界检查方式还要付出显著的性能代价,尤其在某些指针密集型的程序中,性能下降可达30倍之多[Cowan 2000]。
Olatunji Ruwase和Monica Lam[Ruwase 2004]在他们的C范围错误侦测器(C Range Error Detector,CRED)中改进了Jones和Kelley的方法。根据作者的说法,CRED执行一种宽松的正确性标准,这是通过允许程序操作不会引起缓冲区溢出的边界外地址而做到的。这个宽松的正确性标准为现有的软件提供了较高的兼容性。
CRED可以被配置为检测所有数据的边界或者仅检测字符串数据的边界。完全边界检测,比如Jones和Kelley的方法,会产生显著的性能开销。将对边界的检查局限于字符串可改善大多数程序的性能。视应用程序中对字符串的使用情况,这种性能的开销范围为1%~130%。
边界检测可以有效地阻止大多数溢出情况,但这种方式并非完美。以CRED方案为例,它无法检测出一个边界外指针首先利用算术操作强制转换为整数然后强制转换回指针的情况。这种方案确实可以防止栈、堆和数据段的溢出。甚至当优化到仅检测字符串溢出的情况时,CRED也可以有效地检测出由John Wilander和Marian Kamkar开发的用于评估动态缓冲区溢出检测器的20种不同的缓冲区溢出攻击[Wilander 2003]。
CRED已经合并到最新的(针对GCC 3.3.1的)Jones和Kelley检测器中,此检测器目前由Herman ten Brugge维护。
Dinakar Dhurjati和Vikram Adve提出了一个改进的集合,包括池分配,这使得编译器生成在运行时在对象表中搜索对象的代码[Dhurjati 2006]。这个改进的性能有显著提高,但开销仍高达69%。