|
|
1
4
快速刷新
首先,您需要了解现代操作系统中的基本保护单元是进程和内存页。进程是内存保护域;它们是操作系统执行安全策略的级别,因此它们与正在运行的程序强烈对应。(如果没有,可能是因为程序在多个进程中运行,或者是因为程序在某种框架中共享;后一种情况有可能是安全有趣的,但那是‘另一回事’)虚拟内存页是硬件应用安全规则的级别;进程内存中的每一页都有一些属性,这些属性决定了进程可以对该页执行什么操作:它是否可以读取该页,它是否可以写入该页,以及它是否可以在该页上执行程序代码(尽管第三个属性很少使用,也许它应该使用)。编译后的程序代码被映射到内存中,生成可读和可执行但不可写的页面,而堆栈应该可读和可写,但不可执行。
大多数内存页根本不可读、不可写或不可执行;
操作系统只允许一个进程使用它明确要求的所有页面,这就是内存分配库(
分析如果每个堆栈帧小于内存页 [一] 因此,当程序通过堆栈前进时,它会写入每个页面,操作系统(即运行时的特权部分)至少在原则上可以可靠地检测堆栈溢出,并在发生这种情况时终止程序。基本上,执行此检测的所有操作都是在堆栈的末尾有一个程序无法写入的页;如果程序试图写入该页,内存管理硬件将捕获该页,操作系统将有机会进行干预。
后果
这有什么后果?好吧,操作系统必须在内存管理方面做正确的事情。(@Michael's link演示了出错时可能发生的情况。)但让攻击者确定内存分配大小也是危险的,如果不立即强制写入整个分配;
就我个人而言,我更喜欢保持堆栈大小和堆栈帧大小较小,并在堆上执行所有可变大小的分配。在某种程度上,这是在某些类型的嵌入式系统上工作的遗留问题,并且使用了使用大量线程的代码,但这确实使防止堆栈溢出攻击变得简单得多;操作系统可以可靠地捕获它们,然后攻击者所拥有的只是拒绝服务(令人讨厌,但很少致命)。我不知道这是否是所有程序员的解决方案。 典型的页面大小:32位系统为4kB,64位系统为16kB。检查您的系统文档以了解它在您的环境中是什么。 |
|
|
2
3
大多数系统(如Windows)在堆栈溢出时退出。我认为你不太可能在这里看到安全问题。至少,不是特权安全问题的提升。您可能会遇到一些拒绝服务问题。 |
|
|
3
2
值得一提的是,使用计数器设置递归深度的任意但宽泛的限制通常也相当容易:如果是单线程的,则可以使用静态局部变量,或者使用最终参数(如果语言允许,则可以方便地默认为0,否则,第一次使用外部调用方提供0)。 |
|
|
4
1
是的。有一些算法可以避免递归性。例如,在case算术表达式中 reverse polish notation 使您能够避免递归性。背后的主要思想是改变原来的表达方式。可能有一些算法也能帮助你。 堆栈溢出的另一个问题是,如果错误处理不适当,则可能导致任何问题。以Java为例解释 StackOverflowError 是个错误,如果有人抓到 Throwable ,这是一个常见的错误。因此,在堆栈溢出的情况下,错误处理是一个关键问题。 |
|
|
5
0
|