【问题标题】:Overflow over scanf("%8s", string)?scanf("%8s", string) 溢出?
【发布时间】:2010-12-19 18:17:37
【问题描述】:

我知道普通代码有可能溢出:

字符字符串[9];

scanf("%s", 字符串).

但是scanf("%8s", string)有可能溢出吗? 8 只是一个例子。

我知道 "%8s" 就像一个分隔符,但我也注意到当我输入超过 8 个字符的字符串时,程序将因以下原因终止:

* 检测到堆栈破坏 *:./a.out 终止

======= 回溯:=========

...

显然,默认情况下,GCC 有一个检测堆栈粉碎的标志。既然这是一个堆栈粉碎,那么我的猜测是仍然有可能溢出并执行任意代码。

与破坏scanf("%s")调用者的正常溢出相反,如果scanf("%8s")可以溢出,它将在scanf函数内溢出,因此当scanf尝试返回时,获得控制。

但是 scanf 是一个需要模式切换(从用户模式切换到内核模式)的系统调用,并且在内部它会调用诸如读取到标准输入等的东西。所以不确定我们是否可以在内核模式或其他东西中溢出。

欢迎评论!!

更新>>

char string[9] 在上面的例子中被假定。 char string[8] 在下面的实际代码中。

问题实际上是关于安全 scanf("%8s") 和 GCC 因堆栈粉碎而中止之间看似矛盾的故事。

简化代码:

void foo(pass some pointer) {
char input[8];
int input_number = 0;

while (1) { // looping console
   printf some info;
   scanf("%8s", input);

   input_number = atoi(input);

   if ((strlen(input) == 1) && (strncmp(input, "q", 1) == 0)) {
       input_number = -1;
   }
   switch (input_number) {
       case -1: to quit the console if input = 'q';
       default: to print info that pointer refers to;
       ...
   } 

}

}

注意:

  1. foo 被其他人调用。
  2. 虽然字符串实际上是 8 个字节 带有“%8s”的代码,我不这么认为 导致粉碎。

【问题讨论】:

  • scanf 是一个运行时库函数——不需要模式切换,因为它在用户空间中运行,除非它必须请求缓冲区填充,在这种情况下它会调用 read 或 fread。
  • 如答案中多次指出的那样,添加了一个 nul 字节,因此您需要一个 9 个字符的缓冲区来接受最多 8 个字符的输入。
  • 正如很多人指出的那样,您在“注 2”中的假设。是错的。该示例允许单个字节的溢出,这是 gcc 正在检测的。
  • 你们是对的。我已经用一个更简单的程序对其进行了测试,但我上次尝试时它并没有崩溃。现在,当我为 string[8] 和 scanf(%8s) 输入“12345678”时,它会因为堆栈粉碎而崩溃!所以这是吸取的教训。粉碎并不一定意味着存在堆栈溢出攻击。
  • 即使在这种情况下缓冲区恰好在堆栈上,但编程错误是缓冲区溢出而不是堆栈溢出。我相应地重新标记了这个问题。

标签: c scanf buffer-overflow


【解决方案1】:

http://www.opengroup.org/onlinepubs/009695399/functions/scanf.html:

每个指令由以下之一组成...一个可选的非零十进制整数,指定最大字段宽度。

s
匹配不是空白字符的字节序列。应用程序应确保相应的参数是指向 char、signed char 或 unsigned char 数组的初始字节的指针,该数组大到足以接受序列和终止的空字符代码,应自动添加。

所以它不会溢出一个 9 字节的字符串缓冲区。

【讨论】:

  • 如果它不会溢出,为什么 GCC 还要讲 Stack Smashing 的故事?
  • @Figo:不知何故你不明白。 "%8s" 最多可存储 9 个字节,因此您需要一个九字符数组。
【解决方案2】:

如果您希望输入可靠,请不要永远使用scanf(或fscanf)。

您应该使用fgets(或类似的“防止缓冲区溢出”变体),然后使用sscanf

scanffscanf 的主要问题是,如果行不是预期的格式(即,如果 scanf 失败),您的文件指针可能最终处于不确定的位置。使用fgets/sscanf 方法,可以更轻松地确保您在行边界上,而无需使用ftellfseek 在文件中移动。

关于你对缓冲区是否溢出的具体查询,C标准有这样的说法:

...对应的参数应该是一个指向一个足够大的字符数组的初始元素的指针,它可以接受序列和一个终止的空字符,它将自动添加。

因此,对于"%8s" 格式,您需要一个 9 字符数组。

我怀疑您的代码中还有其他问题。使用测试程序:

#include <stdio.h>
int main(int argc, char* argv[]) {
    char x1;
    char a[9];
    char x2;
    x1 = x2 = ' ';
    scanf ("%s",a);
    printf ("[%c] [%s] [%c]\n",x1,a,x2);
    return 0;
}

我明白了:

pax> ./qq.exe
dfjdhadgha...lghjdfgjhd
[s] [dfjdhadgha...lghjdfgjhd] [ ]
  6 [main] qq 4744 _cygtls::handle_exceptions: Error while dumping state
  (probably corrupted stack)
  Segmentation fault (core dumped)

当我将同一个程序更改为使用"%8s" 时,我得到(对于完全相同的输入):

pax> ./qq.exe
dfjdhadgha...lghjdfgjhd
[ ] [dfjdhadg] [ ]

【讨论】:

  • 是的,我知道。但现在我很想知道 scanf(%8s) 是否与 scanf 有同样的问题,因为 GCC 告诉我仍然有堆栈粉碎发生!
  • 描述“健壮”?举几个例子?
  • @ysth: (1) 以行的形式获取您的输入。 (2) 确保获得整行(末尾为 \n 字符),否则会出现“行太长”错误。 (3) 在线使用 sscanf - 您可以在线上任意多次执行此操作,而无需担心底层文件。
  • 我也像你一样测试了一个类似的简化代码,GCC 不会抱怨堆栈粉碎。我已经发布了原始代码,事情是在运行时当我输入 12345678 时会有 GCC 抱怨(注意实际代码中的字符字符串[8])。
  • @Figo,你需要一个 9 个字符的数组来存储一个 8 个字符的字符串。这是因为 8 个字符的字符串由 8 个字符(显然)和一个空终止符(不那么明显)组成。如果你把它改成char string[9],你应该没有任何问题。
【解决方案3】:

如果分配的字符串少于 8 个包,它肯定会覆盖缓冲区,同时 scanf 也不会附加空终止符。但是,只要您在字符串中有足够的空间来存储您的值,就不会被过度覆盖。

【讨论】:

  • 至少需要 9 个字节才能不溢出。
  • 其实我认为 scanf 会在最后放一个 '\0' 。 C 标准说“一个终止的空字符,它将自动添加。”,paxdiablo 也引用了
  • 我的意思是,如果你没有空的空间。
  • 如果你没有空的空间,它无论如何都会把它放在那里(如果需要,覆盖堆栈的其他部分)。
【解决方案4】:

正如 ysth 所指出的,数组应该能够包含字符串 终止空字符,因此使用 8 字节数组(特别是如果它是在堆栈上分配的,因为它是在你的代码中)很可能会搞砸。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-16
    • 1970-01-01
    • 2014-09-04
    • 2021-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-09
    相关资源
    最近更新 更多