278

当我尝试gets()通过 GCC 编译使用该函数的 C 代码时,我收到以下警告:

(.text+0x34): 警告:“gets”功能很危险,不应使用。

我记得这与堆栈保护和安全性有关,但我不确定为什么。

我怎样才能删除这个警告,为什么会有这样一个关于 using 的警告gets()

gets()既然如此危险,为什么我们不能删除它?

4

13 回答 13

216

为了gets安全使用,您必须确切知道要读取多少个字符,以便您可以使缓冲区足够大。只有当您确切知道要读取的数据时,您才会知道这一点。

而不是使用gets,你想使用fgets,它有签名

char* fgets(char *string, int length, FILE * stream);

( fgets,如果它读取整行,则会将 留'\n'在字符串中;您必须处理它。)

gets直到 1999 年 ISO C 标准仍然是该语言的官方部分,但在2011 年标准中被正式删除。大多数 C 实现仍然支持它,但至少 gcc 会对使用它的任何代码发出警告。

于 2009-11-07T18:56:46.980 回答
208

为什么gets()危险

第一个互联网蠕虫(Morris Internet Worm)大约在 30 年前(1988-11-02)逃脱,它使用gets()缓冲区溢出作为其在系统之间传播的方法之一。基本问题是该函数不知道缓冲区有多大,因此它会继续读取,直到找到换行符或遇到 EOF,并且可能会溢出给定缓冲区的边界。

你应该忘记你曾经听说过它的gets()存在。

C11 标准 ISO/IEC 9899:2011gets()作为标准功能被淘汰,这是 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()

fgets()

正如其他人所说,规范的替代方法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;
}

此外,正如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.1gets_s功能

概要

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);

运行时约束

s不得为空指针。n既不能等于 0,也不能大于 RSIZE_MAX。在 n-1stdin. 25)

3 如果存在运行时约束违规,s[0]则设置为空字符,并且从读取和丢弃字符stdin直到读取换行符、文件结束或发生读取错误。

描述

4函数 从 指向的流gets_s中最多读取比 指定的字符数少 1 的字符到 指向的数组中。在换行符(被丢弃)或文件结尾之后不会读取其他字符。丢弃的换行符不计入读取的字符数。在读入数组的最后一个字符之后立即写入一个空字符。nstdins

5 如果遇到文件结尾并且没有字符被读入数组,或者在操作过程中发生读取错误,则s[0]设置为空字符,其他元素s取未指定值。

推荐做法

6 该fgets函数允许正确编写的程序安全地处理输入行太长而无法存储在结果数组中。一般来说,这需要调用者fgets注意结果数组中是否存在换行符。考虑使用fgets(以及基于换行符的任何需要的处理)而不是 gets_s.

25)与 不同的是,该gets_s函数gets使一行输入溢出缓冲区以存储它,从而违反运行时约束。与 不同fgetsgets_s在输入行和对 的成功调用之间保持一对一的关系gets_s。使用的程序gets期望这种关系。

Microsoft Visual Studio 编译器实现了对 TR 24731-1 标准的近似,但 Microsoft 实现的签名与 TR 中的签名之间存在差异。

C11 标准 ISO/IEC 9899-2011 在附件 K 中包含 TR24731 作为库的可选部分。不幸的是,它很少在类 Unix 系统上实现。


getline()— POSIX

POSIX 2008 还提供了一个安全的替代方法来gets()调用getline(). 它动态地为该行分配空间,因此您最终需要释放它。因此,它消除了对线路长度的限制。它还返回读取的数据的长度,或者-1(而不是EOF!),这意味着可以可靠地处理输入中的空字节。还有一个“选择您自己的单字符分隔符”变体,称为getdelim(); 例如,如果您正在处理find -print0文件名末尾标有 ASCII NUL'\0'字符的输出,这将很有用。

于 2010-11-30T01:51:46.033 回答
24

因为在从标准输入获取字节并将它们放在某个地方gets时不做任何检查。一个简单的例子:

char array1[] = "12345";
char array2[] = "67890";

gets(array1);

现在,首先你可以输入你想要多少个字符,gets不用管它。其次,超过您放置它们的数组大小的字节(在这种情况下array1)将覆盖它们在内存中找到的任何内容,因为gets它们会写入它们。在前面的示例中,这意味着如果您输入"abcdefghijklmnopqrts"可能,不可预测地,它也会覆盖array2或其他任何内容。

