访问超出其边界的数组(在C语言中)有多危险?有时会发生这样的情况:我从数组外部读取数据(我现在知道我访问了程序的其他部分使用的内存,甚至超出了这个范围),或者我试图将一个值设置为数组外部的索引。程序有时会崩溃,但有时只是运行,只会给出意想不到的结果。

现在我想知道的是,这到底有多危险?如果它损坏了我的程序,那也不算太坏。另一方面,如果它破坏了我程序之外的东西,因为我设法访问了一些完全不相关的内存,那么我想这是非常糟糕的。 我读过很多“任何事情都可能发生”,“细分市场可能是最不坏的问题”,“你的硬盘可能变成粉红色,独角兽可能在你的窗户下唱歌”,这些都很好,但真正的危险是什么?

我的问题:

Can reading values from way outside the array damage anything apart from my program? I would imagine just looking at things does not change anything, or would it for instance change the 'last time opened' attribute of a file I happened to reach? Can setting values way out outside of the array damage anything apart from my program? From this Stack Overflow question I gather that it is possible to access any memory location, that there is no safety guarantee. I now run my small programs from within XCode. Does that provide some extra protection around my program where it cannot reach outside its own memory? Can it harm XCode? Any recommendations on how to run my inherently buggy code safely?

我使用OSX 10.7, Xcode 4.6。


当前回答

在测试代码时,您可能想尝试使用Valgrind中的memcheck工具——它不会捕获堆栈框架内的单个数组边界违规,但它应该捕获许多其他类型的内存问题,包括那些会导致单个函数范围之外的微妙、更广泛的问题的问题。

摘自手册:

Memcheck is a memory error detector. It can detect the following problems that are common in C and C++ programs. Accessing memory you shouldn't, e.g. overrunning and underrunning heap blocks, overrunning the top of the stack, and accessing memory after it has been freed. Using undefined values, i.e. values that have not been initialised, or that have been derived from other undefined values. Incorrect freeing of heap memory, such as double-freeing heap blocks, or mismatched use of malloc/new/new[] versus free/delete/delete[] Overlapping src and dst pointers in memcpy and related functions. Memory leaks.

ETA:不过,正如卡兹的回答所说,它不是万能的,并且并不总是提供最有帮助的输出,特别是当您使用令人兴奋的访问模式时。

其他回答

我只是想为这个问题添加一些实际的例子——想象一下下面的代码:

#include <stdio.h>

int main(void) {
    int n[5];
    n[5] = 1;

    printf("answer %d\n", n[5]);

    return (0);
}

它具有未定义的行为。例如,如果你启用了clang优化(-Ofast),结果会是这样的:

answer 748418584

(如果你没有编译,可能会输出答案1的正确结果)

这是因为在第一种情况下,赋值给1的函数实际上从未在最终代码中组装(您也可以查看godbolt asm代码)。

(然而,必须注意的是,根据这种逻辑,main甚至不应该调用printf,所以最好的建议是不要依赖于优化器来解决你的UB -而是要知道有时它可能会以这种方式工作)

这里的结论是,现代C优化编译器将假设未定义的行为(UB)永远不会发生(这意味着上面的代码将类似于(但不相同):

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n[5];

    if (0)
        n[5] = 1;

    printf("answer %d\n", (exit(-1), n[5]));

    return (0);
} 

相反,这是完全定义的)。

这是因为第一个条件语句从来没有达到它的真状态(0总是假的)。

在printf的第二个参数上,我们有一个序列点,在此之后,我们调用exit,程序在调用第二个逗号操作符中的UB之前终止(因此它的定义很好)。

所以第二个结论是,只要没有求值,UB就不是UB。

此外,我没有看到这里提到有相当现代的未定义行为消毒器(至少在clang)(选项-fsanitize= Undefined)将在第一个例子(但不是第二个)上给出以下输出:

/app/example.c:5:5: runtime error: index 5 out of bounds for type 'int[5]'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /app/example.c:5:5 in 
/app/example.c:7:27: runtime error: index 5 out of bounds for type 'int[5]'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /app/example.c:7:27 in 

