了解 C 中的堆栈溢出处理

问题描述

我很好奇如何在 C 中捕获堆栈溢出并偶然发现 GNU libsigseg library

这个库可以在很多平台上捕获堆栈溢出并提供一个implementation example
为了使用这个库安装堆栈溢出侦听器,必须首先为备用堆栈保留一些空间。 据我了解,这个备用堆栈用于运行侦听器,因为真正的堆栈不可用。

备用堆栈保留在 altstack.h(第 40 行)中,如下所示:

[][      ][             ][      ]
|    |           |          |
|    |           |          crumple_zone (8 KiB)
|    |           usable_space (16 KiB)
|    crumple_zone (8 KiB)
offset (31 B)

可用空间是实际使用的空间,皱缩区是为了防止备用堆栈上的溢出:如果溢出,它会将其放入分配的空间中,防止段错误,并且人们可能有时间检测它。

但是,

  1. 我不明白为什么在堆栈之前和之后都有一个皱缩区;堆栈仅在一个方向上增长。是不是因为有些平台的堆栈朝一个方向增长,而其他平台则朝另一个方向发展?
  2. 我不明白为什么会有偏移。

以下是作者给出的解释:

glibc 说:用户应该使用 SIGSTKSZ 作为用户提供的缓冲区的大小。 我们希望以更好的方式检测备用堆栈的堆栈溢出,而不仅仅是崩溃,因此与我们处理的 libsigsegv 相比,我们进行了过度分配。 此外,我们故意传递一个未对齐的指针,以确保备用堆栈最终仍然对齐。

最后一条语句让我有点不知所措:“...我们故意传递一个未对齐的指针,以确保备用堆栈最终仍然对齐”。 如果我们使堆栈未对齐,堆栈如何最终对齐?

解决方法

我不明白为什么在stack之前和之后有一个crumple zone;

堆栈被声明为全局 char mystack_storage[...]
假设堆栈向下增长,您需要存储低端的皱缩区来检测备用堆栈本身的溢出。

问题:什么跟随 mystack_storage[] 数组?
回答:你不知道。

如果紧随其后的是另一个数组,并且如果那个数组被写出边界(例如,使用类似 other_array[-20] = 'a'; 之类的东西)怎么办?

要检测这种下溢,您还需要另一端的皱缩区。

如果我们使堆栈未对齐,堆栈如何最终对齐?

mystack 指针故意未对齐。如果直接将其用作备用堆栈,则会违反许多平台上的堆栈对齐要求,并且很可能会导致崩溃。

为了防止这种情况,库不能直接使用 mystack,而是在其他地方对齐

您指向的代码旨在测试其他代码是否正常工作。

更新:

我还是不明白偏移量。为什么 mystack 指针在没有偏移的情况下使用会违反堆栈对齐要求?

如果没有偏移,mystack 的对齐方式是未知的。可能会发生在 16 字节、8 字节甚至 1 字节边界上对齐。

有了偏移量,保证未对齐(在 1 字节边界上对齐)。

为什么不是相反:通过添加偏移量,它故意未对齐并违反堆栈对齐要求。

是的,指针故意未对齐。

重点是:实际使用 mystack的代码(该代码在别处,我没有找),已经准备好处理未对齐的{{1} },通过正确对齐它(我们称之为“对齐代码”)。您指向的代码旨在执行其他“对齐代码”。

现在,哪里是“对齐代码”?

我以为它在图书馆的其他地方,但我错了。未对齐的 mystack 指针直接 here 使用。

那么谁在做所需的对齐呢?内核可以!

来自mystack

man sigaltstack

因此 ss.ss_sp This field specifies the starting address of the stack. When a signal handler is invoked on the alternate stack,the kernel automatically aligns the address given in ss.ss_sp to a suitable address boundary for the underlying hardware architecture. 的未对齐旨在执行的“其他代码”不在库中,而是在内核中。