访问超出其边界的数组(在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。


当前回答

一般来说,现在的操作系统(流行的操作系统)使用虚拟内存管理器在受保护的内存区域中运行所有应用程序。事实证明,简单地读取或写入存在于已分配给进程的区域之外的REAL空间中的位置(就其本身而言)并不容易。

直接回答:

Reading will almost never directly damage another process, however it can indirectly damage a process if you happen to read a KEY value used to encrypt, decrypt, or validate a program / process. Reading out of bounds can have somewhat adverse / unexpected affects on your code if you are making decisions based on the data you are reading The only way your could really DAMAGE something by writing to a loaction accessible by a memory address is if that memory address that you are writing to is actually a hardware register (a location that actually is not for data storage but for controlling some piece of hardware) not a RAM location. In all fact, you still wont normally damage something unless you are writing some one time programmable location that is not re-writable (or something of that nature). Generally running from within the debugger runs the code in debug mode. Running in debug mode does TEND to (but not always) stop your code faster when you have done something considered out of practice or downright illegal. Never use macros, use data structures that already have array index bounds checking built in, etc....

ADDITIONAL I should add that the above information is really only for systems using an operating system with memory protection windows. If writing code for an embedded system or even a system utilizing an operating system (real-time or other) that does not have memory protection windows (or virtual addressed windows) that one should practice a lot more caution in reading and writing to memory. Also in these cases SAFE and SECURE coding practices should always be employed to avoid security issues.

其他回答

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

#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消毒剂打开)

除了你自己的程序,我不认为你会破坏任何东西,在最坏的情况下,你会尝试从一个内存地址读取或写入一个页面,内核没有分配给你的进程,产生适当的异常并被杀死(我的意思是,你的进程)。

一般来说,现在的操作系统(流行的操作系统)使用虚拟内存管理器在受保护的内存区域中运行所有应用程序。事实证明,简单地读取或写入存在于已分配给进程的区域之外的REAL空间中的位置(就其本身而言)并不容易。

直接回答:

Reading will almost never directly damage another process, however it can indirectly damage a process if you happen to read a KEY value used to encrypt, decrypt, or validate a program / process. Reading out of bounds can have somewhat adverse / unexpected affects on your code if you are making decisions based on the data you are reading The only way your could really DAMAGE something by writing to a loaction accessible by a memory address is if that memory address that you are writing to is actually a hardware register (a location that actually is not for data storage but for controlling some piece of hardware) not a RAM location. In all fact, you still wont normally damage something unless you are writing some one time programmable location that is not re-writable (or something of that nature). Generally running from within the debugger runs the code in debug mode. Running in debug mode does TEND to (but not always) stop your code faster when you have done something considered out of practice or downright illegal. Never use macros, use data structures that already have array index bounds checking built in, etc....

ADDITIONAL I should add that the above information is really only for systems using an operating system with memory protection windows. If writing code for an embedded system or even a system utilizing an operating system (real-time or other) that does not have memory protection windows (or virtual addressed windows) that one should practice a lot more caution in reading and writing to memory. Also in these cases SAFE and SECURE coding practices should always be employed to avoid security issues.

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

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

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

因此,直接(无需以根用户身份运行并直接访问/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:不过,正如卡兹的回答所说,它不是万能的,并且并不总是提供最有帮助的输出,特别是当您使用令人兴奋的访问模式时。