【问题标题】:Can undefined behavior erase the hard drive?未定义的行为可以擦除硬盘驱动器吗?
【发布时间】:2017-09-06 09:19:56
【问题描述】:

来自“Effective C++ 3rd edition by Scott Meyers”:

为了强调未定义行为的结果是不可预测的并且可能非常令人不快,有经验的 C++ 程序员经常说未定义行为的程序会擦除您的硬盘。

在什么情况下会发生这种情况?

例如,访问和写入超出数组范围的位置是否会损坏不属于此 C++ 程序或线程的内存?

【问题讨论】:

  • @ShumailMohy-ud-Din 是的,正确的。通过斯科特迈耶斯
  • 举个例子。有一个功能可以擦除硬盘驱动器。现在,您刚刚为函数指针分配了一些整数,不幸的是,它恰好是擦除硬盘驱动器的函数的地址。如果您通过取消引用函数指针来调用该函数,则会擦除 HDD。
  • 多次调用 UB 我已经擦除了我使用的系统闪存的重要部分。
  • 当它调用 UB 时,我差点用条形码扫描仪给自己做激光眼科手术,而且我认识一个人的显示器起火了。但我从未见过硬盘被擦除。但它可能会发生。
  • 未定义的行为会让恶魔飞出你的鼻子...... - catb.org/jargon/html/N/nasal-demons.html

标签: c++ memory undefined-behavior


【解决方案1】:

可以吗?当然。事实上,发生在我身上。

我编写了删除临时目录的代码。这涉及创建recursive delete <temp directory>\*.* 命令。由于一个错误,<temp directory> 字段并不总是被填写。我们的文件系统代码愉快地执行了recursive delete \*.* 命令。

我的同事注意到他们桌面上的图标突然消失了。拿出两台机器。

【讨论】:

  • 听起来这实际上是一种定义明确(尽管不受欢迎)的行为,因为如果不填写该字段,您将确切地知道它会做什么。
  • 哦,显式创建命令只涉及明确定义的行为。实际的错误是 UB,问题是 UB 如何导致硬盘被删除。好吧,就像这样:一个变量被破坏了,在这种情况下是一个路径变量,而其他无错误的代码现在开始以意想不到的方式表现。这是 UB 的一个普遍问题:它打破了对其他代码的强假设。
  • @Zaibis:使用未初始化的内存未定义的行为。
  • 不,未定义的内存中写入的内容是未定义的,但未定义的行为意味着允许实现做任何事情,它甚至不必再关心您的代码。但是要删除随机的东西,因为你的命令是在未知的东西上删除的。当然无法确定会发生什么,但由于这会导致 UB,它不必等于 UB 的结果。例如,UB 的结果是可执行文件开始下载色情内容而不是删除某些内容。但你的例子可能是(我们不知道,因为它可能是 UB)未知调用的结果
  • @Zaibis:你错了。您可能会想到 unspecified 行为,这是相当无害的。但是读取未初始化的内存绝对不是 unspecified 行为,而是彻底的未定义行为。编译器可能会忽略整个读取。例如。 int i; float f = 0.5; f = i; 可能会将 f 设置为 0.5,这正是因为 i 未初始化。优化器确实会删除此类分配,如果 i 的值只是未指定,这将是不合法的。
【解决方案2】:

如果您考虑到 UB 不仅适用于用户模式代码,而且适用于系统程序员。换句话说,如果您正在编写带有 UB(或其他错误!)的驱动程序代码,您最终可能会写入一块内存,该内存后来被写回作为“整个磁盘数据结构的根” .

我确实在我工作的驱动程序中有一个错误,导致磁盘损坏,因为驱动程序使用过时的指针(释放后使用指针)。如果你运气不好,未使用的内存恰好是文件系统拥有的一个块,所以它会将一些随机垃圾写回磁盘。幸运的是,确定问题所在并不难,我只需要在我的测试系统上重新格式化磁盘一次(在处理驱动程序时,您通常使用两台计算机,一台用于构建代码,一台用于测试上的代码 - 测试机器通常具有最小的安装集,并且通常会相对频繁地重新格式化和重新配置)。

我认为 Scott 的提及不一定意味着这种情况,但如果您的代码足够狂野,它完全有可能导致几乎任何事情发生。包括发现安全系统中的漏洞(参见所有成功的堆栈粉碎漏洞)。要做到这一点,你可能很倒霉,但人们也会时不时地赢得那些超级彩票,所以如果你能做到每周一次或每月一次有几百万次机会的事情,那么一台计算机可以每秒执行数百万次操作可以实现不太可能的事情......

