10-小心缓冲区溢出
38.9 小心缓冲区溢出
当输入值或复制的字符串超出分配的缓冲区空间时就需要小心缓冲区溢出了。永远不要使用gets(),在使用诸如scanf()、sprintf()、strcpy()以及strcat()时需要谨慎(如使用if语句防止在使用这些函数时造成缓冲区溢出)。
恶意用户可以通过诸如缓冲区溢出(也被称为栈粉碎)之类的技术将精心编写的字节放入一个栈帧中以强制特权程序执行任意代码。(网上的几个资源站点解释了栈粉碎的细节,读者还可以参考[Erickson, 2008]和[Anley, 2007])。从CERT(http://www.cert.org/)和Bugtraq(http://www.securityfocus.com/)上发表的咨询报告的频率上明显可以看出在一个计算机系统上,缓冲区溢出可能是引起安全性问题的最常见的原因了。缓冲区溢出对于网络服务器来讲特别具有危害性,因为它们使得系统向网络上任意地方的远程攻击打开了大门。
为了使栈崩溃变得更加困难——特别是使得在远程主机上使用此类攻击手段攻击网络服务器时更加耗时——从内核2.6.12开始,Linux实现了地址空间随机化。这项技术使得栈的位置能够在虚拟内存最前面的8M空间内随机变动。此外,如果软RLIMIT_STACK限制有限并且Linux特有的/proc/sys/vm/legacy_va_layout不包含0,那么内存映射的位置也可以随机化。 最新的x86-32架构为将页表变成NX(“no execute”)提供了硬件支持。这个特性用来防止执行栈上的代码,从而使得栈奔溃变得更加困难。
上面提到的很多函数都存在安全的版本——如snprintf()、strncpy()以及strncat()——允许调用者指定需复制的最大字符数。这些函数考虑到了最大数量以防止目标缓冲区的溢出。一般来讲,最好使用这些函数,但使用时仍然需要小心,特别需要注意下列事项。
- 对于其中的大多数函数来讲,如果到达了指定的最大值,那么源字符串的截断部分会被放到目标缓冲区中。由于这样的截断字符串对于程序来讲可能是毫无意义的,因此调用者必须要检查字符串是否发生了截断(如使用snprintf()的返回值)并且在发生截断的时候采取必要的措施。
- 使用strncpy()对性能会有影响。如在strncpy(s1, s2, n)调用中,如果s2指向的字符串的长度小于n字节,那么补全的null字节就会被写入s1以确保总共写入了n字节。
- 如果传入strncpy()的最大大小不足以容纳结尾的null字符,那么目标字符串就会成为不以null结尾的字符串。
一些UNIX实现提供了strlcpy()函数,它接收长度参数n,最多将n – 1个字节复制到目标缓冲区中并且总是会在缓冲区的结尾加上null字符。但SUSv3并没有规定这个函数,并且glibc也没有实现这个函数。此外,如果调用者没有小心检查字符串长度,那么这个函数在解决一个问题(缓冲区溢出)的同时又引入了另一个问题(毫无征兆地丢弃数据)。