C和C++安全编码(原书第2版)
上QQ阅读APP看书,第一时间看更新

2.8 小结

当在为一个特定数据结构分配的内存的边界之外写入数据时,就会发生缓冲区溢出。缓冲区溢出在C和C++程序中司空见惯,因为这两种语言:(1)将字符串定义为以空字符结尾的字符数组;(2)不进行隐式的边界检查;(3)提供了不执行边界检查的标准字符串调用库。这些属性与程序员的漫不经心交织在一起,导致了缓冲区溢出漏洞的发生。

缓冲区溢出是一个棘手的问题,因为在软件应用程序的开发和测试阶段都难以侦测到它们。常用的C和C++编译器在编译时并不识别可能的缓冲区溢出情形,在运行时也不会报告缓冲区溢出异常。只有当测试数据能够引发一个可侦测的溢出时,才能使用动态分析工具来探查缓冲区溢出。

并非所有的缓冲区溢出都会导致可利用的软件漏洞。然而,如果程序的输入数据是由(可能怀有恶意的)用户控制的,那么缓冲区溢出就很可能会造成程序漏洞,从而易受攻击。甚至那些不属于明显漏洞的缓冲区溢出也可能会招致危险。

缓冲区溢出是软件漏洞的一个主要来源。未提供类型安全的语言,例如C和C++,尤其容易产生这种漏洞。现有的利用代码已经存在于多种流行的软硬件平台上,包括Windows、Linux、Solaris和其他一些常见的操作系统,以及常见的硬件架构如Intel、SPARC和Motorola。

针对这种情况,常见的缓解策略就是采用新的、提供更多安全保障的字符串操作库。现在已经有很多这种类型的替代库和函数,它们有着自己的设计哲学,用户可根据自身需要选用。例如,C11附录K边界检查接口就是为既有函数调用设计的方便的替代品。因此,这些函数可以作为预防性的维护措施,以减少现有遗留代码库产生漏洞的可能性。在选择一个适当的程序设计途径时,往往需要在方便和安全之间作出权衡。对于一个给定的输入集合,更安全的函数通常都拥有更多的错误条件,而不那么安全的函数则是尽力给出有效的结果。对库的选择也受到所选择的语言、平台以及可移植性问题的制约。

现在已经有一些可行的缓解策略,它们有助于消除由于缓冲区溢出所导致的漏洞。采用所有可用的避免策略是不实际的,因为每一种策略都要付出工作量,都有时间和许可费用的开销。不过,有些策略彼此互补得很好。在审查源代码的过程中,静态分析可用于识别潜在的问题。源代码审查和测试需要分析相同的问题,因此也会分摊一些开销。可以结合使用动态分析和测试手段来识别溢出条件。

运行时解决方案,例如边界检查、探测仪和安全库,都具有运行时性能开销并且可能发生冲突。例如将探测仪与安全库一起使用并无意义,因为它们多是以不同的方式执行或多或少相同的功能。

缓冲区溢出是最常见的软件漏洞来源,因此绝不应轻视它。我们建议,只要有可能的话,最好采用多种策略进行深层防御(defense-in-depth)。然而,预防缓冲区溢出的首要策略是教育开发者如何避免写出有漏洞的代码。