且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Linux进程堆栈被局部变量溢出(堆栈保护)

更新时间:2023-09-01 21:00:28

_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 *** by making sure you touch an unmappable page before continuing in the case of stack overflow.

Linux将在您触摸时将主线程堆栈 1 增加任意数量的页面(最大为ulimit -s设置的堆栈大小限制;默认为8MiB)如果内存位于旧堆栈页面以下,则该内存位于 上方.

Linux will grow the main-thread stack1 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-*** 当然,它要求程序根据用户的输入为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 ***. https://blog.qualys.com/securitylabs/2017/06/19/the-stack-*** 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-*** bugs/exploits.

在x86-64 GNU/Linux上,gcc -fstack-check将(对于具有VLA或大型fixe-size数组的功能)添加一个循环,该循环将使用or qword ptr [rsp], 0sub rsp,4096进行探针堆栈.对于已知的固定阵列大小,它可以只是一个探针.代码生成看起来效率不高.通常从未在此目标上使用过. (的 Godbolt 该穿过的堆叠阵列到非内联函数强>编译器资源管理器的例子.)

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-*** 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 ***es, 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 ***. 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.)