MISRA C 2012 标准浅析

MISRA(The Motor Industry Software Reliability Association),汽车工业软件可靠性联会; 1994年,英国成立。致力于协助汽车厂商开发安全可靠的软件的跨国协会,其成员包括:AB汽车电子、罗孚汽车、宾利汽车、福特汽车、捷豹汽车、路虎公司、Lotus公司、MIRA公司、Ricardo公司、TRW汽车电子、利兹大学和福特VISTEON汽车系统公司。

MISRA支持C语言标准的发展历程:

MISRA C 2012 标准浅析_第1张图片

MISRA C 2012目前已经修订了多次,最近版本是AMD2,已经支持C11标准。MISRA主要是功能安全,而CERT C/C++则更多的关注安全威胁。MISRA C 2012:AMD2总计规则数175条,其中规则158条,指令17条。其中指令基本上是无法被SAST工具所检测分析的。175条规则中强制为15条、必要规则120条、建议规则40条。

MISRA标准中每一条规则有很多描述信息,包括类别、可判断性、支持语言标准、分析范围、规则编号、规则描述等。请见下图示意:

MISRA C 2012 标准浅析_第2张图片

对于这些规则描述项说明如下:

标准分类:规则(Rule)和指令(Directive)之间的区别:

指令是一种描述性的指导规范,它无法提供执行符合性检查所需的完整描述。为了能够进行检查,需要给评测人员提供额外的信息,如设计文件或需求说明。

指令部分主要分为:实现、编译与构建、要求追踪、代码设计四个部分,共16条规范。

规则可以对相关要求提供完整的描述,评测人员或静态分析工具可以在不需要额外信息的情况下检查源代码是否符合对应规则。

指令:仅仅依靠源代码分析,无法对指令进行合规性判定,往往需要开发人员提供更多的信息,如设计文档和要求说明。静态代码分析工具可以判断代码符合指令,但对于代码不符合指令的情况,代码分析工具给出的结果可能不一致。

规则:仅仅依靠源代码分析,就可以对规则进行判断,不需要开发人员提供更多的信息,所有的静态代码检测工具都应具有对规则进行合规性判定的能力。

Category类别:

Mandatory guidelines 强制的:2012版增加的

声明符合Misra的C代码应遵守所有强制性准则-不允许偏差

Required guidelines必须的:

公司或项目可以选择将任何必须的准则视为强制性准则,不符合需要正式的偏差许可

Advisory guidelines建议的:

建议性的,不符合应被记录,但是无需正式的偏差许可

Decidability可判定性和Undecidable 不可判定性:

Decidable可判断的,总是能够在任何程序中使用工具确定代码是否合规,否则就是不可判定的

例如:

可判定的,规则11.3:取决于源指针和目标指针的类型

不可判定的,规则12.2:取决于移位运算符右侧操作数的值。

Analysis Scope分析范围

Single translation unit rule 单一编译单元

通过隔离分析每个编译单元中的源代码,可以可靠地进行验证的代码

System rules系统级

只能通过分析整个系统中的源代码了完全检查验证

MISRA合规对于企业的价值:

  1. MISRA是汽车行业公认的C/C++语言编码规范;
  2. MISRA和AUTOSAR已经联合,即将发布C++最佳行业实践标准;
  3. 符合编码规范是ISO 26262:2018-6软件功能安全开发标准的内在要求之一,开发ASIL功能安全项目必须满足;
  4. 客户软件过程能力审核要求;
  5. 在研发生命周期早期发现软件中的缺陷,预防成本投入会大幅度降低投产后的售后维护成本。

对于C、C++语言开发,我们关注里面包括的运行时缺陷,例如缓冲区溢出、整数溢出、数组越界、内存泄露、空指针解引用等缺陷。但是对于出海的企业,例如车企其开发软件遵守MISRA标准是必要的。这些规则虽然大多数不会导致程序崩溃,但是会给这些软件运行带来潜在的出错风险,对于可能造成重大生命财产随时的软件还是应该去遵守的。下面我们列举几个例子、说明代码不安全性主要来源。

  1. 开发者编码引入的错误;

     例如M6-2-1:赋值运算符不得用于子表达式;  a = b = c = 10;    if((x=y) != 0)

  1. 开发者对C/C++语言的误解;

      例如:M5-0-2: 表达式中的 C++ 运算符优先规则应受到有限的依赖

     x = ( a + b );       x = static_cast< uint16_t > ( a ) + b; 

  1. 编译器不执行开发人员所期望的操作;

     例如:规则 M4-5-3:类型(普通)char 和 wchar_t 的表达式不得用作除赋值运算符 =、相等运算符 == 和 ! 之外的内置运算符的操作数 = 和一元 & 运算符。

     if ( ( ch >= ‘a’ ) && ( ch <= ‘z’ ) )

  1. 编译器包含错误;

    例如: M1-0-2: (强制性,工具链,非自动化)仅当多个编译器具有通用的定义接口时,才应使用多个编译器;

