在数组边界之外(在C中)访问数组有多危险?有时可能会发生从数组外部读取的错误(我现在知道我可以访问程序的其他部分甚至更多空间使用的内存),或者我试图为数组外部的索引设置值。该程序有时会崩溃,但有时只能运行,只会产生意外的结果。

现在我想知道的是,这真的有多危险?如果它破坏了我的程序,那还不错。另一方面,如果它由于我设法以某种方式设法访问了一些完全不相关的内存而破坏了程序外部的某些内容,那么我想它是非常糟糕的。
我读到很多“可能发生的事情”,“分段可能是最不严重的问题”,“您的硬盘可能变成粉红色,独角兽可能正在窗下唱歌”,这一切都很好,但是真正的危险是什么?

我的问题:


从程序外读取值是否会损坏程序之外的任何内容
?我会想象只是看东西不会改变任何东西,还是会例如改变我碰巧到达的文件的“上次打开时间”属性?
设置值是否可以解决?除了我的程序以外,阵列之外的东西是否损坏?通过这个
堆栈溢出问题,我收集到可以访问
任何内存位置,并且没有安全保证的原因。那
是否在我的程序周围提供了一些额外的保护,使得它不能
超出其自身的内存?它会损害XCode吗?
关于如何安全运行我的固有错误代码的任何建议?

我使用OSX 10.7,Xcode 4.6。

评论

通常,操作系统会保护自己和其他进程免受恶意行为的侵害。不过,这并不是您一定要严重依赖的东西。

同样,当访问和数组索引超出范围时(在ram中),您将永远不会“碰到”硬盘上的文件。

我相信你在问C数组,对不对?因此那与ObjC无关,并且与任何IDE都没有真正的联系。

这是我最喜欢的奇怪结果示例(它处理堆栈,但我发现它确实很有启发性...)。

xkcd.com/371

#1 楼

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


使用非便携式或错误程序构造或错误数据
的行为,而本国际标准不对此施加任何强制性
要求


对此进行了非规范性注释:


可能的不确定行为包括完全忽略情况以产生无法预测的结果,在翻译或程序执行过程中以
环境的特征记录的方式运行(无论是否发出诊断消息),以
终止翻译或执行(发布时)的诊断消息)。


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

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

假设您的程序在试图保护同时运行的进程彼此之间的操作系统下运行。如果您的代码在“裸机”上运行,比如说它是OS内核或嵌入式系统的一部分,则没有这种保护;您行为不当的代码就是应该提供这种保护的内容。在这种情况下,损坏的可能性会更大,包括在某些情况下对硬件(或附近的东西或人)的物理损坏。

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

(一个历史示例:我听说在某些具有核心内存的旧系统上,反复访问一个紧密的循环中的单个内存位置实际上可能导致该内存块融化;其他可能性包括破坏CRT显示屏,并以驱动器柜的谐波频率移动磁盘驱动器的读/写磁头,从而导致其穿越天台掉下来。)

总是有天网担心。

最重要的是:如果您可以编写程序来故意做一些不好的事情,至少从理论上讲,一个bug程序有可能意外地做同样的事情。

在实践中,运行在MacOS X系统上的bug程序不可能做比崩溃更严重的事情。但是不可能完全防止错误的代码做真正的坏事。

评论


谢谢,我实际上完全理解这一点。但是,这立即引发了一个后续问题:新手程序员可以做什么,以保护他/她的计算机免受他/她自己可能可怕的创作的侵害?在对程序进行全面测试之后,我可以将其发布到全世界。但是第一次尝试运行肯定是一个错误的程序。你们如何使您的系统免受安全威胁?

– ChristD
13年3月26日在21:27



@ChrisD:我们很幸运。 8-)}认真的说,这些天操作系统级别的保护非常好。最坏的情况是,如果我写了一个意外的叉子炸弹,我可能必须重新启动才能恢复。但是,只要您的程序不试图在危险的边缘上做某事,对系统的实际损坏就不必担心。如果您真的很担心,那么在虚拟机上运行程序可能不是一个坏主意。

–基思·汤普森(Keith Thompson)
13年3月26日在21:32

