高 庆 陈 静 许 平 张世琨
1(北京大学软件工程国家工程研究中心 北京 100871)2(北京北大软件工程股份有限公司 北京 100080)3(中国电子科技集团公司第三十研究所 成都 610041)
近年来,随着计算机技术的发展,嵌入式系统在航空航天、核能、交通等安全攸关领域的应用越来越广泛.特别是随着工业互联网的发展,工业嵌入式软件呈井喷式的发展.由于软件功能的日益强大,软件正在逐步取代部分硬件的功能,嵌入式软件的规模及复杂程度急剧增加,在安全关键的航空航天、汽车以及工业控制等领域的用户对嵌入式软件质量要求更加严格,使用过程中对嵌入式软件缺陷的容忍度越来越低[1].
随着工业化和信息化的深度融合,针对工业互联网的安全事件频发,越来越多的攻击者把攻击目标锁定在国家关键工业基础设施上.从2000年开始,针对工业控制系统的恶意攻击事件越发频繁,工业控制软件一旦出现缺陷,将导致不可估量的灾难.例如:1997年9月美国舰船由于软件非法计算导致推进系统运转失败;2004年,F22战斗机试飞时FCS软件缺陷导致坠机;2006年,火星全球探勘者号的内存地址错误导致卫星失联;2016年7月美国西南航空调度系统由于软件缓冲区溢出导致系统崩溃,影响了7 000次航班的飞行;2019年3月,委内瑞拉南部玻利瓦尔州的一座主要水电站发生故障,导致大规模停电事件,影响18个州.近年来自动驾驶汽车的故障也造成很多交通事故[2].
工业嵌入式软件运行时间久、强度大,对安全性、可靠性以及资源的节省方面都有更多的要求,对工业嵌入式系统的安全性分析提出了新的挑战[3].许多嵌入式软件开发人员认为,嵌入式系统的安全性应该在系统级别上处理,或者由嵌入式硬件来处理,这种防御是不够的,嵌入式系统的开发者需要通过处理嵌入式软件中的漏洞来建立第3道防线.
嵌入式系统通常由硬件层、驱动层和软件层组成[4].嵌入式软件大体上可分为3类:嵌入式操作系统、嵌入式支撑软件和嵌入式应用软件.嵌入式操作系统主要用于控制、管理系统资源的软件,如Windows CE,Palm OS,Linux,VxWorks,pSoS,QNX,OS-9,LynxOS等;支撑软件是指用于辅助软件开发的软件工具集、交叉开发工具、软件测试工具等;应用软件是嵌入式软件中面向用户体验的软件,一般针对特定应用领域、完成一定功能,达到用户的预期目的.嵌入式应用软件种类最多,它不仅要求准确性、安全性、稳定性等方面能够满足实际应用的要求,而且还要尽可能地进行优化,以减少对系统资源的消耗,降低硬件成本[2].
嵌入式软件的专用性很强,为满足特定的领域需求,要求功能精简,这样有利于控制成本,并且可以更好地保证安全.多数嵌入式软件固化在芯片或单片机上,提供的内核资源相对有限,所以要求嵌入式软件保持效率高、冗余小、功率均衡,不完善的嵌入式软件很容易出现内存问题,导致运行时出现非预期的状况.
嵌入式软件具有高实时性的特点.一方面对嵌入式软件的质量、可靠性和安全性有很高的要求,因为一旦软件固化后就很难发现、调试和修改软件中的缺陷,比如由于缓冲区溢出等代码缺陷带来的安全隐患将更加难以修复[5];另一方面也对嵌入式软件运行时的实时性有比较严格的要求,对软件使用的场景、时间、体积、功耗有严格要求.如果嵌入式软件实时性差,会导致严重后果,如核电站控制、航天器入轨、飞行控制、航空发动机控制等,均要求时间比较精准[1].
工业互联网软件除了面向产品生产的研发软件、管理软件等,很大一部分是自动化控制软件或者面向装置级的嵌入式软件[6],即工业嵌入式软件.工业控制的设备、协议、嵌入式软件和系统在互联网上暴露问题是工业互联网安全的一个基本问题,直接面临互联网上的各类攻击.对于工业嵌入式软件而言,最大的风险来自安全漏洞,包括开发过程中编码不符合安全规范而导致的软件本身的漏洞,以及由于大量使用不安全的第三方组件而出现的安全漏洞[7],第三方组件的安全性和可控性问题日渐突出,一旦出现漏洞就会影响大量的工业产品[8],直接关系工业生产的成败.
C语言是嵌入式软件开发最常用的一种语言[9],C语言运行效率非常高并且可以方便访问硬件.然而,C语言在不同的编译器编译时会出现不同的结果,从而导致不可预期的问题,这是一个极大的隐患.随着工业嵌入式软件程序的日益复杂,软件的代码行数越来越多,如:NASA航天飞机的机载系统有近50万行代码,地面控制和处理也有35万行代码;一般的车辆控制系统有数百万行代码甚至上亿行代码.除了C语言,C++语言也越来越多地用于嵌入式软件开发中,如美国的F-35战斗机的控制系统就是采用C++语言编码,其机载和地面的嵌入式系统代码高达1 500万行.随着工业嵌入式软件代码规模越来越大,越来越多的嵌入式关键系统已经成为软件密集型系统[3].保证工业嵌入式软件代码安全成为越来越严峻的问题.
为了保证嵌入式软件代码的安全性、可靠性和可维护性,嵌入式软件的编码标准也越来越重要[2].国际上已经制定了面向多个工业行业的安全标准,在《IEC 6150电气/电子/可编程电子安全系统的功能安全标准》《ISO 26262道路车辆功能安全标准》《CENELEC EN 50128铁路应用-通信,信号和处理系统-软件铁路控制和保护系统标准》《RTCA DO-178B/C机载软件适航标准》中都提出要通过嵌入式开发语言的编码标准,来验证嵌入式软件安全性的原则.在嵌入式软件开发过程中,软件开发人员严格按照安全编码标准进行编码实施,可以在编码阶段预防安全缺陷的产生[10],并且可以提升安全缺陷的修复效率,在软件开发过程的源头减少安全缺陷,这是避免嵌入式系统在实际应用环境下被攻击的有效方法之一.随着工业软件的发展,嵌入式软件安全编码标准也在不断的发展,其发展过程如图1所示:
目前在工业嵌入式软件开发中使用比较广泛的安全编码标准是MISRA C和MISRA C++[10],其主要来源于福特和罗孚汽车公司的C开发标准,以及美国的联合攻击机、英国国防部的C++编码标准.MISRA 中的规则条目通过不断的更新和更正,成为了最新的MISRA C:2012以及MISRA C++:2008.为了补充嵌入式软件安全编码标准中风格类的编码规则,美国BARR组织推出了BARR C标准用于补充MISRA C:2012标准[12].2020年在德国召开的Embedded World会议上,BARR C:2018和MISRA C:2012协同作用的主题中提到BARR嵌入式C编码标准可以帮助嵌入式软件开发人员在进行调试时,去除嵌入式软件的代码错误,提升其可维护性和可移植性,帮助企业和个人开发出高可靠性的嵌入式软件[12],从而进一步提升软件的安全性.近年来,随着汽车工业的发展,汽车嵌入式软件需求越来越多,汽车电子标准组织AUTOSAR开发联盟提出了AUTOSAR C++编码标准,这套标准的规则主要来源于MISRA C++:2018以及HICPP规范.MISRA C/C++工作组成员Rozenau在2019年曾提出MISRA C++标准和AUTOSAR C++标准的检测规则密不可分,并计划将AUTOSAR C++与MISRA C++相互补充[13].AUTOSAR C++的出现极大推动了工业嵌入式软件安全编码标准的发展.
AUTOSAR C++还借鉴了CERT C和CERT C++安全编码标准,CERT是由软件工程研究所(SEI)为嵌入式开发人员创建的.专家从CERT C中提取出一套适用于静态分析技术的规则集,成为ISO/IEC TS 17961《C安全编码规则》.标准中提出45条适用于工业嵌入式软件的编码标准,一旦违背这些标准,会导致系统崩溃等问题[14].CERT C和CERT C++安全编码标准都收录于CWETM(Common Weakness Enumeration)表中.CWE表是由美国国家安全局首先倡议的战略行动,其中收录了大部分已发现的软硬件安全漏洞.在每年最严重的CWE TOP 25中至少有10个都是嵌入式软件开发相关的安全漏洞,这些条目长年被排在CWE TOP 25中,需要相关人员在嵌入式软件开发中高度重视并且提早解决.CWE TOP 25中的嵌入式软件开发安全漏洞如表1所示:
表1 CWE TOP 25中的嵌入式软件开发安全漏洞
随着国内嵌入式软件的发展,国内推出了工业嵌入式软件标准GB/T 28169—2011《嵌入式软件C语言编码规范》,但是目前应用不是很广泛.GB/T 28169 C—2011中包含一些针对嵌入式软件特定的规则,如中断、寄存器缺陷、硬件系统初始化等.
针对MISRA C++:2008、MISRA C:2012、BARR C:2018、AUTOSAR C++、CERT C/C++、ISO/IE TS 17961 C、CWE表、GB/T 28169 C—2011这些目前比较常见的国内外嵌入式软件安全编码标准,从发起图家和地区、发布时间及情况、发布组织、规则数目、适用领域以及规则集特点进行对比,如表2所示.
表2 嵌入式软件安全编码标准对比
这些常用的嵌入式软件安全编码标准之间,在具体规则项上也有一定的关联和覆盖,如图2所示.
图2 嵌入式软件安全编码标准关联关系[12-16]
本文基于对嵌入式软件安全编码标准的研究,进一步对标准之间的关联关系进行详细的分析、比较和去重,筛选和抽取出工业嵌入式软件开发安全漏洞模式清单.漏洞模式共分为3大类,即嵌入式软件特有安全漏洞模式、通用类严重缺陷模式和其他缺陷模式,每种大类下包含若干种缺陷模式小类.通过进一步分析缺陷模式中各种情况,参考国内外各套安全编码标准的详细内容抽取出若干具体的模式检测规则.
嵌入式特有模式主要与嵌入式软件密切相关,例如硬件初始化、并发性、原子性、中断等嵌入式特定情况相关的一些缺陷模式,这些缺陷模式主要来自于GB/T 28169—2011《嵌入式软件C语言编码规范》,是与嵌入式系统硬件相关或者是嵌入式软件中特有的编程用法,在嵌入式软件中出现这些缺陷后,会导致嵌入式系统崩溃等问题.这类模式包括8种缺陷模式小类、26种模式检测规则,如表3所示:
表3 嵌入式软件特有安全漏洞模式
这些模式检测规则是规定编码的详细条目描述,针对每种模式具体的检测规则举例,如果使用静态代码分析技术对被测嵌入式软件代码进行检测,就要首先对规则进行详细说明,以及添加正确和错误例子代码,建立完整的安全漏洞模式知识库,如表3中,序号“1”中“寄存器缺陷”的漏洞模式规则举例“中断处理程序和主程序寄存器组冲突”,是指在中断函数中调用其他函数,必须和中断函数使用相同的寄存器组.这是因为主程序默认使用的寄存器为BANK0,如果中断函数没有通过using指定寄存器组或者指定了相同的寄存器组,则不会发生冲突;如果中断函数通过using指定了不同的寄存器组,则被调用的函数需要放在#pragma NOAREGS和#pragma AREGS控制参数对中,该参数对的意义是使编译器不要对该函数使用绝对寄存器寻址.当没有用NOAREGS参数作明确的声明,编译器将绝对寄存器寻址方式访问函数选定(即使用using或REGISTERBANK指定)的寄存器组,当函数假定的和实际所选的寄存器组不同时将产生不可预知的结果,从而可能出现参数传递错误,返回值可能会在错误的寄存器组中.
除了嵌入式特有的模式,还有一些安全漏洞模式在所有C/C++语言开发的软件中都通用,并且在嵌入式软件中错误使用会导致严重的问题,如导致嵌入式系统崩溃或者拒绝服务等严重后果.这类缺陷模式归为通用类严重缺陷模式,这类缺陷模式在各个编码标准中都可能出现,可以用于C/C++语言编写的各类应用软件代码的检测中.这类严重缺陷包括20种模式小类、89种模式检测规则,如表4所示:
表4 通用类严重缺陷模式
通用类严重缺陷模式也要在安全漏洞模式库中进行建立,如表4序号2中的“动态链接库”的漏洞模式检测规则“调用外部文件未使用完整路径”,指的是在应用程序加载外部库时,代码应该使用完全限定的路径,这一点很重要.如果指定了不完全限定的路径,则恶意攻击者就可以控制搜索路径,并且将其用作远程执行任意代码的载体,某些 API(例如 SearchPath)也提供可用于恶意攻击的载体,它们会尝试从非预期的源目录中加载库,这些类型的威胁称为二进制植入或DLL预加载攻击.比如直接使用相对路径调用外部文件,写成LoadLibrary(“external_library.dll”).这会导致相对路径可能被恶意攻击者利用以执行任意代码.应该改为在调用外部文件时使用完整路径,写成 LoadLibrary(“C:/libs/external_library.dll”).
表4中序号17中的“密码权限”的漏洞模式检测规则“明文存储密码”也是很常见的一种严重安全漏洞模式.2018年10月9日,美国政府问责局发布了题为“WEAPON SYSTEMS CYBERSECURITY-Just Beginning to Grapple with Scale of Vulnerabilities”的报告中提到美国国防部大部分正在开发的武器嵌入式系统中都存在安全漏洞,最多的安全漏洞就是密码被非法获取.当密码以纯文本格式存储在应用程序的属性或配置文件中时,任何可以读取该文件的人都可以访问受密码保护的资源,极大地危及整个嵌入式系统的安全.应该避免将密码存储在易于访问的位置,也不要使用纯文本格式存储任何敏感信息,并使用合适的加密方法对密码进行加密.
能展示日常工作情况。竞赛是岗位练兵活动中又一大内容,通过竞赛的形式,模拟住宅火灾救援、林地火情控制等等,在竞赛时“滥竽充数”、“混水摸鱼”是行不通的,日常的工作情况将真实的反映在考官的面前,或优或劣的工作情况得到了有效展示,通过竞赛形式,促使了员工立足岗位,苦练过硬本领、学技术,练绝活,干一流,争第一,争当岗位能手的自觉性。
其他缺陷模式检测规则有300多种,主要针对函数、变量、循环、跳转等编码风格、样式规范性的一些要求,这一类缺陷模式也来源于各个编码标准.如果在嵌入式软件的编码过程中出现其他类的缺陷模式,一般不会导致严重的后果,但是会让代码存在潜在隐患,并且导致嵌入式软件的代码不容易维护.如函数类的“有返回值的函数中return必须带有返回值”这条规则在MISRA2012和CWE的编码标准中都有提到.这条规则是指函数类型不是void,但是函数内的return并没有返回值,那么返回值就未被赋值,这可能导致使用未初始化的内存.当返回值在其他地方被用到时,会导致未知的错误.
工业嵌入式软件安全漏洞可以通过静态分析和动态分析2类技术进行检测.其中:动态分析结合程序运行行为进行检测,一般不需要指定漏洞模式,只要程序运行时满足某个条件(如程序崩溃)就可以发现安全漏洞;代码静态分析技术在不运行程序的情况下对代码进行分析,通常以安全漏洞模式为输入,可用于检测嵌入式软件中特定模式的安全漏洞.下面对静态分析技术进行介绍.
代码静态分析技术越来越被认为是高级编程语言的高性能实现和验证系统的基本工具[17].代码静态分析技术主要分为基于语法和基于路径遍历2类静态分析技术,其中基于路径遍历的分析技术以语法分析为基础,进一步分析程序中变量之间的控制流和数据流关系.接下来以2类嵌入式软件漏洞模式为例,介绍对应的分析方法.
对于中断规范这种规则类的漏洞模式,基于语法即可准确分析.根据用户指定的中断服务程序,对代码进行词法、语法分析,生成抽象语法树,树中的各个结点表示函数、语句、表达式、变量等不同类型的程序元素,边表示各个程序元素之间的包含关系.然后,对抽象语法树进行遍历.对于函数定义类型和语句类型的树结点,检查定义中的返回类型声明是否存在,或者语句是否为返回语句,若是即违反“中断服务程序不应带有返回值”的规范;对于函数调用类型的树结点,若存在被调用函数名为printf,malloc的函数,即违反“不应在中断服务程序中使用printf,malloc等函数”的规范.
对于其他大多数的漏洞模式,仅基于语法不能准确分析.例如,对于“可疑的空指针解引用”这种漏洞,必须同时具备以下4个要素:1)在程序中的某个语句Si定义了空指针;2)在程序另一个语句Sj存在该指针的解引用;3)存在Si到Sj的路径且实际可达;4)在所有可达路径中,不存在1条赋值语句,使该指针一定被赋值为非空值.可以看出,仅靠抽象语法树上的信息不足以准确分析出以上信息,需要基于抽象语法树进行关于程序路径的进一步分析,称为基于路径遍历的分析技术,主要包括值流分析[18]和符号执行[19]2类.
对于值流分析,分为以下3个步骤:
步骤1.构建控制流图、函数调用图、指针分析图等若干种基础程序模型,描述函数内语句执行顺序、函数间调用、程序指针指向等关系.
步骤2.基于以上程序模型,构建值流图或值依赖图等综合图模型,表达变量之间的控制流及数据流依赖关系.其中,控制流表达程序语句之间执行顺序的依赖关系,数据流表达变量之间在定义-使用上的依赖关系.图中的每个结点表示语句中的变量,边表示变量之间的数据流关系,边上标记数据流存在的条件,为约束表达式,描述了控制流关系.
步骤3.基于所分析的"可疑的空指针解引用"模式的4个要素,在综合图模型中进行定位和搜索,图模型中的结点可对应到程序语句,有向边对应到程序中的控制流和数据流传递关系.首先,对于每个指针P,在图模型中检查是否存在使其可能为空的赋值,并将对应结点记为N1;然后,进一步在综合图模型中计算从N1出发的所有路径,所得路径即为程序切片;继而,检查所得程序切片中的结点,是否存在对指针P的解引用,若存在则将该结点记为N2,并计算结点N1到达结点N2所需要满足的约束表达式,因N1到N2之间可能存在多条路径,这里对约束条件采用同一路径取交、不同路径取并的计算方法;最后,使用SMT约束求解技术,对前序步骤中所得的约束表达式进行求解,若结果为真,则判定存在对应漏洞,报告相应的路径和可疑的空指针定义点及使用点.
对于其他嵌入式安全漏洞模式,分析技术相似.其中步骤1和步骤2的计算结果为共性的模型,在不同的模式之间可以复用;步骤3特定于每种漏洞模式,但通常都涉及到图中关键结点识别、路径计算、约束求解这3个子步骤.
符号执行技术同样可以检测这些模式,类似于值流分析,需要构建控制流图.与值流分析侧重于通过数据流分析漏洞不同,符号执行更侧重从控制流的角度分析漏洞.其将变量用符号表示,按照程序的执行过程,对程序进行模拟执行,记录每条语句执行后程序中各个变量符号和约束表达式等程序状态的变化.对于“可疑的空指针解引用”模式,当依次监测到可疑空指针赋值和对该指针进行解引用的语句执行后,对所记录的约束表达式进行求解,即可发现是否存在一条可达的路径,进而判断是否存在该漏洞.
可以看出,值流分析和符号执行都使用了约束求解的技术,该技术在大规模程序中会有约束表达式过于复杂导致无法求解的问题.此外,嵌入式程序本身的静态分析模型构建过程也因为程序的复杂性,在跨函数、域敏感、上下文敏感等场景上,必须牺牲部分精度,以达到可接受的分析效率.因此,静态分析的结果不能保证正确,其对漏洞检测的误报率、漏报率和检测效率也是衡量不同检测技术的关键指标.不同的技术路线都有其优化方法[20-23],并可持续改进,以期在满足一定检测效率的基础上,尽可能降低误报和漏报.
本文介绍了工业嵌入式软件的安全特点,并对工业嵌入式软件相关的最常用的C,C++的国内外安全编码标准MISRA C,CERT C,GB/T 28169等进行说明,从各个标准的发展过程、标准特点以及各标准之间关联覆盖关系进行分析,进而提出一套适用于工业嵌入式软件的安全漏洞模式清单,模式分为3大类,包括:嵌入式特有的安全漏洞模式、通用类严重缺陷模式以及规范性的其他缺陷模式.最后介绍了基于语法和基于路径遍历2类静态分析技术对这些安全漏洞模式的代码检测实现,并针对一些工业嵌入式软件开发安全漏洞模式的分析检测实现进行详细说明.通过使用静态分析技术,对工业嵌入式软件进行安全漏洞模式分析和检测,在软件开发过程的源头发现编码安全问题并得到及时解决,可以更好保证工业软件的安全,进一步推动工业软件的发展和工业互联网的发展.