« 上一篇下一篇 »

缓冲区溢出

为了防止缓冲区溢出,我们要么必须通过静态的方法证明每个数组写运算都处于边界之内,要么必须进行适当的动态数组边界检查。因为在C和C++程序中必须手工插入这些边界检查,程序员很容易忘记插人测试代码,或者插人错误的测试代码。人们已经开发了启发式工具来检查是否在调用一个strcpy之前至少进行了某些测试,虽然这些测试不一定是正确的。
当一个由用户提供的精心制作的数据被写到了预想的缓冲区之外并操纵程序的执行时,就发生了缓冲区溢出攻击(buffer overflow attack)。比如,一个C程序可能从用户那里读取一个字符 串h然后使用函数调用

strcpy(b,s);

把它拷贝到一个缓冲区b中。如果字符串s实际上比缓冲区6长,那么在缓冲区b之外的某些内存位置上的值将会被改变。这个情况本身可能会使程序产生故障,或者至少产生错误的答案,因为程序使用的某些数据可能已经被改变了。

但是实际情况会更糟糕,选择字符串s的黑客可以选择一个特别的值,使得它的作用不仅仅是引起一个错误。比如,如果该缓冲区位于一个运行时刻栈中,那么它可能离存放该函数的返回地址的位置很近。一个经过精心选择的恶意的s值可以覆盖掉这个地址,当函数返回时,它跳转到黑客选择的地方。如果黑客熟悉操作系统和硬件,那么他们就能够执行一个命令,让系统赋予他们控制这台计算机的能力。在有些情况下,他们甚至可以有能力让那个假的返回地址把控制传递到作为字符串s的一部分的代码中,这样就能将任何种类的程序插人到正在执行的代码中。

为了防止缓冲区溢出,我们要么必须通过静态的方法证明每个数组写运算都处于边界之内,要么必须进行适当的动态数组边界检查。因为在C和C++程序中必须手工插入这些边界检查,程序员很容易忘记插人测试代码,或者插人错误的测试代码。人们已经开发了启发式工具来检查是否在调用一个strcpy之前至少进行了某些测试,虽然这些测试不一定是正确的。

动态边界检查是不可避免的,因为不可能静态地确定用户输人的大小。静态分析可以做的所有事情就是保证正确地插人了动态检查代码。因此,一个可行的策略是让编译器在每个写操作上插入动态边界检查,并以静态分析为手段尽可能优化掉动态检查代码。这样就不再需要去捕捉每个可能违背边界条件的情况。而且,我们只需要优化那些频繁执行的代码区域。

即使我们不在乎运行开销,在C程序中插入边界检查也不是容易的事情。一个指针可能指向某个数组的中间,而且我们还不知道这个数组的大小。可以使用已有的技术来动态跟踪各个指针指向的缓冲区的大小。这个信息允许编译器为所有的访问都插人数组边界测试。有意思的是,我们并不建议一检测到缓冲区溢出就停止执行程序。实际上,实践中确实会发生缓冲区溢出,如果我们不允许所有的缓冲区溢出,一个程序就很容易出错。解决的方法是动态扩展缓冲区 的大小以应对缓冲区溢出。

可以利用过程间分析技术来提高动态的数组边界检査的速度。比如,假设我们只关注和用户输人字符串有关的缓冲区溢出,那么可以使用静态分析技术来决定哪个变量可能存放了用户提供的内容。和SQL注人一样,如果我们能够跟踪一个输人德在过程间传递复制的过程,就有利于消除不必要的边界检査。

« 上一篇下一篇 »