如果一个模块要用C++以外的语言实现,或者使用不同的编译器编译,需确保这个模块的正确集成。强制性考虑的问题包括:堆栈使用、参数传递、数据值的存储方式(长度、对齐、混叠、叠加等)。

  1. 运行时错误;

 例如A18-0-2: 应检查从字符串到数值的转换的错误状态:

std::uint16_t a;     std::cin >> a;    // 没有检测到错误

目前,业界对于MISRA C 2012:ADM2支持最好的SAST工具是Coverity,支持的规则最多,检测效果最好。其次是北大Cobot,检测效果还可以。下面是MISRA C 2012规则全集,请参考。

分类 子类 编号 类别 规则项 支持 启用
指令 实施/实现 Dir 1.1 必要 程序输出所依赖的任何由实现定义的行为都应被记录和理解
编译与构建 Dir 2.1 必要 所有源文件都应通过编译且没有任何编译错误
需求可追溯性  Dir 3.1 必要 所有代码应可追溯到书面要求
代码设计 Dir 4.1 必要 所有运行时缺陷都最小化
Dir 4.2 建议 汇编语言的所有运用都应记录在案
Dir 4.3 必要 汇编语言应被封装和隔离
Dir 4.4 建议 代码段不应被“注释掉”
Dir 4.5 建议 具有相同可见性的相同名称空间中的标识符在印刷/屏幕显示上应明确
Dir 4.6 建议 应使用指示大小和符号的 typedef 类型代替基本数字类型
Dir 4.7 必要 如果函数返回错误信息,则应测试该错误信息
Dir 4.8 建议 如果一个指向结构体或联合体的指针在编译/解释时从未被反引用,则应隐藏该对象的实现
Dir 4.9 建议 应该优先使用函数,而不是类似函数的宏(如果它们可以互换)
Dir 4.10 必要 应采取预防措施以防止头文件的内容被多次包含
Dir 4.11 必要 应该检查传递给库函数的值的有效性。
Dir 4.12 必要 不得使用动态内存分配
Dir 4.13 建议 用于对资源进行操作的功能应按适当的顺序调用
Dir 4.14 必要 应该检查从外部源接收的值的有效性。 
规则 标准 C 环境 Rule 1.1 必要 程序不得违反标准 C 语法和约束,并且不得超出具体实现的编译限制
Rule 1.2 建议 不应该使用语言扩展
Rule 1.3 必要 不得发生未定义或严重的未指定行为
Rule 1.4 必要 不得使用不成熟的语言特性
未使用的代码 Rule 2.1 必要 项目不能包含不可达代码
Rule 2.2 建议 不得有无效代码(dead code)
Rule 2.3 建议 项目不应包含未被使用的类型(type)声明
Rule 2.4 建议 项目不应包含未被使用的类型标签(tag)声明
Rule 2.5 建议 项目不应包含未被使用的宏(macro)声明
Rule 2.6 建议 函数不应包含未被使用的执行标签(label)声明
Rule 2.7 建议 函数中不应有未使用的变量
注释 Rule 3.1 必要 字符序列注释“/*”和“//”(/*与//)不应出现在注释中
Rule 3.2 必要 “//”注释中不得使用换行(即“//”注释中不得使用行拼接符“\”)
字符集和词汇约定 Rule 4.1 必要 八进制和十六进制转译序列应有明确的终止识别标识
Rule 4.2 建议 禁止使用三字母词(trigraphs)
标识符 Rule 5.1 必要 外部标识符不得重名
Rule 5.2 必要 同范围和命名空间内的标识符不得重名
Rule 5.3 必要 内部声明的标识符不得隐藏外部声明的标识符
Rule 5.4 必要 宏标识符不得重名
Rule 5.5 必要 宏标识符与其他标识符不得重名
Rule 5.6 必要 typedef 名称应是唯一标识符
Rule 5.7 必要 标签(tag)名称应是唯一标识符
Rule 5.8 必要 全局(external linkage)对象和函数的标识符应是唯一的
Rule 5.9 建议 局部全局(internal linkage)对象和函数的标识符应是唯一的
类型 Rule 6.1 必要 位域(位带)仅允许使用适当的类型来声明(位域成员类型限制)
Rule 6.2 必要 单比特(single-bit)位域成员不可声明为有符号类型
字符和常量 Rule 7.1 必要 禁止使用八进制常数
Rule 7.2 必要 后缀“u”或“U”应使用于所有无符号的整数常量
Rule 7.3 必要 小写字符“l”不得作为常量的后缀使用(仅可使用“L”)
Rule 7.4 必要 除非对象的类型为“指向 const char 的指针”,否则不得将字符串常量赋值给该对象
声明和定义 Rule 8.1 必要 类型须明确声明
Rule 8.2 必要 函数类型应为带有命名形参的原型形式
Rule 8.3 必要 对象或函数的所有声明均应使用相同的名称和类型限定符
Rule 8.4 必要 全局(external linkage)的对象和函数,应有显式的合规的声明
Rule 8.5 必要 全局对象或函数应在且只在一个文件中声明一次
Rule 8.6 必要 全局标识符应在且只在一处定义
Rule 8.7 建议 仅在本编译单元中调用的对象和函数,应定义成局部属性
Rule 8.8 必要 “static”修饰符应用在所有局部全局对象和局部函数(internal linkage)的声明中
Rule 8.9 建议 若一个对象的标识符仅在一个函数中出现,则应将它定义在块范围内
Rule 8.10 必要 内联函数应使用静态存储类声明
Rule 8.11 建议 声明具有外部链接的数组时,应明确指定其大小
Rule 8.12 必要 在枚举列表中,隐式指定的枚举常量的值应唯一
Rule 8.13 建议 指针应尽可能指向 const 限定类型
Rule 8.14 必要 不得使用类型限定符“restrict”
初始化 Rule 9.1 强制 具有自动存储持续时间的对象(临时变量)的值在设置前不得读取
Rule 9.2 必要 集合或联合体的初始化应括在花括号“{}”中
Rule 9.3 必要 数组不得部分初始化
Rule 9.4 必要 数组的元素不得被初始化超过一次
Rule 9.5 必要 在使用指定初始化方式初始化数组对象的情况下,应明确指定数组的大小
基本类型模型  Rule 10.1 必要 操作数不得为不适当的基本类型
Rule 10.2 必要 字符类型的表达式不得在加减运算中使用不当
Rule 10.3 必要 表达式的值不得赋值给具有较窄基本类型或不同基本类型的对象
Rule 10.4 必要 执行常规算术转换的运算符的两个操作数应有相同的基本类型
Rule 10.5 建议 表达式的值不应(强制)转换为不适当的基本类型
Rule 10.6 必要 复合表达式的值不得赋值给具有较宽基本类型的对象
Rule 10.7 必要 如果将复合表达式用作执行常规算术转换的运算符的一个操作数,则另一个操作数不得具有更宽的基本类型
Rule 10.8 必要  复合表达式的值不得转换为其他基本类型或更宽的基本类型
指针类型转换 Rule 11.1 必要 不得在指向函数的指针和任何其他类型的指针之间进行转换
Rule 11.2 必要 不得在指向不完整类型的指针和其他任何类型间进行转换
Rule 11.3 必要 不得在指向不同对象类型的指针之间执行强制转换
Rule 11.4 建议 不得在指向对象的指针和整数类型之间进行转换
Rule 11.5 建议 不得将指向 void 的指针转换为指向对象的指针
Rule 11.6 必要 不得在指向 void 的指针和算术类型之间执行强制转换
Rule 11.7 必要 不得在指向对象的指针和非整数算术类型之间执行强制转换
Rule 11.8 必要 强制转换不得从指针指向的类型中删除任何 const 或 volatile 限定符
Rule 11.9 必要 宏“NULL”是整数型空指针常量的唯一允许形式
表达式 Rule 12.1 建议 表达式中运算符的优先级应明确
Rule 12.2 必要 移位运算符的右操作数应在零到比左操作数基本类型的位宽度小一的范围内
Rule 12.3 建议 不得使用逗号(,)运算符
Rule 12.4 建议 常量表达式的求值不应导致无符号整数的回绕
Rule 12.5 建议 sizeof运算符不能有声明为“类型数组”的函数参数作为操作数
副作用 Rule 13.1 必要 初始化程序列表不得包含持久性副作用
Rule 13.2 必要 在所有允许的评估顺序中表达式的值及其持久的其他作用应该保持相同。
Rule 13.3 建议 包含自增(++)或自减(--)运算符的完整表达式,除由自增或自减运算符引起的副作用外,不应有其他潜在的副作用
Rule 13.4 建议 不得使用赋值运算符的结果
Rule 13.5 必要 逻辑与(&&)和逻辑或(||)的右操作数不得含有持久性副作用
Rule 13.6 强制 sizeof 运算符的操作数不得包含任何可能产生副作用的表达
控制语句表达式 Rule 14.1 必要 循环计数器的基本类型不能为浮点型
Rule 14.2 必要  for 循环应为良好格式
Rule 14.3 必要 控制表达式不得是值不变的
Rule 14.4 必要 if 语句和循环语句的控制表达式的基本类型应为布尔型
控制流 Rule 15.1 建议 不应使用 goto 语句
Rule 15.2 必要 goto 语句仅允许跳到在同一函数中声明的稍后位置的标签
Rule 15.3 必要 goto语句引用的标签必须在goto语句所在代码块或包含该代码块的上级代码块中声明
Rule 15.4 建议 最多只能有一个用于终止循环语句的 break 或 goto 语句
Rule 15.5 建议 应仅在函数的末尾有单个函数出口
Rule 15.6 必要 循环语句和选择语句的主体应为复合语句
Rule 15.7 必要 所有的 if…else if 构造都应以 else 语句结束
Switch 语句 Rule 16.1 必要 switch 语句应格式正确
Rule 16.2 必要 switch 标签只能出现在构成 switch 语句主体的复合语句的最外层
Rule 16.3 必要 每一个 switch 子句(switch-clause)都应以无条件 break 语句终止
Rule 16.4 必要 每个 switch 语句都应具有 default 标签
Rule 16.5 必要 Default 标签应作为 switch 语句的第一个或最后一个 switch 标签
Rule 16.6 必要 每个 switch 语句应至少有两个 switch 子句
Rule 16.7 必要 switch 语句的控制表达式(switch-expression)的基本类型不得是布尔型
函数 Rule 17.1 必要 不得使用的功能
Rule 17.2 必要 函数不得直接或间接调用自身(不得使用递归函数)
Rule 17.3 强制 禁止隐式声明函数
Rule 17.4 强制 具有非 void 返回类型的函数的所有退出路径都应为具有带有表达式的显式return 语句
Rule 17.5 建议 与数组型函数形参对应的函数入参应具有适当数量的元素
Rule 17.6 强制 数组形参的声明不得在[]之间包含 static 关键字
Rule 17.7 必要 非 void 返回类型的函数的返回值应该被使用
Rule 17.8 建议 不应更改函数形参
指针和数组 Rule 18.1 必要 指针操作数的算术运算应仅用于寻址与该指针操作数相同数组的元素
Rule 18.2 必要 指针之间的减法应仅用于寻址同一数组元素的指针
Rule 18.3 必要 关系运算符>,> =,<和<=不得应用于指针类型的对象,除非它们指向同一对象
Rule 18.4 建议 “+,-,+=”和“-=”运算符不得应用于指针类型的表达式
Rule 18.5 建议 声明中最多包含两层指针嵌套
Rule 18.6 必要 具有自动存储功能的对象的地址不得复制给在它的生命周期结束后仍会存在的另一个对象
Rule 18.7 必要 不得声明可变的数组成员
Rule 18.8 必要 不得使用可变长数组类型
重叠存储 Rule 19.1 强制 不得将对象赋值或复制给重叠的对象
Rule 19.2 必要 不得使用 union 关键字
预处理指令 Rule 20.1 建议 #include 指令之前仅允许出现预处理指令或注释
Rule 20.2 必要 头文件名中不得出现“'”、“"”、“\”、字符以及“/*”或“//”字符序列
Rule 20.3 必要 #include 指令后须跟随或"filename"序列
Rule 20.4 必要 宏不得与关键字同名
Rule 20.5 建议 不应使用#undef
Rule 20.6 必要 看起来像预处理指令的符号不得出现在宏参数内
Rule 20.7 必要 宏参数展开产生的表达式应放在括号内
Rule 20.8 必要 #if 或#elif 预处理指令的控制表达式的计算结果应为 0 或 1
Rule 20.9 必要 #if 或#elif 预处理指令的控制表达式中使用的所有标识符应在其评估前被#define 定义
Rule 20.10 建议 不应使用“#”和“##”预处理运算符
Rule 20.11 必要 紧跟在“#”运算符之后的宏参数后面不得紧随“##”运算符
Rule 20.12 必要 用作“#”或“##”运算符的操作数的宏参数,不得是本身需要进一步宏替换的操作数
Rule 20.13 必要 以“#”作为第一个字符的一行代码应为有效的预处理指令
Rule 20.14 必要 所有#else,#elif 和#endif 预处理程序指令都应和与其相关的#if,#ifdef 或#ifndef 指令位于同一文件中
标准库 Rule 21.1 必要 不得将#define 和#undef 用于保留的标识符或保留的宏名称
Rule 21.2 必要 不得声明保留的标识符或宏名称
Rule 21.3 必要 不得使用中的内存分配和释放函数
Rule 21.4 必要 不得使用标准头文件
Rule 21.5 必要 不得使用标准头文件
Rule 21.6 必要 不得使用标准库输入/输出函数
Rule 21.7 必要 不得使用中的 atof、atoi、atol 和 atoll 函数
Rule 21.8 必要 不得使用中的 abort, exit, getenv 和 system 函数
Rule 21.9 必要 不得使用中的 bsearch 和 qsort 函数
Rule 21.10 必要 不得使用标准库时间和日期功能
Rule 21.11 必要 不得使用标准头文件
Rule 21.12 建议 不得使用的异常处理功能
Rule 21.13 强制 中传递给函数的任何值都应表示为 unsigned char或者值 EOF。
Rule 21.14 必要 不应使用标准库函数 memcmp 与以 null 终止的字符串进行比 较。
Rule 21.15 必要 标准库函数 memcpy、memmove 和 memcmp 的指针参数应该是指向兼容类型的合格或不合格版本的指针。
Rule 21.16 必要 标准库函数 memcmp 的指针参数应该指向指针类型、本质上signed 类型、本质上 unsigned 类型、本质上 Boolean 类型或者本质上 enum类型。
Rule 21.17 强制 使用来自 中的字符串处理函数不应导致访问超出它们的指针参数引用的对象的范围。
Rule 21.18 强制 传递给 中的任何函数的 size_t 参数都应有一个适当的值。
Rule 21.19 强制 只应将标准库函数 localeconv、getenv、setlocale 或 strerror返回的指针用作它们好像具有指向 const 限定类型的指针。
Rule 21.20 强制 标准库函数 asctime、ctime、gmtime、localtime、localeconv、getenv、setlocale 或 strerror 返回的指针不应后接调用相同函数的后续调用。
Rule 21.21 必要 不应使用 的标准库函数 system。 
资源 Rule 22.1 必要 通过标准库功能动态获取的所有资源均应明确释放
Rule 22.2 强制 只有通过标准库函数分配的内存块才能释放
Rule 22.3 必要 不得在不同的数据流上同时打开同一文件以进行读写访问
Rule 22.4 强制 禁止尝试对以只读方式打开的流执行写操作
Rule 22.5 强制 禁止反引用指向 FILE 对象的指针
Rule 22.6 强制 关联的流关闭后,禁止再使用指向 FILE 的指针值
Rule 22.7 必要 宏 EOF 只应与能够返回 EOF 的任何标准库函数的未修改返回值进行比较。
Rule 22.8 必要 在调用 errno-setting-function 之前,应将 errno 的值设置为零。
Rule 22.9 必要 在调用 errno-setting-function 之后,应测试 errno 的值是否为零。
Rule 22.10 必要 仅当要调用的最后一个函数为 errno-setting-function 时,才应测试 errno 的值。

(结束)

你可能感兴趣的:(软件安全,代码安全,代码审计,静态分析,源代码审计,代码安全,MISRA)