必须(应该)避免使用标准库中的哪些功能?

时间:2010-04-02 08:23:54

标签: c standard-library obsolete

我已经读过Stack Overflow,有些C函数是“过时的”或“应该避免”。你能告诉我一些这种功能的例子吗?

这些功能有哪些替代方案?

我们可以安全地使用它们 - 任何好的做法吗?

13 个答案:

答案 0 :(得分:55)

不赞成使用的功能
不安全
这种函数的一个完美示例是gets(),因为没有办法告诉它目标缓冲区有多大。因此,任何使用gets()读取输入的程序都有buffer overflow vulnerability。出于类似原因,我们应使用strncpy()代替strcpy()strncat()代替strcat()

然而,更多示例包括由tmpfile()引起的mktemp()potential security issues with overwriting temporary files函数,并且被更安全的mkstemp()函数取代。

非重入
其它实例包括gethostbyaddr()gethostbyname()它们是非重入(和,因此,不能保证是线程)以及由折返已经被取代getaddrinfo()freeaddrinfo() < / p>

您可以注意到这里的图案...要么缺乏安全(可能通过不包括在签名中足够的信息来实施可能将其牢固的)或非重入的是弃用的共同来源。

过时,非便携式
其他一些功能只是被弃用,因为它们复制功能并且不像其他变体那样可移植。例如,bzero()已弃用memset()

线程安全和重入
您在帖子中询问了线程安全和重入问题。有一点点差异。如果函数不使用任何共享的可变状态,则该函数是可重入的。因此,例如,如果它需要的所有信息都传递给函数,并且所需的任何缓冲区也传递给函数(而不是由函数的所有调用共享),那么它是可重入的。这意味着通过使用独立参数,不同的线程不会意外地共享状态。重入是比线程安全更有力的保证。如果函数可以同时由多个线程使用,则该函数是线程安全的。如果出现以下函数,则函数是线程安全的:

       
  • 它是可重入的(即它不会在呼叫之间共享任何状态),或者:
  •    
  • 它是不可重入的,但它根据共享状态的需要使用同步/锁定。

通常,在Single UNIX SpecificationIEEE 1003.1(即“POSIX”)中,任何不保证可重入的函数都不能保证是线程安全的。因此,换句话说,只有保证可重入的函数可以在多线程应用程序中可移植地使用(没有外部锁定)。但是,这并不意味着这些标准的实现不能选择使非重入函数成为线程安全的。例如,Linux经常向非重入函数添加同步,以便添加线程安全的保证(超出单一UNIX规范的保证)。

字符串(和一般的内存缓冲区)
您还询问是否存在字符串/数组的一些基本缺陷。有些人可能会说这是事实,但我认为不,这种语言没有根本的缺陷。 C和C ++要求您分别传递数组的长度/容量(它不像某些其他语言那样是“.length”属性)。这本身并不是一个缺陷。任何C和C ++开发人员只需将长度作为参数传递给需要的人就可以编写正确的代码。问题是需要此信息的几个API未能将其指定为参数。或者假设将使用一些MAX_BUFFER_SIZE常量。此类API现在已被弃用,并由允许指定数组/缓冲区/字符串大小的替代API替代。

