linux 内核如何避免堆栈覆盖文本(指令)?

How does the linux kernel avoid the stack overwriting the text (instructions)?

我很好奇内核是如何防止堆栈变得太大的,我发现了:

Q: how does the linux kernel enforce stack size limits?

A: The kernel can control this due to the virtual memory. The virtual memory (also known as memory mapping), is basically a list of virtual memory areas (base + size) and a target physically memory area that the kernel can manipulate that is unique to each program. When a program tries to access an address that is not on this list, an exception happens. This exception will cause a context switch into kernel mode. The kernel can look up the fault. If the memory is to become valid, it will be put into place before the program can continue (swap and mmap not read from disk yet for instance) or a SEGFAULT can be generated.

In order to decide the stack size limit, the kernel simply manipulates the virtual memory map. - Stian Skjelstad

但我对这个答案不太满意。 “当程序试图访问不在此列表中的地址时,就会发生异常。” - 但是程序的文本部分(指令)不是虚拟内存映射的一部分吗?

I'm asking about how the kernel enforces the stack size of user programs.

主堆栈有一个增长限制,设置为 ulimit -s,这将阻止堆栈接近 .text。 (如果堆栈溢出超过增长限制,下面的保护页会确保出现段错误。)参见 。 (或者对于线程堆栈(不是主线程),堆栈内存只是正常的 mmap 分配,没有增长;唯一的惰性分配是支持虚拟页面的物理页面。)

此外,.text 是可执行文件的 read+exec 映射,因此如果不先调用 mprotect 就无法修改它。 (这是一个私有映射,所以这样做只会影响内存中的页面,不会影响实际文件。这就是文本重定位的工作原理:绝对地址的运行时修复,由动态链接器修复。)

限制增长的实际机制是简单地不扩展映射并在进程触发硬件页面错误且堆栈指针低于现有堆栈区域时分配新页面. 因此页面错误是无效页面错误,而不是正常堆栈增长情况下的软页面错误 ,因此传递了 SIGSEGV。


如果程序使用 alloca 或大小未经检查的 C99 VLA,恶意输入可能会使其跳过任何保护页面并进入其他一些 read/write 映射,例如 .data或动态分配的东西。

为了加强有缺陷的代码以防止它出现段错误而不是实际允许 堆栈冲突 攻击,有一些编译器选项可以让它在堆栈增长时触及每个中间页面,所以它肯定会以低于堆栈增长限制的未映射保护页面的形式触发“tripwire”。参见

如果您设置 ulimit -s unlimited,您是否可以将堆栈扩展到其他映射,如果 Linux 在这种情况下确实允许无限增长,而不会在您接近另一个映射时保留保护页。