C的"坏"功能与其"好"替代品相比

Car*_*ers 66 c function

C中的"坏"功能是什么,它们的"好"选择是什么?

为什么坏的不好,是什么让好的更好?

我知道,例如,它gets()是"坏的",因为它没有任何形式的边界检查.什么是更好的选择?gets()

我听说gets()很糟糕,但我不记得为什么.谁知道?什么是最好的选择?

还有更多吗?

Ada*_*kin 46

在过去,大多数字符串函数都没有边界检查.当然,他们不能只删除旧函数,或修改其签名以包含上限,这会破坏兼容性.现在,对于几乎所有这些功能,都有一个替代的"n"版本.例如:

strcpy -> strncpy
strlen -> strnlen
strcmp -> strncmp
strcat -> strncat
strdup -> strndup
sprintf -> snprintf
wcscpy -> wcsncpy
wcslen -> wcsnlen
Run Code Online (Sandbox Code Playgroud)

和更多.

编辑2013-12-03:

另请参阅https://github.com/leafsr/gcc-poison,这是一个创建头文件的项目,如果您使用不安全的函数,该文件会导致gcc报告错误.

  • strncpy不是你应该改变的.例如,将"hello"复制到char [5]中,其大小参数为5.这会导致char [5]不被NULL终止!请使用strlcpy或equiv!或者,如果必须使用strncpy之类的东西,请始终将相同的参数设置为小于最大缓冲区大小的1并且手动为NULL自行终止.http://www.usenix.org/events/usenix99/millert.html (33认同)
  • 请注意,strlen(),strcmp()和strdup()是安全的.'n'替代方案为您提供了额外的功能. (20认同)
  • @Dan strlcpy是一个BSDism - 一个标准的C替代方案是设置dest [0] ='\ 0'; 然后调用strncat() - 与strncpy()不同,strncat()总是nul-终止目标. (8认同)
  • 不,我的意思是dest [0] ='\ 0'; strncat(dest,source,dest_size - 1); - 想法是使用strncat(),因为它具有合理的目标终止行为. (4认同)
  • 我假设你的意思是dest [n](其中n [4表示char [5]),因为将第一个char设置为null并不会在复制缓冲区的最大大小时停止NULL终止问题.这就是为什么我把"param设置为比最大缓冲区大小少1并且手动NULL终止自己" (2认同)
  • 轻微警告:strncpy()不仅复制最多n个字符的字符串,而且如果字符串短于n,则使用NUL字符填充最大长度n。如果您“为了安全”分配了较大的缓冲区但通常处理较小的字符串(例如,将文件名复制到PATH_MAX大小的缓冲区,通常为4K),则这可能会严重打击性能。[而且,是的,我知道PATH_MAX已过时]。 (2认同)
  • 而不是使用设计糟糕的低级函数来处理用于字符串处理的char数组,使用库进行更高级别的字符串抽象更有意义. (2认同)
  • 而且,如果您使用的是MSVC,*_s变体(例如`strcpy_s` /`strncpy_s`).遗憾的是它们并没有被广泛使用,因为`strncpy`仍然对程序员的漏洞开放,因此缓冲溢出并不是完全安全的. (2认同)

caf*_*caf 34

是的,fgets(,, STDIN)是gets()的一个很好的替代品,因为它需要一个size参数.

scanf()在某些情况下被认为是有问题的,而不是直接"坏",因为如果输入不符合预期的格式,则无法合理地恢复(它不会让你回放输入并尝试再次).如果你可以放弃格式错误的输入,它是可用的.这里的"更好"替代方法是使用输入函数(如fgets()或fgetc())来读取输入块,然后使用sscanf()扫描它或使用strchr()和strtol()等字符串处理函数对其进行解析.另请参阅下面的scanf()中"%s"转换说明符的特定问题.

它不是标准的C函数,但BSD和POSIX函数mktemp()通常不可能安全使用,因为在测试文件存在和创建文件之间始终存在竞争条件.mkstemp()或tmpfile()是很好的替代品.

strncpy()是一个稍微棘手的函数,因为如果没有空间,它不会终止目标.您可以通过自己将nul-terminator添加到目标,或者将目标设置为空字符串然后使用strncat()来解决此问题.

在某些情况下,atoi()可能是一个糟糕的选择,因为你无法判断转换时是否有错误(例如,如果数字超出了int的范围).如果这对您很重要,请使用strtol().

strcpy(),strcat()和sprintf()遇到类似于gets()的问题 - 它们不允许您指定目标缓冲区的大小.它仍然是可能的,至少在理论上,安全地使用它们-但你过使用strncat函数()和snprintf的(),而不是更好的(你可以使用函数strncpy(),但是见上文).在同一主题上,如果您使用scanf()系列函数,请不要使用普通的"%s" - 指定目标的大小,例如."%200S".

  • 我个人认为这是一个更好的解决方案,因为它提供了更多的解释,而不仅仅是一个列表,它回答了关于gets()和scanf()的问题. (3认同)

Dav*_*kle 20

strtok()通常被认为是邪恶的,因为它在调用之间存储状态信息.不要尝试在多线程环境中运行THAT!

  • 替代strtok_r (15认同)
  • 线程安全不是`strtok()`的唯一问题.如果使用`strtok()`的代码在使用`strtok()`进行语法分析时调用任何函数,那么这些被调用的函数都不能使用`strtok()`而不会搞砸你的函数.类似地,没有库函数可以使用`strtok()`而不记录它,因为任何使用`strtok()`的调用者都会被搞砸.当然,这些都是相反的主张.所以,如果使用`strtok()`,你必须非常小心.对于你负责所有代码的玩具程序来说这很好; 它对生产质量计划很少有好处. (9认同)
  • 由于修改第一个参数的方式,它也被认为有点疯狂. (3认同)
  • 许多CRT实现使用线程局部变量来保存这种状态信息,因此从技术上讲,它可能是安全的,具体取决于平台,但这绝对不是一个好主意. (2认同)

Sec*_*ure 11

严格来说,有一个非常危险的功能.它是gets()因为它的输入不受程序员的控制.这里提到的所有其他功能本身都是安全的."好"和"坏"归结为防御性编程,即前置条件,后置条件和样板代码.

我们以strcpy()为例.它有一些程序员调用函数之前必须满足的前提条件.两个字符串必须是有效的,非NULL指针,以零终止字符串,并且目标必须提供足够的空间,最终字符串长度在size_t范围内.另外,两个字符串都不允许重叠.

这是很多前提条件,strcpy()都没有检查它们.程序员必须确保它们已经完成,或者在调用strcpy()之前必须用额外的样板代码明确地测试它们:

n = DST_BUFFER_SIZE;
if ((dst != NULL) && (src != NULL) && (strlen(dst)+strlen(src)+1 <= n))
{
    strcpy(dst, src);
}
Run Code Online (Sandbox Code Playgroud)

已经默默地假设非重叠和零终止字符串.

strncpy()确实包含了一些这些检查,但它增加了程序员调用函数必须注意的另一个后置条件,因为结果可能不是零终止的.

strncpy(dst, src, n);
if (n > 0)
{
    dst[n-1] = '\0';
}
Run Code Online (Sandbox Code Playgroud)

为什么这些功能被认为是"坏"?因为当程序员假定有效性错误时,他们需要为每次调用提供额外的样板代码以确保安全,程序员往往会忘记这段代码.

甚至反对它.以printf()系列为例.这些函数返回指示错误和成功的状态.谁检查输出到stdout或stderr是否成功?认为当标准通道不起作用时你根本无法做任何事情.那么,如何使用错误指示退出代码来挽救用户数据并终止程序呢?而不是可能的崩溃和烧毁替代与损坏的用户数据.

在时间和资金有限的环境中,始终存在的问题是您真正想要多少安全网以及最终的最坏情况?如果是str函数的缓冲区溢出,那么禁止它们是有意义的,并且可能提供已经在其中的安全网的包装函数.

关于这一点的最后一个问题:是什么让你确定你的"好"替代方案真的很好

  • 就"strncpy()"而言,它不是一个"好"或安全的功能.它被微软和我的代码禁止. (3认同)
  • 这些函数很糟糕,因为它们可以很容易地编写错误的代码.更糟糕的是,有缺陷的代码通常是引入安全漏洞的类型.当然,它们通常可以*安全正确地使用,但是它们太容易使用它们.我从微软在数百万行代码中所做的经验和研究中了解到这一点.MS不会因为'而禁止使用功能'.他们这样做是因为他们有关于代码类型的硬统计信息导致错误(特别是安全漏洞).无论Microsoft是否是您喜欢或尊重的公司,这都是难以理解的数据. (2认同)

Mit*_*eat 7

任何不采用最大长度参数而是依赖于标记结束的函数(例如许多"字符串"处理函数).

任何在调用之间维护状态的方法.


Art*_*yom 6

  • sprintf 很糟糕,不检查尺寸,使用 snprintf
  • gmtime,localtime- use gmtime_r,localtime_r