【讨论】:

    【解决方案3】:

    来自 C++11 标准(实际上来自草案 N3337),在第 1.3 节术语和定义 [intro.defs](强调我的):

    未定义的行为
    本国际标准没有要求
    的行为 [注意:当本国际标准省略任何明确的定义时,可能会出现未定义的行为 行为或程序使用错误构造或错误数据时。允许的未定义行为 范围从以不可预测的结果完全忽略情况,到在翻译过程中表现或 以环境特征的文件化方式执行程序(有或没有发布 诊断消息),终止翻译或执行(发出诊断消息)。 许多错误的程序结构不会产生未定义的行为;他们需要被诊断出来。 — 尾注 ]

    从“没有要求”+“不可预测的结果”我们可以得出结论(理论上)任何事情都可能发生

    现在,没有“合理”的编译器会故意发出代码来擦除硬盘驱动器,例如除以 0,但是如果你弄乱了它可能会发生文件系统,或者实际上,如您所说,如果您损坏了内存(edit: 请参阅MSalters' comment on their own answer)。

    这里的重点是:始终小心永远不要调用未定义的行为。 “这里是龙。”

    (在实践中,很难确定您的程序是否定义明确。有一些建议。熟悉您的语言,并远离尘土飞扬的角落。如果一段代码看起来可疑或过于复杂,请尝试重写它以使其更简单和更清晰。始终使用最高级别的警告进行编译,不要忽略它们。还有像 -fcatch-undefined-behavior 这样的编译器标志和像 lint 这样的工具可以提供帮助。当然还有测试,但这有点晚了。)

    【讨论】:

    • 除以零之类的东西并不像看起来那么无害。许多处理器都有一个“除以零”陷阱,一些操作系统将允许用户代码为其安装处理程序。如果一个例程安装了一个除零处理程序,该处理程序修改了该例程的一个自动变量并返回,并且该例程在仍然安装的处理程序的情况下退出,那么在另一个完全不同的例程中发生的除零可能会破坏一些任意在继续执行之前堆栈上的值,导致无限的肮脏。
    【解决方案4】:

    理论上,内存冲突会导致您的程序执行错误的代码。如果您非常不走运,则可能是代码删除了硬盘上的内容。我怀疑它不太可能走那么远,除非您自己处理低级磁盘操作。

    我认为该声明的重点是您需要非常认真地对待未定义的行为,并尽一切可能防范它(即防御性编程)。我见过太多糟糕的程序员天真地依赖一些未定义的行为,假设它会一直工作。在实践中,这是不可预测的,有时结果可能是灾难性的。

    【讨论】:

      【解决方案5】:

      一个简单的例子是你碰巧损坏了你正在写入的块号,或者你要删除的文件名。

      【讨论】:

        【解决方案6】:

        是的。

        考虑一个处理外部输入的应用程序(例如 Web 应用程序的一个组件)并且存在缓冲区溢出,这是一种相当常见的未定义行为类型。

        攻击者注意到了这一点,并故意制作了删除所有数据的输入。 (大多数攻击者实际上并没有这样做:他们想要做的是检索您的数据,或在您的网站上植入内容。但偶尔有些人确实想删除您的文件。)

        损害的最大程度取决于攻击者能够绕过哪些安全层。如果服务器没有安全配置,或者攻击者可以利用其他漏洞,那么攻击者可能能够获得该机器的管理员权限或将其用作攻击其他机器的中继。所有这一切都来自一个缓冲区溢出。

        要从中吸取的教训是,未定义的行为不仅仅与可能发生的事情有关。不仅会发生您意想不到的事情(一些编译器非常擅长拾取奇怪的优化,这些优化仅在变量在序列点之间没有被修改两次时才正确,否则会做一些非常令人惊讶的事情),而且事情可能会发生在数学上是极不可能的,因为有人故意不遗余力地让它们发生。

        【讨论】:

          【解决方案7】:

          Linux中,当你是root用户时,任何操作都是有效的。甚至破坏你的根文件系统。 rm -rf /

          当您是root 时,每个代码段(有错误)都会愉快地执行。所有 UB 都被假定为具有sudo 权限。

          【讨论】:

          • 我不确定这是解释这一点的最佳方式。并不是有意赋予未定义的行为根源,它只是……未定义。从理论上讲,非 root 未定义行为应该不能做任何常规非 root 代码不能做的事情,但如果你已经不走运了......好吧,请参阅上面@Mat 的故事。
          【解决方案8】:

          [这个答案晚了四年。谁会读它?我们拭目以待。]

          呃,没有冒犯,但根据我的经验,其他几个答案是错误的或至少具有误导性。

          C++ 标准不限制未定义的行为。然而,操作系统通常会限制它。原因:行为未定义相对于 C++。

          ...永远小心不要调用未定义的行为。

          废话。经验掩盖了这个建议。 C++ 程序员经常在测试期间无意中调用未定义的行为。有时我是故意这样做的,只是为了看看会发生什么。

          现在,我意识到有人认为我在这里炫耀鲁莽,但实际上,您的笔记本电脑不太可能因未定义行为而不是已定义行为而着火。 C++ 中的未定义行为发出带有 已定义 行为的汇编代码。考虑一下。装配行为保持定义。只是 C++ 标准不再了解机制。

          有时你想激发未定义的行为只是为了看看堆栈上发生了什么。

          如果您所处的环境可以编写一个已定义 C++ 程序使您的笔记本电脑着火,那么无论如何您都必须小心;但这种情况下的主要问题是缺乏基于硬件和/或内核的保护。

          总之,不要让 C++ 标准让您感到困惑。它只是告诉你它自己的能力限制是什么。

          【讨论】:

          • 您所描述的哲学恕我直言,适合编写高质量通用编译器的人,但现代优化器允许 UB 将时间和因果关系抛到窗外。如果编译器能够计算出 x 不是 42 的任何状态都会产生 UB,那么它可能(并且据我所知,根据某些人的哲学,应该)生成表现得好像 x 是 42 的代码。如果一个程序当 x 为 42 时会做一些危险的事情,并且依赖于安全代码来防止 x 在不应该的时候变为 42,这样的优化可能会完全绕过所有安全措施。
          猜你喜欢
          • 2012-11-03
          • 2011-08-06
          • 1970-01-01
          • 2014-10-11
          • 2011-06-10
          • 2011-02-07
          • 2016-10-31
          • 2012-02-08
          • 2018-04-14
          相关资源
          最近更新 更多