让调用堆栈向上增长会使缓冲区溢出更安全吗? [英] Would having the call stack grow upward make buffer overruns safer?

查看:27
本文介绍了让调用堆栈向上增长会使缓冲区溢出更安全吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

每个线程都有自己的堆栈来存储局部变量.但堆栈也用于在调用函数时存储返回地址.

Each thread has its own stack to store local variables. But stacks are also used to store return addresses when calling a function.

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

In x86 assembly, esp points to the most-recently allocated end of the stack. Today, most CPUs have stack grow negatively. This behavior enables arbitrary code execution by overflowing the buffer and overwriting the saved return address. If the stack was to grow positively, such attacks would not be feasible.

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

Is it safer to have the call stack grow upwards? Why did Intel design 8086 with the stack growing downward? Could they have changed things in any later CPUs to let modern x86 have stacks that grow upwards?

推荐答案

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

Interesting point; most buffer overruns do go past the end, not before the beginning, so this would almost certainly help. Compilers could put local arrays at the highest address in a stack frame, so there wouldn't be any scalar locals to overwrite located after an array.

但是,如果将本地数组的地址传递给另一个函数,仍然存在危险.因为被调用函数的返回地址将位于数组末尾之后.

There's still danger if you pass the address of a local array to another function, though. Because the return address of the called function would be located just past the end of the array.

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.
}

所以可能会有很多缓冲区溢出.这个想法只有在不安全的数组写入代码被内联时才能解决缓冲区溢出,因此溢出发生在数组上方没有任何重要内容的情况下.

So probably a lot of buffer overruns will still be possible. This idea only defeats buffer overruns when the unsafe array-writing code is inlined, so the overrun happens with nothing important above the array.

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

However, some other common causes of buffer overruns can easily be inlined, like strcat. Upward growing stacks will help sometimes.

安全措施不一定是万无一失的,所以有时这肯定会有所帮助.对于任何想要更改现有架构(如 x86)的人来说,这可能还不够,但对于新架构来说是一个有趣的想法.不过,堆栈增长是 CPU 中几乎通用的标准.有什么东西使用向上增长的调用堆栈吗?有多少软件实际上取决于这个假设?希望不多……

Security measures don't have to be foolproof to be useful, so this would definitely help sometimes. Probably not enough for anyone to want to change an existing architecture like x86, but an interesting idea for new architectures. Stack-grows-down is a nearly universal standard in CPUs, though. Does anything use an upward-growing call stack? How much software actually depends on that assumption? Hopefully not much...

传统布局为堆和/或堆栈的增长留出了空间,只有在它们在中间相遇时才会出现问题.

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

Predictable code/data addresses are more important than predictable stack addresses, so a computer with more RAM could put the stack farther from data/code, while still loading code/data at a constant address. (This is very hand-wavy. I consider myself lucky not to have written actual 16-bit programs, and only learned about but not used segmentation. Perhaps someone that still remembers DOS can shed some light here on why it works well to have the stack at a high address, instead of an upward-growing stack at the bottom of your segment and data/code at the top. e.g. with a "tiny" code model where everything is in one segment).

唯一真正改变这种行为的机会是使用 AMD64,这是 x86 第一次真正打破向后兼容性.现代英特尔 CPU 仍然支持 8086 未记录的操作码,例如 D6:SALC(Set AL from Carry Flag),限制ISA扩展的编码空间.(例如 SSSE3 和 SSE4 指令将缩短 1 个字节,如果 Intel放弃了对无证操作码的支持.

The only real chance to change this behaviour was with AMD64, which is the first time x86 has ever really broken backwards compatibility. Modern Intel CPUs still support 8086 undocumented opcodes like 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 if Intel dropped support for undocumented opcodes.

即便如此,也只是新模式;AMD64 CPU 仍然必须支持传统模式,当处于 64 位模式时,它们必须混合长模式和兼容模式(通常从 32 位二进制文​​件运行 32 位用户空间进程).

Even then, it would only be for the new mode; AMD64 CPUs still have to support legacy mode, and when in 64-bit mode they have to mix long mode with compat mode (usually to run 32-bit user-space processes from 32-bit binaries).

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

AMD64 could maybe have added a stack-direction flag, but that would have made the hardware more complex. As I argued above, I don't think it would have been a big benefit for security. Otherwise, perhaps AMD architects would have considered it, but still unlikely. They were definitely aiming for minimally intrusive, and weren't sure it would catch on. They didn't want to be stuck with extra baggage to maintain AMD64 compatibility in their CPUs if the world mostly just kept running 32-bit OSes and 32-bit code.

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

That's a shame, because there are a lot of minor things they could have done that would probably not have required too many extra transistors in the execution units. (e.g. in long mode, replace setcc r/m8 with setcc r/m32).

这篇关于让调用堆栈向上增长会使缓冲区溢出更安全吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