Scanf(回答您的上一个问题)
就个人而言,我使用C ++ Iostreams库(标准:: CIN,性病:: COUT中,&lt;&LT;和&gt;&GT;经营者,的std ::函数getline,性病:: istringstream,性病:: ostringstream等),所以我通常不会处理这个问题。如果我被迫使用纯C,不过,我个人只使用fgetc()getchar()strtol()strtoul()等,分析事物结合手动的,因为我不是varargs或格式字符串的忠实粉丝。也就是说,据我所知,[f]scanf()[f]printf()等没有问题,只要您自己制作格式字符串,就不会传递任意格式字符串或允许用户输入用作格式字符串,并在适当的位置使用<inttypes.h>中定义的格式化宏。 (注意,snprintf()应该用来代替sprintf(),但这与未能指定目标缓冲区的大小而不是格式字符串的使用有关。我还应该指出,在C ++中,boost::format提供了类似printf的格式,没有varargs。

答案 1 :(得分:22)

人们再次重复,像咒语一样,荒谬的断言是“n”版本的str函数是安全版本。

如果这是他们的意图,那么他们总是会终止字符串。

函数的“n”版本是为了与固定长度字段(例如早期文件系统中的目录条目)一起使用而编写的,其中仅当字符串未填充字段时才需要nul终止符。这也是为什么函数具有奇怪的副作用的原因,如果仅用作替换,这些副作用毫无意义 - 例如:strncpy():

  

如果s2指向的数组是a   短于n个字节的字符串,   null字节被附加到副本中   s1指向的数组,直到n   所有字节均写入。

由于分配用于处理文件名的缓冲区通常为4千字节,因此可能导致性能大幅下降。

如果您想要“假定的”安全版本,那么获取 - 或编写您自己的 - strl例程(strlcpy,strlcat等),这些例程总是无法终止字符串并且没有副作用。请注意,虽然这些并不是非常安全,因为它们可以默默地截断字符串 - 这在任何真实世界的程序中都不是最好的行动方案。在某些情况下,这是可以的,但也有许多情况可能会导致灾难性后果(例如打印出医疗处方)。

答案 2 :(得分:19)

此处有几个答案建议使用strncat()而不是strcat();我建议也应该避免strncat()(和strncpy())。它存在的问题使得难以正确使用并导致错误:

  • strncat()的长度参数与(但不完全 - 请参阅第3点)可以复制到目标的最大字符数而不是目标缓冲区的大小相关。这使strncat()更难以使用,特别是如果多个项目将连接到目的地。
  • 可能难以确定结果是否被截断(可能重要也可能不重要)
  • 很容易出现一个错误。正如C99标准所指出的那样,“因此,对于看起来像s1 strlen(s1)+n+1指向的数组中可以结束的最大字符数为strncat( s1, s2, n)” >

strncpy()也有一个问题,可能会导致您尝试以直观的方式使用它的错误 - 它不能保证目标为空终止。为了确保您必须通过自己在缓冲区的最后位置放置'\0'来确保专门处理该角落案例(至少在某些情况下)。

我建议使用OpenBSD的strlcat()strlcpy()之类的东西(虽然我知道有些人不喜欢这些功能;我相信它们比strncat() /更容易安全使用strncpy())。

以下是Todd Miller和Theo de Raadt对strncat()strncpy()问题所说的一些内容:

  

strncpy()strncat()用作strcpy()strcat()的安全版本时,会遇到几个问题。这两个函数以不同的和非直观的方式处理NUL终止和长度参数,甚至使有经验的程序员感到困惑。它们也没有提供检测何时发生截断的简单方法。 ......在所有这些问题中,由长度参数和相关的NUL终止问题引起的混乱是最重要的。当我们审核OpenBSD源代码树以查找潜在的安全漏洞时,我们发现了strncpy()strncat()的滥用行为。虽然并非所有这些都导致可利用的安全漏洞,但他们明确表示在安全字符串操作中使用strncpy()strncat()的规则被广泛误解。

OpenBSD的安全审计发现具有这些功能的错误“猖獗”。与gets()不同,这些函数可以安全使用,但实际上存在很多问题,因为界面混乱,不直观且难以正确使用。我知道微软也做过分析(虽然我不知道他们可能发布了多少数据),结果已被禁止(或者至少非常强烈劝阻 - “禁令”可能不是绝对的)使用strncat()strncpy()(以及其他功能)。

一些链接包含更多信息:

答案 3 :(得分:7)

有些人会声称应该避免strcpystrcat,这有利于strncpystrncat。在我看来,这有点主观。

在处理用户输入时一定要避免使用它们 - 毫无疑问。

在远离用户的代码中,当您只知道缓冲区足够长时,strcpystrcat可能会更高效,因为计算{ {1}}传递给他们的堂兄弟可能是多余的。

答案 4 :(得分:7)

应该永远不会使用的标准库函数:

setjmp.h

  • setjmp()。与longjmp()一起,这些函数被广泛认为使用极其危险:它们导致意大利面条编程,它们带有多种形式的未定义行为,它们可能在程序环境中引起意外的副作用,例如影响值存储在堆栈中。参考文献:MISRA-C:2012年规则21.4,CERT C MSC22-C
  • longjmp()。请参阅setjmp()

<强> stdio.h中

  • gets()。该功能已从C语言中删除(根据C11),因为它根据设计不安全。该功能已在C99中标记为已过时。请改用fgets()。参考文献:ISO 9899:2011 K.3.5.4.1,另见注释404)。

stdlib.h

  • atoi()系列功能。它们没有错误处理,但每当发生错误时都会调用未定义的行为。完全多余的功能,可以用strtol()系列功能替换。参考文献:MISRA-C:2012规则21.7。

