我们已经在Visual Studio 2017中改进了每个C++编译器更新的C++代码分析工具集。 版本15.6,现在预览 ,包括一组算术溢出检查。本文将讨论这些检查以及为什么要在代码中启用它们。
如果您只是在VisualStudio中开始使用C++代码分析,请了解更多的整体体验。 在这篇综述性的博客文章中 .
动机
作为C++代码分析团队的一部分,我们与公司的各个小组一起工作,以识别出可以通过更好的工具和语言支持来检测的漏洞类别。最近,作为微软最安全敏感的代码库之一的安全审查的一部分,我们发现我们需要添加检查来检测一个常见的算术溢出类。
模式示例:
uint32 ConvertOffset(uint32 ptr, uint32 fromSize, uint32 toSize){ if (fromSize == toSize) { return ptr; } uint64 tmp = ptr * fromSize; // “tmp” can overflow here if ptr * fromSize is wider than uint32 // More code}templateclass BucketEntryIterator sealed : public IteratorBase<TDictionary, BucketEntryIterator>{private: uint bucketIndex; // Rest of the data memberspublic: BucketEntryIterator(TDictionary &dictionary) : Base(dictionary, -1), bucketIndex(0u – 1) // This wraps past 0 to a really big number, which can overflow bucketIndex // Initialize other data members of the class};
我们查看了 C++核心指南 看看这个地方有没有具体的指导方针。
算术规则
- 不要混合使用有符号和无符号算术
- ES.101:使用无符号类型进行位操作
- ES.102:算术使用有符号类型
- 诗103:不要溢出
- ES.104:不要下溢
- 诗105:不要被零除
- ES.106:不要试图通过使用无符号来避免负值
- 不要对下标使用unsigned,而应该使用gsl::index
指南指出,其中一些规则在实践中很难执行。因此,我们采取了一种方法,试图将一套指导方针与 我们希望在实现中检测的缺陷模式。
检查
以下是 一组算术检查,我们添加到C++核心检查15.6版本:
- C26450算术运算结果可证明有损 [operator]操作导致编译时溢出。使用更宽的类型存储操作数。此警告表示算术运算在编译时有损。当操作数都是编译时常量时,可以断言这一点。目前,我们检查左移位、乘法、加法和减法运算是否存在此类溢出。
// Example source:int multiply() { const int a = INT_MAX; const int b = 2; int c = a * b; // C26450 reported here return c;}
// Corrected source:long long multiply() { const int a = INT_MAX; const int b = 2; long long c = (long long)a * b; // OK return c;}
在校正源代码中,左操作数被转换为更宽的类型,以使算术运算的结果更宽。
- C26451算术运算的结果被转换成较大的大小 对[size1]字节值使用运算符[operator],然后将结果强制转换为[size2]字节值。在调用operator[operator]之前将值强制转换为更宽的类型以避免溢出此警告表示由于整数提升规则和类型大于通常执行算术的规则和类型而导致的错误行为。我们检测窄型整数值何时左移、乘法、加法或减法,以及算术运算的结果何时转换为宽型整数值。如果操作溢出了窄类型值,则数据丢失。通过在算术运算之前将值强制转换为更宽的类型,可以防止这种损失。
// Example source:void leftshift(int i) { unsigned long long x; x = i << 31; // C26451 reported here // code}
// Corrected source:void leftshift(int i) { unsigned long long x; x = (unsigned long long)i << 31; // OK // code}
在校正源代码中,左操作数被转换为更宽的类型,以使算术运算的结果更宽。
- C26452移位计数为负或太大 左移位计数为负或大于或等于未定义行为的操作数大小此警告表示移位计数为负或大于或等于正在移位的操作数的位数,从而导致未定义行为。
// Example source:unsigned long long combine(unsigned lo, unsigned hi){ return (hi << 32) | lo; // C26252 here}
// Corrected source:unsigned long long combine(unsigned lo, unsigned hi){ return ((unsigned long long)hi << 32) | lo; // OK}
在校正后的源代码中,左操作数被强制转换为64位值,然后左移32位。
- C26453左移位负号 负符号数字的左移是未定义的行为此警告表示我们正在左移负符号整数值,这是一个坏主意,并触发实现定义的行为。
// Example source:void leftshift(int shiftCount) { const auto result = -1 << shiftCount; // C26453 reported here // code}
// Corrected source:void leftshift(int shiftCount) { const auto result = 4294967295 << shiftCount; // OK // code}
在校正源中,移位操作使用正积分值。
- C26454算术运算结果负数无符号 [operator]操作换行超过0,并在编译时生成一个大的无符号数此警告表示减法操作生成在无符号上下文中计算的负结果。这会导致结果超过0并产生一个非常大的无符号数,这可能会导致意外的溢出。
// Example source:unsigned int negativeunsigned() { const unsigned int x = 1u - 2u; // C26454 reported here return x;}
// Corrected source:unsigned int negativeunsigned() { const unsigned int x = 4294967295; // OK return x;}
在校正后的源代码中,为无符号结果指定了一个正值。
结果
我们在一个安全敏感的代码库中运行了这些检查,发现了有趣的bug模式。我正在重新分享这个博客文章开头看起来可疑的示例模式,以及它们现在触发的代码分析警告。
Warning C26451 Arithmetic overflow: Using operator '*' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator '*' to avoid overflow
uint32 ConvertOffset(uint32 ptr, uint32 fromSize, uint32 toSize){ if (fromSize == toSize) { return ptr; } uint64 tmp = ptr * fromSize; // C26451 // More code}
Warning C26454 Arithmetic overflow: '-' operation produces a negative unsigned result at compile time, resulting in an overflow
templateclass BucketEntryIterator sealed : public IteratorBase<TDictionary, BucketEntryIterator>{private: uint bucketIndex; // Rest of the data memberspublic: BucketEntryIterator(TDictionary &dictionary) : Base(dictionary, -1), bucketIndex(0u – 1) // C26454 // Initialize other data members of the class};
反馈
我们希望你能亲自试试这些支票。 下载版本15.6预览版6 如果你有发现就告诉我们 代码库中有趣的bug模式。
一如既往,如果您对我们有任何反馈或建议,请告知我们。我们可以通过以下评论和电子邮件联系到您( visualcpp@microsoft.com )你可以通过 帮助>报告产品中的问题 ,或通过 开发者社区 . 你也可以在Twitter上找到我们( @视觉 )还有Facebook( msftvisualcpp软件 ).