哪些整数操作不安全

Which integer operations are unsafe?

本文关键字:不安全 操作 整数      更新时间:2023-10-16

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

所有计算均以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]范围内。 有关更详细的方法,请参阅此处: 如何检测整数溢出?

操作本身并不是不安全的。问题是有符号整数溢出,这是未定义的行为。这样(几乎)所有运算符都可以参与导致 UB,尽管您可能会使用算术运算符到达那里。长话短说:不要让有符号整数溢出/调用 UB。

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

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

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

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

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

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

最后,像UBSAN(Clang/GCC中未定义的行为清理器)这样的东西存在。如果允许在一个位置进行有符号整数溢出,并尝试从 UBSAN 获得有意义的结果,则可能会遇到意外陷阱和/或过多误报。

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

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

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

哪一个更安全?实际上,这在很大程度上取决于您的应用程序域。您的意见似乎是,崩溃的可能性越小等于"更安全"。但是,请考虑上述OpenSSH漏洞,可能是这样。当从远程客户端输入垃圾数据(可能还有外壳代码)时,您希望让 SSH 服务器做什么?

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

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

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