<强> string.h中

  • strncat()。有一个经常被滥用的尴尬界面。它主要是一个多余的功能。另请参阅strncpy()的评论。
  • strncpy()。这个函数的意图永远不是strcpy()的更安全版本。它的唯一目的始终是在Unix系统上处理一种古老的字符串格式,并且它包含在标准库中是一个已知的错误。此函数很危险,因为它可能会使字符串没有空终止,并且程序员通常会错误地使用它。参考文献:Why are strlcpy and strlcat considered insecure?

应谨慎使用的标准库函数:

assert.h

  • assert()。带有开销,通常不应在生产代码中使用。最好使用特定于应用程序的错误处理程序来显示错误,但不一定会关闭整个程序。

signal.h

  • signal()。参考文献:MISRA-C:2012规则21.5,CERT C SIG32-C

<强> STDARG.H

  • va_arg()系列功能。 C程序中存在可变长度函数几乎总是表明程序设计不佳。除非你有非常具体的要求,否则应该避免。

<强> stdio.h中
通常,不建议将整个库用于生产代码,因为它会出现许多行为定义不明确且类型安全性差的案例。

  • fflush()。非常适合用于输出流。如果用于输入流,则调用未定义的行为。
  • gets_s()。 C11边界检查界面中包含gets()的安全版本。根据C标准建议,最好使用fgets()。参考文献:ISO 9899:2011 K.3.5.4.1。
  • printf()系列功能。资源繁重的功能带来许多未定义的行为和类型安全性差。 sprintf()也存在漏洞。在生产代码中应避免使用这些功能。参考文献:MISRA-C:2012年规则21.6。
  • scanf()系列功能。请参阅有关printf()的评论。此外,如果未正确使用, - scanf()容易受到缓冲区溢出的影响。在可能的情况下,最好使用fgets()。参考文献:CERT C INT05-C,MISRA-C:2012年规则21.6。
  • tmpfile()系列功能。附带各种漏洞问题。参考文献:CERT C FIO21-C

stdlib.h

  • malloc()系列函数。完全可以在托管系统中使用,但要注意C90中的众所周知的问题,因此don't cast the resultmalloc()系列函数绝不应用于独立应用程序。参考文献:MISRA-C:2012年规则21.3。

    另请注意,如果您使用realloc()的结果覆盖旧指针,realloc()会很危险。如果函数失败,则会产生泄漏。

  • system()。有很多开销,虽然是可移植的,但通常最好使用特定于系统的API函数。伴随着各种定义不明确的行为。参考文献:CERT C ENV33-C

<强> string.h中

  • strcat()。请参阅strcpy()的评论。
  • strcpy()。除非要复制的数据大小未知或大于目标缓冲区,否则完全可以使用。如果未检查传入数据大小,则可能存在缓冲区溢出。这不是strcpy()本身的错误,而是调用应用程序的错误 - strcpy()不安全主要是a myth created by Microsoft
  • strtok()。改变调用者字符串并使用内部状态变量,这可能使其在多线程环境中不安全。

答案 5 :(得分:6)

避免

  • strtok用于多线程程序,因为它不是线程安全的。
  • gets因为它可能导致缓冲区溢出

答案 6 :(得分:5)

可能值得再次补充一点,strncpy()不是strcpy()的通用替代品,而是名称可能暗示的。它设计用于不需要nul-terminator的固定长度字段(它最初设计用于UNIX目录条目,但对加密密钥字段之类的东西很有用)。

然而,使用strncat()替代strcpy()很容易:

if (dest_size > 0)
{
    dest[0] = '\0';
    strncat(dest, source, dest_size - 1);
}

if测试显然可以放弃常见的情况,你知道dest_size肯定是非零的。)

答案 7 :(得分:5)

另请查看Microsoft的banned APIs列表。这些是API(包括此处已列出的许多API),因为它们经常被滥用并导致安全问题,因此禁止使用Microsoft代码。

你可能不同意所有这些,但它们都值得考虑。当滥用导致许多安全漏洞时,他们会在列表中添加API。

答案 8 :(得分:2)

几乎任何处理NUL终止字符串的函数都可能不安全。 如果您正在接收来自外部世界的数据并通过str *()函数进行操作,那么您可以为灾难做好准备

答案 9 :(得分:2)

