C++代码分析中的安全规则

Visual Studio版本16.8 预览3 ,  我们 已添加 一 很少的 C++代码分析的安全规则 那可以 找到 一些常见的错误,可能导致 虫子范围 从简单的断开特征到 代价高昂的安全漏洞。 这些新规则是围绕着在 生产 软件via 安全审查和事故 需要 昂贵的 服务 . 作为安全和法规遵从性要求的一部分,微软的每一款软件都运行这些规则。

null

这个博客系列的第一部分, 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型 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或建议特性的最佳方法是通过开发者社区。

© 版权声明
THE END
喜欢就支持一下吧,技术咨询可以联系QQ407933975
点赞0 分享