在现实世界中,读取堆栈分配数组的越界会导致任何问题吗?



尽管这是一种不好的做法,但是下面的代码在现实生活中是否会造成麻烦呢?请注意,我只是读取越界,而不是写入:

#include <iostream>
int main() {
int arr[] = {1, 2, 3};
std::cout << arr[3] << 'n';
}

如前所述,它不"安全";读取堆栈末尾以外的内容。但听起来你真的想问哪里出了问题?,通常,答案是"不多"。理想情况下,您的程序会因段错误而崩溃,但它可能会继续愉快地运行,而不知道它进入了未定义行为。当然,这样一个程序的结果将是垃圾,但没有什么会着火(可能…)。

人们总是错误地编写带有未定义行为的代码,并且已经花费了大量的努力来帮助他们捕获此类问题并将其危害最小化。由于隔离的地址空间和其他特性,在用户空间中运行的程序不会影响同一台机器上的其他程序,而且像杀菌器这样的软件可以帮助检测开发过程中的UB和其他问题。一般来说,你可以先解决这个问题,然后再去做更重要的事情。

也就是说,顾名思义,UB是未定义的。这意味着一旦你要求你的计算机执行UB,它就可以做任何它想做的事情。它可以格式化你的硬盘,烧坏你的处理器,甚至"让恶魔从你的鼻子里飞出来"。一台合理的计算机不会做这些事情,但是可以

进入UB的程序最重要的问题就是它不会做你想让它做的事情。如果你试图删除/foo,但你从堆栈的末端读取,你可能最终将/bar传递给你的删除函数。如果你访问一个攻击者也可以访问的内存,你可能会以他们的名义执行代码。大量的主要安全漏洞归结为一些代码行,这些代码行以错误的方式触发UB,恶意用户可以利用这些代码行。

这取决于你对堆栈的定义。如果是整个堆栈,那么不行,你不能那样做,这会导致分割错误。不是因为那里有其他进程的记忆(这不是它的工作原理),而是因为那里什么都没有。通过查看程序使用的各种地址,您可以直观地看到这一点。例如,堆栈位于~0x7f7d4af48040,这超出了任何计算机作为内存的范围。您的程序看到的内存与物理内存不同。

如果你的意思是读取超出当前方法的堆栈帧:是的,从技术上讲,你可以安全地这样做。下面是一个例子

void stacktrace(){
std::cerr << "Received SIGSEGV. Stack trace:n";
void** bp;
asm(R"(
.intel_syntax noprefix
mov %[bp], rbp
.att_syntax
)"
: [bp] "=r" (bp));
size_t i = 0;
while(true){
std::cerr << "[" << i++ << "] " << bp[1] << 'n';
if(bp > *bp) break;
bp = (void**) *bp;
}
exit(1);
}

这是我写的一个非常基本的程序,看看我是否可以手动生成堆栈跟踪。如果您不熟悉,这可能不是很明显,但是在x64上,rbp中包含的地址是当前堆栈帧的基础。在c++中,堆栈帧看起来像:

return pointer
previous value of rsp [rsp = stack pointer] <- rbp points here
local variables (may be some other stuff like stack cookie)
...
local variables <- rsp points here

地址越低越小。在我上面给出的例子中,你可以看到我得到了rbp的值,它指向当前堆栈帧之外,并从那里移动。因此,您可以从堆栈帧以外的内存中读取,但通常不应该这样做,即使如此,为什么要这样做呢?

注意:Evg指出了这一点。如果你读取某个对象,超出堆栈可能/可能会触发段错误,这取决于对象类型,所以只有当你非常确定你在做什么时才应该这样做。

如果你不拥有内存,或者你拥有内存但你没有初始化它,你不允许读取它。这似乎是一个迂腐和无用的规则。毕竟,内存是存在的,我并没有试图覆盖任何东西,对吧?朋友间的字节是什么,让我来读一下。

关键是c++是一种高级语言。编译器只尝试解释您编写的代码并将其转换为汇编。如果你输入胡言乱语,你将得到胡言乱语。这有点像强迫别人翻译"问"字。从英语到德语

但这在现实生活中会造成问题吗?我大概知道会生成什么asm指令。何苦呢?

这个视频讲的是facebook的字符串实现中的一个bug,他们读取了一个字节的未初始化内存,他们读取了有,但是它造成了一个很难发现的bug。

关键是,硅不是直观的。不要试图依靠你的直觉。

最新更新