另一方面,我已经看到许多奇怪的事情在我使用的计算机上发生(损坏的文件,不可恢复的系统错误等),而且我不知道其中有多少可能是由于某些C程序的展示引起的可怕的不确定行为。 (到目前为止,还没有真正的恶魔从我的鼻子里飞出来。)

–基思·汤普森(Keith Thompson)
13年3月26日在21:33

感谢您教我叉炸弹-尝试掌握递归时,我所做的工作很接近:)

– ChristD
13年3月26日在21:40

scienceamerican.com/article/…因此,现代电子设备仍可能引起火灾。

–鸭鸭
14年7月22日在23:19

#2 楼

通常,当今的操作系统(无论如何还是流行的操作系统)都使用虚拟内存管理器在受保护的内存区域中运行所有应用程序。事实证明,简单地读写在已分配/分配给您的进程的区域之外的REAL空间中存在的位置并不是很容易的事情。

直接答案:

1)读取几乎永远不会直接破坏另一个进程,但是,如果您碰巧读取用于加密,解密或验证程序/进程的KEY值,则读取可能会间接破坏进程。如果您根据所读取的数据做出决定,则超出范围的读取可能会对您的代码产生一些不利/意想不到的影响

2)只有这样,您才能通过写入可访问的位置来真正破坏某些东西如果您要写入的内存地址实际上是硬件寄存器(实际上不是用于数据存储而是用于控制某些硬件的位置),而不是RAM位置,则表示内存地址。实际上,除非您编写的是一次性不可写的可编程位置(或类似性质的东西),否则您仍然通常不会损坏某些东西。

3)通常在调试器内部运行调试模式下的代码。当您执行了一些被认为不可行或彻头彻尾的非法操作时,在调试模式下运行确实会使TEND(但并非总是)更快地停止代码。

4)切勿使用宏,使用已具有数组的数据结构内置索引范围检查等。...

ADDITIONAL
我应该补充一点,以上信息实际上仅适用于使用带有内存保护窗口的操作系统的系统。如果为嵌入式系统或什至使用没有内存保护窗口(或虚拟寻址窗口)的操作系统(实时或其他)的系统编写代码,则在读写内存时应多加注意。同样在这种情况下,应始终采用SAFE和SECURE编码做法,以避免安全问题。

评论


应始终采用安全的编码惯例。

– Nik Bougalis
13年3月26日在21:05

我建议不要对错误的代码使用try / catch,除非您捕获到非常具体的异常并知道如何从异常中恢复。 Catch(...)是您可以添加到错误代码中的最糟糕的事情。

– Eugene
2013年3月26日21:05



@NikBougalis-我完全同意,但是如果操作系统不包括内存保护/虚拟地址空间,或者缺少操作系统,则更为重要:-)

–trumpetlicks
13年3月26日在21:07

@Eugene-我从来没有注意到这对我来说是个问题,但是我同意你的看法,我是否已将其删除:-)

–trumpetlicks
13年3月26日在21:09

1)你的意思是损害,因为我会透露本应保密的东西? 2)我不确定我明白你的意思,但是我想我只是在尝试访问数组边界之外的位置而访问RAM?

– ChristD
2013年3月26日21:13



#3 楼

不检查边界会导致难看的副作用,包括安全漏洞。丑陋的事情之一是任意代码执行。在经典示例中:如果您有一个固定大小的数组,并使用strcpy()在此处放置用户提供的字符串,则用户可以给您一个字符串,该字符串使缓冲区溢出并覆盖其他内存位置,包括当您的计算机返回时应返回的代码地址函数完成。

这意味着您的用户可以向您发送一个字符串,该字符串将导致您的程序实质上调用exec("/bin/sh"),这会将其转换为Shell,执行他在系统上执行的所有操作,包括收集所有数据并将您的机器变成僵尸网络节点。

有关如何做到这一点的详细信息,请参见粉碎堆栈以获取乐趣和收益。

评论


我知道我不应该超出界限访问数组元素,这要感谢您的支持。但是问题是,除了对程序造成各种伤害之外,我是否还会无意中超出程序的存储范围?我的意思是在OSX上。

– ChristD
13年3月27日在22:47

