56

这是受到这个问题和对一个特定答案的评论的启发,因为我了解到这strncpy在 C 中不是一个非常安全的字符串处理函数,并且它会填充零,直到达到n,这是我不知道的。

具体来说,引用R..

strncpy 不会以空值终止,而是对目标缓冲区的整个剩余部分进行空值填充,这是对时间的巨大浪费。您可以通过添加自己的空填充来解决前者,但不能解决后者。它从未打算用作“安全字符串处理”功能,而是用于处理 Unix 目录表和数据库文件中的固定大小字段。snprintf(dest, n, "%s", src) 是标准 C 中唯一正确的“安全 strcpy”,但它可能会慢很多。顺便说一句,截断本身可能是一个主要错误,并且在某些情况下可能会导致特权提升或 DoS,因此抛出“安全”字符串函数来截断其输出并不是一种使其“安全”或“安全的”。反而,

来自乔纳森·莱弗勒

请注意,strncat() 的接口比 strncpy() 更令人困惑——这个长度参数到底是什么?根据您提供的 strncpy() 等,这不是您所期望的 - 所以它甚至比 strncpy() 更容易出错。对于复制字符串,我越来越认为有一个强有力的论点是你只需要 memmove() 因为你总是提前知道所有的大小并确保提前有足够的空间。使用 memmove() 优先于 strcpy()、strcat()、strncpy()、strncat()、memcpy() 中的任何一个。

所以,我显然对 C 标准库有点生疏了。因此,我想提出一个问题:

哪些 C 标准库函数使用不当/可能导致/导致安全问题/代码缺陷/效率低下?

为了客观起见,我有一些答案标准:

  • 如果可以的话,请引用相关功能背后的设计原因,即其预期目的。
  • 请突出显示代码当前被滥用的地方。
  • 请说明为什么这种滥用会导致问题。我知道这应该很明显,但它会阻止软答案。

请避免:

  • 关于函数命名约定的辩论(除非这明确引起混淆)。
  • “我更喜欢 x 而不是 y” - 偏好是可以的,我们都有它们,但我对实际的意外副作用以及如何防范它们感兴趣。

由于这可能被认为是主观的并且没有明确的答案,因此我立即标记为社区 wiki。

我也在按照 C99 工作。

4

14 回答 14

34

哪些 C 标准库函数使用不当/可能导致/导致安全问题/代码缺陷/效率低下?

我会选择显而易见的:

char *gets(char *s);

凭借其非凡的特殊性,根本不可能适当地使用它。

于 2011-01-03T21:50:02.557 回答
25

该函数的一个常见缺陷strtok()是假设解析后的字符串保持不变,而实际上它用 . 替换了分隔符'\0'

此外,strtok()通过对其进行后续调用来使用,直到整个字符串被标记化。一些库实现将的内部状态存储在全局变量中,如果同时从多个线程调用strtok(),这可能会引发一些令人讨厌的惊喜。strtok()

CERT C 安全编码标准列出了您询问的许多此类陷阱。

于 2011-01-03T22:23:44.937 回答
21

在几乎所有情况下,atoi()都不应该使用(这也适用于atof(),atol()atoll())。

这是因为这些函数根本不会检测超出范围的错误——标准只是简单地说“如果结果的值无法表示,则行为未定义”。. 因此,唯一可以安全使用它们的情况是,如果您可以证明输入肯定会在范围内(例如,如果您将长度为 4 或更短的字符串传递给atoi(),则它不会超出范围)。

相反,请使用strtol()函数族之一。

于 2011-01-03T23:44:14.753 回答
11

让我们将问题扩展到更广泛意义上的接口。

errno

从技术上讲,它甚至不清楚它是什么,一个变量,一个宏,一个隐式函数调用?在现代系统的实践中,它主要是一个宏,它转换为函数调用以具有线程特定的错误状态。这是邪恶的:

  • 因为它可能会导致调用者访问该值、检查“错误”(这可能只是一个异常事件)的开销
  • 因为它甚至在某些地方强制调用者在进行库调用之前清除这个“变量”
  • 因为它通过设置库的全局状态来实现简单的错误返回。

即将发布的标准对定义的定义errno更加直截了当,但这些丑陋仍然存在

于 2011-01-03T22:50:44.210 回答
6

通常有一个strtok_r。

对于realloc,如果你需要使用旧指针,使用另一个变量并不难。如果您的程序因分配错误而失败,则通常不需要清理旧指针。

于 2011-01-03T23:02:53.653 回答
5

