【问题标题】:scanf %d segfault at large input大输入时 scanf %d 段错误
【发布时间】:2011-07-02 02:25:36
【问题描述】:

所以我在一些 c 代码上运行了一些静态代码分析器,让我感到惊讶的是一个警告:

int val;
scanf("%d", &val);

这表示对于足够大的输入,这可能会导致段错误。毫无疑问,这确实会发生。现在修复很简单(指定一些宽度;毕竟我们知道一个有效整数最多可以有多少个位置,具体取决于架构)但我想知道的是为什么会发生这种情况以及为什么会这样'不被视为 libc 中的一个错误(并且是一个简单的修复)?

现在我假设我首先错过了这种行为的某些原因?

编辑:好的,因为这个问题似乎不是那么明确,多一点解释: 不,代码分析器一般不会对 scanf 发出警告,但会在 scanf 读取没有特定宽度的数字时发出警告。

所以这是一个最小的工作示例:

#include <stdlib.h>
#include <stdio.h>

int main() {
    int val;
    scanf("%d", &val);
    printf("Number not large enough.\n");
    return 0;
}

我们可以通过发送一个巨大的数字来获得一个段错误(使用例如 Python):

import subprocess
cmd = "./test"
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, shell=True)
p.communicate("9"*50000000000000)
# program will segfault, if not make number larger

【问题讨论】:

  • 你的静态代码分析器是窥视scanf格式字符串还是只是盲目地抱怨scanf
  • 你到底用的是哪个程序?

标签: c scanf


【解决方案1】:

如果静态分析器是 cppcheck,那么它会发出警告,因为 glibc 中的一个 bug 已被修复:http://sources.redhat.com/bugzilla/show_bug.cgi?id=13138

【讨论】:

    【解决方案2】:

    已编辑,因为我错过了您用它提供静态代码分析器的事实

    如果%d 的格式与int 的大小匹配,则溢出的不应是通过指针写入val 的内容,因为它应该始终是int。尝试将指针传递给long int 并查看分析器是否仍然给出警告。尝试将%d改成%ld,保持long int指针不变,看看是否再次给出警告。

    我想标准应该说明%d,它需要的类型。也许分析器担心在某些系统上int 可能比%d 的含义更短?对我来说这听起来很奇怪。


    运行用 gcc 编译的示例(我有 python 2.6.6)我获得

    Traceback (most recent call last):
      File "./feed.py", line 4, in <module>
        p.communicate("9"*50000000000000)
    OverflowError: cannot fit 'long' into an index-sized integer
    Number not large enough.
    

    然后我尝试运行它:

    perl -e 'print "1"x6000000000000000;' |./test
    

    并修改了C部分写

    printf("%d Number not large enough.\n", val);
    

    我作为输出获得

    5513204 Number not large enough.
    

    每次运行时数字都会改变...永远不会出现段错误... GNU scanf 实现是安全的...虽然结果数字是错误的...

    【讨论】:

    • 虽然可能是这种情况(尽管 afaik 标准将 %d 定义为整数大小),但分析器会针对不同的问题发出警告。我希望添加的示例使其更清楚。
    • 在我看来,它通常在抱怨 scanf,作为一个本质上“危险”的功能(因为它会是 gets)(但问题取决于实现)。正如另一条评论中所说,scanf 不应该尝试用输入填充固定大小的内部缓冲区,而不检查它是否会溢出!相反,它应该停止尝试转换大于 INT_MAX 的数字并存储 INT_MAX(并使用其余的输入直到非数字),或者使用 EOVERFLOW 错误(但这是 POSIX.1)或者可能是 ERANGE(C99)或其他什么,然后退出。
    • 有趣的是,我使用 glibc 2.9 在一些较旧的(我担心很古老;不是我的选择)系统上运行该程序。刚刚在我通常的开发环境和 cygwin 中尝试过,它们给出的错误与你的相同。所以看起来这确实是 glibc 中的一个错误,但在较新的版本上已修复。让我有权不仔细检查最新版本。令人惊讶的是这样的错误存在这么长时间,但至少在任何现代系统上我都不必担心它。
    • afaik 当前 glibc v 是 2.14 (gnu.org/s/libc/#CurrentStatus) 也许你的意思是 gcc 版本(最后应该是 4.3.6),我想 gcc 2.9 “船”与一个非常旧的 glibc ... :)
    • 不,我的意思是 glibc 2.9 和 2.09 一样——有趣的是,他们确实称它为 2.9 而不是 2.09,我同意这有点违反直觉;)
    【解决方案3】:

    处理整数的第一步是隔离数字序列。如果该序列比预期的长,它可能会溢出固定长度的缓冲区,从而导致分段错误。

    你可以用双打来达到类似的效果。推到极端,您可以写 1 后跟一千个零,以及 -1000 的指数(净值为 1)。实际上,当我几年前测试这个时,Solaris 处理了 1000 位数字。它在 1024 多一点时遇到了麻烦。

    因此,有一个 QoI 元素 - 实施质量。还有一个元素是“遵循 C 标准,scanf() 在遇到非数字之前不能停止阅读”。这些是相互矛盾的目标。

    【讨论】:

    • 因为它试图将所有输入读入一个大小有限的缓冲区并且不检查缓冲区溢出(也许更快)?并且不应该这样实现,因为格式是 %d,一旦达到会溢出格式的值,它就会停止填充缓冲区,尽管它继续消耗数字只是为了符合标准?
    • IIUC, scanf 可以返回任何它想要的超出范围的输入。 Glibc 实际上返回 INT_MAX。因此,您可以只为更大的范围内输入提供一个足够大的缓冲区,然后丢弃后面的每个数字。
    • 是的,据我所知 ninjalj 是正确的 - C 中未定义溢出行为,因此 scanf 在这种情况下可以返回它想要的任何内容(大概这就是为什么段错误也可以?)。但我只是觉得这种行为非常奇怪 - 毕竟没有理由将整个字符串保存在缓冲区中只是为了读取它。
    猜你喜欢
    • 1970-01-01
    • 2015-03-15
    • 2022-09-29
    • 1970-01-01
    • 1970-01-01
    • 2021-07-31
    • 1970-01-01
    • 1970-01-01
    • 2018-01-24
    相关资源
    最近更新 更多