C标准库中的哪些功能通常会鼓励不良做法?

时间:2011-01-03 21:34:04

标签: c security c99 standard-library

这受到this question的启发以及对一个特定答案的评论,我了解到strncpy在C中不是一个非常安全的字符串处理函数,它填充零,直到达到{{ 1}},我不知道的东西。

具体来说,引用R..

  

strncpy不会终止,并且   对所有剩余部分进行空填充   目标缓冲区,是一个   浪费时间。你可以工作   围绕前者添加自己的   null填充,但不是后者。它   从来没有打算用作“安全的”   字符串处理“功能,但用于   在Unix中使用固定大小的字段   目录表和数据库文件。   snprintf(dest,n,“%s”,src)是   只在标准中纠正“安全strcpy”   C,但它可能会慢得多。   顺便说一下,截断本身就可以   是一个主要的错误,在某些情况下可能会   导致特权提升或DoS,所以   抛出“安全”的字符串函数   截断问题的输出是   不能让它“安全”或   “安全”。相反,你应该确保   目标缓冲区是   正确的大小,只需使用strcpy(或   更好的是,如果你已经知道,请记住   源字符串长度)。

来自Jonathan Leffler

  

请注意,strncat()甚至更多   混淆其界面比   strncpy() - 到底是什么   长度论点,又一次?这不是什么   你期望根据你提供的东西   strncpy()等 - 所以它更错误   甚至比strncpy()更容易发生。用于复制   周围的字符串,我越来越多了   认为有强烈的意见   你只需要memmove()的论点   因为你总是知道所有尺寸   提前并确保有   足够的空间提前。使用   memmove()优先于任何一个   strcpy(),strcat(),strncpy(),   strncat(),memcpy()。

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

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

为了客观性,我有一些答案的标准:

  • 如果可以的话,请引用相关功能背后的设计原因,即其预期目的。
  • 请突出显示当前代码的误用情况。
  • 请说明滥用可能导致问题的原因。我知道这应该是显而易见的,但它会阻止软答案。

请避免:

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

由于这可能被认为是主观的,并且没有明确的答案,我现在正在为社区维基进行标记。

我也按照C99工作。

14 个答案:

答案 0 :(得分:34)

  

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

我会选择明显的:

char *gets(char *s);

由于其显着的特殊性,根本无法正确使用它。

答案 1 :(得分:23)

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

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

CERT C Secure Coding Standard列出了您提出的许多陷阱。

答案 2 :(得分:21)

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

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

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

答案 3 :(得分:11)

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

errno

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

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

即将出台的标准使errno的定义更加直接,但这些情况仍然存在

答案 4 :(得分:6)

通常有一个strtok_r。

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

答案 5 :(得分:5)

我会将printfscanf放在此列表中。您必须使格式化说明符完全正确的事实使得这些函数难以使用并且极易出错。读取数据时,也很难避免缓冲区溢出。而且,当好心的程序员将客户指定的字符串指定为printf的第一个参数时,“printf格式字符串漏洞”可能会造成无数的安全漏洞,只是为了发现堆栈被破坏并且安全性受到了多年的影响。

答案 6 :(得分:4)

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

编辑: rand()与它看起来属于同一类别。至少没有线程安全的保证,在我的Linux系统上,手册页警告它是不可重入且非线程安全的。

答案 7 :(得分:4)

我的一个bêtesnoire是strtok(),因为它是不可重入的,并且因为它破坏了它正在处理成碎片的字符串,所以在它隔离的每个标记的末尾插入NUL。这个问题很多;令人沮丧的是经常被吹捧为问题的解决方案,但本身往往是一个问题。并非总是如此 - 它可以安全使用。但只有你小心。大多数函数也是如此,gets()除了{{1}}之外无法安全使用。

答案 8 :(得分:4)

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

如果正确使用增长数据(以避免将对象增长到大小O(n^2)的最坏情况n性能的方式,即在空间不足时以几何方式而不是线性增长缓冲区),realloc对于简单地执行新的mallocmemcpyfree周期具有可疑的好处。 realloc可以避免在内部执行此操作的唯一方法是在堆顶部使用单个对象时。

如果您希望使用calloc对新对象进行零填充,则很容易忘记realloc不会对新零件进行零填充。

最后,realloc的另一个常见用途是分配超出您需要的数量,然后将分配的对象调整到所需的大小。但这实际上对于按大小严格隔离块的实现是有害的(额外分配和memcpy),在其他情况下可能会增加碎片(通过拆分大块空闲块的一部分来存储新的小对象,而不是使用现有的小型免费块。)

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

答案 9 :(得分:4)

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

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

我怀疑正确的解决方案通常是长期使用malloc执行小常规分配的程序,但要保留大型,长寿命的数据结构,以便可以重建它们并定期更换以对抗碎片,或者替换为包含许多结构的大malloc块,这些结构构成应用程序中的单个大数据单元(如浏览器中的整个网页表示),或者是磁盘上的固定大小的内存缓存或内存映射文件。

答案 10 :(得分:2)

在一个完全不同的方面,当atan()时,我从未真正理解atan2()的好处。不同之处在于atan2()接受两个参数,并返回-π.. +π范围内的任何角度。此外,它避免了零除错误和精度误差的损失(将非常小的数除以非常大的数,反之亦然)。相比之下,atan()函数只返回-π/ 2 .. +π/ 2范围内的值,您必须事先进行除法(我不记得atan()的情况可以在没有划分的情况下使用,而不是简单地生成一个反复无常的表格。当给出一个简单的值时,提供1.0作为atan2()的除数不会超出极限。

答案 11 :(得分:2)

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

  • 具有未指定的随机质量
  • 它不是可重入的

答案 12 :(得分:1)

其中一些功能正在修改某些全局状态。 (在Windows中)每个线程共享此状态 - 您可以获得意外结果。例如,在每个线程中第一次调用rand将得到相同的结果,并且需要一些注意使其伪随机,但确定性(用于调试目的)。

答案 13 :(得分:-2)

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