不要忘记sprintf - 它是许多问题的原因。这是正确的,因为替代方案,snprintf有时会有不同的实现,这些实现会使您的代码无法移植。

  1. linux:http://linux.die.net/man/3/snprintf

  2. windows:http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx

  3. 在case 1(linux)中,返回值是存储整个缓冲区所需的数据量(如果它小于给定缓冲区的大小,则输出被截断)

    在情况2(窗口)中,如果输出被截断,则返回值为负数。

    通常你应该避免使用不是的函数:

    1. 缓冲区溢出安全(这里已经提到了很多功能)

    2. 线程安全/不可重入(例如strtok)

    3. 在每个功能的手册中,您应该搜索以下关键字:安全,同步,异步,线程,缓冲区,错误

答案 10 :(得分:1)

安全使用scanf非常困难。好好使用scanf可以避免缓冲区溢出,但在读取不符合请求类型的数字时,仍然容易受到未定义行为的影响。在大多数情况下,fgets后面进行自解析(使用sscanfstrchr等)是更好的选择。

但我不会说“一直避免scanf”。 scanf有其用途。例如,假设您想要读取长度为10个字节的char数组中的用户输入。您想要删除尾随换行符(如果有)。如果用户在换行前输入超过9个字符,则需要将前9个字符存储在缓冲区中并丢弃所有内容,直到下一个换行符为止。你可以这样做:

char buf[10];
scanf("%9[^\n]%*[^\n]", buf));
getchar();

一旦你习惯了这个习语,它就会更短,而且在某些方面比以前更清晰:

char buf[10];
if (fgets(buf, sizeof buf, stdin) != NULL) {
    char *nl;
    if ((nl = strrchr(buf, '\n')) == NULL) {
        int c;
        while ((c = getchar()) != EOF && c != '\n') {
            ;
        }
    } else {
        *nl = 0;
    }
}

答案 11 :(得分:0)

在所有的字符串复制/移动场景中 - strcat(),strncat(),strcpy(),strncpy()等等 - 如果简单的话,情况要好得多(更安全)启发式实施:

1.在添加数据之前,始终NUL填充缓冲区 2.使用宏常量将字符缓冲区声明为[SIZE + 1]
例如,给定:

#define   BUFSIZE   10
char      Buffer[BUFSIZE+1] = { 0x00 };  /* The compiler NUL-fills the rest */

我们可以使用以下代码:

memset(Buffer,0x00,sizeof(Buffer));
strncpy(Buffer,BUFSIZE,"12345678901234567890");
相对安全。 memset()应该出现在strncpy()之前,即使我们在编译时初始化了Buffer,因为在调用函数之前我们不知道其他代码放入了什么垃圾。 strncpy()会将复制的数据截断为“1234567890”,并且不会 NUL-终止它。但是,因为我们已经NUL填充了整个缓冲区 - sizeof(缓冲区)而不是BUFSIZE - 所以只要我们使用BUFSIZE约束我们的写入,就可以保证最终“超出范围”终止NUL。常量,而不是sizeof(缓冲区)。

缓冲区和BUFSIZE同样适用于snprintf():

memset(Buffer,0x00,sizeof(Buffer));
if(snprintf(Buffer,BUFIZE,"Data: %s","Too much data") > BUFSIZE) {
    /* Do some error-handling */
}   /* If using MFC, you need if(... < 0), instead */

即使snprintf()专门写入BUFIZE-1字符,然后是NUL,这也可以安全地工作。所以我们在Buffer的末尾“浪费”一个无关的NUL字节...我们防止了缓冲区溢出和未终止的字符串条件,因为内存成本非常小。

我对strcat()和strncat()的调用更加强硬:不要使用它们。安全地使用strcat()很困难,并且strncat()的API非常直观,以至于正确使用它所需的努力会抵消任何好处。我建议如下:

#define strncat(target,source,bufsize) snprintf(target,source,"%s%s",target,source)

很有可能创建一个strcat()插件,但不是一个好主意:

#define strcat(target,source) snprintf(target,sizeof(target),"%s%s",target,source)

因为target可能是一个指针(因此sizeof()不会返回我们需要的信息)。我对你的代码中的strcat()实例没有一个好的“通用”解决方案。

我经常从“strFunc() - 意识到”程序员遇到的问题是尝试通过使用strlen()来防止缓冲区溢出。如果保证内容被NUL终止,这很好。否则,strlen()本身可能会导致缓冲区溢出错误(通常导致分段违规或其他核心转储情况),然后才能达到您要保护的“有问题”代码。

答案 12 :(得分:-1)

atoi不是线程安全的。根据手册页的建议,我使用strtol。