原创:汽车软件开发中的静态分析技术
如今,汽车中运行的网络软件数量惊人,并且这一数字还在持续增加,其中大部分软件用于驱动安全关键系统。汽车的几乎每一个方面都由软件控制,包括油门、变速箱、刹车、仪表盘、气候控制系统、灯光、导航以及娱乐系统。由于这些车辆通常搭载着超过1亿行代码,运行在60台或更多的互联嵌入式计算机上,因此软件缺陷和安全漏洞可能会对安全运行构成威胁,给人类安全带来巨大的风险。
随着汽车行业不断创新,其复杂性与对安全关键型软件的依赖也在不断增长,这导致了汽车召回率的飙升,其中软件缺陷占据了当今召回事件的60-70%。例如,丰田汽车因线控油门系统中出现的意外加速缺陷而遭受了大约50亿美元的损失和收入减少。丰田的这个例子代价高昂,为软件安全领域提供了一个重要的教训——即软件开发必须持续改进,以帮助团队开发出高质量的软件,并避免那些“承担不起”的后果。
01.软件标准的引入
汽车行业已经认识到软件安全问题的日益严重及其重要性,并因此制定了一套专门针对 C/C++ 编程语言的软件开发指南,这套指南被称为 MISRA(Motor Industry Software Reliability Association)。这些指南旨在促进编码的最佳实践,从而提高软件的安全性、可移植性和可靠性。
此外,在2011年,汽车行业定义了 ISO 26262 这一国际标准。ISO 26262与母标准 IEC 61508 类似,是一项基于风险的安全标准。该标准通过定性评估危险操作情况的风险,定义了相应的安全措施来避免或控制系统故障,同时检测或控制随机硬件故障,或者减轻它们的影响。该标准的具体实施内容包括:
● 保证整个生命周期内的安全性,包括管理、开发、生产、运行、服务、报废,并在这些阶段内帮助定制必要的工作;
● 提供了一种汽车行业专有的基于风险的分析方法,来决定汽车安全等级;
● 使用ASIL等级来标明可执行的需求,以达到避免不合理的残余风险;
● 为验证和确认测试提供需求,来保证达到足够的且可接受的ASIL等级;
● 为供应商提供需求。
这些标准定义得非常明确。既然汽车行业已经努力采用了这些标准和最佳实践,为什么生产高质量的软件仍然如此困难呢?制造商可以采取哪些措施来确保他们的软件是安全可靠的?
02.汽车软件开发商面临的特殊挑战
汽车中大规模使用多功能软件来提供通常与娱乐和通信应用相关的功能的子系统。自从汽车诞生以来,车辆系统一直专注于具有明确单一功能的设计,依赖于易于测试的机械和模拟系统。然而,系统软件比传统的机械系统复杂得多,并且由于系统范围的扩大、新技能的需求、对威胁的认识以及系统的开放性,对开发人员提出了新的挑战。
软件使得一些原本不切实际的功能成为可能,例如优化燃油消耗、提高安全性的自动驾驶辅助、实时更新等。但由于其复杂性,不仅编写无缺陷的应用程序变得困难,软件系统还可能存在许多其他问题,如子系统之间的不兼容性、由于互联网连接而增加的网络安全威胁、导致系统不稳定的竞争条件等。总之,汽车软件非常复杂,对于高度连接的安全关键型软件的复杂性,新手团队面临的负担尤其沉重。
那么问题来了:如何确保程序能够正确运行?对于危及人命的安全关键型代码,我们如何判断程序是否可以安全使用?
03.静态分析的优势
静态分析的优势已被大多数对安全至关重要的行业所熟知和利用,从支持 DO-178B/C 认证的商用航空到支持 IEC 61508 认证的工业自动化。MISRA C 指南明确建议使用自动化静态分析工具来查找违反标准的行为。静态分析可以被认为是一种自动化代码审查,使用一套算法检查源代码以查找潜在问题。
静态分析工具可用于查找多种类型的问题,包括违反编码标准和规则、滥用语言 API、并发问题、数据流完整性问题以及安全缺陷。高级静态分析工具对整个程序具有深入的语义理解能力,这使得它能够在无需创建测试用例的情况下识别整个应用程序中的关键缺陷。通过启用 MISRA 或 ISO 26262 检查器,可以轻松识别出合规性问题。
04.汽车静态分析
要真正理解静态分析为何对于开发汽车应用的安全关键型软件极其重要,我们首先需要了解缺陷是如何进入程序的,其次,如何发现和修复这些缺陷。缺陷是编写代码的一个不幸但不可避免的副作用。研究表明,软件开发人员平均每十行代码就会引入一个缺陷。大部分开发时间都花在查找和修复大多数缺陷上。
代码发布时,大多数商业软件每 1,000 行代码(KLOC)仍会存在约1个缺陷。开源代码稍好一些,为 0.68/KLOC。净室软件结合了需求和设计的正式方法以及统计使用测试,结果显示其缺陷率为 0.1/KLOC。考虑到当今豪华汽车的软件内容远超 1 亿行代码,即使在绝对最佳的情况下,仍会存在至少 10,000 个潜在软件缺陷,实际上,缺陷数量可能超过 100,000 个。软件中存在的缺陷通常很难在测试中检测到,并且可能导致与其他子系统或未经检查的接口的互操作性差、行为异常或彻底失败。
在测试过程中查找和修复缺陷非常耗时。当测试人员发现错误或故障时,很多情况下根本原因未知,迫使开发人员追溯问题的根源。这意味着必须重现导致故障的情况,开发人员必须研究和理解相关代码。现实情况是,许多错误无法按要求重现,也永远不会得到修复。
静态分析的强大之处在于它不依赖测试用例来发现问题,也不需要重现错误或故障。高级静态分析工具无需实际运行程序即可推断程序的运行时行为。此外,当它发现问题时,它还会精确定位代码中导致故障的位置。这使得调试工作变得简单得多。
静态分析不能完全用于测试,但它可以补充测试活动,提供额外的关键验证技术。现实情况是,在大型复杂软件系统中,存在如此多的可能状态和如此多的可能执行路径,因此不可能对它们全部进行详尽的测试。另一方面,静态分析可以总体探索这些路径和状态条件,并能够发现测试遗漏的问题。
05.静态分析如何工作?
多年来,静态分析工具已经发展得极为复杂。早期的工具主要关注单个语句和声明的结构或行为,而如今的高级工具则能处理完整的源代码,执行整个程序的分析。这些工具的工作原理类似于编译器,它们接收源代码和二进制代码作为输入,对其进行解析,并转换为中间表示(Intermediate Representation, IR)。
编译器通常利用IR生成目标代码,而静态分析工具则保留IR作为程序的模型。这种模型的有效性取决于探索算法的复杂程度。最复杂的算法能够将程序的状态建模为一组抽象方程,这些方程涵盖了整个程序的执行路径,包括跨越函数和编译单元的边界。
静态分析工具通常被组织为一组检查器,这些检查器对代码执行不同类型的分析。它们通过遍历或查询模型来查找特定类型的缺陷和策略违规,识别表明问题存在的特定属性或模式。为了探索程序路径和推断程序变量及其关系,静态分析工具使用符号分析引擎。此外,高级的数据流分析技术能够帮助剔除不可行的程序路径。
静态代码分析包括两个主要步骤:
1.将代码转换为抽象语法树(AST);
2.应用分析规则查找潜在问题。
抽象语法树(AST)采用树形结构来表示代码,其中每个构成部分都被视为一个节点。每个节点有一个父节点,并可以有零个或多个子节点。节点的类型代表着代码中的表达式或字面值。
第1步:解析代码并将其转换为抽象语法树
将程序转换成抽象语法树(AST)的过程包括解析代码、理解其结构,并最终将其转换为 AST。
在解析代码的过程中,会遇到一些问题。首先,如果代码在语法上存在错误,就会导致解析失败,进而无法生成 AST。然而,有一些解析器具备较强的容错能力,即使遇到错误也能继续工作,并尝试根据可解析的部分生成 AST。
编程语言版本不同也会造成问题。随着编程语言的发展以及新语法或关键字的引入,解析器也需要随之更新,以适应各种语言版本。有些代码可能会因为使用了较新版本的语言特性而被旧版解析器误认为含有语法错误,而实际上这些代码是正确的,并利用了语言的最新功能。以Python为例,当它引入类型注解时,支持旧版本语言的解析器就无法正确处理带有类型注释的代码。
第2步:浏览抽象语法树
有了完整的 AST 后,就可以对其进行分析了。静态分析规则会分析 AST 并检测代码中的潜在问题。代码会在 AST 中遍历,当需要检查特定类型的节点时,代码会分析节点的叶子并检查是否存在错误。
以分析 Python 代码的规则为例,检查 requests 包中的 get 方法是否使用了 timeout 参数。
错误代码如下:
正确代码如下:
检查timeout参数是否赋值的伪代码如下:
应用AST 时,如果函数调用 requests.get 只有一个名为 url 的参数,则该规则将返回 False,因为没有 timeout 参数。只有当 timeout 参数被传递给 requests.get 函数调用时,函数 checkNode 才会返回 True。
第3步:设计规则
检查大型代码库和许多错误需要为每个潜在错误编写规则。这是一项非常耗时的工作。流行的静态代码分析器已预先配置了数百条规则。
06.静态代码分析的局限性
静态代码分析功能强大,可以标记出不同类别的问题(包括安全性、功能性和性能)。然而,静态代码分析仍有许多局限性。
一是编写规则非常耗时,因为每种语言的每个潜在问题都需要编写新的规则。此外,规则还需要针对特定工具,这增加了开发方面的成本。
二是出现误报的情况很常见(例如,在代码没有任何问题的情况下,规则却标记出了错误)。这一直是静态分析工具的最大问题之一,开发人员需要过滤静态分析工具报告的所有潜在问题中的噪音。很多静态分析引擎有一个额外的层来过滤误报,也有些工具允许用户禁用每个项目的规则。
三是静态分析工具无法检测依赖于运行时行为的问题。在特定运行时出现的问题是无法检测到的。同样,对于某些未定义行为的语言(如 C++),静态分析工具也无法准确诊断问题是否会发生。
鉴于需要对大量的情况组合进行建模和探索,静态分析工具必须采取多种策略以确保可扩展性。这些策略包括在分析过程中细化和压缩分析过程。通过这种方式,静态分析能够检测出那些仅通过测试难以发现的真实错误,如缓冲区溢出、内存泄漏、未初始化数据的使用、空指针取消引用以及并发错误。
07.结论
为安全关键型应用(如汽车系统)开发嵌入式软件需要采取严格的方法。最佳实践,包括流程和编码标准,是提高代码质量的有效手段。高级静态分析工具不仅有助于自动执行编码标准并支持标准合规性活动,而且在查找其他验证与确认(V&V)活动中可能遗漏的代码缺陷方面也发挥着至关重要的作用。它们帮助开发人员理解和纠正代码中的问题。
在大型复杂程序的背景下,由于代码执行路径的数量极为庞大,实现详尽的测试几乎是一项不可能完成的挑战。因此,静态分析成为了在开发阶段全面探索和验证软件完整性的唯一可行且高效的手段。通过采用高级静态分析工具,我们能够在不实际执行代码的情况下,深入检查代码结构,识别潜在的错误和缺陷。这种方法不仅能够补充传统的质量保证实践,而且能够显著降低软件在实际运行中遭遇故障的风险,从而凸显了静态分析在确保软件质量和可靠性的重要地位。
END
作者:Vova Zhu
文章来源:sasetech
推荐阅读
- 汽车网络安全 -- 后量子密码时代还远吗?
- 汽车信息安全 -- 存到HSM中的密钥还需包裹吗?
- 功能安全开发之MPU
- 内核 | little-kernel分析
- arm-trusted-firmware分析(上)
- arm-trusted-firmware分析(下)
更多物联网安全,PSA等技术干货请关注平台安全架构(PSA)专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入PSA技术交流群,请备注研究方向。