该函数是不安全的,因为它假定输入一致。永远不要使用它!

于 2009-11-07T19:03:26.013 回答
17

您不应该使用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但它还通知调用者发生了什么并清除多余的字符,这样它们就不会影响您的下一个输入操作。

随意使用它,我特此在“做你想做的事”许可下发布它:-)

于 2010-11-30T01:56:28.430 回答
14

得到

从标准输入读取:

char string[512];

fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
于 2010-11-30T01:28:13.927 回答
11

您无法在不破坏 API 的情况下删除 API 函数。如果您愿意,许多应用程序将根本无法编译或运行。

这就是一个参考给出的原因:

读取溢出 s 指向的数组的行会导致未定义的行为。建议使用 fgets()。

于 2009-11-07T18:58:21.843 回答
5

我最近在USENET 帖子中comp.lang.c读到,该帖子gets()已从标准中删除。呜呼

您会很高兴知道委员会刚刚投票(一致,事实证明)也将 gets() 从草案中删除。

于 2009-11-07T19:21:20.033 回答
5

在 C11(ISO/IEC 9899:201x) 中,gets()已被删除。(在 ISO/IEC 9899:1999/Cor.3:2007(E) 中已弃用)

此外fgets(),C11 还引入了一种新的安全替代方案gets_s()

C11 K.3.5.4.1gets_s功能

#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);

但是,在推荐实践部分,fgets()仍然是首选。

fgets函数允许正确编写的程序安全地处理输入行太长而无法存储在结果数组中。一般来说,这需要调用者fgets注意结果数组中是否存在换行符。考虑使用fgets(以及基于换行符的任何需要的处理)而不是 gets_s.

于 2013-10-06T06:15:49.650 回答
5

gets()是危险的,因为用户在提示中输入过多可能会使程序崩溃。它无法检测到可用内存的结束,因此如果您分配的内存量太小,可能会导致段错误和崩溃。有时用户似乎不太可能在提示人名的提示符中输入 1000 个字母,但作为程序员,我们需要让我们的程序防弹。(如果用户发送过多数据可能导致系统程序崩溃,也可能存在安全风险)。

fgets()允许您指定从标准输入缓冲区中取出多少字符,因此它们不会超出变量。

于 2017-08-22T09:19:46.773 回答
3

C 的 get 函数很危险,而且是一个代价高昂的错误。Tony Hoare 在他的演讲“空引用:十亿美元的错误”中特别提到了这一点:

http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

整个小时都值得一看,但他的评论从 30 分钟开始,具体在 39 分钟左右受到批评。

希望这能激起你对整个演讲的兴趣,这会引起人们对我们如何需要语言中更正式的正确性证明的关注,以及如何将语言设计者而不是程序员归咎于他们的语言中的错误。这似乎是糟糕语言的设计者以“程序员自由”为幌子将责任推给程序员的全部可疑原因。

于 2016-05-01T01:00:46.270 回答
1

我想向仍然包含gets在他们的库中的任何 C 库维护者发出诚挚的邀请,“以防万一有人仍然依赖它”:请将您的实现替换为等效的

char *gets(char *str)
{
    strcpy(str, "Never use gets!");
    return str;
}

这将有助于确保没有人仍然依赖它。谢谢你。

于 2016-03-31T21:52:25.650 回答
0

简而言之gets(),(可能)很危险,因为用户可能输入的内容大于变量有足够空间存储的内容。第一个答案说明了fgets()为什么它更安全。

于 2022-02-28T15:56:01.673 回答
0

附加信息:

man 3 getsLinux Ubuntu 上,您将看到(添加了重点):

DESCRIPTION
       Never use this function.

而且,从这里的 cppreference.com wiki ( https://en.cppreference.com/w/c/io/gets ) 你会看到Notes Never use gets().

笔记

gets()函数不执行边界检查,因此该函数极易受到缓冲区溢出攻击。它不能安全使用(除非程序在限制可以出现的内容的环境中运行stdin)。出于这个原因,该功能已在 C99 标准的第三次勘误中被弃用,并在 C11 标准中完全删除。fgets()并且gets_s()是推荐的替代品。

永远不要使用gets().

如您所见,该功能已在 C11 或更高版本中完全弃用和删除。

使用fgets()gets_s()代替。

于 2022-03-05T23:13:45.793 回答