@ChrisD:OS X是现代操作系统,因此它将为您提供完整的内存保护。例如。您不应局限于允许您的程序执行的操作。这不应该包括与其他进程的混乱(除非您以root特权运行)。

–che
13年3月28日在10:53



我宁愿说环0特权,而不是root特权。

–俄罗斯
15年7月10日在9:20

更有趣的是,超现代的编译器可能会决定,如果代码先前尝试过根据数组长度检查len来执行或跳过一段代码,然后尝试读取foo [0]至foo [len-1],则编译器应该随意地无条件运行其他代码,即使应用程序拥有超过数组的存储空间并且读取它的效果本来是良性的,但调用其他代码的效果也不会。

–超级猫
16年6月23日在16:38

#4 楼

您写道:


我读到很多'可能发生的事情','分段可能是
最不严重的问题','您的硬盘可能变成粉红色而独角兽可能
在窗户下唱歌”,这一切都很好,但真正的危险是什么?


让我们这样说:装上枪。将其指向窗户外,没有任何特定目标和火力。有什么危险?

问题是您不知道。如果您的代码覆盖了使您的程序崩溃的内容,那么您会很好,因为它将使程序停止进入已定义状态。但是,如果它没有崩溃,则问题开始出现。哪些资源受程序控制,它可能对它们做什么?哪些资源可能会受到您程序的控制,它可能会对它们做什么?我知道至少有一个主要问题是由这种溢出引起的。问题出在看似毫无意义的统计功能中,该功能弄乱了生产数据库的一些不相关的转换表。结果是随后进行了一些非常昂贵的清理。实际上,如果此问题格式化了硬盘,则本来便宜很多,也更容易处理……换句话说,粉红色独角兽可能是您遇到的最小问题。

您的操作系统将保护你是乐观的。如果可能,请尝试避免超出范围。

评论


好的,这正是我所担心的。我将“尽力避免超出范围”,但是,鉴于最近几个月来我一直在做的事情,我肯定会继续做很多事情。如果没有安全的练习方法,你们是如何在编程方面如此出色的?

– ChristD
2013年3月26日21:05



谁说过任何事都是安全的;)

– Udo Klein
13年3月27日在6:39

#5 楼

不以root用户或任何其他特权用户身份运行程序不会损害您的系统,因此通常这是个好主意。

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

如果尝试访问未分配给进程的任何内存,操作系统将停止您的程序的执行分段错误。

因此,直接(无需以root用户身份运行和直接访问/ dev / mem之类的文件)就不会存在程序干扰操作系统上运行的任何其他程序的危险。 />
尽管如此-也许这是您在危险方面所听到的-意外地将随机数据盲目地写入随机存储器中,您肯定会损坏任何可能损坏的东西。

例如,您的程序可能想要删除由存储在程序中某个位置的文件名指定的特定文件。如果不经意间只是覆盖了文件名的存储位置,则可以删除一个完全不同的文件。

评论


但是,如果您以root用户(或其他特权用户)身份运行,请当心。缓冲区和阵列溢出是常见的恶意软件利用。

–约翰·博德
13 Mar 26 '13在20:52

实际上,我用于所有日常计算的帐户不是管理员帐户(我使用OSX术语,因为这是我的系统)。您是说告诉我,我无法通过尝试设置任何内存位置来破坏某些内容吗?这实际上是个好消息!

– ChristD
13年3月26日在21:08

如前所述,您可能偶然造成的最严重伤害就是您作为用户可能造成的最严重伤害。如果您想100%确保不破坏任何数据,则可能要在计算机上添加其他帐户并进行试验。

– mikyra
13年3月26日在21:14

@mikyra:仅当系统的保护机制有效100%时才是正确的。恶意软件的存在表明您不能始终依靠它。 (我不想暗示这一定值得担心;程序可能(但不太可能)意外地利用了恶意软件利用的相同安全漏洞。)

–基思·汤普森(Keith Thompson)
2013年3月26日22:25



这里的列表包括:来自不受信任来源的运行代码。只需在防火墙的任何弹出窗口上单击“确定”按钮,甚至都不会读它的内容,或者在无法建立所需的网络连接时完全将其关闭。使用来自可疑来源的最新hack修补二进制文件。如果拥有者自愿邀请任何一个带双臂并开着坚固的强化门的防盗,这不是金库的错。

