Chapter 5. 避免缓存溢出

 

An enemy will overrun the land; he will pull down your strongholds and plunder your fortresses.

 Amos 3:11 (NIV)
Table of Contents
C/C++中的危险
C/C++中库的解决方案
C/C++的编译解决方案
其它语言

一个非常普遍的安全性缺陷是“缓存溢出”。从技术上来说,缓存溢出是程序内部实现的问题,但它是个非常普遍而且严重的问题,所以把它放在单独的一节里进行说明。在CERT,1998年13篇报告中的9篇和至少1999年一半的报告都与缓存溢出有关,这大概可以加深你对该问题重要性的认识。Bugtraq的一项非正式调查发现大约2/3的响应认为缓存溢出是安全性薄弱环节的首要因素(其余的响应认为“配置错误”是首要原因)[Cowan 1999]。这是一个古老而且众所周知的问题,但它还是不断地重复出现[McGraw 2000]。

如果把一组值(通常是一个字符串)写入某个固定长度的缓存区并越过缓存边界(一般是越过缓存的结尾)持续写入至少一个值时,缓存溢出就发生了。缓存溢出在从用户那里读入输入放进缓存时会发生,但也会在程序的其它处理过程中发生。

如果某个安全程序允许缓存溢出,它就经常会被对手利用。如果该缓存为局部C变量,溢出就可以被用来强迫函数运行攻击者选择的代码。这种特殊的攻击手段被称为“堆栈冲击”攻击。放置在堆中的缓存也好不到哪里去;攻击者可以用这样的溢出来控制程序里的变量。更多的细节可以从Aleph1 [1996]、Mudge [1995]或 http://destroy.net/machines/security/ 上Nathan P. Smith的“Stack Smashing Security Vulnerabilities”WEB站点上找到。

大多数编程语言从根本上避免了这个问题,或者是因为它们自动地重新设置数组大小(如Perl),或者是因为它们一般检测并防止缓存溢出(如Ada95)。但是,C语言根本没有提供对此问题的保护,C++在使用时也很容易导致此问题。

C/C++中的危险

C用户必须避免使用不检查边界的危险函数,除非它们能确保边界不会被超过。在大多数情况下应避免的函数包括strcpy(3)、strcat(3)、sprintf(3)(以及相近的vsprintf(3))和gets(3)函数。它们应该被相应的诸如strncpy(3)、strncat(3)、snprintf(3)和fgets(3)函数所替代,但请阅读下面的讨论。函数strlen(3)应该被避免,除非能确定可以找到一个作为终止的NIL字符。scanf()函数族(scanf(3)、fscanf(3)、sscanf(3)、vscanf(3)、vsscanf(3)和vfscanf(3))在应用时经常是危险的;不要在没有控制最大长度(格式%s是个特别普遍的问题)的情况下使用它来向某个字符串发送数据。其它可能会允许缓存溢出(与应用有关)的危险函数包括realpath(3)、getopt(3)、getpass(3)、streadd(3)、strecpy(3)和strtrns(3)。必须小心getwd(3);发送给getwd(3)的缓存必须至少有PATH_MAX个字节长。如果你很重视可移植性问题,那么还有一个额外的问题:某些系统上的snprintf对缓存溢出并无实际的保护;据我所知,Linux下的版本是正确工作的。 [vf]scanf(const char *format, ...) 参数可能溢出。 realpath(char *path, char resolved_path[]) 路径缓存可能溢出。 [v]sprintf(char *str, const char *format, ...) str缓存可能溢出。