为什么 gets 函数如此危险以至于不应该使用它?

发布于 2024-10-04 21:56:28 字数 271 浏览 1 评论 0 原文

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

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

我记得这与堆栈保护和安全性有关,但我不确定具体原因。

如何删除此警告以及为什么使用 gets() 会出现这样的警告?

如果 gets() 如此危险,那么为什么我们不能删除它呢?

When I try to compile C code that uses the gets() function with GCC, I get this warning:

(.text+0x34): warning: the `gets' function is dangerous and should not be used.

I remember this has something to do with stack protection and security, but I'm not sure exactly why.

How can I remove this warning and why is there such a warning about using gets()?

If gets() is so dangerous then why can't we remove it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

恏ㄋ傷疤忘ㄋ疼 2024-10-11 21:56:28

为什么 gets() 很危险

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

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

C11 标准 ISO/IEC 9899:2011 取消了 gets() 作为标准函数,这是一件好事™(它在 ISO/IEC 9899 中被正式标记为“过时”和“已弃用”: 1999/Cor.3:2007 — C99 的技术勘误 3,然后在 C11 中删除)。遗憾的是,出于向后兼容性的原因,它将在库中保留很多年(意思是“几十年”)。如果由我决定,gets() 的实现将变为:

char *gets(char *buffer)
{
    assert(buffer != 0);
    abort();
    return 0;
}

鉴于您的代码迟早会崩溃,最好尽早解决问题。我准备添加一条错误消息:

fputs("obsolete and dangerous function gets() called\n", stderr);

如果链接 gets(),现代版本的 Linux 编译系统会生成警告 - 并且对于其他一些也存在安全问题的函数 (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() 不会出现这个问题,因为它不知道缓冲区在哪里结束,并且会愉快地践踏缓冲区的末尾,对您精心维护的内存布局造成严重破坏,经常弄乱返回堆栈( a Stack Overflow) 如果缓冲区是在堆栈上分配的,或者如果缓冲区是动态分配的,则践踏控制信息,或者通过其他方式复制数据如果缓冲区是静态分配的,则珍贵的全局(或模块)变量。这些都不是一个好主意——它们集中体现了“未定义行为”这个短语。


还有 TR 24731-1 (来自 C 标准委员会的技术报告),它为各种函数提供了更安全的替代方案,包括 gets()

§6.5.4.1 gets_s 函数

###剧情简介

<前><代码>#define __STDC_WANT_LIB_EXT1__ 1
#include ;
char *gets_s(char *s, rsize_t n);

运行时约束

s 不得为空指针。 n 既不能等于 0,也不能大于 RSIZE_MAX。从 stdin 读取 n-1 个字符时应发生换行符、文件结束符或读取错误。25)< /p>

3 如果存在运行时约束违规,则将 s[0] 设置为空字符,并从 stdin 读取并丢弃字符,直到出现新的-读取行字符、文件结束或读取错误。

描述

4 gets_s 函数从 stdin 指向的流中最多读取比 n 指定的字符数少 1 个的字符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() — POSIX

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

Why is gets() dangerous

The first internet worm (the Morris Internet Worm) escaped about 30 years ago (1988-11-02), and it used gets() and a buffer overflow as one of its methods of propagating from system to system. The basic problem is that the function doesn't know how big the buffer is, so it continues reading until it finds a newline or encounters EOF, and may overflow the bounds of the buffer it was given.

You should forget you ever heard that gets() existed.

The C11 standard ISO/IEC 9899:2011 eliminated gets() as a standard function, which is A Good Thing™ (it was formally marked as 'obsolescent' and 'deprecated' in ISO/IEC 9899:1999/Cor.3:2007 — Technical Corrigendum 3 for C99, and then removed in C11). Sadly, it will remain in libraries for many years (meaning 'decades') for reasons of backwards compatibility. If it were up to me, the implementation of gets() would become:

char *gets(char *buffer)
{
    assert(buffer != 0);
    abort();
    return 0;
}

Given that your code will crash anyway, sooner or later, it is better to head the trouble off sooner rather than later. I'd be prepared to add an error message:

fputs("obsolete and dangerous function gets() called\n", stderr);

Modern versions of the Linux compilation system generates warnings if you link gets() — and also for some other functions that also have security problems (mktemp(), …).

Alternatives to gets()

fgets()

As everyone else said, the canonical alternative to gets() is fgets() specifying stdin as the file stream.

char buffer[BUFSIZ];

while (fgets(buffer, sizeof(buffer), stdin) != 0)
{
    ...process line of data...
}

What no-one else yet mentioned is that gets() does not include the newline but fgets() does. So, you might need to use a wrapper around fgets() that deletes the newline:

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;
}

Or, better:

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;
}

Also, as caf points out in a comment and paxdiablo shows in their answer, with fgets() you might have data left over on a line. My wrapper code leaves that data to be read next time; you can readily modify it to gobble the rest of the line of data if you prefer:

        if (len > 0 && buffer[len-1] == '\n')
            buffer[len-1] = '\0';
        else
        {
             int ch;
             while ((ch = getc(fp)) != EOF && ch != '\n')
                 ;
        }

The residual problem is how to report the three different result states — EOF or error, line read and not truncated, and partial line read but data was truncated.

This problem doesn't arise with gets() because it doesn't know where your buffer ends and merrily tramples beyond the end, wreaking havoc on your beautifully tended memory layout, often messing up the return stack (a Stack Overflow) if the buffer is allocated on the stack, or trampling over the control information if the buffer is dynamically allocated, or copying data over other precious global (or module) variables if the buffer is statically allocated. None of these is a good idea — they epitomize the phrase 'undefined behaviour`.