– mikyra
13 Mar 26 '13 at 22:52

#6 楼

在Objective-C中为NSArray分配了特定的内存块。超出数组的界限意味着您将访问未分配给数组的内存。这意味着:


该内存可以具有任何值。无法根据您的数据类型知道数据是否有效。
此内存可能包含敏感信息,例如私钥或其他用户凭据。
该内存地址可能无效或受保护。
/>内存的值可能会发生变化,因为它正在被另一个程序或线程访问。
其他东西会使用内存地址空间,例如内存映射的端口。
将数据写入未知的内存地址可能会导致崩溃

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

评论


NSArrays具有超出范围的异常。这个问题似乎与C数组有关。

– DrummerB
13年3月26日在20:59

我确实是说C数组。我知道有NSArray,但目前我的大部分练习都使用C语言

– ChristD
13年3月26日在21:04

#7 楼

您可能需要在测试代码时尝试使用Valgrind中的memcheck工具-它不会捕获堆栈框架内单个数组边界冲突,但是它应该可以捕获许多其他类型的内存问题,包括那些可能导致细微的内存问题,单个功能范围以外的其他问题。
从手册:

Memcheck是一种内存错误检测器。它可以检测到C和C ++程序中常见的以下问题。

访问您不应该访问的内存,例如
使用未定义的值,即未初始化的值或从其他未定义的值派生的值。 />不正确的释放堆内存,例如两次释放堆块,或者不正确使用malloc / new / new []与free / delete / delete []
在memcpy和相关函数中重叠src和dst指针。
内存泄漏。


ETA:尽管,正如Kaz的回答所说,它不是灵丹妙药,也不总是提供最有用的输出,尤其是当您使用时令人兴奋的访问模式。

评论


我怀疑XCode的分析器会找到大部分?我的问题不是如何找到这些错误,而是执行仍然存在这些错误的程序对于未分配给我的程序的内存很危险。我将必须执行程序才能看到错误发生

– ChristD
13年3月27日在10:24

#8 楼

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

一个示例是对闪存进行编程。通过将特定的值序列写入芯片地址范围内的特定位置,可以启用存储芯片上的编程模式。如果在此过程中另一过程要写入芯片中的任何其他位置,则将导致编程周期失败。

在某些情况下,硬件会将地址换行(最高有效位/地址字节将被忽略),因此写入超出物理地址空间末尾的地址实际上将导致数据被写入中间事物。

最后,像MC68000这样的旧式CPU可以锁定到只有硬件重置才能使它们重新运行的程度。几十年来没有对它们进行任何操作,但是我相信这是在尝试处理异常时遇到总线错误(不存在的内存)的情况,它只会暂停直到硬件复位被断言为止。

我最大的建议是为产品提供公然的插件,但我对此没有任何个人兴趣,并且我与它没有任何关系-而是基于数十年的C编程和嵌入式系统,其中可靠性至关重要,Gimpel的PC Lint不仅会检测到此类错误,而且还会不断地向您宣传不良习惯,从而使您成为一个更好的C / C ++程序员。

如果您可以从某人那里获取副本,我还建议您阅读MISRA C编码标准。我最近没看过任何东西,但是在过去,他们很好地解释了为什么/不应该做他们报道的事情。

Dunno关于您,但是大约第二或第三次我从任何应用程序中获得核心转储或挂断时,我对生产该产品的任何公司的看法下降了一半。第4或第5次,无论包装是什么,都变成了架子餐具,我用木桩穿过包装/光盘的中心,以确保它永远不会再次困扰我。

评论


根据系统的不同,超出范围的读取也可能会触发不可预测的行为,或者可能是良性的,尽管超出范围的负载的良性硬件行为并不意味着良性的编译器行为。

–超级猫
16年6月23日在16:28

#9 楼

我正在与用于DSP芯片的编译器一起工作,该编译器有意生成从C代码中访问数组末尾的代码,而C代码则不会!

这是因为循环的结构使得迭代结束时为下一次迭代预取一些数据。因此,在最后一次迭代结束时预取的数据实际上从未被使用过。

