【问题标题】:if one complains about gets(), why not do the same with scanf("%s",...)?如果有人抱怨gets(),为什么不对scanf("%s",...) 做同样的事情呢?
【发布时间】:2011-02-27 23:38:48
【问题描述】:

来自man gets

永远不要使用gets()。因为它是 不知道是不可能的 数据提前多少 字符 gets() 将读取,并且 因为gets() 会继续存储 超出缓冲区末尾的字符, 使用起来非常危险。 它已被用来破坏计算机 安全。请改用 fgets()。

我几乎在所有地方都看到scanf 以应该有同样问题的方式使用 (buffer overflow/buffer overrun):scanf("%s",string)。这种情况下存在这个问题吗?为什么scanf 手册页中没有关于它的引用?为什么使用-Wall 编译时 gcc 没有警告?

ps:我知道有一种方法可以在格式字符串中用scanf指定字符串的最大长度:

char str[10];
scanf("%9s",str);

编辑:我不是要确定前面的代码是否正确。我的问题是:如果scanf("%s",string) 总是错误的,为什么没有警告并且手册页中没有任何相关内容?

【问题讨论】:

  • 您的维基百科链接显示scanf 不安全
  • @aviraldg 你说得对,我以前读过它,但我没有找到写标题的好方法。我编辑了它。
  • 大家都回答:scanf("%s",...) 不安全。关于此的另一个参考:c-faq.com/stdio/scanfprobs.html。我仍然不明白手册页中没有关于此的内容。

标签: c scanf buffer-overflow gets buffer-overrun


【解决方案1】:

答案很简单,没有人在 GCC 中编写代码来产生该警告。

正如您所指出的,针对"%s"(没有字段宽度)的特定情况发出警告是非常合适的。

但是,请记住,这仅适用于 scanf()vscanf()fscanf()vfscanf() 的情况。此格式说明符与sscanf()vsscanf() 一起使用是完全安全的,因此在这种情况下不应发出警告。这意味着您不能简单地将其添加到现有的“scanf-style-format-string”分析代码中;您必须将其拆分为“fscanf-style-format-string”和“sscanf-style-format-string”选项。

我敢肯定,如果您为最新版本的 GCC 制作补丁,它很有可能被接受(当然,您还需要为 glibc 头文件提交补丁)。

【讨论】:

  • 为什么你说sscanf()vsscanf() 可以完全安全?是不是因为您可以检查 scanf 正在读取的原始字符串(第一个参数)并确保它适合? (例如,如果两个尺寸相同,如果您寻找空间位置等)
  • 是的,完全正确。 可能安全地使用它,因为您控制输入。
【解决方案2】:

使用gets() 永远不会安全。正如您在问题中所说,scanf() 可以安全使用。但是,确定您是否安全地使用它对于编译器来说是一个更难解决的问题(例如,如果您在一个函数中调用 scanf() 并传入缓冲区并将字符数作为参数,它不会无法分辨);在这种情况下,它必须假设您知道自己在做什么。

【讨论】:

  • 是的,你是对的,但是当参数的数量或类型不正确时,gcc 会警告你。在这些情况下,它并不假定您知道自己在做什么。
  • @dbarbosa:编译器可以使用所有信息来检查可选参数的数量是否与格式字符串中的格式说明符的数量相同。同样,它也有足够的信息来检查格式字符串中是否有%d(例如)对应的参数是一个整数。
  • 它不能知道指定的尺寸是否正确,但是当它没有任何尺寸规格时它可以知道它只是错误的。 scanf("%5s",string) 的正确或错误取决于string 的大小,它不能像你说的那样说出来。但是,scanf("%s",string) 总是因为缓冲区溢出问题而出错。
  • @dbarbosa:您只使用“%s”作为格式说明符是正确的,但我无法找到 gcc 选项的任何组合(我使用的是 4.4.4)这将发出警告。
【解决方案3】:

当编译器查看scanf 的格式化字符串时,它看到的是一个字符串!这是假设在运行时没有输入格式化字符串。如果在编译时输入,一些编译器(如 GCC)有一些额外的功能来分析格式化字符串。这种额外的功能并不全面,因为在某些情况下需要运行时开销,这对于像 C 这样的语言来说是不可能的。例如,在这种情况下,您能否在不插入一些额外隐藏代码的情况下检测到不安全的用法:

char* str;
size_t size;
scanf("%z", &size);
str = malloc(size);
scanf("%9s"); // how can the compiler determine if this is a safe call?!

当然,如果您指定要读取的字符数,并且有足够的内存来保存字符串,则可以使用scanf 编写安全代码。在gets的情况下,没有办法指定要读取的字符数。

【讨论】:

  • 很难确定这是否是一个安全的呼叫,但很容易看到scanf("%s", str) 并警告用户,甚至比检查它已经对号码和传递给scanf 的参数类型。 (顺便说一句,gcc 会为您的代码显示warning: too few arguments for format,因为格式中的%9s 没有参数)。
【解决方案4】:

我不确定为什么 scanf 的手册页没有提到缓冲区溢出的可能性,但 vanilla scanf 不是一个安全的选项。一个相当过时的链接 - http://blogs.msdn.com/b/rsamona/archive/2005/10/24/484449.aspx 显示了这种情况。另外,检查这个(不是 gcc,但信息丰富) - http://blogs.msdn.com/b/parthas/archive/2006/12/06/application-crash-on-replacing-sscanf-with-sscanf-s.aspx

【讨论】:

    【解决方案5】:

    可能很简单,scanf会根据读取的数据量在堆上分配空间。由于它不会分配缓冲区然后读取直到读取到空字符,所以它不会冒覆盖缓冲区的风险.相反,它会读入自己的缓冲区,直到找到空字符为止,并且可能会在读取结束时将该缓冲区复制到另一个正确大小的缓冲区中。

    【讨论】:

    • 不; scanf 不在堆上分配空间。提供缓冲区是程序员的工作。 dbbarbosa 所说的使用 Scanf 是不安全的。
    • 看看这个教程,它更详细、更准确地解释了我刚才所说的。 crasseux.com/books/ctutorial/String-overflows-with-scanf.html
    • scanf 不分配自己的存储空间。您链接的 a 标志是 GNU 扩展,不是 C 标准的一部分。
    • 您链接到的教程说 scanf() 分配自己的内存的标志是 Gnu 特定的扩展。在标准 C 中,scanf() 不分配存储空间。
    • 应该注意的是,这个 GNU 扩展不仅在很大程度上是无用的(因为您的代码将是不可移植的,并且无法在应用程序级别将“a”功能添加到 scanf缺少它的系统),但它与 C99 功能直接冲突 - %a 表示十六进制浮点数,如 printf,并且是使用 scanf 读取浮点数的替代说明符。
    猜你喜欢
    • 1970-01-01
    • 2014-06-16
    • 1970-01-01
    • 2021-07-08
    • 2022-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多