There is also the TR 24731-1 (Technical Report from the C Standard Committee) which provides safer alternatives to a variety of functions, including gets():

§6.5.4.1 The gets_s function

###Synopsis

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

Runtime-constraints

s shall not be a null pointer. n shall neither be equal to zero nor be greater than RSIZE_MAX. A new-line character, end-of-file, or read error shall occur within reading n-1 characters from stdin.25)

3 If there is a runtime-constraint violation, s[0] is set to the null character, and characters are read and discarded from stdin until a new-line character is read, or end-of-file or a read error occurs.

Description

4 The gets_s function reads at most one less than the number of characters specified by n from the stream pointed to by stdin, into the array pointed to by s. No additional characters are read after a new-line character (which is discarded) or after end-of-file. The discarded new-line character does not count towards number of characters read. A null character is written immediately after the last character read into the array.

5 If end-of-file is encountered and no characters have been read into the array, or if a read error occurs during the operation, then s[0] is set to the null character, and the other elements of s take unspecified values.

Recommended practice

6 The fgets function allows properly-written programs to safely process input lines too long to store in the result array. In general this requires that callers of fgets pay attention to the presence or absence of a new-line character in the result array. Consider using fgets (along with any needed processing based on new-line characters) instead of gets_s.

25) The gets_s function, unlike gets, makes it a runtime-constraint violation for a line of input to overflow the buffer to store it. Unlike fgets, gets_s maintains a one-to-one relationship between input lines and successful calls to gets_s. Programs that use gets expect such a relationship.

The Microsoft Visual Studio compilers implement an approximation to the TR 24731-1 standard, but there are differences between the signatures implemented by Microsoft and those in the TR.

The C11 standard, ISO/IEC 9899-2011, includes TR24731 in Annex K as an optional part of the library. Unfortunately, it is seldom implemented on Unix-like systems.


getline() — POSIX

POSIX 2008 also provides a safe alternative to gets() called getline(). It allocates space for the line dynamically, so you end up needing to free it. It removes the limitation on line length, therefore. It also returns the length of the data that was read, or -1 (and not EOF!), which means that null bytes in the input can be handled reliably. There is also a 'choose your own single-character delimiter' variation called getdelim(); this can be useful if you are dealing with the output from find -print0 where the ends of the file names are marked with an ASCII NUL '\0' character, for example.

迎风吟唱 2024-10-11 21:56:28

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

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

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

(fgets,如果它读取整行,将留下'\n' 在字符串中;你必须处理这个问题。)

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