这样的C代码的编写会调用未定义的行为,但这只是来自标准文档的形式,该文档本身具有最大的可移植性。

更常见的是,不能巧妙地优化访问范围的程序。这简直是​​越野车。该代码获取一些垃圾值,并且与上述编译器的优化循环不同,该代码随后在后续计算中使用该值,从而破坏了主题。

值得捕获这样的错误,因此值得仅出于这种原因就使行为无法定义:这样,运行时就可以生成诊断消息,例如“ main.c第42行中的数组溢出”。

在具有虚拟内存的系统上,可能恰好分配了一个数组,使得其后的地址位于虚拟内存的未映射区域中。然后访问将轰炸程序。



另外,请注意,在C语言中,我们允许创建一个指针,该指针位于数组末尾。而且此指针必须比指向数组内部的任何指针都更大。
这意味着C实现无法将数组直接放在内存末尾,在内存末尾,一个加号将环绕并看起来小于数组中的其他地址。


但是,即使未最大程度地移植,访问未初始化或超出范围的值有时仍是一种有效的优化技术。例如,这就是为什么Valgrind工具不会在未初始化的数据发生访问时报告该访问,而是仅在以后以某种可能会影响程序结果的方式使用该值时才报告对未初始化数据的访问的原因。您会得到类似“ xxx:nnn中的条件分支取决于未初始化的值”的诊断信息,有时可能很难找到它的起源。如果所有这些访问都被立即捕获,那么编译器优化的代码以及正确的手工优化的代码将产生许多误报。

说到这一点,我正在使用来自移植到Linux并在Valgrind下运行时会释放这些错误的供应商。但是供应商说服我,实际上所使用的值只有几位来自未初始化的内存,逻辑上仔细地避免了这些位。.仅使用了值的好位,而Valgrind没有能力跟踪到单个位。未初始化的材料来自读取已编码数据的位流末尾的单词,但是代码知道该流中有多少位,并且不会使用比实际更多的位。由于超出位流阵列末尾的访问不会对DSP架构造成任何损害(阵列之后没有虚拟内存,没有内存映射端口,并且地址没有包装),因此这是一种有效的优化技术。

“未定义行为”的含义并不多,因为根据ISO C,仅包括未在C标准中定义的标头,或者调用在程序本身或C标准中未定义的函数,都是未定义的示例。行为。未定义的行为并不意味着“地球上没有任何人定义”,而只是“ ISO C标准未定义”。但是当然,有时未定义的行为实际上绝对不是任何人都定义的。

评论


此外,只要存在至少一个程序,即使该程序名义上对标准中规定的所有实施限制进行了征税,该程序也可以正确执行,该程序在馈入不受约束约束的任何其他程序时仍可以任意执行,合规”。因此,有99.999%的C程序(平台的“一个程序”以外的任何程序)都依赖于标准没有要求的行为。

–超级猫
16年7月11日在17:12

#10 楼

除了您自己的程序外,我认为您不会破坏任何内容,在最坏的情况下,您将尝试从与内核未分配给您的过程的页面相对应的内存地址进行读取或写入,从而生成适当的异常并被杀死(我是说,您的过程)。

评论


..什么?如何在您自己的进程中覆盖内存,该进程用于存储稍后使用的某些变量……现在它神秘地改变了它的值!我向您保证,这些错误很有趣。分段故障将是最好的结果。 -1

– Ed S.
2013年3月26日20:59



我的意思是,除了他自己的程序外,他不会“破坏”其他过程;)

– jbgs
2013年3月26日21:00

我确实不在乎是否破坏自己的程序。我只是在学习,如果我访问数组范围之外的任何内容,该程序显然是错误的。我只是越来越担心调试我的作品时会破坏其他东西的风险

– ChristD
13年3月26日在21:09

问题是:如果我尝试访问未分配给我的内存,可以确定我的进程将被杀死吗? (在OSX上)

– ChristD
13年3月26日在21:21

多年前,我曾经是一个笨拙的C程序员。我无数次访问了数组。除了我的进程被操作系统杀死之外,什么都没有发生。

– jbgs
13年3月26日在21:22