问题描述
_chkstk()
是特殊的
即它正在做一些堆栈探测(这是一个 LLVM示例).
这种情况是Windows特定的.因此Windows有解决该问题的方法.
i.e. it's doing some stack probing (here is an LLVM example).
This case is Windows-specific. So Windows has some solution to the problem.
让我们考虑Linux(或其他类似Unix)下的类似情况:我们有很多函数的局部变量.第一个堆栈变量访问位于堆栈段的后面(例如mov eax, [esp-LARGE_NUMBER]
,此处esp-LARGE_NUMBER位于堆栈段的后面).是否有任何功能可以防止可能的页面错误,或者在Linux中(也许是其他Unix之类的)或开发工具(例如 gcc , lang ,等等? -fstack-check
( GCC堆栈检查)是否可以解决此问题? 此答案指出它与_chkstk()
非常相似.
Let's consider the similar conditions under Linux (or some other Unix-like): we have a lot of function's local variables. The first stack variable access is behind the stack segment (e.g. mov eax, [esp-LARGE_NUMBER]
, here esp-LARGE_NUMBER is something behind the stack segment). Is there any features to prevent possible page fault or whatever in Linux (perhaps other Unix-like) or development tools like gcc, clang, etc? Does -fstack-check
(GCC stack checking) somehow solve this problem? This answer states that it is something very similar to _chkstk()
.
P.S.这些帖子 1 ,没什么帮助.
P.S. These posts 1, 2 didn't help a lot.
P.P.S.总的来说,问题在于操作系统(最 Linux 与Windows)的实现方式差异(挣扎)具有大量的堆栈变量,这些变量都落后于堆栈段.添加了C ++和C标签,因为这是关于Linux本机二进制文件生成的,但是汇编代码与编译器相关.
P.P.S. In general, the question is about implementation differences between OSs (foremost Linux vs Windows) approaches of struggling with huge amount of stack variables, that climb behind the stack segment. Both C++ and C tags are added because it's about Linux native binary producing, but the assembly code is compiler-related.
推荐答案
_chkstk
确实会堆叠 probes ,以确保在(可能)大量分配(例如)之后按顺序触摸每个页面.阿洛卡.因为Windows一次只能将堆栈增加一页,直到堆栈大小限制.
_chkstk
does stack probes to make sure each page is touched in order after a (potentially) large allocation, e.g. an alloca. Because Windows will only grow the stack one page at a time up to the stack size limit.
触摸该防护页"将触发堆栈增长.它不能防止堆栈溢出.我认为您在这种用法中误解了保护页"的含义.
Touching that "guard page" triggers stack growth. It doesn't guard against stack overflow; I think you're misinterpreting the meaning of "guard page" in this usage.
函数名称也可能会引起误解. _chkstk
文档只是说:当函数中有多于一页的局部变量时,由编译器调用.它并不会真正地检查任何东西,它只是确保在插入内存之前已触及中间的页面使用esp
/rsp
左右.即,唯一可能的影响是:什么都没有(可能包括有效的软页面错误)或堆栈溢出时的无效页面错误(试图触摸Windows拒绝增加堆栈包括的页面.)确保通过无条件地写入来分配堆栈页面.
The function name is also potentially misleading. _chkstk
docs simply say: Called by the compiler when you have more than one page of local variables in your function. It doesn't truly check anything, it just makes sure that intervening pages have been touched before memory around esp
/rsp
gets used. i.e. the only possible effects are: nothing (possibly including a valid soft page fault) or an invalid page-fault on stack overflow (trying to touch a page that Windows refused to grow the stack to include.) It ensures that the stack pages are allocated by unconditionally writing them.
我想您可以将其视为检查堆栈冲突,方法是确保在发生堆栈溢出之前先触摸不可映射的页面,然后再继续操作.
I guess you could look at this as checking for a stack clash by making sure you touch an unmappable page before continuing in the case of stack overflow.
Linux将在您触摸时将主线程堆栈增加任意数量的页面(最大为ulimit -s
设置的堆栈大小限制;默认为8MiB)如果内存位于旧堆栈页面以下,则该内存位于 上方.
Linux will grow the main-thread stack by any number of pages (up to the stack size limit set by ulimit -s
; default 8MiB) when you touch memory below old stack pages if it's above the current stack pointer.
如果您在增长限制之外触摸内存,或者不先移动堆栈指针,则只会出现段错误.因此,Linux不需要堆栈探针,只需将堆栈指针移动您想要保留的字节数.编译器知道这一点并相应地发出代码.
If you touch memory outside the growth limit, or don't move the stack pointer first, it will just segfault. Thus Linux doesn't need stack probes, merely to move the stack pointer by as many bytes as you want to reserve. Compilers know this and emit code accordingly.
另请参见堆栈如何使用'push'或'sub'x86指令分配的内存?有关Linux内核的功能以及Linux上的glibc pthreads的更多底层细节.
See also How is Stack memory allocated when using 'push' or 'sub' x86 instructions? for more low-level details on what the Linux kernel does, and what glibc pthreads on Linux does.
Linux上足够大的alloca
可以将堆栈一直移动到堆栈增长区域的底部,超出其下方的保护页面,并进入另一个映射;这是堆栈冲突. https://blog.qualys.com/securitylabs/2017/06/19/the-stack-clash 当然,它要求程序根据用户的输入为alloca使用可能很大的大小. 对CVE-2017-1000364的缓解是为了保留1MiB防护区域,需要比正常更大的分配空间才能通过保护页面.
A sufficiently large alloca
on Linux can move the stack all the way past the bottom of the stack growth region, beyond the guard pages below that, and into another mapping; this is a Stack Clash. https://blog.qualys.com/securitylabs/2017/06/19/the-stack-clash It of course requires that the program uses a potentially-huge size for alloca, dependent on user input. The mitigation for CVE-2017-1000364 is to leave a 1MiB guard region, requiring a much larger alloca than normal to get past the guard pages.
此1MiB保护区低于ulimit -s
(8MiB)增长限制,不低于当前堆栈指针.它与Linux的常规堆栈增长机制是分开的.
This 1MiB guard region is below the ulimit -s
(8MiB) growth limit, not below the current stack pointer. It's separate from Linux's normal stack growth mechanism.
gcc -fstack-check
的效果与Windows上始终需要的效果相同(MSVC通过调用_chkstk
来实现):在移动它时,触摸上一个和新堆栈指针之间的堆栈页面或运行时可变的数量.
The effect of gcc -fstack-check
is essentially the same as what's always needed on Windows (which MSVC does by calling _chkstk
): touch stack pages in between previous and new stack pointer when moving it by a large or runtime-variable amount.
但是这些探针的目的/好处在Linux上是不同的.在GNU/Linux上的无错误程序中,它从来不需要正确性.它仅"防御栈冲突bug/漏洞.
But the purpose / benefit of these probes is different on Linux; it's never needed for correctness in a bug-free program on GNU/Linux. It "only" defends against stack-clash bugs/exploits.
在x86-64 GNU/Linux上,gcc -fstack-check
将(对于具有VLA或大型fixe-size数组的功能)添加一个循环,该循环将使用or qword ptr [rsp], 0
和sub rsp,4096
进行探针堆栈.对于已知的固定阵列大小,它可以只是一个探针.代码生成看起来效率不高.通常从未在此目标上使用过. (的 该穿过的堆叠阵列到非内联函数编译器资源管理器的例子.)
On x86-64 GNU/Linux, gcc -fstack-check
will (for functions with a VLA or large fixe-size array) add a loop that does stack probes with or qword ptr [rsp], 0
along with sub rsp,4096
. For known fixed array sizes, it can be just a single probe. The code-gen doesn't look very efficient; it's normally never used on this target. (Godbolt compiler explorer example that passes a stack array to a non-inline function.)
https://gcc.gnu.org/onlinedocs/gccint/Stack -Checking.html 描述了一些控制-fstack-check
功能的GCC内部参数.
https://gcc.gnu.org/onlinedocs/gccint/Stack-Checking.html describes some GCC internal parameters that control what -fstack-check
does.
如果您想要绝对安全的堆栈冲突攻击,则应该这样做.不过,正常操作并不需要它,对于大多数人来说1MiB保护页就足够了.
If you want absolute safety against stack-clash attacks, this should do it. It's not needed for normal operation, though, and a 1MiB guard page is enough for most people.
请注意,-fstack-protector-strong
完全不同,并且可以防止通过本地数组上的缓冲区溢出来覆盖返回地址.与堆栈冲突无关,攻击是针对已经存在的内容.堆栈放在一个小的局部数组上方,而不是通过大量移动堆栈来与其他内存区域冲突.
Note that -fstack-protector-strong
is completely different, and guards against overwrite of the return address by buffer overruns on local arrays. Nothing to do with stack clashes, and the attack is against stuff already on the stack above a small local array, not against other regions of memory by moving the stack a lot.
脚注1:Linux上的线程堆栈(对于最初的线程以外的线程)必须预先分配好,因为不可思议的增长功能不起作用.只有进程的初始主线程才可以具有该主线程.
Footnote 1: Thread stacks on Linux (for threads other than the initial one) have to be fully allocated up front because the magic growth feature doesn't work. Only the initial aka main thread of a process can have that.
(有一个mmap(MAP_GROWSDOWN)
功能,但它不是安全的,因为没有限制,并且因为没有其他阻止动态分配的功能来随机选择靠近当前堆栈的页面,从而将未来的增长限制在很小的范围内堆栈冲突之前的大小.这也是因为只有当您触摸保护页面时它才会增长,因此它需要堆栈探针.由于这些原因, MAP_GROWSDOWN
不用于线程堆栈.因为主堆栈依赖于内核中的不同魔术,而确实可以防止其他分配窃取空间.)
(There's an mmap(MAP_GROWSDOWN)
feature but it's not safe because there's no limit, and because nothing stops other dynamic allocations from randomly picking a page close below the current stack, limiting future growth to a tiny size before a stack clash. Also because it only grows if you touch the guard page, so it would need stack probes. For these showstopper reasons, MAP_GROWSDOWN
is not used for thread stacks. The internal mechanism for the main stack relies on different magic in the kernel which does prevent other allocations from stealing space.)
这篇关于Linux进程堆栈被局部变量溢出(堆栈保护)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!