在 Visual Studio版本16.8 预览3 , 我们 已添加 一 很少的 C++代码分析的安全规则 那可以 找到 一些常见的错误,可能导致 虫子范围 从简单的断开特征到 代价高昂的安全漏洞。 这些新规则是围绕着在 生产 软件via 安全审查和事故 需要 昂贵的 服务 . 作为安全和法规遵从性要求的一部分,微软的每一款软件都运行这些规则。
这个博客系列的第一部分, C++代码分析中的安全规则 ,介绍 新规则相关 到 这个 误用 属于 VARIANT
以及它的兄弟类型,比如 VARIANTARG
,或
PROPVARIANT
.
这一秒 零件 系列的 介绍 s 关于“使用枚举作为索引”和“使用布尔作为HRESULT”的新规则。 帮助解决问题 东南方 新规则, 我们 建造 二 代码分析扩展 s ,调用 EnumIndex
和 HResultCheck
那个 发现违反这些新规则的行为 在代码中。
A n 枚举数 我 在 或 枚举 是 用户定义的 完整的 由 n可选 一组命名的整数常量,称为枚举数 (也称为枚举常量) . 通常情况下 n枚举提供上下文来描述 价值观 ( (称为枚举数) 表示为命名的 常量 .
安 枚举 可以通过指定类或 结构 关键字后的 enum
关键字,例如:
enum class Suit { Diamonds, Hearts, Clubs, Spades };
没有 class
或 struct
关键字,一个 枚举 变成 无范围 .
使用 /std:c++17 ,安 枚举 (正则或作用域)可以用显式基础类型定义,而不使用枚举数,这实际上引入了一个新的整数类型,该类型没有到任何其他类型的隐式转换。
无范围 枚举数 可以隐式转换为 int
. 不能隐式转换作用域枚举数 到 int
. 转换时需要强制转换 int的作用域枚举数 转换 int
到 有范围的或 无范围 枚举器。
事实上 一 枚举 操作 是一种积分类型 包括 s 属于 有限集 命名常量值(枚举数) 哪一个 可以是 隐式或显式转换为 int
使用枚举数作为索引值非常常见 . 例如:
const auto& colorInfo = ColorTable[color];
你会在网上找到很多关于使用的讨论 枚举 值作为数组索引。这在很多情况下都是有意义的。
F 经常 , 什么时候 开发商 使用 枚举 作为数组的索引,它们知道 枚举 类型的值从零开始到已知的最大值,增量为1,并且任何一对连续枚举数之间没有间隔 . 因此, 大部分 开发人员认为,将接收到的枚举数值与已知的最大值进行比较可以确保它的有效性。
但是,使用 枚举数 因为数组索引不是很安全。 不幸的是,似乎 关于它为什么会危险的讨论并不多。
让我们看一个例子。 考虑以下几点 枚举 和 一 函数指针表 我们想用 枚举 作为索引的值 :
// MyHeader.h #pragma once #include <iostream> typedef int (*FP)(); enum FunctionId { Function1, Function2, Function3, FunctionCount }; template <int val> int GetValue() { return val; }; int DoNotCallMe() { std::cout << "This shouldn't be called!"; return -1; } FP fp = DoNotCallMe; FP Functions[] { GetValue<0>, GetValue<1>, GetValue<2> };
现在,在 一 在源文件中,让我们定义一个函数来从表中选择一个函数,使用 一 的枚举数 枚举 作为索引 对于函数指针表 :
#include "MyHeader.h"
FP GetFunction(FunctionId funcId)
{
if (funcId < FunctionId::FunctionCount)
return Functions[funcId];
return nullptr;
}
整洁,不是吗? T o 防止流氓或错误的来电者, 我检查一下 枚举器 价值 相对于已知的最大值 FunctionId
,所以 它不会导致 函数 访问超出其边界的表。我知道 的枚举数 FunctionId
枚举 类型 将从零开始 ,递增1, 结束于 FunctionId :: FunctionCount
– 1
, FunctionCount
是世界上最后一个枚举数 枚举 .
让我们继续添加更多使用此函数的代码 . 我们的客户代码将整数值作为函数的选择器,并希望我们通过函数返回整数值 :
int GetValue(int funcIdx) { const auto fp = GetFunction(static_cast<FunctionId>(funcIdx)); return fp ? fp() : -1; }
作为 解释 上面,我需要 一 铸造 转换 整数值 函数表索引 到 枚举 类型 传递给 GetFunction
. 这将确保 int
值已正确转换为 人口统计员 FunctionId
枚举 . 到目前为止,还不错, 希望如此。
现在,让我们考虑一个函数 电话 GetValue
得到 价值观 通过函数 :
int main() { return GetValue(-1); }
你去哪了 -1
从哪里来?在这次讨论中,这是不可能的 重要的 . 假设是从 用户的 输入 . 不管怎样,t 他的观点显然是错误的。 然而,我并没有从编译器得到任何关于这个调用潜在问题的提示,即使是在 /Wall
. 其实,没有什么是“错的” 考虑到 所涉及的类型及其使用方法。但我们知道这是错误的。 做 GetFunction
真的要保护自己不受这个召唤吗? 一个简单的答案是——不。
问题 微软 是 , 那个 你可以投任何一个 int
价值观 枚举 类型 ,和 那个 一 枚举 基础类型默认为 int
– signed int
. 对于有符号值,如果您检查上限 但不是它的下限,你最终允许负值。 在上面的例子中,它最终调用了 DoNotCallMe
功能, 正好在那之前 函数指针表。在现实生活中 一种虫子 可以 导致 可利用的 安全漏洞。
不太可能有人 检查 对于较低的 跳跃 但忘了检查上限。然而,这也会导致同样的问题 ,通过允许超出数组边界的访问。
只是为了好玩,运行上面的例子 生产 以下输出 为了我 :
This shouldn't be called! C:TempSample.exe (process 9748) exited with code -1.
枚举索引 规则
这个 EnumIndex
延伸 发现 缺陷 如上图所示, 和报告 通过他们 以下警告:
- C330型 1 0 : 未选中的下限 枚举 ‘
enum
‘用作索引。
- C330型 11 : 未选中的上限 枚举 ‘
enum
‘用作索引。
警告C33010
将触发此警告 对于 一 枚举 它用作数组的索引,前提是检查其值的上限,而不是下限。
下面是一个简化的示例:
typedef void (*PFN)(); enum class Index { Zero, One, Two, Three, Max }; void foo(Index idx, PFN(&functions)[5]) { if (idx > Index::Max) return; auto pfn = functions[static_cast<int>(idx)]; // C33010 if (pfn != nullptr) (*pfn)(); // ...... }
通过检查索引值是否存在较低的值,可以更正这些警告 跳跃 也:
typedef void (*PFN)(); enum class Index { Zero, One, Two, Three, Max }; void foo(Index idx, PFN(&functions)[5]) { if (idx < Index::Zero || idx > Index::Max) return; auto pfn = functions[static_cast<int>(idx)]; // OK if (pfn != nullptr) (*pfn)(); // ...... }
警告C33011
将触发此警告 对于 一 枚举 它用作数组的索引,前提是检查其值的下界,而不是上界。
下面是一个简化的示例:
typedef void (*PFN)(); enum class Index { Zero, One, Two, Three, Max }; void foo(Index idx, PFN(&functions)[5]) { if (idx < Index::Zero) return; auto pfn = functions[static_cast<int>(idx)]; // C33011 if (pfn != nullptr) (*pfn)(); // ...... }
通过检查索引值的上限也可以纠正这些警告:
typedef void (*PFN)(); enum class Index { Zero, One, Two, Three, Max }; void foo(Index idx, PFN(&functions)[5]) { if (idx < Index::Zero || idx > Index::Max) return; auto pfn = functions[static_cast<int>(idx)]; // OK if (pfn != nullptr) (*pfn)(); // ...... }
有可能 枚举索引 Visual Studio中的规则
您可以启用 EnumIndex
通过选择不同的 规则集 对于您的项目:
规则ID | 扩展 | 本机最小规则 | 本地推荐规则 | 所有规则 |
C330型 10 | 枚举索引 | 十 | 十 | 十 |
C330型 11 | 枚举索引 | 十 | 十 |
虽然这可能不是故意的, 我们已经看到 代码在哪里 Boolean
价值 s 是 用作 HRESULT
价值 s ,反之亦然 . C/C++允许隐式转换 他们 ,编译器不会对这些隐式转换发出警告。然而,一个 Boolean
价值与价值 HRESULT
具有不同的语义,不能互换使用。
这就是为什么已经有了一条禁止这种滥用的规则。 考虑以下示例:
#include <windows.h> BOOL IsEqual(REFGUID, REFGUID); HRESULT foo(REFGUID riid1, REFGUID riid2) { return IsEqual(riid1, riid2); }
目的 foo(
)
是到 比较两个值并 返回 S_OK
什么时候 他们 我们是平等的。但是,它会回来的 S_FALSE
如果值相等,以及 S_OK
如果值不同。 这与 预期的 行为。然而,这段代码可能编译得很好,而不会得到关于这个潜在缺陷的警告。幸运的是,C++代码分析可以检测到这一点,并将报告 C6216型 警告,哪个 是关于 隐性的 铸造 Boolean
价值 HRESULT
.
在…之间 各种各样的 潜在误用 s 属于 Boolean
和 HRESULT
价值观,我们 我知道了 一个特定的场景 发生频率高于 其他,以及 导致更明显的错误。我们已经创建了一个额外的扩展来覆盖这个场景– H R esultCheck
.
HResult公司 规则
The HResultCheck
延伸 发现 C风格 BOOL FALSE
从函数返回作为 HRESULT
价值,导致回归 S_OK
当意图可能返回失败结果时 :
- C330型 2 0 : 可能不正确
HRESULT
检测到使用情况。
- C330型 22 : 可能不正确
HRESULT
检测到使用情况(低置信度) .
警告C33020
这是 一 高置信度警告表明 HRESULT
-返回函数返回 FALSE
. 在许多情况下,开发人员会考虑 FALSE
作为失败值,并从函数返回它,以指示 f 疾病 . 然而 FALSE
是 0
. W 母鸡被解释为 HRESULT
价值观, 此值变为 S_OK
,代表成功 .
这是一个 简化 例子:
#include <Windows.h> HRESULT foo() { // ...... return FALSE; // C33020 }
这是可以解决的 返回 适当的 HRESULT
价值:
#include <Windows.h> HRESULT foo() { // ...... return E_FAIL; // OK }
警告C33022
这是 低的 -返回的函数的置信度警告 HRESULT
,如果有的话 FALSE
最终返回的路线上的某个地方。
这是一个 简化 例子:
#include <Windows.h> #define RETURN_FAILURE(x) do { *res = x; return FALSE; } while(false); HRESULT test04(BOOL* res) { // ... RETURN_FAILURE(FALSE); // ... return S_OK; }
这是可以解决的 通过使用 适当的 HRESULT
价值:
#define RETURN_FAILURE(x) do { *res = x; return E_FAIL; } while(false); HRESULT test04(BOOL* res) { // ... RETURN_FAILURE(FALSE); // ... return S_OK; }
有可能 HResult公司 Visual Studio中的规则
您可以启用 HResult
通过选择不同的 规则集 对于您的项目:
规则ID | 扩展 | 本机最小规则 | 本地推荐规则 | 所有规则 |
C330型 2 0 | HRESULT检查 | 十 | 十 | 十 |
C330型 22 | HRESULT检查 | 十 |
看看这些新增加的规则,让我们知道 怎样 它们帮助你编写更安全的C++。请继续关注我们在VisualStudio未来版本中添加的更多安全规则。
下载 Visual Studio 2019版本16.8预览版3 今天 试试看。我们很乐意收到您的来信,帮助我们确定优先级并为您构建合适的功能。我们可以通过以下评论联系到您, 开发者社区 , 和 推特( @视觉 ). 提交bug或建议特性的最佳方法是通过开发者社区。