【问题标题】:Which integer operations are unsafe?哪些整数运算不安全?
【发布时间】:2018-11-23 00:35:32
【问题描述】:

我的应用程序计算用户指定的一些整数表达式。我想检测所有潜在的错误并报告它们。

所有计算都在int64_t(签名)中完成。公式可能包含几乎所有 C++ 二元运算符(+-*/%|||&&& 和六个比较运算符)和整数(可能是负数)。

问题是:在评估可能导致我的程序终止的表达式时,可能会发生哪些错误?我想出了其中两个:

  1. 除以零(或模数)
  2. std::numeric_limits<int64_t>::min() 除以 -1。

也可能发生有符号整数溢出,但据我所知,在这种设置下,它不会对大多数 CPU 造成任何有害影响,因此我们忽略它。

【问题讨论】:

  • CPU 不(直接)相关。你的 CPU 不知道 C++。重要的是编译器对您的代码做了什么,它很可能会将其转换为仅在不发生溢出时才有效的东西。
  • 有符号整数溢出导致未定义行为。它可能是无害的,也可能会召唤nasal demons
  • IIRC 转移负数也有一个棘手的部分,但无论如何你都不允许<< / >>
  • @KavehVahedipour 运算符用于内置类型?扔?..
  • @IvanSmirnov 他们可能会。如果您非常担心非法数学运算,您绝对应该关心 UB,即溢出。

标签: c++ arithmetic-expressions


【解决方案1】:

这是一个很好的参考:https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow

正如它所解释的,有符号整数溢出是未定义的行为。您可能认为这无关紧要,因为您已经观察到 INT64_MAX + x 在您的特定系统上没有做任何奇怪的事情。你可能还认为它永远不会做任何奇怪的事情,因为优化器无法知道x 的值。

但是未定义的行为仍然是未定义的,并且在许多其他可能的结果中,某些平台可能会终止您的程序(您说过要避免),因为它们在硬件中实现了溢出捕获或算术异常。

要编写一个对有符号整数进行算术运算的符合标准的 C++ 程序,您必须首先检查它们的值。一种可能足够好的便宜且简单的方法是在添加或减去之前检查每个整数是否在[INT64_MIN/2, INT64_MAX/2] 内。更详细的方法见这里:How to detect integer overflow?

【讨论】:

    【解决方案2】:

    并不是操作本身不安全。这是有符号整数溢出undefined behavior,这就是问题所在。这样(几乎)所有操作员都可以参与导致 UB,尽管您可能会使用arithmetic operators 到达那里。长话短说:不要让有符号整数溢出/调用 UB。

    【讨论】:

      【解决方案3】:

      总结之前的观察,与您的操作列表相关的未定义行为的可能原因有两个:

      • 除以零
      • 溢出(无论是负的还是正的)——你否定std::numeric_limits<int64_t>::min()的例子——无论是通过除法还是其他方式——只是一个例子。

      只有算术运算符(列表中的前五个)会受到这些问题中的任何一个的影响,所有其他的所有输入都有明确定义的行为。

      我想要做的是扩展整数溢出和未定义行为的危险。首先,我强烈建议您观看 Piotr Padlewski 的 Undefined Behavior is Awesome 和 Chandler Carruth 的 Garbage In, Garbage Out 演讲。

      此外,请考虑整数溢出如何成为 CVE(软件漏洞报告)中反复出现的主题。整数溢出本身通常不会造成直接损坏,但溢出会导致许多其他问题。你可以把溢出物比作针刺,它本身基本上是无害的,但可以帮助危险的毒素和细菌绕过你身体的免疫系统。

      例如,至少有一个hole in OpenSSH 与整数溢出直接相关,而这个甚至没有涉及任何“疯狂”的编译器优化,或者,就此而言,根本没有任何优化。

      最后,存在诸如 UBSAN(Clang/GCC 中的未定义行为清理器)之类的东西。如果您在一处允许有符号整数溢出,并尝试从 UBSAN 获得有意义的结果,您可能会遇到意外陷阱和/或太多误报。

      TL;DR:避免所有未定义的行为。

      John Zwinck 提到添加范围检查作为补救措施,小心避免任何会溢出的中间操作。假设你只需要支持 GCC,如果你觉得懒惰的话,还有两个命令行选项对你有很大帮助:

      • -ftrapv 会导致有符号整数溢出陷阱。
      • -fwrapv 将导致有符号整数在溢出时换行。

      哪个更安全?实际上,这在很大程度上取决于您的应用程序域。您的意见似乎是,崩溃的机会越少,就等于“更安全”。但是,请考虑上面提到的 OpenSSH 漏洞。当从远程客户端提供垃圾数据和可能的 shellcode 时,您希望 SSH 服务器做什么?

      • A) 终止(-ftrapv 会发生这种情况)
      • B) 继续并可能执行 shellcode(就像-fwrapv 一样)

      我很确定大多数管理员会选择 A),如果要终止的进程不是侦听实际套接字的进程,而是专门 fork()ed 来处理当前连接,则更是如此,所以甚至没有太多的 DoS。换句话说,虽然-fwrapv 为您提供已定义 行为,但并不一定意味着该行为在使用时是预期,因此是“安全的”。

      另外,我建议您避免在脑海中做出错误的二分法,例如进程崩溃与继续处理垃圾数据。您可以从范围广泛的错误处理策略中进行选择如果您添加了正确的检查,无论是使用特殊返回值还是异常处理,以安全地摆脱紧张的空间,而不必完全停止服务请求。

      【讨论】:

        猜你喜欢
        • 2011-09-27
        • 2021-06-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-27
        • 1970-01-01
        • 2011-08-28
        相关资源
        最近更新 更多