当我尝试gets()
通过GCC 编译使用该函数的C代码时,收到以下警告:
(.text + 0x34):警告:“ gets”功能很危险,不应使用。
我记得这与堆栈保护和安全性有关,但是我不确定为什么。
如何删除此警告,为什么会有这样的使用警告gets()
?
如果gets()
是如此危险,那我们为什么不能将其删除?
当我尝试gets()
通过GCC 编译使用该函数的C代码时,收到以下警告:
(.text + 0x34):警告:“ gets”功能很危险,不应使用。
我记得这与堆栈保护和安全性有关,但是我不确定为什么。
如何删除此警告,为什么会有这样的使用警告gets()
?
如果gets()
是如此危险,那我们为什么不能将其删除?
Answers:
为了gets
安全使用,您必须确切地知道将要读取多少个字符,以便可以使缓冲区足够大。您只会知道,如果您确切知道将要读取的数据。
gets
您要使用而不是使用fgets
,它具有签名
char* fgets(char *string, int length, FILE * stream);
(fgets
,如果它读取整行,则将保留'\n'
在字符串中;您必须处理该问题。)
直到1999 ISO C标准,它仍然是该语言的正式组成部分,但2011年标准正式将其删除。大多数C实现仍然支持它,但是至少gcc对使用它的任何代码发出警告。
gets()
使用时会导致编译器发出警告。
gets()
危险第一个Internet蠕虫(Morris Internet Worm)大约在30年前(1988-11-02)逃脱了,它使用gets()
缓冲区溢出作为其在系统之间传播的方法之一。基本问题是该函数不知道缓冲区有多大,因此它将继续读取直到找到换行符或遇到EOF为止,并且可能会溢出给定缓冲区的范围。
您应该忘记曾经听说过的gets()
存在。
取消gets()
了C11标准ISO / IEC 9899:2011 的标准功能,即A Good Thing™(在ISO / IEC 9899:1999 / Cor.3:2007 —技术勘误中正式标记为“过时”和“已弃用”) 3表示C99,然后在C11中删除)。可悲的是,由于向后兼容的原因,它将在库中保留很多年(意思是“十年”)。如果由我决定,的实现gets()
将变为:
char *gets(char *buffer)
{
assert(buffer != 0);
abort();
return 0;
}
鉴于您的代码迟早会崩溃,因此最好早点解决此问题,而不是稍后再解决。我准备添加一条错误消息:
fputs("obsolete and dangerous function gets() called\n", stderr);
如果您进行链接,则现代版本的Linux编译系统会生成警告gets()
,并且还会针对某些也存在安全问题的其他功能(mktemp()
,…)发出警告。
gets()
就像其他人所说的,规范的替代选择gets()
是fgets()
指定stdin
为文件流。
char buffer[BUFSIZ];
while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
...process line of data...
}
还没有人提到的是gets()
不包括换行符,而是包含换行符fgets()
。因此,您可能需要使用包装器fgets()
来删除换行符:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
size_t len = strlen(buffer);
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
return buffer;
}
return 0;
}
或更好:
char *fgets_wrapper(char *buffer, size_t buflen, FILE *fp)
{
if (fgets(buffer, buflen, fp) != 0)
{
buffer[strcspn(buffer, "\n")] = '\0';
return buffer;
}
return 0;
}
此外,如咖啡厅指出,在评论和paxdiablo显示在他的回答,跟fgets()
你可能有数据遗留在一条线上。我的包装器代码使该数据下次可以读取;您可以根据需要随时对其进行修改,以吞噬其余数据行:
if (len > 0 && buffer[len-1] == '\n')
buffer[len-1] = '\0';
else
{
int ch;
while ((ch = getc(fp)) != EOF && ch != '\n')
;
}
剩余的问题是如何报告三种不同的结果状态-EOF或错误,行读取但未截断,部分行读取但数据被截断。
gets()
之所以不会出现此问题,是因为它不知道缓冲区的结束位置,并且会在缓冲区的尽头践踏,对漂亮的内存布局造成严重破坏,如果在缓冲区上分配了缓冲区,则经常弄乱返回堆栈(Stack Overflow)堆栈,或者如果动态分配了缓冲区,则践踏控制信息;如果静态分配了缓冲区,则通过其他宝贵的全局(或模块)变量复制数据。这些都不是一个好主意-它们概括了“未定义的行为”一词。
还有TR 24731-1(C标准委员会的技术报告),它为各种功能提供了更安全的替代方案,其中包括gets()
:
§6.5.4.1
gets_s
功能概要
#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> char *gets_s(char *s, rsize_t n);
运行时约束
s
不得为空指针。n
既不等于零也不大于RSIZE_MAX。n-1
从中读取字符时会出现换行符,文件结尾或读取错误stdin
。25)3如果存在运行时约束冲突,
s[0]
则将其设置为空字符,并从stdin
开始读取和丢弃字符开始,直到读取新行字符,文件结束或读取错误。描述
4该
gets_s
函数n
从所指向的流中读取最多小于所指定的字符数的1stdin
到所指向的数组中s
。在换行符(已被丢弃)之后或文件结尾之后,不会再读取其他字符。丢弃的换行符不计入读取的字符数。在将最后一个字符读入数组后,立即写入空字符。5如果遇到文件结尾并且没有字符读入数组,或者在操作过程中发生读取错误,则将
s[0]
其设置为空字符,而其他元素则s
采用未指定的值。推荐做法
6该
fgets
函数允许正确编写的程序安全地处理过长的输入行,以至于无法存储在结果数组中。通常,这要求调用者fgets
注意结果数组中是否存在换行符。考虑使用fgets
(以及基于换行符的所有必要处理)代替gets_s
。25)该
gets_s
函数不同于gets
,它使输入行溢出缓冲区以存储它,从而违反了运行时约束。不同于fgets
,gets_s
在输入行和对的成功调用之间保持一对一的关系gets_s
。使用的程序gets
期望这种关系。
Microsoft Visual Studio编译器实现了对TR 24731-1标准的近似,但是Microsoft与TR中的签名之间存在差异。
C11标准ISO / IEC 9899-2011将TR24731包含在附件K中,作为库的可选部分。不幸的是,它很少在类Unix系统上实现。
getline()
— POSIXPOSIX 2008还提供了一个安全的替代gets()
叫getline()
。它为该行动态分配空间,因此最终需要释放它。因此,它消除了对线长的限制。它还返回读取的数据的长度,或者-1
(不是EOF
!),这意味着可以可靠地处理输入中的空字节。还有一个称为“选择您自己的单字符定界符”的变体getdelim()
;例如,如果要处理find -print0
文件名末尾用ASCII NUL '\0'
字符标记的输出,这将很有用。
fgets()
,您的fgets_wrapper()
版本将在输入缓冲区中留下超长行的尾部,以供下一个输入函数读取。在许多情况下,您需要读取和丢弃这些字符。
fgets()
是,如果文件包含一个空字节,则无法确定从空字节到行尾(或EOF)的末尾有多少数据。 strlen()
最多只能报告数据中的空字节;在那之后,这是猜测,因此几乎可以肯定是错误的。
gets()
存在。” 当我这样做时,我会再次遇到它并回到这里。您是否正在入侵stackoverflow以获得投票?
因为gets
在从stdin获取字节并将它们放在某处时不进行任何类型的检查。一个简单的例子:
char array1[] = "12345";
char array2[] = "67890";
gets(array1);
现在,首先您可以输入想要的字符数,gets
而不用担心。其次,超过放置它们的数组大小的字节(在这种情况下为array1
)将覆盖它们在内存中找到的所有内容,因为gets
将对其进行写入。在前面的示例中,这意味着,如果您输入的内容"abcdefghijklmnopqrts"
可能无法预料,它将覆盖array2
所有内容。
该函数不安全,因为它假定输入一致。永远不要使用它!
gets
彻底无法使用的是它不具有数组长度/计数参数,它需要; 如果它在那里,那将是另一个普通的C标准函数。
gets
是什么,为什么没有标准的fgets变体在不需要换行作为输入一部分的用例中变得如此方便?
gets
是,如果使用的是硬件行缓冲的I / O系统,该系统实际上无法提交一定长度的行,并且程序的预期寿命是比硬件的寿命短。在这种情况下,如果硬件无法提交超过127个字节的行,则可以将其提交gets
到128字节的缓冲区中是合理的,尽管我认为当期望较小的输入时能够指定较短的缓冲区的好处远比证明行之有效。成本。
gets
并strcat
安全地接受尽可能多的方法。
您不应该使用gets
它,因为它无法停止缓冲区溢出。如果用户输入的数据超出缓冲区的容量,则很可能导致损坏甚至更糟。
实际上,ISO实际上已经采取了从C标准中删除 的步骤(gets
从C11开始,尽管在C99中已弃用),考虑到它们向后兼容的程度,这应该表明该功能有多糟糕。
正确的做法是将fgets
函数与stdin
文件句柄一起使用,因为可以限制从用户读取的字符。
但这还存在以下问题:
为此,几乎每个C程序员在其职业生涯中的某个时候都将编写一个更有用的包装器fgets
。这是我的:
#include <stdio.h>
#include <string.h>
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
int ch, extra;
// Get line with buffer overrun protection.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
if (buff[strlen(buff)-1] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[strlen(buff)-1] = '\0';
return OK;
}
带有一些测试代码:
// Test program for getLine().
int main (void) {
int rc;
char buff[10];
rc = getLine ("Enter string> ", buff, sizeof(buff));
if (rc == NO_INPUT) {
printf ("No input\n");
return 1;
}
if (rc == TOO_LONG) {
printf ("Input too long\n");
return 1;
}
printf ("OK [%s]\n", buff);
return 0;
}
它提供了与fgets
防止缓冲区溢出相同的保护,但它也将发生的情况通知调用者并清除了多余的字符,以使它们不会影响您的下一个输入操作。
随意使用它,我特此根据“做你该死的想要的”许可发布它:-)
gets()
在定义的第7.19.7.7节或第7.26.9节的未来库说明以及的子节中明确弃用<stdio.h>
。甚至没有脚注说明它很危险。(说到这里,我看到“它弃用ISO / IEC 9899:1999 / Cor.3:2007(E))”中的答案由俞灏),但C11也从标准中删除它-而不是之前的时间!
int getLine (char *prmpt, char *buff, size_t sz) { ... if (fgets (buff, sz, stdin) == NULL)
隐藏size_t
到int
的转换sz
。 sz > INT_MAX || sz < 2
将捕获的奇怪值sz
。
if (buff[strlen(buff)-1] != '\n') {
是一种黑客利用方式,因为邪恶用户输入的第一个字符可能是嵌入的空字符呈现buff[strlen(buff)-1]
UB。 while (((ch = getchar())...
用户输入空字符时会遇到麻烦。
要从标准输入中读取:
char string[512];
fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
最近我看到,在USENET帖子comp.lang.c
,该gets()
正从标准中删除。OH
您会很高兴知道委员会刚刚投票(结果一致同意)也从草案中删除了gets()。
gcc -std=c2012 -pedantic ...
gets()进行编译时将无法通过。(我刚刚编造了-std
参数)
在C11(ISO / IEC 9899:201x)中,gets()
已被删除。(在ISO / IEC 9899:1999 / Cor.3:2007(E)中已弃用)
除此以外fgets()
,C11还引入了一种新的安全替代方法gets_s()
:
C11 K.3.5.4.1
gets_s
功能#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> char *gets_s(char *s, rsize_t n);
但是,在“ 推荐做法”部分,fgets()
仍然是首选。
该
fgets
函数允许编写正确的程序安全地处理过长的输入行,以至于无法存储在结果数组中。通常,这要求调用者fgets
注意结果数组中是否存在换行符。考虑使用fgets
(以及基于换行符的所有必要处理)代替gets_s
。
gets()
这是很危险的,因为用户可能会在提示中输入过多内容,从而使程序崩溃。它无法检测到可用内存的结束,因此,如果为此目的分配的内存太小,则可能导致段错误和崩溃。有时,用户似乎不太可能在提示一个人名字的提示符下键入1000个字母,但是作为程序员,我们需要使程序防弹。(如果用户通过发送太多数据而使系统程序崩溃,也可能会带来安全隐患)。
fgets()
允许您指定从标准输入缓冲区中取出多少个字符,因此它们不会超出变量。
我谨向所有仍在gets
其库中的C库维护者致以诚挚的邀请:“以防万一有人仍然依赖它”:请用
char *gets(char *str)
{
strcpy(str, "Never use gets!");
return str;
}
这将有助于确保没有人仍然依赖它。谢谢。
C gets函数是危险的,并且是非常昂贵的错误。Tony Hoare在他的演讲“空引用:十亿美元的错误”中特别提到了这一点:
http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare
整个小时都值得一看,但从30分钟起,他的评论意见就遭到了批评,其中大约39分钟就引起了批评。
希望这能引起您对整个演讲的兴趣,从而引起我们的注意,即引起我们注意如何在语言中需要更多正式的正确性证明,以及应该为语言设计人员而不是程序员指责语言设计人员的错误。这似乎是不良语言设计师以“程序员自由”为幌子将责怪推给程序员的全部可疑原因。
gets()
Buffer_overflow_attack