Science for all





C/C++的编译解决方案

C/C++的编译解决方案

有个完全不同的方案是使用执行边界检查的编译方法(参见[Sitaker 1999]中所列的方法)。在我看来,这样的工具在进行多层次防范时很有用,但把该技术作为唯一的防范手段就不是很明智。至少有两个理由这样讲。首先,大多数这样的工具都只对缓存溢出提供部分保护(“完全的”防范一般会慢12-30倍);C和C++根本就不是为防止缓存溢出而设计的。其次,对于开放源代码程序,人们无法确定将使用什么工具来编译程序;对于某个给定系统使用缺省的“常用”编译器可能会突然打开安全漏洞。

一个更有用的工具是“StackGuard”,标准GNU的C编译器gcc的一个修改版。StackGuard通过在返回地址前插入一个“守卫”值(称作“canary”)起作用;如果缓存溢出改写了返回地址,canary的值(很可能)改变了,系统在使用地址前会察觉出来。这很有价值,但要注意这并没有防止缓存溢出改变其它的值(而这还是可以被用来对系统进行攻击)。有个叫“PointGuard”的工具把StackGuard扩展为可以在其它数据项前增加canary,PointGuard会自动保护特定的值(如函数指针和远程跳转缓存)。但是,使用PointGuard来保护其它变量类型要求程序员明确地介入(程序员必须指定哪一个数据值需要用canary保护)。这可能有价值,但容易意外地忘记保护一个被认为不需要保护的数据值 -- 但它实际上是需要进行保护的。更多有关StackGuard、PointGuard和其它替代方案可参见Cowan [1999]。

与之相关,可以修改Linux内核,使堆栈段不可执行;这样的Linux补丁已经有了(参见包含此部分的Solar Designer的补丁 http://www.openwall.com/linux/)。 但是,这种做法不是建立在Linux内核里的。一部分理由是因为这种保护并不像看起来那样完善;攻击者可以简单地迫使系统调用其它已经在程序中的某些“有趣”的位置(如库、堆或静态数据段)。同样,有时Linux确实需要堆栈中的可执行代码,比如用来实现信号和用来实现GCC的“trampolines”。Solar Designer的补丁确实可以处理这些问题,但这使补丁变得复杂。就个人而言,我希望它被结合进主要的Linux发行版中,因为它确实使攻击变得更困难,而且可抵御一部分现有的攻击。尽管如此,我同意Linus Torvalds和其他人的观点,即它并没有增加如显示的那样多的保护,而且可以被相对容易地绕过。可以看一下Linus Torvalds对不包括该支持的解释 http://lwn.net/980806/a/linus-noexec.html

简而言之,最好是先开发自己能抵御缓存溢出的正确程序。然后,再使用诸如StackGuard等技术和工具来作为额外的安全网络。如果在代码本身下了工夫以消除缓存溢出,那么StackGuard就会更为有效,因为需要调用StackGuard进行保护的“盔甲上的缝隙”会少很多。