让调用堆栈向上增长会使缓冲区溢出更安全吗?

Would having the call stack grow upward make buffer overruns safer?

每个线程都有自己的栈来存放局部变量。但是调用函数时,堆栈也用于store return addresses

在 x86 汇编中,esp 指向最近分配的堆栈末端。今天,大多数 CPU 的堆栈都是负增长的。此行为通过溢出缓冲区并覆盖保存的 return 地址来启用任意代码执行。如果堆栈正向增长,这样的攻击是不可行的。

调用栈向上增长是不是更安全?为什么Intel设计的8086堆栈向下增长?他们能否在任何后来的 CPU 中进行更改,让现代 x86 拥有向上增长的堆栈?

有趣的一点;大多数超过 运行 的缓冲区确实超过了结尾,而不是在开始之前,所以这几乎肯定会有所帮助。编译器可以将局部数组放在堆栈帧中的最高地址,因此不会有任何标量局部变量覆盖位于数组之后。

不过,如果将本地数组的地址传递给另一个函数,仍然存在危险。因为被调用函数的 return 地址刚好位于数组末尾之后。

unsafe() {
    char buf[128];
    gets(buf);      // stack grows upward: exploit happens when gets executes `ret`
    // stack grows down: exploit happens when the `ret` at the end of *this* function executes.
}

所以可能 很多 缓冲区 运行s 仍然是可能的 。当内联不安全的数组写入代码时,这个想法只会击败缓冲区 over运行s,所以 over运行 发生在数组之上没有任何重要的东西。

但是,缓冲区超过 运行 的一些其他常见原因可以很容易地内联,例如 strcat向上增长的堆栈有时会有所帮助。

安全措施不一定万无一失才有用,所以有时这肯定会有帮助。可能不足以让任何人想要更改 x86 等现有架构,但新架构的有趣想法。不过,Stack-grows-down 几乎是 CPU 中的通用标准。有什么使用向上增长的调用堆栈吗?有多少软件实际上取决于该假设?希望不会太多...


传统布局为堆留有空间 and/or 堆栈增长,只有在中间相遇时才会出现问题。

可预测的 code/data 地址比可预测的堆栈地址更重要,因此具有更多 RAM 的计算机可以将堆栈放置在距离 data/code 更远的地方,同时仍然在恒定地址加载 code/data . (这是非常麻烦的。我认为自己很幸运,没有编写过实际的 16 位程序,并且只了解但没有使用分段。也许还记得 DOS 的人可以在这里阐明为什么拥有堆栈在高地址,而不是向上增长的堆栈位于段的底部,data/code 位于顶部。例如,使用 "tiny" 代码模型,其中所有内容都在一个段中)。


唯一真正改变这种行为的机会是 AMD64,这是 x86 第一次真正打破向后兼容性。现代英特尔 CPU 仍然支持 8086 个未记录的操作码,例如 D6: SALC (Set AL from Carry Flag), limiting the coding space for ISA extensions. (e.g. SSSE3 and SSE4 instructions would be 1 byte shorter 如果英特尔放弃对未记录的操作码的支持。

即便如此,也只是为了新模式; AMD64 CPU 仍然必须支持传统模式,并且在 64 位模式下,它们必须将长模式与兼容模式混合使用(通常是 运行 32 位用户-space 来自 32 位二进制文​​件的进程) .

AMD64 可能会添加一个堆栈方向标志,但这会使硬件更加复杂。正如我上面所说,我认为这不会对安全性有很大好处。否则,也许 AMD 架构师会考虑它,但仍然不太可能。他们的目标绝对是将侵入性降到最低,并且不确定它会流行起来。如果世界大多只保留 运行ning 32 位操作系统和 32 位代码,他们不想背负额外的包袱来保持 CPU 中的 AMD64 兼容性。

真可惜,因为有很多他们本可以做的小事,可能不需要在执行单元中使用太多额外的晶体管。 (例如,在长模式下,将 setcc r/m8 替换为 setcc r/m32)。