C 语言用户必须避免使用不进行边界检查的危险函数,除非他们已确保边界永远不会被超出。在大多数情况下应避免使用(或确保保护)的函数包括 strcpy(3)、strcat(3)、sprintf(3)(及其变体 vsprintf(3))和 gets(3)。这些函数应分别替换为诸如 strncpy(3)、strncat(3)、snprintf(3) 和 fgets(3) 之类的函数,但请参阅下面的讨论。除非你能确保会找到终止符 NIL 字符,否则应避免使用函数 strlen(3)。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 字节长。select(2) 辅助宏 FD_SET()、FD_CLR() 和 FD_ISSET() 不检查索引 fd 是否在界限内;请确保 fd >= 0 且 fd <= FD_SETSIZE(这个特定问题已在 pppd 中被利用)。
不幸的是,snprintf() 的变体还有其他问题。从官方角度来看,snprintf() 不是 ISO 1990 (ANSI 1989) 标准中的标准 C 函数,而 sprintf() 是,因此并非所有系统都包含 snprintf()。更糟糕的是,某些系统的 snprintf() 实际上并不防止缓冲区溢出;它们只是直接调用 sprintf。旧版本的 Linux libc4 依赖于执行这种可怕事情的 ``libbsd'',并且我听说一些旧的 HP 系统也这样做过。Linux 当前版本的 snprintf 已知可以正常工作,也就是说,它实际上确实尊重所请求的边界。snprintf() 的返回值也各不相同;Single Unix Specification (SUS) 版本 2 和 C99 标准在 snprintf() 返回的内容上有所不同。最后,似乎至少某些版本的 snprintf 不保证其字符串将以 NIL 结尾;如果字符串太长,它根本不会包含 NIL。请注意,glib 库(GTK 的基础,与 GNU C 库 glibc 不同)有一个 g_snprintf(),它具有一致的返回语义,始终以 NIL 结尾,最重要的是始终尊重缓冲区长度。
当然,问题不仅仅是不正确地调用字符串函数。以下是一些额外的缓冲区溢出问题类型的示例,由 Timo Sirainen 友好地提出,涉及操纵数字以导致缓冲区溢出。
首先,存在符号性的问题。如果您读取影响缓冲区大小的数据,例如“要读取的字符数”,请务必检查该数字是否小于零或一。否则,负数可能会被转换为无符号数,并且结果产生的大正数可能会导致缓冲区溢出问题。请注意,有时攻击者可以提供一个大的正数,并发生相同的事情;在某些情况下,大值将被解释为负数(如果未检查小于一的值,则会绕过对大数的检查),然后稍后被解释为大的正值。
/* 1) signedness - DO NOT DO THIS. */ char *buf; int i, len; read(fd, &len, sizeof(len)); /* OOPS! We forgot to check for < 0 */ if (len > 8000) { error("too large length"); return; } buf = malloc(len); read(fd, buf, len); /* len casted to unsigned and overflows */ |
这是 Timo Sirainen 提出的第二个示例,涉及整数大小截断。有时,可以利用整数的不同大小来导致缓冲区溢出。基本上,请确保不要截断任何用于计算缓冲区大小的整数结果。这是 Timo 针对 64 位架构的示例
/* An example of an ERROR for some 64-bit architectures, if "unsigned int" is 32 bits and "size_t" is 64 bits: */ void *mymalloc(unsigned int size) { return malloc(size); } char *buf; size_t len; read(fd, &len, sizeof(len)); /* we forgot to check the maximum length */ /* 64-bit size_t gets truncated to 32-bit unsigned int */ buf = mymalloc(len); read(fd, buf, len); |
这是 Timo Sirainen 提供的第三个示例,涉及整数溢出。当与 malloc() 结合使用时,这尤其令人讨厌;攻击者可能能够创建一个计算出的缓冲区大小小于要放入其中的数据的情况。这是 Timo 的示例
/* 3) integer overflow */ char *buf; size_t len; read(fd, &len, sizeof(len)); /* we forgot to check the maximum length */ buf = malloc(len+1); /* +1 can overflow to malloc(0) */ read(fd, buf, len); buf[len] = '\0'; |