提问人:Vinit Dhatrak 提问时间:11/8/2009 最后编辑:S.S. AnneVinit Dhatrak 更新时间:9/2/2022 访问量:234784
为什么 gets 函数如此危险,以至于不应该使用它?
Why is the gets function so dangerous that it should not be used?
问:
当我尝试编译使用GCC函数的C代码时,我收到以下警告:gets()
(.text+0x34):警告:“gets”函数很危险,不应使用。
我记得这与堆栈保护和安全性有关,但我不确定确切的原因。
我怎样才能删除这个警告,为什么会有这样的关于使用的警告?gets()
如果如此危险,那我们为什么不能删除它呢?gets()
答:
为了安全使用,您必须确切地知道您将读取多少个字符,以便您可以使缓冲区足够大。只有当您确切地知道您将要读取哪些数据时,您才会知道这一点。gets
您希望使用 fgets
,而不是使用 ,它具有签名gets
char* fgets(char *string, int length, FILE * stream);
(fgets
,如果它读取了一整行,则会在字符串中留下 ;你必须处理这个问题。'\n'
gets
直到 1999 年 ISO C 标准,它仍然是该语言的官方部分,但在 2011 年标准中被正式删除。大多数 C 实现仍然支持它,但至少 gcc 会对使用它的任何代码发出警告。
评论
gets()
在不中断 API 的情况下,无法删除 API 函数。如果愿意,许多应用程序将不再编译或运行。
这就是一个参考文献给出的原因:
读取溢出的行 s 指向的数组结果 未定义的行为。fgets() 的使用 是推荐的。
因为在从 stdin 获取字节并将它们放在某个地方时不做任何类型的检查。一个简单的例子:gets
char array1[] = "12345";
char array2[] = "67890";
gets(array1);
现在,首先,您可以输入所需的字符数,不会在乎。其次,超过放置它们的数组大小的字节(在本例中)将覆盖它们在内存中找到的任何内容,因为会写入它们。在前面的示例中,这意味着如果您输入 maybe,不可预测,它也会覆盖其他内容。gets
array1
gets
"abcdefghijklmnopqrts"
array2
该函数不安全,因为它假定输入一致。永远不要使用它!
评论
gets
gets
gets_s
采用输入缓冲区的大小。不过我对这部分一无所知。gets
stdin
fgets
gets
gets
gets
strcat
我最近在 comp.lang.c
的 USENET 帖子中读到,它正在从标准中删除。呜呼gets()
你会很高兴地知道 委员会刚刚投票(一致通过,如 事实证明)从 草案也是如此。
评论
gcc -std=c2012 -pedantic ...
-std
要从 stdin 中读取:
char string[512];
fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
为什么很危险gets()
第一个互联网蠕虫(Morris Internet 蠕虫)在大约 30 年前(1988-11-02)逃脱,它使用缓冲区溢出作为其从一个系统传播到另一个系统的方法之一。基本问题是该函数不知道缓冲区有多大,因此它继续读取,直到找到换行符或遇到 EOF,并且可能会溢出给定缓冲区的边界。gets()
你应该忘记你曾经听说过它的存在。gets()
C11 标准 ISO/IEC 9899:2011 作为标准功能被取消,这是一件好事™(它在 ISO/IEC 9899:1999/Cor.3:2007 — C99 的技术勘误 3 中被正式标记为“过时”和“弃用”,然后在 C11 中删除)。可悲的是,由于向后兼容性的原因,它将在库中保留多年(即“几十年”)。如果由我来决定,那么gets()
gets()
char *gets(char *buffer)
{
assert(buffer != 0);
abort();
return 0;
}
鉴于您的代码迟早会崩溃,最好尽早解决麻烦。我准备添加一条错误消息:
fputs("obsolete and dangerous function gets() called\n", stderr);
现代版本的 Linux 编译系统会在链接时生成警告,并且对于其他一些也存在安全问题的函数(,...)。gets()
mktemp()
的替代品gets()
fgets()
正如其他人所说,规范的替代方案是 fgets()
指定为文件流。gets()
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;
}
此外,正如 caf 在评论中指出的那样,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()
还有TR 24731-1(C标准委员会的技术报告),它为各种功能提供了更安全的替代方案,包括:gets()
§6.5.4.1 函数
gets_s
###Synopsis
#define __STDC_WANT_LIB_EXT1__ 1 #include <stdio.h> char *gets_s(char *s, rsize_t n);
运行时约束
s
不应为 null 指针。 既不得等于零,也不得大于RSIZE_MAX。在从 读取字符时,应发生换行符、文件末尾或读取错误。25)n
n-1
stdin
3 如果存在运行时约束冲突,则设置为 null 字符,并且读取和丢弃字符,直到读取换行符,或者发生文件结束或读取错误。
s[0]
stdin
描述
4 该函数最多从 所指向的流中读取比 指定的字符数少 1 个字符数到 所指向的数组中。在换行符(被丢弃)或文件末尾之后不会读取其他字符。丢弃的换行符不计入读取的字符数。null 字符在读入数组的最后一个字符之后立即写入。
gets_s
n
stdin
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 将附录 K 中的TR24731作为库的可选部分。不幸的是,它很少在类 Unix 系统上实现。
getline()
— POSIX的
POSIX 2008 还提供了 getline()
的安全替代方案。它动态地为生产线分配空间,因此您最终需要释放它。因此,它消除了对行长度的限制。它还返回读取的数据的长度,或者(而不是!),这意味着可以可靠地处理输入中的空字节。还有一个“选择你自己的单字符分隔符”变体,称为;例如,如果要处理文件名末尾标有 ASCII NUL 字符的输出,这将非常有用。gets()
-1
EOF
getdelim()
find -print0
'\0'
评论
fgets()
fgets_wrapper()
getline()
及其相对 ,它们确实返回命令读取的“行”的长度,并根据需要分配空间以能够存储整行。即使这样也会导致问题,如果您最终得到一个大小为数 GB 的单行 JSON 文件;你能买得起所有这些内存吗?(当我们使用它时,我们是否可以拥有 和 变体,在末尾返回指向空字节的指针?等等)getdelim()
strcpy()
strcat()
fgets()
strlen()
gets()
不应使用,因为它无法阻止缓冲区溢出。如果用户输入的数据超出了缓冲区的容量,则很可能最终导致损坏或更糟。gets
事实上,ISO 实际上已经采取了从 C 标准中删除的步骤(从 C11 开始,尽管它在 C99 中被弃用),考虑到它们对向后兼容性的评价有多高,这应该表明该功能有多糟糕。gets
正确的做法是将函数与文件句柄一起使用,因为您可以限制从用户读取的字符。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()
<stdio.h>
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') {
是一个黑客漏洞,因为邪恶用户输入的第一个字符可能是嵌入的空字符渲染 UB。 如果用户输入空字符,则有问题。buff[strlen(buff)-1]
while (((ch = getchar())...
在 C11(ISO/IEC 9899:201x) 中,已删除。(在 ISO/IEC 9899:1999/Cor.3:2007(E) 中已弃用)gets()
除此之外,C11 还引入了一种新的安全替代方案:fgets()
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()
该函数也允许正确编写的程序安全地处理输入行 long 存储在 result 数组中。一般来说,这需要付费的呼叫者 注意结果数组中是否存在换行符。考虑 使用(以及基于换行符的任何所需处理)而不是 .
fgets
fgets
fgets
gets_s
评论
scanf("%s", arr)
gets()
我想向任何仍在他们的库中包含“以防万一仍然依赖它”的 C 库维护者发出诚挚的邀请: 请将您的实现替换为等效的gets
char *gets(char *str)
{
strcpy(str, "Never use gets!");
return str;
}
这将有助于确保没有人仍然依赖它。谢谢。
评论
fgets()
gets()
C gets函数是危险的,并且是一个非常昂贵的错误。Tony Hoare 在他的演讲“Null References: The Billion Dollar Mistake”中特别提到了这一点:
http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare
整整一个小时都值得一看,但对于他的评论,从 30 分钟开始,具体在 39 分钟左右受到批评。
希望这能激起你对整个演讲的兴趣,它引起了人们的注意,即我们如何需要更正式的语言正确性证明,以及语言设计者应该如何为他们的语言中的错误负责,而不是程序员。这似乎是不良语言设计者以“程序员自由”为幌子将责任推给程序员的全部可疑原因。
gets()
很危险,因为用户可能会因在提示中键入过多内容而使程序崩溃。它无法检测到可用内存的末尾,因此,如果分配的内存量太小,则可能会导致段故障和崩溃。有时,用户似乎不太可能在提示中输入 1000 个字母来输入一个人的名字,但作为程序员,我们需要使我们的程序防弹。(如果用户可能因发送过多数据而使系统程序崩溃,也可能存在安全风险)。
fgets()
允许您指定从标准输入缓冲区中取出多少个字符,以便它们不会超出变量。
评论
简而言之,(可能)是危险的,因为用户输入的内容可能大于变量有足够的空间来存储的内容。第一个答案是关于它以及为什么它更安全。gets()
fgets()
评论
附加信息:
在Linux Ubuntu上,你会看到(强调后加):man 3 gets
DESCRIPTION Never use this function.
而且,从这里的 cppreference.com wiki (https://en.cppreference.com/w/c/io/gets) 你会看到:Notes Never use gets().
笔记
该函数不执行边界检查,因此此函数极易受到缓冲区溢出攻击。它不能安全地使用(除非程序在限制可以出现的内容的环境中运行)。因此,该功能在 C99 标准的第三份勘误中已被弃用,并在 C11 标准中完全删除。 并且是推荐的替代品。
gets()
stdin
fgets()
gets_s()
永远不要使用
gets()。
如您所见,该函数已在 C11 或更高版本中弃用并完全删除。
这是我的演示用法,带有完整的错误检查:fgets()
来自 read_stdin_fgets_basic_input_from_user.c:
#include <errno.h> // `errno`
#include <stdio.h> // `printf()`, `fgets()`
#include <stdlib.h> // `exit()`
#include <string.h> // `strerror()`
// int main(int argc, char *argv[]) // alternative prototype
int main()
{
char buf[10];
// NEVER USE `gets()`! USE `fgets()` BELOW INSTEAD!
// USE THIS!: `fgets()`: "file get string", which reads until either EOF is
// reached, OR a newline (`\n`) is found, keeping the newline char in
// `buf`.
// For `feof()` and `ferror()`, see:
// 1. https://en.cppreference.com/w/c/io/feof
// 1. https://en.cppreference.com/w/c/io/ferror
printf("Enter up to %zu chars: ", sizeof(buf) - 1); // - 1 to save room
// for null terminator
char* retval = fgets(buf, sizeof(buf), stdin);
if (feof(stdin))
{
// Check for `EOF`, which means "End of File was reached".
// - This doesn't really make sense on `stdin` I think, but it is a good
// check to have when reading from a regular file with `fgets
// ()`. Keep it here regardless, just in case.
printf("EOF (End of File) reached.\n");
}
if (ferror(stdin))
{
printf("Error indicator set. IO error when reading from file "
"`stdin`.\n");
}
if (retval == NULL)
{
printf("ERROR in %s(): fgets() failed; errno = %i: %s\n",
__func__, errno, strerror(errno));
exit(EXIT_FAILURE);
}
size_t num_chars_written = strlen(buf) + 1; // + 1 for null terminator
if (num_chars_written >= sizeof(buf))
{
printf("Warning: user input may have been truncated! All %zu chars "
"were written into buffer.\n", num_chars_written);
}
printf("You entered \"%s\".\n", buf);
return 0;
}
示例运行和输出:
eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=c17 read_stdin_fgets_basic_input_from_user.c -o bin/a && bin/a
Enter up to 9 chars: hello world!
Warning: user input may have been truncated! All 10 chars were written into buffer.
You entered "hello wor".
eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=c17 read_stdin_fgets_basic_input_from_user.c -o bin/a && bin/a
Enter up to 9 chars: hey
You entered "hey
".
评论
gets()
Buffer_overflow_attackgets()
scanf("%s", b)
gets
gets()