标准库有哪些function必须/应该避免?
我读过堆栈溢出一些C函数是“过时”或“应该避免”。 请给我举一些这样的function和原因的例子吗?
这些function有哪些替代scheme?
我们可以安全地使用它们吗?
弃用函数
不安全
这个函数的一个很好的例子是gets() ,因为没有办法告诉它目的缓冲区有多大。 因此,任何使用gets()读取input的程序都有一个缓冲区溢出漏洞 。 由于类似的原因,应该使用strncpy()代替strcat()和strncat()代替strcat() 。
还有一些例子包括tmpfile()和mktemp()函数,这是由于覆盖临时文件的潜在安全问题,并且被更安全的mkstemp()函数所取代。
非重入
其他的例子包括gethostbyaddr()和gethostbyname() ,它们是不可重入的(因此不能保证是线程安全的),并被可重入的getaddrinfo()和freeaddrinfo()所取代。
您可能会注意到这里的一个模式…或者缺乏安全性(可能是因为没有在签名中包含足够的信息来安全地实现它)或不可再生是常见的贬值来源。
过时的,不便携的
一些其他function简单地被弃用,因为它们重复function并且不像其他变体那样便携。 例如, bzero()已被弃用,以支持memset() 。
线程安全和再入
您在post中询问了线程安全性和再入行。 有一点点区别。 一个函数是可重入的,如果它不使用任何共享的,可变的状态。 因此,例如,如果所有需要的信息都被传递到函数中,并且所需的任何缓冲区也被传递到函数中(而不是被所有函数调用共享),那么它是可重入的。 这意味着不同的线程,通过使用独立的参数,不会冒险分享状态。 重入是比线程安全更有力的保证。 如果一个函数可以被多个线程同时使用,则该函数是线程安全的。 一个函数是线程安全的,如果:
- 它是可重入的(即它不会在通话之间共享任何状态),或者:
- 它是不可重入的,但它根据共享状态的需要使用同步/locking。
一般来说,在单一UNIX规范和IEEE 1003.1 (即“POSIX”)中,不保证任何不能重入的函数都是线程安全的。 所以,换句话说,只有保证可重入的函数才能被移植到multithreading应用程序中(无需外部locking)。 但是,这并不意味着这些标准的实现不能select使非重入函数线程安全。 例如,Linux经常向不可重入函数添加同步,以便为threadsafety添加一个保证(超出了单一UNIX规范的保证)。
string(和内存缓冲区,一般)
你还问到了string/数组是否存在一些根本的缺陷。 有些人可能会认为是这样,但我认为不是,这个语言没有根本的缺陷。 C和C ++要求您分别传递一个数组的长度/容量(不像其他一些语言中那样是“.length”属性)。 这本身并不是一个缺陷。 任何C和C ++开发人员都可以根据需要简单地将长度作为参数来编写正确的代码。 问题是需要这些信息的几个API没有把它指定为一个参数。 或者假设使用一些MAX_BUFFER_SIZE常量。 现在这样的API已被弃用,并被允许指定数组/缓冲区/string大小的替代API取代。
Scanf(回答您的最后一个问题)
就我个人而言,我使用C ++ iostreams库(std :: cin,std :: cout,<<和>>运算符,std :: getline,std :: istringstream,std :: ostringstream等),所以我不通常处理这一点。 如果我不得不使用纯C,我个人只需要使用fgetc()或getchar()与strtol() , strtoul()等结合使用,并手动parsing,因为我不是可变参数或格式string。 这就是说,据我所知,只要你自己制作格式化string,你就不会传递任意格式的string,也不允许用户使用[f] scanf() , [f] printfinput用作格式string,并在适当的地方使用<inttypes.h>中定义的格式化macros。 (注意,应该使用snprintf()来代替sprintf() ,但是这与无法指定目标缓冲区的大小以及不使用格式string有关。 我还应该指出,在C ++中, boost :: format提供了不带可变参数的类似printf的格式。
人们又一次重复着,口头禅般地说,“n”版本的strfunction是安全的。
如果这是他们的目标,那么他们总是会终止string。
这些函数的“n”版本是为固定长度的字段(例如早期文件系统中的目录项)而编写的,其中只有在string不填充字段时才需要nul终止符。 这也是为什么这些函数具有奇怪的副作用,如果只是作为replace使用,效率低下 – 以strncpy()为例:
如果由s2指向的数组是一个比n个字节短的string,则空字节会被附加到s1所指向的数组的副本中,直到写入全部的n个字节为止。
由于缓冲区分配处理文件名通常是4KB,这可能会导致性能的大幅恶化。
如果你想“所谓”安全的版本,然后获得 – 或写自己的strl例程(strlcpy,strlcat等),总是最终的string,没有副作用。 请注意,虽然这些并不安全,因为它们可以静静地截断string – 这在任何真实世界的程序中都不是最好的行为方式。 在有些情况下,这是可以的,但也有很多情况下会导致灾难性的结果(例如打印医疗处方)。
这里有几个答案build议在strcat()
使用strncat()
strcat()
; 我build议strncat()
(和strncpy()
)也应该避免。 它有问题,使得难以正确使用,并导致错误:
-
strncat()
的长度参数与(但不完全正确 – 参见第三点)相关,即可以复制到目标的最大字符数,而不是目标缓冲区的大小。 这使得strncat()
比它应该更难使用,特别是如果多个项目将连接到目的地。 - 可能难以确定结果是否被截断(可能是或可能不重要)
- 有一个错误的错误很容易。 正如C99标准所指出的那样,“因此,对于看起来像
strncat( s1, s2, n)
的调用,s1
指向的数组中可以结束的最大字符数为strlen(s1)+n+1
”
strncpy()
也有一个问题,可能会导致错误,您尝试以直观的方式使用它 – 它不能保证目的地是null终止。 为了确保你必须确保你特别处理那个angular落的情况下,通过在缓冲区的最后一个位置丢掉一个'\0'
(至less在某些情况下)。
我build议使用OpenBSD的strlcat()
和strlcpy()
(尽pipe我知道有些人不喜欢这些函数;我相信它们比strncat()
/ strncpy()
)更安全。
以下是关于strncat()
和strncpy()
一些问题,Todd Miller和Theo de Raadt对此有何评论:
当使用
strncpy()
和strncat()
作为strcpy()
和strcat()
安全版本时遇到了几个问题。 这两个函数都以不同的和非直观的方式处理NUL终止和长度参数,甚至使有经验的程序员感到困惑。 他们也没有提供简单的方法来检测何时发生截断。 在所有这些问题中,由长度参数引起的混淆和NUL终止的相关问题是最重要的。 当我们审查OpenBSD源代码树时,发现潜在的安全漏洞,我们发现了对strncpy()
和strncat()
滥用。 尽pipe并非所有这些都导致了可利用的安全漏洞,但他们明确指出,在安全string操作中使用strncpy()
和strncat()
的规则被广泛误解。
OpenBSD的安全审计发现,这些function的错误是“猖獗”。 与gets()
不同,这些函数可以安全地使用,但实际上存在很多问题,因为界面混乱,不直观,难以正确使用。 我知道微软也做了分析(尽pipe我不知道他们可能发布了多less数据),结果被禁止了(或者至less是非常强烈的沮丧 – “禁令”可能不是绝对的),使用strncat()
和strncpy()
(以及其他函数)。
一些链接更多的信息:
- http://www.usenix.org/events/usenix99/full_papers/millert/millert_html/
- http://en.wikipedia.org/wiki/Off-by-one_error#Security_implications
- http://blogs.msdn.com/michael_howard/archive/2004/10/29/249713.aspx
- http://blogs.msdn.com/michael_howard/archive/2004/11/02/251296.aspx
- http://blogs.msdn.com/michael_howard/archive/2004/12/10/279639.aspx
- http://blogs.msdn.com/michael_howard/archive/2006/10/30/something-else-to-look-out-for-when-reviewing-code.aspx
避免
- multithreading程序的
strtok
不是线程安全的。 - 因为它可能会导致缓冲区溢出
有些人会声称应该避免使用strcpy
和strcat
,而使用strncpy
和strncat
。 在我看来,这有点主观。
在处理用户input时绝对应该避免 – 毫无疑问在这里。
在远离用户的代码中,当你知道缓冲区足够长的时候, strcpy
和strcat
可能会更高效一些,因为计算n
传给它们的表兄弟可能是多余的。
几乎所有处理NUL终止string的函数都可能是不安全的。 如果您从外部接收数据并通过str *()函数操作,那么您将自己设置为灾难
strncpy()
不是strcpy()
的通用替代品,它可能是build议的。 它是为不需要终结符的固定长度字段而devise的(最初devise用于UNIX目录条目,但可用于诸如encryption密钥字段之类的内容)。
但是,使用strncat()
作为strcpy()
的替代品很容易:
if (dest_size > 0) { dest[0] = '\0'; strncat(dest, source, dest_size - 1); }
( if
你知道dest_size
肯定是非零的话, if
testing显然可以放弃。
不要忘记sprintf – 这是许多问题的原因。 这是真的,因为替代,snprintf有时不同的实现,可以使你的代码不可移植。
-
linux: http : //linux.die.net/man/3/snprintf
-
windows: http : //msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx
在情况1(linux)中,返回值是存储整个缓冲区所需的数据量(如果它小于给定缓冲区的大小,则输出被截断)
在情况2(窗口)中,如果输出被截断,则返回值是负数。
一般来说,你应该避免不是的function:
-
缓冲区溢出安全(很多function已经在这里提到)
-
线程安全/不可重入(例如,strtok)
在每个函数的手册中,您应该search关键字如:安全,同步,asynchronous,线程,缓冲区,错误
另外检查一下微软禁止使用的API列表。 这些API(包括许多已经列在这里的)被禁止使用微软代码,因为它们经常被滥用,并导致安全问题。
你可能不同意所有这些,但是他们都值得考虑。 当它的滥用导致了一些安全漏洞时,他们将API添加到列表中。
不应该使用的标准库函数:
SETJMP.H
-
setjmp()
。 与longjmp()
一起,这些函数被广泛认为是非常危险的使用:它们导致意大利面程序devise,它们有许多未定义的行为forms,它们会在程序环境中引起意想不到的副作用,比如影响存储在堆栈。 参考文献:MISRA-C:2012规则21.4, CERT C MSC22-C 。 -
longjmp()
。 请参阅setjmp()
。
stdio.h中
-
gets()
。 该function已从C语言中删除(按照C11),因为按devise它是不安全的。 该function在C99中已经被标记为过时。 使用fgets()
来代替。 参考文献:ISO 9899:2011 K.3.5.4.1,另见注释404)。
stdlib.h中
-
atoi()
函数族。 这些没有error handling,但每当发生错误时调用未定义的行为。 完全多余的函数可以用strtol()
函数族来替代。 参考文献:MISRA-C:2012规则21.7。
string.h中
-
strncat()
。 有一个尴尬的界面,经常被滥用。 它主要是一个多余的function。 另请参阅strncpy()
注释。 -
strncpy()
。 这个函数的意图从来不是一个更安全的strcpy()
版本。 它的唯一目的是在Unix系统上总是处理一个古老的string格式,而它被包含在标准库中是一个已知的错误。 这个函数是危险的,因为它可能会使string没有空终止,并且程序员经常使用它不正确。 参考文献: 为什么strlcpy和strlcat被认为是不安全的? 。
标准库函数应谨慎使用:
ASSERT.H
-
assert()
。 带有开销,一般不应用于生产代码。 最好使用特定于应用程序的error handling程序来显示错误,但不一定closures整个程序。
signal.h中
-
signal()
。 参考文献:MISRA-C:2012规则21.5, CERT C SIG32-C 。
STDARG.H
-
va_arg()
函数族。 C程序中变长函数的存在几乎总是表示程序devise不佳。 应该避免,除非你有非常具体的要求。
stdio.h中
一般来说, 整个库不build议用于生产代码 ,因为它有很多不明确的行为和不良types安全的情况。
-
fflush()
。 完美的罚款用于输出stream。 如果用于inputstream,则调用未定义的行为。 -
gets_s()
。 C11边界检查界面中包含gets()
安全版本。 根据C标准build议,最好使用fgets()
。 参考文献:ISO 9899:2011 K.3.5.4.1。 -
printf()
系列函数。 资源繁重的function,伴随着大量未定义的行为和差的安全性。sprintf()
也有漏洞。 在生产代码中应该避免这些function。 参考文献: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()
函数族。 在托pipe系统中使用完美无瑕,但请注意C90中的众所周知的问题,因此不会产生结果 。malloc()
系列函数决不能在独立应用程序中使用。 参考文献:MISRA-C:2012规则21.3。还要注意,如果用
realloc()
的结果覆盖旧的指针,realloc()
是危险的。 如果该function失败,你创build一个泄漏。 -
system()
。 有很多的开销,虽然可移植,但通常使用系统特定的API函数更好。 带有各种不明确的行为。 参考文献: CERT C ENV33-C 。
string.h中
-
strcat()
。 请参阅strcpy()
注释。 -
strcpy()
。 除非要复制的数据的大小未知或大于目标缓冲区,否则完全可以使用。 如果没有检查传入的数据大小,可能会出现缓冲区溢出。 这不是strcpy()
本身的问题,而是调用应用程序的问题 –strcpy()
是不安全的,大多是微软创造的一个神话 。 -
strtok()
。 修改调用者string并使用内部状态variables,这可能会使其在multithreading环境中变得不安全。
安全地使用scanf
是非常困难的。 使用scanf
可以避免缓冲区溢出,但是在读取不符合请求types的数字时,您仍然容易受到未定义的行为的影响。 在大多数情况下, fgets
后跟自parsing(使用sscanf
, strchr
等)是一个更好的select。
但是我不会说“总是避免scanf
”。 scanf
有它的用途。 作为一个例子,假设您想要读取长度为10个字节的char
数组中的用户input。 你想删除尾随的换行符,如果有的话。 如果用户在换行符之前input了多于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; } }
在所有的string复制/移动场景中,strcat(),strncat(),strcpy(),strncpy()等等 – 如果强制执行几个简单的启发式,事情会变得更好( 更安全 )
1.在添加数据之前,始终NUL填充您的缓冲区。
2.用macros常量声明字符缓冲区为[SIZE + 1]。
例如,给出:
#define BUFSIZE 10
char Buffer [BUFSIZE + 1] = {0x00}; / *编译器NUL填充其余* /
我们可以使用如下代码:
memset的(缓冲液,0×00,的sizeof(缓冲液));
函数strncpy(缓冲液,BUFSIZE, “12345678901234567890”);
相对安全。 即使我们在编译时初始化了Buffer,memset()也应该出现在strncpy()之前,因为我们不知道在我们的函数被调用之前,其他的代码被放到了什么地方。 strncpy()会将复制的数据截断为“1234567890”,而不会 NUL终止它。 但是,由于我们已经用NUL填充了整个buffer-sizeof(Buffer),而不是BUFSIZE,所以只要我们使用BUFSIZE来限制我们的写操作,保证会有一个最终的“超出范围”的终止NUL常量,而不是sizeof(Buffer)。
缓冲区和BUFSIZE同样适用于snprintf():
memset的(缓冲液,0×00,的sizeof(缓冲液));
if(snprintf(Buffer,BUFIZE,“Data:%s”,“Too much data”)> BUFSIZE){
/ *做一些error handling/
} /如果使用MFC,你需要if(… <0),而不是* /
尽pipesnprintf()特别写了BUFIZE-1字符,然后是NUL,但是这个工作是安全的。 所以我们在Buffer的末尾“浪费”了一个无关的NUL字节……我们防止了缓冲区溢出和未终止的string条件,只花费相当小的内存成本。
我对strcat()和strncat()的调用更强硬:不要使用它们。 安全地使用strcat()是困难的,而strncat()的API是非常直观的,所以使用它的努力正确地否定了任何好处。 我提出以下的build议:
#define strncat(target,source,bufsize)snprintf(target,source,“%s%s”,target,source)
创build一个strcat()插件很诱人,但不是一个好主意:
#define strcat(target,source),snprintf(target,sizeof(target),“%s%s”,target,source)
因为target可能是一个指针(因此sizeof()不会返回我们需要的信息)。 我没有一个很好的“通用”解决scheme在您的代码strcat()的实例。
我经常遇到的“strFunc()感知”程序员的问题是通过使用strlen()来防止缓冲区溢出。 如果内容保证是NUL终止的,这很好。 否则,strlen()本身可能会导致缓冲区溢出错误(通常导致分段违规或其他核心转储情况),在您到达您要保护的“有问题”的代码之前。
所有具有n版本的函数应该被避免。 例如,使用strncpy而不是strcpy。
atoi不是线程安全的。 我使用strtol,而不是从手册页推荐。