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

54 c security c99 standard-library

这是受到这个问题以及对一个特定答案的评论的启发,我strncpy在C 中学到了这不是一个非常安全的字符串处理函数,它会填充零,直到它到达n,我不知道的东西.

具体来说,引用R ..

strncpy不会为null终止,并且会对目标缓冲区的其余部分进行空填充,这是一个巨大的浪费时间.您可以通过添加自己的空填充来解决前者,但不能添加后者.它从未打算用作"安全字符串处理"功能,但是用于处理Unix目录表和数据库文件中的固定大小字段.snprintf(dest,n,"%s",src)是标准C中唯一正确的"安全strcpy",但它可能会慢很多.顺便说一下,截断本身可能是一个主要的错误,并且在某些情况下可能会导致权限提升或DoS,因此抛出"安全"字符串函数会在问题中截断其输出并不是使其"安全"或"安全".相反,您应该确保目标缓冲区的大小合适,并且只需使用strcpy(或者更好的是,如果您已经知道源字符串长度,则使用memcpy).

来自Jonathan Leffler

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

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

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

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

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

请避免:

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

由于这可能被认为是主观的,并且没有明确的答案,我正在为社区维基立刻做好准备.

我也按照C99工作.

ice*_*ime 34

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

我会选择明显的:

char *gets(char *s);
Run Code Online (Sandbox Code Playgroud)

由于其显着的特殊性,它根本不可能适当地使用它.

  • `gets()`:软件安全性的绝对零度. (8认同)
  • 请注意,C0x将从标准中删除`gets()`.不幸的是,在将其从大多数实现中删除之前,它将在10到20年后完成 - 向后兼容不安全性决定了这一点. (8认同)
  • MacOS X实际上在您使用它时会打印出运行时警告. (6认同)
  • @onemasse:真的吗?我没有注意到(但是,我不使用它,即使是一次性代码!).关于`mktemp()`的警告要好得多,我确实在我工作的一些代码中定期看到它. (2认同)
  • MSVC可能.他们与委员会达成的协议是,如果委员会将所有可怕的"*_s""安全"功能添加到标准中,迫使*nix实施污染自己,他们将支持新标准.;-) (2认同)

miz*_*izo 24

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

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

CERT C安全编码标准许多这些陷阱你问的名单.


caf*_*caf 21

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

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

相反,使用strtol()一系列功能.

  • 如果您知道代码将在哪个平台上运行,那么它实际上非常方便,您可能会这样做.例如MSVC说*如果输入无法转换为该类型的值,那么`atoi`和`_wtoi`的返回值为0.,所以它非常好.(另外,这是"未定义"和"实现定义"实际上并不完全不同的另一个例子 - 它们都可以由实现来定义.) (2认同)

Jen*_*edt 11

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

errno:

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

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

即将出台的标准得到了errno更直接的定义,但这些概念仍然存在

  • 标准:"宏...`errno`,它扩展到一个类型为`int` ..."的可修改的左值.所以很明显它是一个宏. (2认同)

dav*_*vep 6

通常有一个strtok_r.

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

  • 我要说这应该是一个评论,而不是一个答案,但你不能没有代表发表评论,所以在这里,有一些. (4认同)
  • 当你说"经常有`strtok_r()`"时,你会遇到"偶尔没有"和"当它不可用时你打算做什么?".第二个问题是假定的平台 - 问题是关于C99,其中`strtok_r()`不可用(一般来说也不是`strtok_s()` - 来自TR 24731-1). (3认同)

tem*_*def 5

我把printfscanf相当高的了这个名单上.您必须使格式化说明符完全正确的事实使得这些函数难以使用并且极易出错.读取数据时,也很难避免缓冲区溢出.此外,当好心的程序员将客户指定的字符串指定为printf的第一个参数时,"printf格式字符串漏洞"可能会造成无数的安全漏洞,但却发现堆栈被粉碎并且安全性受到了多年的影响.

  • 如果您的编译器无法告诉您"您已将%x与int号一起使用",请将其删除,或打开其警告标志. (7认同)
  • 我不同意.只有在运行时计算格式字符串而不是常量字符串时才会变得危险.对于那种情况,GCC甚至有一个很好的警告选项`-Wformat-nonliteral`(当然应该与`-Werror`组合以发出警告错误). (7认同)
  • 如果变量输出为大于预期的值,则sprintf()函数也可能导致缓冲区溢出.例如,代码可能期望"unsigned long"最多需要十一个字节(十位数和一个空终止符),但在64位系统上崩溃,其中unsigned long可能需要21. (3认同)
  • 当你在它时,只需启用-Wall,它也启用-Wformat. (2认同)