我会在这个名单上排名很高printfscanf您必须完全正确地获取格式说明符这一事实使得这些函数难以使用并且极容易出错。读取数据时也很难避免缓冲区溢出。此外,当好心的程序员将客户端指定的字符串指定为 printf 的第一个参数时,“printf 格式字符串漏洞”可能已经造成了无数的安全漏洞,结果却发现堆栈被破坏,并且多年后的安全性受到损害。

于 2011-01-03T21:38:26.500 回答
4

任何操纵全局状态的函数,例如gmtime()or localtime()。这些函数根本不能在多个线程中安全地使用。

编辑: rand()似乎属于同一类别。至少不能保证线程安全,并且在我的 Linux 系统上,手册页警告说它是不可重入和非线程安全的。

于 2011-01-03T21:50:23.003 回答
4

我最喜欢的事情之一是strtok(),因为它是不可重入的,并且因为它将正在处理的字符串破解成碎片,在它隔离的每个标记的末尾插入 NUL。这方面的问题很多。令人痛苦的是,它经常被吹捧为问题的解决方案,但它本身往往也是一个问题。并非总是如此 - 它可以安全使用。但前提是你要小心。大多数函数也是如此,除了gets()不能安全使用的显着例外。

于 2011-01-03T22:23:05.967 回答
4

关于 已经有一个答案realloc,但我对此有不同的看法。很多时候,我看到人们写的realloc是他们的意思freemalloc- 换句话说,当他们有一个充满垃圾的缓冲区时,需要在存储新数据之前更改大小。这当然会导致memcpy即将被覆盖的垃圾的潜在大缓存抖动。

如果正确使用不断增长的数据(以一种避免O(n^2)将对象增长到 size 的最坏情况性能的方式n,即当空间不足时以几何方式而不是线性地增长缓冲区),realloc与简单地做自己的 new malloc, memcpy,相比具有令人怀疑的好处和free循环。唯一realloc可以避免在内部执行此操作的方法是在堆顶部使用单个对象时。

如果您喜欢用 对新对象进行零填充calloc,很容易忘记realloc不会对新部分进行零填充。

最后,一种更常见的用法realloc是分配比您需要的更多的空间,然后将分配的对象调整为所需的大小。但这实际上对memcpy按大小严格隔离块的实现是有害的(额外的分配和空闲块)。

我不确定我是否会说realloc 鼓励不良做法,但这是我要提防的功能。

于 2011-01-04T03:41:37.400 回答
4

一般家庭情况如何malloc?我见过的绝大多数大型、长期存在的程序都使用动态内存分配,就好像它是免费的一样。当然,实时开发人员知道这是一个神话,不小心使用动态分配会导致内存使用量的灾难性爆炸和/或地址空间的碎片化到内存耗尽的程度。

在一些没有机器级指针的高级语言中,动态分配并不是那么糟糕,因为实现可以在程序的生命周期内移动对象和整理内存,只要它可以保持对这些对象的引用是最新的。一个非常规的 C 实现也可以做到这一点,但是解决细节并非易事,并且会在所有指针取消引用中产生非常大的成本并使指针相当大,因此出于实际目的,在 C 中是不可能的。

我的怀疑是,正确的解决方案通常是让长期存在的程序像往常一样执行它们的小例程分配malloc,但将大型、长期存在的数据结构保持在可以定期重建和替换以对抗碎片的形式,或者作为malloc包含许多结构的大块,这些结构构成应用程序中的单个大数据单元(如浏览器中的整个网页呈现),或者具有固定大小的内存缓存或内存映射文件的磁盘.

于 2011-01-05T15:48:41.037 回答
2

在一个完全不同的策略上,我从来没有真正理解atan()atan2(). 不同之处在于它atan2()接受两个参数,并返回 -π..+π 范围内的任意角度。此外,它避免了除以零错误和精度误差损失(将非常小的数字除以非常大的数字,反之亦然)。相比之下,该atan()函数仅返回 -π/2..+π/2 范围内的值,并且您必须事先进行除法(我不记得atan()可以在没有除法的情况下使用的场景,短简单地生成反正切表)。atan2()当给定一个简单的值时,提供 1.0 作为除数并没有突破极限。

于 2011-01-03T22:44:35.887 回答
2

另一个答案,因为这些并不真正相关,rand

  • 它具有未指定的随机质量
  • 它不是可重入的
于 2011-01-03T22:52:15.583 回答
1

其中一些函数正在修改一些全局状态。(在 Windows 中)这个状态是每个线程共享的——你会得到意想不到的结果。例如,rand每个线程中的第一次调用将给出相同的结果,并且需要注意使其成为伪随机但确定性的(用于调试目的)。

于 2011-01-03T22:53:14.390 回答
-2

basename()并且dirname()不是线程安全的。

于 2011-01-03T22:49:31.563 回答