nar*_*llo 9 c linux assembly gcc stack-memory
在堆栈的末尾,有一个保护页被映射为不可访问的内存——如果程序访问它(因为它试图使用比当前映射更多的堆栈),就会出现访问冲突。
_chkstk()
是一个特殊的编译器辅助函数,它
确保局部变量有足够的空间
即它正在做一些堆栈探测(这是一个LLVM 示例)。
这种情况是特定于 Windows 的。所以Windows有一些解决问题的方法。
让我们考虑 Linux(或其他一些类 Unix)下的类似情况:我们有很多函数的局部变量。第一个堆栈变量访问在堆栈段后面(例如mov eax, [esp-LARGE_NUMBER]
,这里 esp-LARGE_NUMBER 是堆栈段后面的内容)。是否有任何功能可以防止可能的页面错误或 Linux(可能是其他类 Unix)或开发工具(如gcc、clang等)中的任何功能?-fstack-check
(GCC 堆栈检查)是否以某种方式解决了这个问题?这个答案表明它与_chkstk()
.
PPS 一般来说,问题是关于操作系统(最重要的Linux与 Windows)之间的实现差异,这些方法与大量堆栈变量作斗争,爬到堆栈段后面。添加 C++ 和 C 标记是因为它是关于 Linux 本地二进制生成的,但汇编代码与编译器相关。
_chkstk
进行堆栈探测以确保在(可能)大分配(例如 alloca)之后按顺序触摸每个页面。因为 Windows 一次只会将堆栈增长一页,直至达到堆栈大小限制。
触摸那个“保护页面”会触发堆栈增长。它不防止堆栈溢出;我认为您误解了这种用法中“保护页面”的含义。
函数名称也可能具有误导性。 _chkstk
文档简单地说:当您的函数中有超过一页的局部变量时,由编译器调用。 它并没有真正检查任何东西,它只是确保在esp
/周围的内存rsp
被使用之前已经触及了中间页面。即唯一可能的影响是:没有(可能包括有效的软页面错误)或堆栈溢出时的无效页面错误(尝试触摸 Windows 拒绝增加堆栈以包含的页面。)它确保堆栈页面是通过无条件写入它们来分配。
我想您可以将其视为检查堆栈冲突,方法是确保在堆栈溢出的情况下继续之前触摸不可映射的页面。
ulimit -s
当您触摸旧堆栈页面下方的内存(如果它位于当前堆栈指针上方)时,Linux 会将主线程堆栈1增加任意数量的页面(最多由 设置的堆栈大小限制;默认为 8MiB)。
如果您触及超出增长限制的内存,或者不先移动堆栈指针,它只会出现段错误。 因此,Linux 不需要堆栈探测,只需将堆栈指针移动您想要保留的字节数即可。编译器知道这一点并相应地发出代码。
另请参阅使用“push”或“sub”x86 指令时如何分配堆栈内存?有关 Linux 内核的功能以及 Linux 上的 glibc pthreads 功能的更多低级详细信息。
alloca
Linux 上足够大的堆栈可以将堆栈一直移动到堆栈增长区域的底部,超出其下方的保护页,并进入另一个映射;这是一个堆栈冲突。 https://blog.qualys.com/securitylabs/2017/06/19/the-stack-clash 这当然要求程序使用潜在的巨大的 alloca 大小,这取决于用户输入。CVE-2017-1000364的缓解措施是留下 1MiB 的保护区域,需要比正常情况大得多的 alloca 才能通过保护页面。
这个 1MiB 保护区域低于ulimit -s
(8MiB) 增长限制,而不是低于当前堆栈指针。它与 Linux 的正常堆栈增长机制是分开的。
gcc -fstack-check
的效果与gcc -fstack-check
Windows 上始终需要的效果基本相同(MSVC 通过调用 来实现_chkstk
):将堆栈页面移动到大的或运行时变量时,触摸前一个和新堆栈指针之间的堆栈页面。
但是这些探针的目的/好处在 Linux 上是不同的;在 GNU/Linux 上的无错误程序中,它永远不需要正确性。它“仅”防御堆栈冲突错误/漏洞利用。
在 x86-64 GNU/Linux 上,gcc -fstack-check
将(对于具有 VLA 或大型固定大小数组的函数)添加一个循环,该循环or qword ptr [rsp], 0
与sub rsp,4096
. 对于已知的固定阵列大小,它可以只是一个探针。代码生成看起来效率不高;它通常从未用于此目标。(Godbolt编译器资源管理器示例将堆栈数组传递给非内联函数。)
https://gcc.gnu.org/onlinedocs/gccint/Stack-Checking.html描述了一些 GCC 内部参数来控制什么-fstack-check
。
如果您想要绝对安全地抵御堆栈冲突攻击,则应该这样做。但是,正常操作不需要它,对于大多数人来说,1MiB 保护页就足够了。
请注意,这-fstack-protector-strong
是完全不同的,并且防止通过本地数组上的缓冲区溢出来覆盖返回地址。 与堆栈冲突无关,攻击是针对小本地数组上方堆栈中已经存在的内容,而不是通过大量移动堆栈来针对内存的其他区域。
脚注 1:Linux 上的线程堆栈(对于初始线程以外的线程)必须预先完全分配,因为魔术增长功能不起作用。只有进程的初始 aka 主线程才能拥有它。
(这里有一个mmap(MAP_GROWSDOWN)
功能,但它不是,因为没有安全极限,因为没有什么可以阻止其他动态分配从随机挑选当前堆栈下方页面关闭,限制了未来增长的小规模堆栈冲突之前,还因为它只生长,如果你触摸保护页,所以它需要堆栈探测。由于这些showstopper原因,MAP_GROWSDOWN
不用于线程堆栈。主堆栈的内部机制依赖于内核中的不同魔法,它确实防止其他分配窃取空间。)
归档时间: |
|
查看次数: |
314 次 |
最近记录: |