哪些整数运算是不安全的?

Which integer operations are unsafe?

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

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

问题是:在计算这样的表达式时可能会发生什么错误而使我的程序终止?我想到了其中两个:

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

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

这里有一个很好的参考: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?

不是操作本身不安全。这是 undefined behavior that is the problem. That way (almost) all the operators can participate in causing UB, although you will probably get there using the arithmetic operators 的有符号整数溢出。长话短说:不要让有符号整数溢出/调用 UB。

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

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

只有算术运算符(您列表中的前五个)受这些问题中的任何一个影响,所有其他运算符对所有输入都有 well-defined 行为。

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

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

例如,至少有一个 hole in OpenSSH 与整数溢出直接相关,而这个甚至不涉及任何 "crazy" 编译器优化,或者就此而言,任何优化完全没有。

最后,像 UBSAN(Clang/GCC 中的未定义行为消毒剂)这样的东西存在。如果你允许有符号整数在一个地方溢出,并​​试图从 UBSAN 获得有意义的结果,你可能会遇到意想不到的陷阱 and/or 太多误报。

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

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

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

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

  • A) 终止(与 -ftrapv 一样)
  • B) 继续并可能执行 shellcode(与 -fwrapv 一样)

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

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