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


当前回答

就ISO C标准(该语言的官方定义)而言,在其边界之外访问数组具有“未定义行为”。字面意思是:

行为,在使用不可移植或错误的程序构造或 错误的数据,本标准没有规定 需求

一个非规范性的注释对此进行了扩展:

可能的未定义行为包括忽略情况 翻译过程中的行为完全无法预测结果 或以文件化的方式执行程序的特点 环境(有或没有发出诊断消息),到 终止翻译或执行(通过发出 诊断消息)。

这就是理论。现实是什么?

在“最佳”情况下,您将访问一些内存,这些内存要么属于当前运行的程序(这可能导致您的程序行为不正常),要么不属于当前运行的程序(这可能导致您的程序因分段错误而崩溃)。或者你可能会尝试写入程序拥有的内存,但它被标记为只读;这也可能导致您的程序崩溃。

That's assuming your program is running under an operating system that attempts to protect concurrently running processes from each other. If your code is running on the "bare metal", say if it's part of an OS kernel or an embedded system, then there is no such protection; your misbehaving code is what was supposed to provide that protection. In that case, the possibilities for damage are considerably greater, including, in some cases, physical damage to the hardware (or to things or people nearby).

即使在受保护的操作系统环境中,保护也不总是100%。例如,有些操作系统错误允许非特权程序获得根(管理)访问权限。即使使用普通用户权限,出现故障的程序也会消耗过多的资源(CPU、内存、磁盘),可能导致整个系统瘫痪。许多恶意软件(病毒等)利用缓冲区溢出来获得对系统的未经授权的访问。

(一个历史上的例子:我听说在一些具有核心内存的旧系统上,在一个紧密循环中重复访问单个内存位置可能会导致内存块融化。其他的可能性包括损坏CRT显示器,以驱动器柜的谐波频率移动磁盘驱动器的读写头,导致它走过桌子,掉到地板上。)

而且还要担心天网。

底线是这样的:如果你可以编写一个程序故意做一些不好的事情,那么至少在理论上,一个有bug的程序可能会意外地做同样的事情。

实际上,在MacOS X系统上运行的有bug的程序不太可能出现比崩溃更严重的情况。但是完全阻止有bug的代码做一些非常糟糕的事情是不可能的。

其他回答

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

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

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

Objective-C中的nsarray被分配一个特定的内存块。超过数组的边界意味着您将访问没有分配给数组的内存。这意味着:

This memory can have any value. There's no way of knowing if the data is valid based on your data type. This memory may contain sensitive information such as private keys or other user credentials. The memory address may be invalid or protected. The memory can have a changing value because it's being accessed by another program or thread. Other things use memory address space, such as memory-mapped ports. Writing data to unknown memory address can crash your program, overwrite OS memory space, and generally cause the sun to implode.

从程序的角度来看,您总是想知道代码何时超出了数组的边界。这可能导致返回未知值,导致应用程序崩溃或提供无效数据。

就ISO C标准(该语言的官方定义)而言,在其边界之外访问数组具有“未定义行为”。字面意思是:

行为,在使用不可移植或错误的程序构造或 错误的数据,本标准没有规定 需求

一个非规范性的注释对此进行了扩展:

可能的未定义行为包括忽略情况 翻译过程中的行为完全无法预测结果 或以文件化的方式执行程序的特点 环境(有或没有发出诊断消息),到 终止翻译或执行(通过发出 诊断消息)。

这就是理论。现实是什么?

在“最佳”情况下,您将访问一些内存,这些内存要么属于当前运行的程序(这可能导致您的程序行为不正常),要么不属于当前运行的程序(这可能导致您的程序因分段错误而崩溃)。或者你可能会尝试写入程序拥有的内存,但它被标记为只读;这也可能导致您的程序崩溃。

That's assuming your program is running under an operating system that attempts to protect concurrently running processes from each other. If your code is running on the "bare metal", say if it's part of an OS kernel or an embedded system, then there is no such protection; your misbehaving code is what was supposed to provide that protection. In that case, the possibilities for damage are considerably greater, including, in some cases, physical damage to the hardware (or to things or people nearby).

即使在受保护的操作系统环境中,保护也不总是100%。例如,有些操作系统错误允许非特权程序获得根(管理)访问权限。即使使用普通用户权限,出现故障的程序也会消耗过多的资源(CPU、内存、磁盘),可能导致整个系统瘫痪。许多恶意软件(病毒等)利用缓冲区溢出来获得对系统的未经授权的访问。

(一个历史上的例子:我听说在一些具有核心内存的旧系统上,在一个紧密循环中重复访问单个内存位置可能会导致内存块融化。其他的可能性包括损坏CRT显示器,以驱动器柜的谐波频率移动磁盘驱动器的读写头,导致它走过桌子,掉到地板上。)

而且还要担心天网。

底线是这样的:如果你可以编写一个程序故意做一些不好的事情,那么至少在理论上,一个有bug的程序可能会意外地做同样的事情。

实际上,在MacOS X系统上运行的有bug的程序不太可能出现比崩溃更严重的情况。但是完全阻止有bug的代码做一些非常糟糕的事情是不可能的。

如果您曾经做过系统级编程或嵌入式系统编程,如果您随机写入内存位置,可能会发生非常糟糕的事情。旧系统和许多微控制器使用内存映射IO,因此写入映射到外设寄存器的内存位置可能会造成严重破坏,特别是如果它是异步完成的。

一个例子是编程闪存。内存芯片上的编程模式是通过将特定的值序列写入芯片地址范围内的特定位置来实现的。如果在此期间另一个进程写入芯片中的任何其他位置,则会导致编程周期失败。

在某些情况下,硬件会将地址环绕起来(地址中最重要的位/字节会被忽略),因此写入超出物理地址空间末端的地址实际上会导致数据在中间写入。

最后,像MC68000这样的老cpu可能会锁定到只有硬件重置才能让它们重新工作的地步。我已经几十年没有使用它们了,但我相信当它在试图处理异常时遇到总线错误(不存在内存)时,它会简单地停止,直到断言硬件重置。

我最大的建议是为一个产品做一个明显的宣传,但我个人对此没有兴趣,我也不以任何方式与他们联系——但基于几十年的C编程和嵌入式系统,可靠性是至关重要的,Gimpel的PC Lint不仅可以检测这类错误,还可以通过不断地唠叨你的坏习惯,让你成为更好的C/ c++程序员。

我还建议你阅读MISRA C编码标准,如果你能从别人那里得到一份的话。我没有看到最近的任何一个,但在过去的日子里,他们给了一个很好的解释,为什么你应该/不应该做他们覆盖的事情。

我不知道你的情况,但当我第二次或第三次从任何应用程序中得到一个coredump或挂起时,我对任何公司的看法都会下降一半。第四次或第五次,不管是什么包装都变成了架子,我用一根木桩穿过包装/光盘的中心,只是为了确保它永远不会回来缠着我。

在测试代码时,您可能想尝试使用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:不过,正如卡兹的回答所说,它不是万能的,并且并不总是提供最有帮助的输出,特别是当您使用令人兴奋的访问模式时。