360 安全规则集合
Bjarne Stroustrup: “C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.”
针对 C 和 C++ 语言,本文收录了 532 种需要重点关注的问题, 每个问题对应一条规则,可作为代码审计依据或编程规范条款,也可为相关培训提供指导意见。
本文是适用于不同应用场景的规则集合,读者可从中选取某个子集以适应自身需求。
规则分类
本文以 ISO/IEC 9899:2011、ISO/IEC 14882:2011 为依据,兼顾 C++17 及历史标准,将规则分为:
- Security:敏感信息防护与安全策略
- Resource:资源管理
- Precompile:预处理、宏、注释、文件
- Global:全局及命名空间作用域
- Type:类型设计与实现
- Declaration:声明
- Exception:异常
- Function:函数实现
- Control:流程控制
- Expression:表达式
- Literal:字面常量
- Cast:类型转换
- Buffer:缓冲区
- Pointer:指针
- Interruption:中断与信号处理
- Concurrency:异步与并发
- Style:样式与风格
规则组成
- 标识:规则的唯一标识,以“ID_”开头
- 标题:规则的定义和简要说明
- 说明:规则设立的原因、违反规则的后果
- 示例:例举符合规则(Compliant)的和违反规则(Non-compliant)的代码
- 相关:与当前规则有相关性的规则,可作为扩展阅读的线索
- 依据:规则依照的 ISO/IEC 标准条目
- 配置:某些规则的细节可灵活设置,审计工具可以此为参照实现定制化功能
- 参考:规则参考的其他规范条目,如 C++ Core Guidelines、MISRA、SEI CERT Coding Standards 等
其中“依据”以“标准名称:版本 章节编号(段落编号)-性质”的格式引用标准,“性质”一项分为:
- undefined:可使程序产生未定义的行为,其后果是不可预期的
- unspecified:可使程序产生未声明的行为,具体行为由编译器或环境定义,没有明确的文档
- implementation:可使程序产生由实现定义的行为,具体行为由编译器或环境定义,具有明确的文档,但不具备可移植性
- deprecated:已被废弃的或不建议继续使用的编程方式
没有特殊说明的规则同时适用于 C 语言和 C++ 语言,只适用于某一种语言的规则会有相关说明。
规则目录
- Security
- Resource
- 避免资源泄露
- 避免内存泄露
- 不可访问未初始化或已释放的资源
- 使资源接受对象化管理
- 资源的分配与回收方法应成对提供
- 资源的分配与回收方法应配套使用
- 不应在模块之间传递容器类对象
- 不应在模块之间传递非标准布局类型的对象
- 对象申请的资源应在析构函数中释放
- 对象被移动后应重置状态再使用
- 构造函数抛出异常需避免相关资源泄漏
- 不可重复释放资源
- 用 delete 释放对象需保证其类型完整
- 用 delete 释放对象不可多写中括号
- 用 delete 释放数组不可漏写中括号
- 不可释放非动态分配的内存
- 在一个表达式语句中最多使用一次 new
- 流式资源对象不应被复制
- 避免使用变长数组
- 避免使用在栈上动态分配内存的函数
- 非动态分配的数组不应过大
- 避免不必要的内存分配
- 避免分配大小为零的内存空间
- 避免动态内存分配
- 判断资源分配函数的返回值是否有效
- 在 C++ 代码中禁用 C 资源管理函数
- Precompile
- Include
- Macro-definition
- Macro-usage
- Directive
- Comment
- File
- Other
- Global
- Type
- Class
- 类的非常量数据成员均应为 private
- 类的非常量数据成员不应为 protected
- 类不应既有 public 数据成员又有 private 数据成员
- 有虚函数的基类应具有虚析构函数
- 避免多重继承自同一非虚基类
- 存在析构函数或拷贝赋值运算符时,不应缺少拷贝构造函数
- 存在拷贝构造函数或析构函数时,不应缺少拷贝赋值运算符
- 存在拷贝构造函数或拷贝赋值运算符时,不应缺少析构函数
- 存在任一拷贝、移动、析构相关的函数时,应定义所有相关函数
- 避免重复实现由默认拷贝、移动、析构函数完成的功能
- 可接受一个参数的构造函数需用 explicit 关键字限定
- 重载的类型转换运算符需用 explicit 关键字限定
- 不应过度使用 explicit 关键字
- 带模板的赋值运算符不应与拷贝或移动赋值运算符混淆
- 带模板的构造函数不应与拷贝或移动构造函数混淆
- 抽象类禁用拷贝和移动赋值运算符
- 数据成员的数量应在规定范围之内
- 数据成员之间的填充数据不应被忽视
- 常量成员函数不应返回数据成员的非常量指针或引用
- 类成员应按 public、protected、private 的顺序声明
- POD 类和非 POD 类应分别使用 struct 和 class 关键字定义
- 继承层次不应过深
- Enum
- Union
- Class
- Declaration
- Naming
- Qualifier
- Specifier
- Declarator
- Parameter
- Initializer
- Object
- Function
- Bitfield
- Complexity
- Old-style
- Other
- Exception
- 保证异常安全
- 处理所有异常
- 不应抛出过于宽泛的异常
- 不应捕获过于宽泛的异常
- 不应抛出非异常类型的对象
- 不应捕获非异常类型的对象
- 全局对象的初始化过程不可抛出异常
- 析构函数不可抛出异常
- 内存回收函数不可抛出异常
- 对象交换过程不可抛出异常
- 移动构造函数和移动赋值运算符不可抛出异常
- 异常类的拷贝构造函数不可抛出异常
- 异常类的构造函数和异常信息相关的函数不应抛出异常
- 与标准库相关的 hash 过程不应抛出异常
- 异常类的拷贝、移动构造函数和析构函数均应是可访问的
- 使用 noexcept 关键字标注不抛出异常的函数
- 由 noexcept 关键字标注的函数不可产生未处理的异常
- 避免异常类多重继承自同一非虚基类
- 通过引用捕获异常
- 捕获异常时不应产生对象切片问题
- 捕获异常后不应直接再次抛出异常
- 重新抛出异常时应使用空 throw 表达式(throw;)
- 不应在 catch 子句外使用空 throw 表达式(throw;)
- 不应抛出指针
- 不应抛出 NULL
- 不应抛出 nullptr
- 不应在模块之间传播异常
- 禁用动态异常说明
- 禁用 C++ 异常
- Function
- main 函数的返回类型只应为 int
- main 函数不应被调用、重载或被 inline、static 等关键字限定
- 参数名称在声明处和实现处应保持一致
- 多态类的对象作为参数时不应采用值传递的方式
- 不应存在未被使用的具名形式参数
- 形式参数不应被修改
- 复制成本高的参数不应按值传递
- 转发引用只应作为 std::forward 的参数
- 局部对象在使用前应被初始化
- 动态创建的对象在使用前应被初始化
- 成员须在声明处或构造时初始化
- 基类对象构造完毕之前不可调用成员函数
- 在面向构造或析构函数体的 catch 子句中不可访问非静态成员
- 成员初始化应遵循声明的顺序
- 在对象的构造函数中不应使用其动态类型
- 在对象的析构函数中不应使用其动态类型
- 避免在析构函数中调用 exit 函数
- 避免在拷贝构造函数中实现复制之外的功能
- 避免在移动构造函数中实现数据移动之外的功能
- 拷贝赋值运算符应处理参数是自身对象时的情况
- 不应存在无效的写入操作
- 不应存在没有副作用的语句
- 不应存在得不到执行机会的代码
- 有返回值的函数其所有分枝都应显式返回
- 不可返回局部对象的地址或引用
- 不可返回临时对象的地址或引用
- 不可引用失效的临时对象
- 合理设置 lambda 表达式的捕获方式
- 函数返回值不应为右值引用
- 函数返回值不应为常量对象
- 函数返回值不应为基本类型的常量
- 被返回的表达式应与函数的返回类型一致
- 被返回的表达式不应为相同的常量
- 具有 noreturn 属性的函数不应返回
- 具有 noreturn 属性的函数返回类型只应为 void
- 由 atexit、at_quick_exit 指定的处理函数应正常返回
- 函数模板不应被特化
- 函数的退出点数量应在规定范围之内
- 函数的标签数量应在规定范围之内
- 函数的行数应在规定范围之内
- lambda 表达式的行数应在规定范围之内
- 函数参数的数量应在规定范围之内
- 不应定义复杂的内联函数
- 避免函数调用自身
- 不可递归调用析构函数
- 作用域及类型嵌套不应过深
- 汇编代码不应与普通代码混合
- 避免重复的函数实现
- Control
- If
- if 语句不应被分号隔断
- if...else-if 分枝的条件不应重复
- if...else-if 分枝的条件不应被遮盖
- if 分枝和 else 分枝的代码不应完全相同
- 不应存在完全相同的 if...else-if 分枝
- if 分枝和隐含的 else 分枝不应完全相同
- 没有 else 子句的 if 语句与其后续代码相同是可疑的
- if 分枝和 else 分枝的起止语句不应相同
- if 语句作用域的范围不应有误
- 如果 if 关键字前面是右大括号,if 关键字应另起一行
- if 语句的条件不应为赋值表达式
- if 语句不应为空
- if...else-if 分枝数量应在规定范围之内
- if 分枝中的语句应该用大括号括起来
- 所有 if...else-if 分枝都应以 else 子句结束
- For
- While
- Do
- Switch
- switch 语句不应被分号隔断
- switch 语句不应为空
- case 标签的值不可超出 switch 条件表达式的取值范围
- switch 语句的子句均应从属于某个 case 或 default 分枝
- case 和 default 标签应直接从属于 switch 语句
- 不应存在紧邻 default 标签的空 case 标签
- 不应存在完全相同的 case 或 default 分枝
- switch 语句的条件表达式不应为 bool 型
- switch 语句不应只包含 default 标签
- switch 语句不应只包含一个 case 标签
- switch 语句分枝数量应在规定范围之内
- switch 语句应配有 default 分枝
- switch 语句的每个非空分枝都应该用无条件的 break 或 return 语句终止
- switch 语句应该用大括号括起来
- switch 语句不应嵌套
- Try-catch
- Jump
- If
- Expression
- Logic
- Evaluation
- Operator
- Assignment
- Comparison
- Call
- Sizeof
- Assertion
- Complexity
- Other
- Literal
- Cast
- 避免类型转换造成数据丢失
- 避免数据丢失造成类型转换失效
- 避免有符号整型与无符号整型相互转换
- 不应将负数转为无符号数
- 避免与 void* 相互转换
- 避免向下类型转换
- 指针与整数不应相互转换
- 类型转换不应去掉 const、volatile 等属性
- 不应转换无继承关系的指针或引用
- 不应转换无 public 继承关系的指针或引用
- 非 POD 类型的指针与基本类型的指针不应相互转换
- 不同的字符串类型之间不可直接转换
- 避免向对齐要求更严格的指针转换
- 避免转换指向数组的指针
- 避免转换函数指针
- 向下动态类型转换应使用 dynamic_cast
- 判断 dynamic_cast 转换是否成功
- 不应转换 new 表达式的类型
- 不应存在多余的类型转换
- 可用其他方式完成的转换不应使用 reinterpret_cast
- 合理使用 reinterpret_cast
- 在 C++ 代码中禁用 C 风格类型转换
- Buffer
- Pointer
- 避免空指针解引用
- 注意逻辑表达式内的空指针解引用
- 不可解引用未初始化的指针
- 不可解引用已失效的指针
- 避免指针运算的结果溢出
- 未指向同一数组的指针不可相减
- 未指向同一数组或同一对象的指针不可比较大小
- 未指向数组元素的指针不应与整数加减
- 避免无效的空指针检查
- 不应重复检查指针是否为空
- 不应使用非零常量对指针赋值
- 不应使用常量 0 表示空指针
- 在 C++ 代码中 NULL 和 nullptr 不应混用
- 在 C++ 代码中用 nullptr 代替 NULL
- 不应使用 false 对指针赋值
- 不应使用 '\0' 等字符常量对指针赋值
- 指针不应与 false 比较大小
- 指针不应与 '\0' 等字符常量比较大小
- 指针与空指针不应比较大小
- 不应判断 this 指针是否为空
- 禁用 delete this
- 释放指针后应将指针赋值为空或其他有效值
- 函数取地址时应显式使用 & 运算符
- 指针与整数的加减运算应使用数组下标的方式
- Interruption
- Concurrency
- Style