c中不安全的函数
今天,编写的程序仍然利用这些调用,因为从来没有人教开发人员避免使用它们。某些人从各处获得某个提示,但即使是优秀的开发人员也会被这弄糟。他们也许在危险函数的自变量上使用自己总结编写的检查,或者错误地推论出使用潜在危险的函数在某些特殊情况下是“安全”的。
第 一位公共敌人是 gets()。永远不要使用 gets()。该函数从标准输入读入用户输入的一行文本,它在遇到 EOF 字符或换行字符之前,不会停止读入文本。也就是:gets() 根本不执行边界检查。因此,使用 gets() 总是有可能使任何缓冲区溢出。作为一个替代方法,可以使用方法 fgets()。它可以做与 gets() 所做的同样的事情,但它接受用来限制读入字符数目的大小参数,因此,提供了一种防止缓冲区溢出的方法。例如,不要使用以下代码:
void main() { char buf[1024]; gets(buf); } |
而使用以下代码:
#define BUFSIZE 1024 void main() { char buf[BUFSIZE]; fgets(buf, BUFSIZE, stdin); } |
C 编程中的主要陷阱
C 语言中一些标准函数很有可能使您陷入困境。但不是所有函数使用都不好。通常,利用这些函数之一需要任意输入传递给该函数。这个列表包括:
- strcpy()
- strcat()
- sprintf()
- scanf()
- sscanf()
- fscanf()
- vfscanf()
- vsprintf
- vscanf()
- vsscanf()
- streadd()
- strecpy()
- strtrns()
坏消息是我们推荐,如果有任何可能,避免使用这些函数。好消息是,在大多数情况下,都有合理的替代方法。我们将仔细检查它们中的每一个,所以可以看到什么构成了它们的误用,以及如何避免它。
strcpy()函数将源字符串复制到缓冲区。没有指定要复制字符的具体数目。复制字符的数目直接取决于源字符串中的数目。如果源字符串碰巧来自用户输入,且没有专门限制其大小,则有可能会陷入大的麻烦中!
如果知道目的地缓冲区的大小,则可以添加明确的检查:
if(strlen(src) >= dst_size) { /* Do something appropriate, such as throw an error. */ } else { strcpy(dst, src); |
完成同样目的的更容易方式是使用 strncpy() 库例程:
strncpy(dst, src, dst_size-1); dst[dst_size-1] = " "; /* Always do this to be safe! */ |
如果 src 比 dst 大,则该函数不会抛出一个错误;当达到最大尺寸时,它只是停止复制字符。注意上面调用 strncpy() 中的 -1。如果 src 比 dst 长,则那给我们留有空间,将一个空字符放在 dst 数组的末尾。
当然,可能使用 strcpy() 不会带来任何潜在的安全性问题,正如在以下示例中所见:
strcpy(buf, "Hello!"); |
即使这个操作造成 buf 的溢出,但它只是对几个字符这样而已。由于我们静态地知道那些字符是什么,并且很明显,由于没有危害,所以这里无须担心 ― 当然,除非可以用其它方式覆盖字符串“Hello”所在的静态存储器。
确保 strcpy() 不会溢出的另一种方式是,在需要它时就分配空间,确保通过在源字符串上调用 strlen() 来分配足够的空间。例如:
dst = (char *)malloc(strlen(src)); strcpy(dst, src); |
strcat()函数非常类似于 strcpy(),除了它可以将一个字符串合并到缓冲区末尾。它也有一个类似的、更安全的替代方法 strncat()。如果可能,使用 strncat() 而不要使用 strcat()。
函数 sprintf()和 vsprintf()是用来格式化文本和将其存入缓冲区的通用函数。它们可以用直接的方式模仿 strcpy() 行为。换句话说,使用 sprintf() 和 vsprintf() 与使用 strcpy() 一样,都很容易对程序造成缓冲区溢出。例如,考虑以下代码:
void main(int argc, char **argv) { char usage[1024]; sprintf(usage, "USAGE: %s -f flag [arg1] ", argv[0]); } |
我们经常会看到类似上面的代码。它看起来没有什 么危害。它创建一个知道如何调用该程序字符串。那样,可以更改二进制的名称,该程序的输出将自动反映这个更改。 虽然如此, 该代码有严重的问题。文件系统倾向于将任何文件的名称限制于特定数目的字符。那么,您应该认为如果您的缓冲区足够大,可以处理可能的最长名称,您的程序会 安全,对吗?只要将 1024 改为对我们的操作系统适合的任何数目,就好了吗?但是,不是这样的。通过编写我们自己的小程序来推翻上面所说的,可能容易地推翻这个限制:
void main() { execl("/path/to/above/program", <<insert really long string here>>, NULL); } |
函数 execl() 启动第一个参数中命名的程序。第二个参数作为 argv[0] 传递给被调用的程序。我们可以使那个字符串要多长有多长!
那么如何解决 {v}sprintf() 带来得问题呢?遗憾的是,没有完全可移植的方法。某些体系结构提供了 snprintf() 方法,即允许程序员指定将多少字符从每个源复制到缓冲区中。例如,如果我们的系统上有 snprintf,则可以修正一个示例成为:
void main(int argc, char **argv) { char usage[1024]; char format_string = "USAGE: %s -f flag [arg1] "; snprintf(usage, format_string, argv[0], 1024-strlen(format_string) + 1); } |
注意,在第四个变量之前,snprintf() 与 sprintf() 是一样的。第四个变量指定了从第三个变量中应被复制到缓冲区的字符最大数目。注意,1024 是错误的数目!我们必须确保要复制到缓冲区使用的字符串总长不超过缓冲区的大小。所以,必须考虑一个空字符,加上所有格式字符串中的这些字符,再减去格式 说明符 %s。该数字结果为 1000, 但上面的代码是更具有可维护性,因为如果格式字符串偶然发生变化,它不会出错。
{v}sprintf() 的许多(但不是全部)版本带有使用这两个函数的更安全的方法。可以指定格式字符串本身每个自变量的精度。例如,另一种修正上面有问题的 sprintf() 的方法是:
void main(int argc, char **argv) { char usage[1024]; sprintf(usage, "USAGE: %.1000s -f flag [arg1] ", argv[0]); } |
注意,百分号后与 s 前的 .1000。该语法表明,从相关变量(本例中是 argv[0])复制的字符不超过 1000 个。
如果任一解决方案在您的程序必须运行的系统上行不通,则最佳的解决方案是将 snprintf() 的工作版本与您的代码放置在一个包中。可以找到以 sh 归档格式的、自由使用的版本;请参阅参考资料。
继续, scanf系列的函数也设计得很差。在这种情况下,目的地缓冲区会发生溢出。考虑以下代码:
void main(int argc, char **argv) { char buf[256]; sscanf(argv[0], "%s", &buf); } |
如果输入的字大于 buf 的大小,则有溢出的情况。幸运的是,有一种简便的方法可以解决这个问题。考虑以下代码,它没有安全性方面的薄弱环节:
void main(int argc, char **argv) { char buf[256]; sscanf(argv[0], "%255s", &buf); } |
百分号和 s 之间的 255 指定了实际存储在变量 buf 中来自 argv[0] 的字符不会超过 255 个。其余匹配的字符将不会被复制。
接下来,我们讨论 streadd()和 strecpy()。由于,不是每台机器开始就有这些调用,那些有这些函数的程序员,在使用它们时,应该小心。这些函数可以将那些含有不可读字符的字符串转换成可打印的表示。例如,考虑以下程序:
#include <libgen.h> void main(int argc, char **argv) { char buf[20]; streadd(buf, " ", ""); printf(%s ", buf); } |
该程序打印:
而不是打印所有空白。如果程序员没有预料到需要 多大的输出缓冲区来处理输入缓冲区(不发生缓冲区溢出),则 streadd() 和 strecpy() 函数可能有问题。如果输入缓冲区包含单一字符 ― 假设是 ASCII 001(control-A)― 则它将打印成四个字符“ 01”。这是字符串增长的最坏情况。如果没有分配足够的空间,以至于输出缓冲区的大小总是输入缓冲区大小的四倍,则可能发生缓 冲区溢出。
另一个较少使用的函数是 strtrns(),因为许多机器上没有该函 数。函数 strtrns() 取三个字符串和结果字符串应该放在其内的一个缓冲区,作为其自变量。第一个字符串必须复制到该缓冲区。一个字符被从第一个字符串中复制到缓冲区,除非那个 字符出现在第二个字符串中。如果出现的话,那么会替换掉第三个字符串中同一索引中的字符。这听上去有点令人迷惑。让我们看一下,将所有小写字符转换成大写 字符的示例:
#include <libgen.h> void main(int argc, char **argv) { char lower[] = "abcdefghijklmnopqrstuvwxyz"; char upper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; char *buf; if(argc < 2) { printf("USAGE: %s arg ", argv[0]); exit(0); } buf = (char *)malloc(strlen(argv[1])); strtrns(argv[1], lower, upper, buf); printf("%s ", buf); } |
以上代码实际上不包含缓冲区溢出。但如果我们使用了固定大小的静态缓冲区,而不是用 malloc() 分配足够空间来复制 argv[1],则可能会引起缓冲区溢出情况。
![]() ![]() |
![]()
|
避免内部缓冲区溢出
realpath() 函数接受可能包含相对路径的字符串,并将它转换成指同一文件的字符串,但是通过绝对路径。在做这件事时,它展开了所有符号链接。
该 函数取两个自变量,第一个作为要规范化的字符串,第二个作为将存储结果的缓冲区。当然,需要确保结果缓冲区足够大,以处理任何大小的路径。分配的 MAXPATHLEN 缓冲区应该足够大。然而,使用 realpath() 有另一个问题。如果传递给它的、要规范化的路径大小大于 MAXPATHLEN,则 realpath() 实现内部的静态缓冲区会溢出!虽然实际上没有访问溢出的缓冲区,但无论如何它会伤害您的。结果是,应该明确不使用 realpath(),除非确保检查您试图规范化的路径长度不超过 MAXPATHLEN。
其它广泛可用的调用也有类 似的问题。经常使用的 syslog() 调用也有类似的问题,直到不久前,才注意到这个问题并修正了它。大多数机器上已经纠正了这个问题,但您不应该依赖正确的行为。最好总是假定代码正运行在可 能最不友好的环境中,只是万一在哪天它真的这样。getopt() 系列调用的各种实现,以及 getpass() 函数,都可能产生内部静态缓冲区溢出问题。如果您不得不使用这些函数,最佳解决方案是设置传递给这些函数的输入长度的阈值。
自己模拟 gets() 的安全性问题以及所有问题是非常容易的。 例如,下面这段代码:
char buf[1024]; int i = 0; char ch; while((ch = getchar()) != " ") { if(ch == -1) break; buf[i++] = ch; ------------------------------ </pre><ol type="1" style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; line-height: 23px; list-style-type: decimal; "><span style="font-family: 宋体; font-size: 16px; "><strong>概述</strong></span></li></ol><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">在前面的安全编码实践的文章里,我们讨论了</span><span style="font-family: Calibri; line-height: 20px; ">GS</span><span style="font-family: 宋体; line-height: 20px; ">编译选项,数据执行保护</span><span style="font-family: Calibri; line-height: 20px; ">DEP</span><span style="font-family: 宋体; line-height: 20px; ">功能,以及静态代码分析工具</span><span style="font-family: Calibri; line-height: 20px; ">Prefast</span><span style="font-family: 宋体; line-height: 20px; ">。这里,我们讨论在</span><span style="font-family: Calibri; line-height: 20px; ">C/C++</span><span style="font-family: 宋体; line-height: 20px; ">代码中禁用危险的</span><span style="font-family: Calibri; line-height: 20px; ">API</span><span style="font-family: 宋体; line-height: 20px; ">,其主要目的是为了减少代码中引入安全漏洞的可能性。</span></p><ol type="1" style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; line-height: 23px; list-style-type: decimal; "><span style="font-family: 宋体; font-size: 16px; "><strong>那些是危险的</strong></span><span style="font-family: "Times New Roman"; font-size: 16px; "><strong>API</strong></span></li></ol><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Times New Roman"; line-height: 24px; font-size: 16px; "><strong>2.1</strong></span><span style="font-family: 宋体; line-height: 24px; font-size: 16px; "><strong>历史</strong></span></p></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">在微软产品的安全漏洞中,有很大一部分是由于不正确的使用</span><span style="font-family: Calibri; line-height: 20px; ">C</span><span style="font-family: 宋体; line-height: 20px; ">动态库(</span><span style="font-family: Calibri; line-height: 20px; ">C Runtime Library</span><span style="font-family: 宋体; line-height: 20px; ">)</span><span style="font-family: Calibri; line-height: 20px; "> </span><span style="font-family: 宋体; line-height: 20px; ">的函数,特别是有关字符串处理的函数导致的。表一给出了微软若干由于不当使用</span><span style="font-family: Calibri; line-height: 20px; ">C</span><span style="font-family: 宋体; line-height: 20px; ">动态库函数而导致的安全漏洞【</span><span style="font-family: Calibri; line-height: 20px; ">1</span><span style="font-family: 宋体; line-height: 20px; ">,</span><span style="font-family: Calibri; line-height: 20px; ">p242</span><span style="font-family: 宋体; line-height: 20px; ">】。</span></p><a target=_blank name="0.1_table01" style="color: rgb(51, 102, 153); "></a><div><table border="2" cellspacing="0" width="638"><tbody><tr valign="top"><td style="text-align: left; "><span style="font-family: 宋体; ">微软安全公告</span></td><td style="text-align: left; "><span style="font-family: 宋体; ">涉及产品</span></td><td style="text-align: left; "><span style="font-family: 宋体; ">涉及的函数</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: Calibri; ">MS02-039</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">Microsoft SQL Server 2000</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">sprint</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: Calibri; ">MS05-010</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">Microsoft License Server</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">lstrcpy</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: Calibri; ">MS04-011</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">Microsoft Windows (DCPromo)</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">wvsprintf</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: Calibri; ">MS04-011</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">Microsoft Windows (MSGina)</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">lstrcpy</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: Calibri; ">MS04-031</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">Microsoft Windows (NetDDE)</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">wcscat</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: Calibri; ">MS03-045</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">Microsoft Windows (USER)</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">wcscpy</span></td></tr></tbody></table></div><p align="center" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 黑体; font-size: 10px; line-height: 15px; ">表</span><span style="font-family: "Times New Roman"; font-size: 10px; line-height: 15px; ">1</span><span style="font-family: 黑体; font-size: 10px; line-height: 15px; ">:不当使用</span><span style="font-family: "Times New Roman"; font-size: 10px; line-height: 15px; ">C</span><span style="font-family: 黑体; font-size: 10px; line-height: 15px; ">动态库函数而导致的安全漏洞</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">不当使用</span><span style="font-family: Calibri; line-height: 20px; ">C</span><span style="font-family: 宋体; line-height: 20px; ">动态库函数容易引入安全漏洞,这一点并不奇怪。</span><span style="font-family: Calibri; line-height: 20px; ">C</span><span style="font-family: 宋体; line-height: 20px; ">动态库函数的设计大约是</span><span style="font-family: Calibri; line-height: 20px; ">30</span><span style="font-family: 宋体; line-height: 20px; ">年前的事情了。当时,安全方面的考虑并不是设计上需要太多注意的地方。</span></p><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Times New Roman"; line-height: 24px; font-size: 16px; "><strong>2.2 </strong></span><span style="font-family: 宋体; line-height: 24px; font-size: 16px; "><strong>危险</strong></span><span style="font-family: "Times New Roman"; line-height: 24px; font-size: 16px; "><strong>API</strong></span><span style="font-family: 宋体; line-height: 24px; font-size: 16px; "><strong>的列表</strong></span></p></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">有关完整的危险</span><span style="font-family: Calibri; line-height: 20px; ">API</span><span style="font-family: 宋体; line-height: 20px; ">的禁用列表,大家可以参见</span><a target=_blank href="http://msdn.microsoft.com/en-us/library/bb288454.aspx" target="_blank" style="color: rgb(51, 102, 153); text-decoration: none; "><span style="font-family: Calibri; line-height: 20px; "><span style="text-decoration: underline; ">http://msdn.microsoft.com/en-us/library/bb288454.aspx</span></span></a><span style="font-family: Calibri; line-height: 20px; ">.</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">在这里我们列出其中的一部分,以便大家对那些</span><span style="font-family: Calibri; line-height: 20px; ">API</span><span style="font-family: 宋体; line-height: 20px; ">被禁用有所体会。</span></p><a target=_blank name="0.1_table02" style="color: rgb(51, 102, 153); "></a><div><table border="2" cellspacing="0" width="632"><tbody><tr valign="top"><td style="text-align: left; "><span style="font-family: 宋体; ">禁用的</span><span style="font-family: Calibri; ">API</span></td><td style="text-align: left; "><span style="font-family: 宋体; ">替代的</span><span style="font-family: Calibri; ">StrSafe</span><span style="font-family: 宋体; ">函数</span></td><td style="text-align: left; "><span style="font-family: 宋体; ">替代的</span><span style="font-family: Calibri; ">Safe CRT</span><span style="font-family: 宋体; ">函数</span></td></tr><tr valign="top"><td colspan="3" style="text-align: left; "><span style="font-family: 宋体; ">有关字符串拷贝的</span><span style="font-family: Calibri; ">API</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: "Courier New"; font-size: 10px; ">strcpy, wcscpy, _tcscpy, _mbscpy, StrCpy, StrCpyA, StrCpyW, lstrcpy, lstrcpyA, lstrcpyW, strcpyA, strcpyW, _tccpy, _mbccpy</span></td><td style="text-align: left; "><span style="font-family: "Courier New"; font-size: 10px; ">StringCchCopy, StringCbCopy,</span><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">StringCchCopyEx, StringCbCopyEx</span></p></td><td style="text-align: left; "><span style="font-family: "Courier New"; font-size: 10px; ">strcpy_s</span></td></tr><tr valign="top"><td colspan="3" style="text-align: left; "><span style="font-family: 宋体; ">有关字符串合并的</span><span style="font-family: Calibri; ">API</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: "Courier New"; font-size: 10px; ">strcat, wcscat, _tcscat, _mbscat, StrCat, StrCatA, StrCatW, lstrcat, lstrcatA, lstrcatW, StrCatBuffW, StrCatBuff, StrCatBuffA, StrCatChainW, strcatA, strcatW, _tccat, _mbccat</span></td><td style="text-align: left; "><span style="font-family: "Courier New"; font-size: 10px; ">StringCchCat, StringCbCat,</span><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">StringCchCatEx, StringCbCatEx</span></p></td><td style="text-align: left; "><span style="font-family: "Courier New"; font-size: 10px; ">strcat_s</span></td></tr><tr valign="top"><td colspan="3" style="text-align: left; "><span style="font-family: 宋体; ">有关</span><span style="font-family: Calibri; ">sprintf</span><span style="font-family: 宋体; ">的</span><span style="font-family: Calibri; ">API</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: "Courier New"; font-size: 10px; ">wnsprintf, wnsprintfA, wnsprintfW, sprintfW, sprintfA, wsprintf, wsprintfW, wsprintfA, sprintf, swprintf, _stprintf</span></td><td style="text-align: left; "><span style="font-family: "Courier New"; font-size: 10px; ">StringCchPrintf, StringCbPrintf,</span><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">StringCchPrintfEx, StringCbPrintfEx</span></p></td><td style="text-align: left; "><span style="font-family: "Courier New"; font-size: 10px; ">_snprintf_s</span><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">_snwprintf_s</span></p></td></tr></tbody></table></div><p align="center" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 黑体; font-size: 10px; line-height: 15px; ">表</span><span style="font-family: "Times New Roman"; font-size: 10px; line-height: 15px; ">2</span><span style="font-family: 黑体; font-size: 10px; line-height: 15px; ">:禁用</span><span style="font-family: "Times New Roman"; font-size: 10px; line-height: 15px; ">API</span><span style="font-family: 黑体; font-size: 10px; line-height: 15px; ">的列表(部分)</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">其它被禁用的</span><span style="font-family: Calibri; line-height: 20px; ">API</span><span style="font-family: 宋体; line-height: 20px; ">还有</span><span style="font-family: Calibri; line-height: 20px; ">scanf, strtok, gets, itoa</span><span style="font-family: 宋体; line-height: 20px; ">等等。</span><span style="font-family: Calibri; line-height: 20px; "> ”n”</span><span style="font-family: 宋体; line-height: 20px; ">系列的字符串处理函数,例如</span><span style="font-family: Calibri; line-height: 20px; ">strncpy</span><span style="font-family: 宋体; line-height: 20px; ">等,也在被禁用之列。</span></p><ol type="1" style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; line-height: 23px; list-style-type: decimal; "><span style="font-family: 宋体; font-size: 16px; "><strong>如何替代被禁用的危险</strong></span><span style="font-family: "Times New Roman"; font-size: 16px; "><strong>API</strong></span></li></ol><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">从上面的介绍可以看出绝大多数</span><span style="font-family: Calibri; line-height: 20px; ">C</span><span style="font-family: 宋体; line-height: 20px; ">动态库中的字符串处理函数都被禁用。那么,如何在代码中替代这些危险的</span><span style="font-family: Calibri; line-height: 20px; ">API</span><span style="font-family: 宋体; line-height: 20px; ">呢?在表</span><span style="font-family: Calibri; line-height: 20px; ">2</span><span style="font-family: 宋体; line-height: 20px; ">里,我们看到有两种替代方案:</span></p><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; line-height: 23px; list-style-type: disc; "><ul type="disc" style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; list-style-type: disc; "><span style="font-family: Calibri; ">StrSafe</span></li><li style="margin: 0px; padding: 2px 0px; list-style-type: disc; "><span style="font-family: Calibri; ">Safe CRT</span></li></ul></li></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">后面我们会讨论这两种方案的不同之处。这里我们先说它们的共同点:提供更安全的字符串处理功能。特别在以下几个方面:</span></p><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; line-height: 23px; list-style-type: disc; "><ul type="disc" style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; list-style-type: disc; "><span style="font-family: 宋体; ">目标缓存区的大小被显式指明。</span></li><li style="margin: 0px; padding: 2px 0px; list-style-type: disc; "><span style="font-family: 宋体; ">动态校验。</span></li><li style="margin: 0px; padding: 2px 0px; list-style-type: disc; "><span style="font-family: 宋体; ">返回代码。</span></li></ul></li></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">以</span><span style="font-family: Calibri; line-height: 20px; ">StringCchCopy</span><span style="font-family: 宋体; line-height: 20px; ">举例。它的定义如下:</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">HRESULT StringCchCopy( </span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> LPTSTR pszDest,</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> size_t cchDest,</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> LPCTSTR pszSrc</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">);</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: Calibri; line-height: 20px; ">cchDest</span><span style="font-family: 宋体; line-height: 20px; ">指明目标缓存区</span><span style="font-family: Calibri; line-height: 20px; ">pszDest</span><span style="font-family: 宋体; line-height: 20px; ">最多能容纳字符的数目,其值必须在</span><span style="font-family: Calibri; line-height: 20px; ">1</span><span style="font-family: 宋体; line-height: 20px; ">和</span><span style="font-family: Calibri; line-height: 20px; ">STRSAFE_MAX_CCH</span><span style="font-family: 宋体; line-height: 20px; ">之间。</span><span style="font-family: Calibri; line-height: 20px; ">StringCchCopy</span><span style="font-family: 宋体; line-height: 20px; ">总是确保</span><span style="font-family: Calibri; line-height: 20px; ">pszDest</span><span style="font-family: 宋体; line-height: 20px; ">被拷贝的字符串是以</span><span style="font-family: Calibri; line-height: 20px; ">NULL</span><span style="font-family: 宋体; line-height: 20px; ">结尾。并且提供以下的返回代码:</span><span style="font-family: Calibri; line-height: 20px; "> S_OK</span><span style="font-family: 宋体; line-height: 20px; ">,</span><span style="font-family: Calibri; line-height: 20px; ">STRSAFE_E_INVALID_PARAMETER</span><span style="font-family: 宋体; line-height: 20px; ">,和</span><span style="font-family: Calibri; line-height: 20px; ">STRSAFE_E_INSUFFICIENT_BUFFER</span><span style="font-family: 宋体; line-height: 20px; ">。这样,采用</span><span style="font-family: Calibri; line-height: 20px; ">StringCchCopy</span><span style="font-family: 宋体; line-height: 20px; ">来替代被禁用的</span><span style="font-family: Calibri; line-height: 20px; ">strcpy</span><span style="font-family: 宋体; line-height: 20px; ">的话,就可以有效降低由于误用字符串拷贝而导致缓存溢出的可能。</span></p><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Times New Roman"; line-height: 24px; font-size: 16px; "><strong>3.1</strong></span><span style="font-family: 宋体; line-height: 24px; font-size: 16px; "><strong>使用</strong></span><span style="font-family: "Times New Roman"; line-height: 24px; font-size: 16px; "><strong>StrSafe</strong></span></p></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">使用</span><span style="font-family: Calibri; line-height: 20px; ">StrSafe</span><span style="font-family: 宋体; line-height: 20px; ">非常简单。在</span><span style="font-family: Calibri; line-height: 20px; ">C/C++</span><span style="font-family: 宋体; line-height: 20px; ">代码中加入以下的头文件即可。</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">#include "strsafe.h"</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: Calibri; line-height: 20px; ">StrSafe.h</span><span style="font-family: 宋体; line-height: 20px; ">包含在</span><span style="font-family: Calibri; line-height: 20px; ">Windows Platform SDK</span><span style="font-family: 宋体; line-height: 20px; ">中。用户可以通过在微软的网站直接下载。</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">下面给出一个使用</span><span style="font-family: Calibri; line-height: 20px; ">StrSafe</span><span style="font-family: 宋体; line-height: 20px; ">的代码示例【</span><span style="font-family: Calibri; line-height: 20px; ">2</span><span style="font-family: 宋体; line-height: 20px; ">】。</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">不安全的代码:</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">void UnsafeFunc(LPTSTR szPath,DWORD cchPath) {</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> TCHAR szCWD[MAX_PATH];</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> GetCurrentDirectory(ARRAYSIZE(szCWD), szCWD);</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> strncpy(szPath, szCWD, cchPath);</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> strncat(szPath, TEXT("\"), cchPath);</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> strncat(szPath, TEXT("desktop.ini"),cchPath);</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">}</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">在以上代码里存在着几个问题:首先,没有错误代码的校验。更严重的是,在</span><span style="font-family: Calibri; line-height: 20px; ">strncat</span><span style="font-family: 宋体; line-height: 20px; ">中,</span><span style="font-family: Calibri; line-height: 20px; ">cchPath</span><span style="font-family: 宋体; line-height: 20px; ">是目标缓存区可以存放字符的最大数目,而正确传递的参数应该是目标缓存区剩余的字符数目。</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">使用</span><span style="font-family: Calibri; line-height: 20px; ">StrSafe</span><span style="font-family: 宋体; line-height: 20px; ">后的代码是</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">bool SaferFunc(LPTSTR szPath,DWORD cchPath) {</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> TCHAR szCWD[MAX_PATH];</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> if (GetCurrentDirectory(ARRAYSIZE(szCWD), szCWD) &&</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> SUCCEEDED(StringCchCopy(szPath, cchPath, szCWD)) &&</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> SUCCEEDED(StringCchCat(szPath, cchPath, TEXT("\"))) &&</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> SUCCEEDED(StringCchCat(szPath, cchPath, TEXT("desktop.ini")))) {</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> return true;</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> }</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> return false;</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">}</span></p><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Times New Roman"; line-height: 24px; font-size: 16px; "><strong>3.2</strong></span><span style="font-family: 宋体; line-height: 24px; font-size: 16px; "><strong>使用</strong></span><span style="font-family: "Times New Roman"; line-height: 24px; font-size: 16px; "><strong>Safe CRT</strong></span></p></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: Calibri; line-height: 20px; ">SafeCRT</span><span style="font-family: 宋体; line-height: 20px; ">自</span><span style="font-family: Calibri; line-height: 20px; ">Visual Studio 2005</span><span style="font-family: 宋体; line-height: 20px; ">起开始支持。当代码中使用了禁用的危险的</span><span style="font-family: Calibri; line-height: 20px; ">CRT</span><span style="font-family: 宋体; line-height: 20px; ">函数,</span><span style="font-family: Calibri; line-height: 20px; ">Visual Studio 2005</span><span style="font-family: 宋体; line-height: 20px; ">编译时会报告相应警告信息,以提醒开发人员考虑将其替代为</span><span style="font-family: Calibri; line-height: 20px; ">Safe CRT</span><span style="font-family: 宋体; line-height: 20px; ">中更为安全的函数。</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">下面给出一个使用</span><span style="font-family: Calibri; line-height: 20px; ">Safe CRT</span><span style="font-family: 宋体; line-height: 20px; ">的代码示例【</span><span style="font-family: Calibri; line-height: 20px; ">3</span><span style="font-family: 宋体; line-height: 20px; ">】。</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">不安全的代码:</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">void UnsafeFunc (const wchar_t * src)</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">{</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> // Original</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> wchar_t dest[20];</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> wcscpy(dest, src); // </span><span style="font-family: 宋体; font-size: 10px; line-height: 15px; ">编译警告</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> wcscat(dest, L"..."); // </span><span style="font-family: 宋体; font-size: 10px; line-height: 15px; ">编译警告</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">}</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">以上这段代码里存在着明显缓存溢出的问题。</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋体; line-height: 20px; ">使用</span><span style="font-family: Calibri; line-height: 20px; ">Safe CRT</span><span style="font-family: 宋体; line-height: 20px; ">后的代码是</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">errno_t SaferFunc(const wchar_t * src)</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">{</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> wchar_t dest[20];</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> </span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> errno_t err = wcscpy_s(dest, _countof(dest), src);</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">if (!err)</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; "> return err;</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">return wcscat_s(dest, _countof(dest), L"...");</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Courier New"; font-size: 10px; line-height: 15px; ">}</span></p><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: "Times New Roman"; line-height: 24px; font-size: 16px; "><strong>3.3 StrSafe </strong></span><s |