只有当一个重要的指针被覆盖时,缓冲区溢出才会导致段错误吗?

Does buffer-overflow causes segfault only when an important pointer is overwritten?

假设我有一个程序声明了一个 char buffer[size] 和另一个变量,并使用 gets(buffer); 将数据写入 buffer。如果 gets 提供的输入太长,那么它将从缓冲区溢出,进入下一个变量(假设该变量位于 buffer 之后的下一个地址):

void f(){
    char str[12] = "hello_world";
    char buffer[1];

    gets(buffer); // provided with a random char and then "hello_kitty"

    printf("str = %s\n", str); // no crash. Just prints "hello_kitty" as expected
}

当 运行 这与 "legal input" (意思是 - 没有溢出第二个缓冲区)时,这是可以的。缓冲区有点溢出还好,输入太多程序就崩溃了。

据我了解,这(意思是 - 不溢出第二个缓冲区)不应导致任何崩溃。可能导致崩溃的原因是破坏了保存指令指针的内存,因此它现在将指向一个无效地址(这是页面错误吗?)。

这是正确的吗?影响 stack/frame/instruction 指针的 none 的错误写入会导致崩溃吗?

Does buffer-overflow causes segfault only when an important pointer is overwritten?

没有必要,让我们从头开始。在大多数处理器中,内存可以分为大块或小块。大块通常称为段。一小块通常称为页面。

如果你在缓冲区后面写(缓冲区溢出)和"things"在这个缓冲区后面被覆盖属于同一个进程,不会立即发生错误。

          SEGMENT n     |    SEGMENT n
           buffer1      |     buffer2
+-----+-----+-----+-----+-----+-----+-----+-----+
| 'a' | 'a' | 'a' | 'a' | 'a' | 'a' | 'a' | 'a' |
+-----+-----+-----+-----+-----+-----+-----+-----+
   ^-----^-----^-----^-----^-----^-----^-----^-------- Writing to variables of same process (UB)  

SEGFAULT 当溢出的缓冲区在内存段的末尾结束时发生。无论如何,在该处理器的当前状态下,位于该段之外的是当前未定义的。因此,在此空隙中进行内存访问将导致总线故障。没有可用于处理器的内存内容。它不知道去哪里。所以在这种情况下,错误是一场直接的灾难。

x86ARM 上,当写入不在任何映射区域中的页面时,您会得到 SIGSEGV,或位于映射为只读的内存区域中的页面,或从不在任何映射区域中的地址读取。

                  END OF SEGMENT
                        |
          SEGMENT n     v    SEGMENT n+1
+-----+-----+-----+-----+-----+-----+-----+-----+
| 'a' | 'a' | 'a' | 'a' |     |     |     |     |
+-----+-----+-----+-----+-----+-----+-----+-----+
   ^-----^-----^-----^-----^--------------------------- Writing to another segment
                           |
                           Writing here will cause SEGFAULT

SEGFAULT 通常是在程序计数器 return 地址(在参数后面的堆栈上)被覆盖时引起的,并且在 pop back 继续执行函数后,程序计数器跳到某处(取决于已写入指针的值)并且无法访问那里读取。


It was OK even when I overflowed the buffer a little bit

您在同一段中覆盖了 个属于您的进程的内存(没有return 地址),但它仍然是UB。

but after entering too much the program crashed.

是的,您可能写入了另一个段或覆盖了return地址。

简明扼要地回答你的问题,没有。继续阅读以获得更长的答案。

缓冲区溢出导致段错误的最典型方式是溢出的缓冲区驻留在堆栈上,溢出覆盖return指针。当 return 指针在函数 return 上弹回指令指针时,通常会发生分段错误,因为 处理器试图读取您无权访问的内存。

最后一句话很重要。覆盖 return 指针只是发生分段错误的一种方式。事实上,任何覆盖 稍后用于访问内存 的内存地址的缓冲区溢出都可能导致分段错误,或者如果试图写入内存,则会导致访问冲突.

例如,假设您在堆上分配了一个结构。该结构采用以下形式:

 struct sample_struct {
   char bytes[20];
   struct sample_struct *next;
 };

如果在没有正确验证边界的情况下将数据复制到 bytes 成员中,内存中的下一项将是 next 指针。如果这个指针被覆盖并且随后试图从中读取,则很可能会发生段错误,假设现在那里的值表示一个您无法控制的内存地址。如果您碰巧以内存中的地址结束 space,结果将是尝试将位于那里的字节解释为 struct sample_struct,可能会导致其他问题。


请注意,不要假设覆盖上面示例结构中的指针只需要 21 到 24 个字节;除非您指示编译器打包结构,否则该结构的内存分配可能会包含额外的字节以进行对齐。

如果您读过 Aleph One 的 smashing the stack for fun and profit,您就会知道 计算机会因 溢出缓冲区(并覆盖重要指针)而被黑客攻击 )...

...正如文章所述,没有 segfault 因为部分漏洞利用代码通常是 exit(0);,前提是攻击者无法恢复程序以更好的方式。

所以回答你的问题:

is this a page fault?

不一定。 C的世界里不需要这样的概念。

Is this correct? can a bad write that affects none of the stack/frame/instruction pointers cause a crash?

当然可以,但这并不可靠。这就是为什么人们建议 未定义的行为 例如 缓冲区溢出 会导致非常剧烈的事情发生(例如 dragons 核浩劫).

我从其他评论中看出了这个问题背后的意图:

在这种情况下,你走在正确的轨道上......你错过的一件事是你提到的那些随机字符是机器代码,而指令指针需要被覆盖以指向该机器代码。 Aleph One 对此进行了更详细的介绍,尽管您的里程数肯定会有所不同,因为它是一份古老的文件。

如果不是因为这个问题有许多未定义的行为,所有 C 的专家可能会拦截信号以毫无问题地恢复他们的软件,但是唉, C 不是 Java...最好遵循专家的示例并避免未定义的行为...

假设一个体面的指南会尽力帮助您尽可能避免未定义的行为。至少有一本书形式的体面指南,我们称之为 "bible" 形式,部分原因是它是由一些权威人士撰写的。上面有一个很大的蓝色"C",现在"SECOND EDITION"是用红色写的...

找到那本书对你来说是一种练习。我有信心。