堆栈溢出是安全漏洞吗?

发布于 2024-09-30 17:11:58 字数 190 浏览 2 评论 0原文

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

如果我编写了一个正确的程序,但它接受来自互联网的输入来确定它调用的递归函数的递归级别,这是否足以允许某人危害机器?

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

只是好奇...

Note: this question relates to stack overflows (think infinite recursion), NOT buffer overflows.

If I write a program that is correct, but it accepts an input from the Internet that determines the level of recursion in a recursive function that it calls, is that potentially sufficient to allow someone to compromise the machine?

I know someone might be able to crash the process by causing a stack overflow, but could they inject code? Or does the c runtime detect the stack overflow condition and abort cleanly?

Just curious...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

牵强ㄟ 2024-10-07 17:11:58

快速复习

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

分析

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

如果操作系统被欺骗而不设置这样的页面,或者堆栈帧变得如此之大并且写入稀疏以至于跳过保护页面,那么就会出现潜在的问题。 (保留更多的保护页将有助于以很少的成本防止第二种情况;强制可变大小的堆栈分配 - 例如,alloca() - 在将控制权返回给程序之前始终写入它们分配的空间,因此检测到损坏的堆栈,将在速度方面以一定成本阻止第一个,尽管写入可以相当稀疏以保持成本相当小。)

后果

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

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


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

Rapid Refresher

First off, you need to understand that the fundamental units of protection in modern OSes are the process and the memory page. Processes are memory protection domains; they are the level at which an OS enforces security policy, and they thus correspond strongly with a running program. (Where they don't, it's either because the program is running in multiple processes or because the program is being shared in some kind of framework; the latter case has the potential to be “security-interesting” but that's 'nother story.) Virtual memory pages are the level at which the hardware applies security rules; every page in a process's memory has attributes that determine what the process can do with the page: whether it can read the page, whether it can write to it, and whether it can execute program code on it (though the third attribute is rather more rarely used than perhaps it should be). Compiled program code is mapped into memory into pages that are both readable and executable, but not writable, whereas the stack should be readable and writable, but not executable. Most memory pages are not readable, writable or executable at all; the OS only lets a process use as many pages as it explicitly asks for, and that's what memory allocation libraries (malloc() et al.) manage for you.

Analysis

Provided each stack frame is smaller than a memory page[1] so that, as the program advances through the stack, it writes to each page, the OS (i.e., the privileged part of the runtime) can at least in principle detect stack overflows reliably and terminate the program if that occurs. Basically, all that happens to do this detection is that there is a page that the program cannot write to at the end of the stack; if the program tries to write to it, the memory management hardware traps it and the OS gets a chance to intervene.

The potential problems with this come if the OS can be tricked into not setting such a page or if the stack frames can become so large and sparsely written to that the guard page is jumped over. (Keeping more guard pages would help prevent the second case with little cost; forcing variable-sized stack allocations – e.g., alloca() – to always write to the space they allocate before returning control to the program, and so detect a smashed stack, would prevent the first with some cost in terms of speed, though the writes could be reasonably sparse to keep the cost fairly small.)

Consequences

