堆栈是否溢出安全漏洞?

Jer*_*ner 7 stack-overflow security code-injection

注意:这个问题涉及堆栈溢出(认为无限递归),而不是缓冲区溢出.

如果我编写一个正确的程序,但是它接受来自Internet的输入来确定它调用的递归函数中的递归级别,那么这可能足以让某人破坏机器吗?

我知道有人可能通过导致堆栈溢出来崩溃进程,但是他们是否可以注入代码?或者c运行时是否检测到堆栈溢出情况并干净地中止?

只是好奇...

Don*_*ows 5

快速复习

首先,您需要了解现代操作系统中的基本保护单元是进程和内存页。进程是内存保护域;它们是操作系统强制执行安全策略的级别,因此它们与正在运行的程序强烈对应。(如果他们不这样做,要么是因为程序在多个进程中运行,要么是因为程序在某种框架中共享;后一种情况有可能是“安全相关的”,但那是“另一回事”。)虚拟内存页是硬件应用安全规则的级别;进程内存中的每个页面都有决定进程可以对页面做什么的属性:它是否可以读取页面,是否可以写入页面,以及它是否可以在其上执行程序代码(尽管第三个属性比它应该使用的更少使用)。编译后的程序代码被映射到内存中,成为可读和可执行但不可写的页面,而堆栈应该是可读和可写的,但不可执行。大多数内存页根本不可读、不可写或可执行;操作系统只允许进程使用它明确要求的尽可能多的页面,这就是内存分配库(malloc()等)为您管理的内容。

分析

假设每个堆栈帧都小于内存页[1],这样,当程序在堆栈中前进时,它会写入每个页面,操作系统(即运行时的特权部分)至少在原则上可以检测到堆栈溢出如果发生这种情况,请可靠地终止程序。基本上,这种检测所发生的一切只是在堆栈的末尾有一个程序无法写入的页面;如果程序试图写入它,内存管理硬件就会捕获它,操作系统就有机会进行干预。

如果操作系统可能被欺骗而没有设置这样的页面,或者如果堆栈帧变得如此之大且写入稀疏以至于保护页面被跳过,则潜在的问题就会出现。(保留更多的保护页将有助于以很小的代价防止第二种情况;强制可变大小的堆栈分配——例如alloca()——在将控制权返回给程序之前总是写入它们分配的空间,从而检测被破坏的堆栈,将防止首先在速度方面有一些成本,尽管写入可以合理地稀疏以保持成本相当小。)

结果

这样做的后果是什么?好吧,操作系统必须在内存管理方面做正确的事情。(@Michael 的链接说明了当它出错时会发生什么。)但让攻击者确定内存分配大小也是危险的,而您不立即强制写入整个分配;alloca和 C99 可变大小的数组是一个特别的威胁。此外,我会更怀疑 C++ 代码,因为它倾向于进行更多基于堆栈的内存分配;可能没问题,但出现问题的可能性更大。

就个人而言,我更喜欢将堆栈大小和堆栈帧大小保持较小,并在堆上进行所有可变大小的分配。在某种程度上,这是在某些类型的嵌入式系统和使用大量线程的代码上工作的遗产,但它确实使防止堆栈溢出攻击变得更加简单;操作系统可以可靠地捕获它们,然后攻击者所拥有的只是拒绝服务(令人讨厌,但很少致命)。我不知道这是否是所有程序员的解决方案。


[1]典型页面大小:32 位系统上为 4kB,64 位系统上为 16kB。检查您的系统文档以了解它在您的环境中的内容。