In order to use gets safely, you have to know exactly how many characters you will be reading, so that you can make your buffer large enough. You will only know that if you know exactly what data you will be reading.

Instead of using gets, you want to use fgets, which has the signature

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

(fgets, if it reads an entire line, will leave the '\n' in the string; you'll have to deal with that.)

gets remained an official part of the language up to the 1999 ISO C standard, but it was officially removed in the 2011 standard. Most C implementations still support it, but at least gcc issues a warning for any code that uses it.

无名指的心愿 2024-10-11 21:56:28

因为 gets 在从 stdin 获取字节并将它们放在某处时不会进行任何类型的检查。一个简单的例子:

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

gets(array1);

现在,首先允许你输入你想要的字符数,gets不会关心它。其次,超过放置它们的数组大小的字节(在本例中是array1)将覆盖它们在内存中找到的任何内容,因为gets将写入它们。在前面的示例中,这意味着如果您输入 "abcdefghijklmnopqrts" ,可能会出乎意料地覆盖 array2 或其他内容。

该函数不安全,因为它假设输入一致。 切勿使用它!

Because gets doesn't do any kind of check while getting bytes from stdin and putting them somewhere. A simple example:

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

gets(array1);

Now, first of all you are allowed to input how many characters you want, gets won't care about it. Secondly the bytes over the size of the array in which you put them (in this case array1) will overwrite whatever they find in memory because gets will write them. In the previous example this means that if you input "abcdefghijklmnopqrts" maybe, unpredictably, it will overwrite also array2 or whatever.

The function is unsafe because it assumes consistent input. NEVER USE IT!

薆情海 2024-10-11 21:56:28

您不应该使用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 相同的保护,因为它可以防止缓冲区溢出,但它也通知调用者发生了什么并清除多余的字符,以便它们不会影响您的下一个输入操作。

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

You should not use gets since it has no way to stop a buffer overflow. If the user types in more data than can fit in your buffer, you will most likely end up with corruption or worse.

In fact, ISO have actually taken the step of removing gets from the C standard (as of C11, though it was deprecated in C99) which, given how highly they rate backward compatibility, should be an indication of how bad that function was.

The correct thing to do is to use the fgets function with the stdin file handle since you can limit the characters read from the user.

But this also has its problems such as:

  • extra characters entered by the user will be picked up the next time around.
  • there's no quick notification that the user entered too much data.

To that end, almost every C coder at some point in their career will write a more useful wrapper around fgets as well. Here's mine:

#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;
}

with some test code:

// 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;
}

It provides the same protections as fgets in that it prevents buffer overflows but it also notifies the caller as to what happened and clears out the excess characters so that they do not affect your next input operation.

Feel free to use it as you wish, I hereby release it under the "do what you damn well want to" licence :-)

隱形的亼 2024-10-11 21:56:28

fgets

从标准输入读取:

char string[512];

fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */

fgets.

To read from the stdin:

char string[512];

fgets(string, sizeof(string), stdin); /* no buffer overflows here, you're safe! */
农村范ル 2024-10-11 21:56:28

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

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

读取溢出的行
s 指向的数组结果为
未定义的行为。 fgets()的使用
推荐。

You can't remove API functions without breaking the API. If you would, many applications would no longer compile or run at all.

This is the reason that one reference gives:

Reading a line that overflows the
array pointed to by s results in
undefined behavior. The use of fgets()
is recommended.

下雨或天晴 2024-10-11 21:56:28

我最近在 USENET 帖子中读到 comp.lang.c gets() 正在从标准中删除。 哇哦

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

I read recently, in a USENET post to comp.lang.c, that gets() is getting removed from the Standard. WOOHOO

You'll be happy to know that the
committee just voted (unanimously, as
it turns out) to remove gets() from
the draft as well.

渡你暖光 2024-10-11 21:56:28

在 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 ;
char *gets_s(char *s, rsize_t n);

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

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