What are the consequences of this? Well, the OS has to do the right thing with memory management. (@Michael's link illustrates what can happen when it gets that wrong.) But also it is dangerous to let an attacker determine memory allocation sizes where you don't force a write to the whole allocation immediately; alloca and C99 variable-sized arrays are a particular threat. Moreover, I would be more suspicious of C++ code as that tends to do a lot more stack-based memory allocation; it might be OK, but there's a greater potential for things to go wrong.

Personally, I prefer to keep stack sizes and stack-frame sizes small anyway and do all variable-sized allocations on the heap. In part, this is a legacy of working on some types of embedded system and with code which uses very large numbers of threads, but it does make protecting against stack overflow attacks much simpler; the OS can reliably trap them and all the attacker has then is a denial-of-service (annoying, but rarely fatal). I don't know whether this is a solution for all programmers.


[1] Typical page sizes: 4kB on 32-bit systems, 16kB on 64-bit systems. Check your system documentation for what it is in your environment.

王权女流氓 2024-10-07 17:11:58

大多数系统(如 Windows)在堆栈溢出时退出。我认为您可能不会在这里看到安全问题。至少,不是特权提升安全问题。您可能会遇到一些拒绝服务问题。

Most systems (like Windows) exit when the stack is overflowed. I don't think you are likely to see a security issue here. At least, not an elevation of privilege security issue. You could get some denial of service issues.

神妖 2024-10-07 17:11:58

没有普遍正确的答案......在某些系统上,堆栈可能会向下或向上增长以覆盖程序使用的其他内存(或另一个程序或操作系统),但在任何设计良好、具有模糊安全意识的操作系统(Linux ,任何常见的 UNIX 变体,甚至 Windows)都不会有权限升级的可能性。在某些系统上,禁用堆栈大小检查时,地址空间可能会接近或超过可用虚拟内存大小,从而使内存耗尽对整个机器产生负面影响,甚至导致整个机器瘫痪,而不仅仅是进程,但在良好的操作系统上,默认情况下会有一个对此进行限制(例如 Linux 的 limit / ulimit 命令)。

值得一提的是,使用计数器来设置任意但慷慨的递归深度限制通常也很容易:如果是单线程,您可以使用静态局部变量,或者使用最终参数(如果您的语言允许,则可以方便地默认为 0,否则有外部调用者第一次提供 0)。

There is no universally correct answer... on some system the stack might grow down or up to overwrite other memory that the program's using (or another program, or the OS), but on any well designed, vaguely security-conscious OS (Linux, any common UNIX variant, even Windows) there will be no rights escalation potential. On some systems, with stack size checks disabled, the address space might approach or exceed the free virtual memory size, allowing memory exhaustion to negatively affect or even bring down the entire machine rather than just the process, but on good OSes by default there's a limit on that (e.g. Linux's limit / ulimit commands).

Worth mentioning that it's typically pretty easy to use a counter to put an arbitrary but generous limit of recursive depth too: you can use a static local variable if single-threaded, or a final parameter (conveniently defaulted to 0 if your language allows it, else have an outer caller provide 0 the first time).

手心的温暖 2024-10-07 17:11:58

是的。有一些算法可以避免递归。例如,在算术表达式中,反向波兰表示法使您能够避免递归。背后的主要思想是改变原来的表达方式。可能有一些算法也可以帮助您。

堆栈溢出的另一个问题是,如果错误处理不当,可能会导致任何问题。以 Java 为例 StackOverflowError 是一个错误,如果有人捕获 Throwable,这是一个常见的错误。因此,错误处理是堆栈溢出时的关键问题。

Yes it is. There are algorithms to avoid recursiveness. For example in case arithmetic expressions the reverse polish notation enable you to avoid recursiveness. The main idea behind is to alter the original expression. There could be some algorythm that can help you as well.

One other problem with stack overflow, that if error handling is not appropiate, it can cause anything. To explain it for example in Java StackOverflowError is an error, and it is caught if someone catches Throwable, which is a common mistake. So error handling is a key question in case of stack overflow.

想你的星星会说话 2024-10-07 17:11:58

是的。可用性是安全性的一个重要方面,但常常被忽视。

不要掉进那个坑里。

编辑

作为现代操作系统中对安全意识知之甚少的一个例子,请看一下一个相对新发现的 漏洞,尚未有人完全修补。还有无数其他特权升级漏洞的例子,操作系统开发人员将其视为拒绝服务攻击。

Yes, it is. Availability is an important aspect of security, that is mostly overlooked.

Don't fall into that pit.

edit

As an example of poorly understood security-consciousness in modern OSs, take a look at a relatively newly discovered vulnerability that nobody yet patched completely. There are countless other examples of privilege escalation vulnerabilities that OS developers have written off as denial of service attacks.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文