以下是godbolt的所有样品:

https://godbolt.org/z/eY9ja4fdh(第一个示例,没有标记)

https://godbolt.org/z/cGcY7Ta9M(第一个示例和-Ofast clang)

https://godbolt.org/z/cGcY7Ta9M(第二个例子和UB消毒剂打开)

https://godbolt.org/z/vE531EKo4(第一个例子和UB消毒剂打开)

你写的:

我读过很多“任何事情都可能发生”,“市场细分可能是。 “最不坏的问题”,“你的硬盘可能会变成粉红色,独角兽也可能 在你的窗下唱歌,这是很好的,但真正的 危险吗?

这么说吧,给枪上膛。瞄准窗外,不要瞄准,然后开火。危险在哪里?

The issue is that you do not know. If your code overwrites something that crashes your program you are fine because it will stop it into a defined state. However if it does not crash then the issues start to arise. Which resources are under control of your program and what might it do to them? I know at least one major issue that was caused by such an overflow. The issue was in a seemingly meaningless statistics function that messed up some unrelated conversion table for a production database. The result was some very expensive cleanup afterwards. Actually it would have been much cheaper and easier to handle if this issue would have formatted the hard disks ... with other words: pink unicorns might be your least problem.

认为操作系统会保护你的想法是乐观的。如果可能,尽量避免越界写作。

不检查边界可能会导致严重的副作用,包括安全漏洞。其中最糟糕的是任意执行代码。在经典示例中:如果您有一个固定大小的数组,并使用strcpy()在那里放置一个用户提供的字符串,用户可以给您一个字符串,该字符串会溢出缓冲区并覆盖其他内存位置,包括函数结束时CPU应该返回的代码地址。

这意味着你的用户可以向你发送一个字符串,这将导致你的程序实质上调用exec(“/bin/sh”),这将把它变成shell,在你的系统上执行任何他想要的东西,包括收集你的所有数据,并将你的机器变成僵尸网络节点。

关于如何做到这一点的详细信息,请参见Smashing The Stack For Fun And Profit。

不以根用户或其他特权用户身份运行程序不会对任何系统造成损害,因此通常这可能是一个好主意。

通过将数据写入某个随机的内存位置,你不会直接“破坏”计算机上运行的任何其他程序,因为每个进程都运行在自己的内存空间中。

如果你试图访问任何没有分配给你的进程的内存,操作系统将停止你的程序执行分割错误。

因此,直接(无需以根用户身份运行并直接访问/dev/mem之类的文件),您的程序不会干扰在您的操作系统上运行的任何其他程序。

尽管如此——这可能是你听说过的危险——盲目地将随机数据写入随机的内存位置,你肯定会损坏任何你能损坏的东西。

例如,您的程序可能希望删除存储在程序某处的文件名所给出的特定文件。如果你不小心覆盖了文件名所在的位置,你可能会删除一个完全不同的文件。

在测试代码时,您可能想尝试使用Valgrind中的memcheck工具——它不会捕获堆栈框架内的单个数组边界违规,但它应该捕获许多其他类型的内存问题,包括那些会导致单个函数范围之外的微妙、更广泛的问题的问题。

摘自手册:

Memcheck is a memory error detector. It can detect the following problems that are common in C and C++ programs. Accessing memory you shouldn't, e.g. overrunning and underrunning heap blocks, overrunning the top of the stack, and accessing memory after it has been freed. Using undefined values, i.e. values that have not been initialised, or that have been derived from other undefined values. Incorrect freeing of heap memory, such as double-freeing heap blocks, or mismatched use of malloc/new/new[] versus free/delete/delete[] Overlapping src and dst pointers in memcpy and related functions. Memory leaks.

ETA:不过,正如卡兹的回答所说,它不是万能的,并且并不总是提供最有帮助的输出,特别是当您使用令人兴奋的访问模式时。