In C11(ISO/IEC 9899:201x), gets() has been removed. (It's deprecated in ISO/IEC 9899:1999/Cor.3:2007(E))

In addition to fgets(), C11 introduces a new safe alternative gets_s():

C11 K.3.5.4.1 The gets_s function

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

However, in the Recommended practice section, fgets() is still preferred.

The fgets function allows properly-written programs to safely process input lines too
long to store in the result array. In general this requires that callers of fgets pay
attention to the presence or absence of a new-line character in the result array. Consider
using fgets (along with any needed processing based on new-line characters) instead of
gets_s.

一直在等你来 2024-10-11 21:56:28

gets() 很危险,因为用户可能会因在提示中输入过多内容而导致程序崩溃。它无法检测可用内存的末尾,因此如果您分配的内存量对于该目的而言太小,则可能会导致段错误和崩溃。有时,用户似乎不太可能在代表某人姓名的提示中输入 1000 个字母,但作为程序员,我们需要使我们的程序防弹。 (如果用户发送过多数据可能导致系统程序崩溃,也可能存在安全风险)。

fgets() 允许您指定从标准输入缓冲区中取出多少个字符,这样它们就不会溢出变量。

gets() is dangerous because it is possible for the user to crash the program by typing too much into the prompt. It can't detect the end of available memory, so if you allocate an amount of memory too small for the purpose, it can cause a seg fault and crash. Sometimes it seems very unlikely that a user will type 1000 letters into a prompt meant for a person's name, but as programmers, we need to make our programs bulletproof. (it may also be a security risk if a user can crash a system program by sending too much data).

fgets() allows you to specify how many characters are taken out of the standard input buffer, so they don't overrun the variable.

怪我入戏太深 2024-10-11 21:56:28

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

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

整个小时都值得观看,但对于他来说评论从 30 分钟开始,具体内容在 39 分钟左右受到批评。

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

The C gets function is dangerous and has been a very costly mistake. Tony Hoare singles it out for specific mention in his talk "Null References: The Billion Dollar Mistake":

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

The whole hour is worth watching but for his comments view from 30 minutes on with the specific gets criticism around 39 minutes.

Hopefully this whets your appetite for the whole talk, which draws attention to how we need more formal correctness proofs in languages and how language designers should be blamed for the mistakes in their languages, not the programmer. This seems to have been the whole dubious reason for designers of bad languages to push the blame to programmers in the guise of 'programmer freedom'.

迷离° 2024-10-11 21:56:28

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

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

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

I would like to extend an earnest invitation to any C library maintainers out there who are still including gets in their libraries "just in case anyone is still depending on it": Please replace your implementation with the equivalent of

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

This will help make sure nobody is still depending on it. Thank you.

榕城若虚 2024-10-11 21:56:28

附加信息:

从 Linux Ubuntu 上的 man 3 gets 中,您将看到(添加了重点):

<前><代码>描述
切勿使用此功能。

并且,来自此处的 cppreference.com wiki (https://en.cppreference.com/ w/c/io/gets)你会看到:注释永远不要使用 gets()。

注释

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

切勿使用gets()

如您所见,该函数已被弃用并在 C11 或更高版本中完全删除。

使用 fgets()gets_s() 代替。

这是我对 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
".

Additional info:

From man 3 gets on Linux Ubuntu you'll see (emphasis added):

DESCRIPTION
       Never use this function.

And, from the cppreference.com wiki here (https://en.cppreference.com/w/c/io/gets) you'll see: Notes Never use gets().:

Notes

The gets() function does not perform bounds checking, therefore this function is extremely vulnerable to buffer-overflow attacks. It cannot be used safely (unless the program runs in an environment which restricts what can appear on stdin). For this reason, the function has been deprecated in the third corrigendum to the C99 standard and removed altogether in the C11 standard. fgets() and gets_s() are the recommended replacements.

Never use gets().

As you can see, the function has been deprecated and removed entirely in C11 or later.

Use fgets() or gets_s() instead.

Here is my demo usage of fgets(), with full error checking:

From 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;
}

Sample runs and output:

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
".
风吹雨成花 2024-10-11 21:56:28

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

In a few words gets() (can) be dangerous because the user might input something bigger than what the variable has enough space to store. First answer says about fgets() and why it is safer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文