如需转载请注明出处,侵权必究。
论文题目:Silent Bugs Matter: A Study of Compiler-Introduced Security Bugs
发表会议:Security 2023
本文第一作者是徐坚皓,师从茅兵教授,目前在南京大学Seclab攻读博士学位,研究方向是编译器安全、操作系统安全和程序分析。
概述
编译器确保任何优化后的代码在语义上与原始代码等效。然而,即使“正确”的(correct)编译器在编译过程中也可能会引入安全漏洞。当本身没有漏洞的源码被编译器“正确”地转换,进而引入安全漏洞时,用户与编译器之间就会产生分歧。编译器开发人员希望用户严格遵循语言规范并理解所有假设,而编译器用户基于源码的正确性,错误地认为编译后的代码应当是安全的。
基于上述分歧,本文对此类编译器引入的安全错误(CISB)及其根本原因进行了全面研究。通过手动分析最流行的编译器(GCC和Clang)的4,827个潜在漏洞报告,作者收集了大量CISB报告并进行了归纳和分类。在此基础上,作者开展了一项用户研究,以了解编译器用户如何看待此类编译器行为。调查和研究表明,编译器引入的安全漏洞很常见,并且可能会产生严重的安全影响,而期望编译器用户理解并遵守编译器假设是不现实的。
背景
CISB定义
以下情况下的软件错误定义为CISB:
代码在未经优化的情况下执行时不存在安全问题;
编译器执行优化,在编译期间修改代码,从而产生漏洞;
代码本身不包含任何语言关键字的错误用法;
编译器优化在形式上是正确的,即编译器不违反任何语言规范。
本文研究问题
前人的研究各自侧重于几种特定的CISB模式,但由于CISB与编译器紧密相关,导致相关漏洞涉及面广,表现形式多样,并且伴随着难以排查、重视不足等诸多问题,已经广泛威胁到了现实世界中的代码安全。因此作者认为有必要对现实世界的CISB进行更加全面的研究。为此,作者提出以下四个研究问题:
RQ1:CISB形成的根本原因、形成过程和可能的安全影响。
RQ2:程序员对CISB及其缓解措施的了解和看法。
RQ3:现有缓解措施的风险。
RQ4:进一步研究的挑战。
CISB数据集搜集
当编译器引入的漏洞被发现时,一般有两种修复方式:要么编译器维护者修复编译器,要么编译器用户修改其代码以避免编译器优化。因此作者从以下两个来源搜集CISB相关的漏洞报告。
(1)编译器Bugzilla,主要是用户提交给编译器bug跟踪器的漏洞报告(包括GCCBugzilla和LLVM-Bugzilla)。
(2)与编译器问题相关的程序补丁。通常,在发现编译器引入的漏洞后,通知并等待编译器修复需要较长的周期,在这种情况下,编译器用户往往选择直接修复源代码,而相关修复中通常包含有关编译器如何引入漏洞的具体信息。因此,作者选择在流行的OSS程序的补丁历史中寻找CISB相关的漏洞。此处作者特地选择Linux内核作为目标,因为Linux内核拥有庞大且多样化的代码库,其中有许多会触发优化问题的极端情况。
在数据集搜集环节,作者最终收集了共计120个CISB;其中68个来自1,601个Bugzilla报告,52个来自3,226个Linux补丁。
CISB的根本原因
编译器编译后的代码必须符合源代码的语义,如果编译器生成的代码在语义上与原始等效,则编译器被认为是正确的。然而,这种正确性保证并不能确保代码的安全性,存在两个原因导致编译器在编译时引入安全漏洞。
隐式规范 (ISpec)
规范可以是隐式的。该规范允许编译器仅为“定义明确”的代码提供正确性保证。此类隐式规范允许编译器执行可能会破坏安全属性的激进优化。例如,程序员会添加像if(x+10<x)< span="">这样的边界检查来检测有符号变量x的整数溢出,但编译器根据语言规范会假设源代码不存在符号溢出,进而消除if检查。
作者将隐式规范分为三类:
无未定义行为假设(No-UB)。编译器假定源代码不存在未定义行为(UB)。语言规范允许这种假设,以降低编译器的复杂性并帮助编译器生成更快的代码。当程序员依赖UB的执行来实现其代码功能时,就会出现冲突。
默认行为假设(Default-behavior),编译器认为适合执行某些默认行为或做出某些默认假设。例如,编译器可能会将其他类型(例如char或byte)提升为int(整数提升),以及编译器默认函数调用总会在调用结束后返回调用者的上下文。当程序员的编码意图与编译器假设的默认行为不符时,就会出现冲突。
环境假设(Environment)。编译器假定环境的某些实现细节,例如是否应采用某些特定于环境的指令或是否对特定数据要求内存对齐。当程序员在目标机器上写出与不符合环境假设的特定代码时,就会出现冲突。
正交规范 (OSpec)
正交规范指与安全相关的程序状态超过语言规范的语义功能范围。为了实现正确性,编译器可以不受安全性约束,允许编译器为了实现语义功能完全忽略安全属性。一个具体的例子如下所示,第5行的memset应该清除内存上的敏感数据以防止信息泄漏。然而,如果没有规范编译器应该如何防止信息泄漏,编译器会在推断出变量hash后续不会被使用后消除memset函数。
CISB类别
作者根据CISB编译过程中的不安全优化行为以及其导致的安全后果对搜集到的CISB报告进行归纳和分类,如见下表所示。
隐式规范 (ISpec)
除了上述消除if(x+10<x)< span="">这类安全相关代码的影响外,ISpec还会打乱顺序敏感的安全代码和引入不安全指令。如下图所示,编译器默认第3行的ereport函数会返回(return),进而继续向下执行代码。编译器推断出第7行的代码无论如何都会被执行。在此推论的基础上,某些架构的编译器会将第7行的除法移到第2行的检查之前,将会导致除零错误。
当编译器假设环境是默认对齐时,会引入某些不安全指令。例如如下Read函数,在编译时将被优化为VLDR指令,而VLDR要求传入的地址是对齐的。当编译器用户传入的地址非对齐时,VLDR指令会在运行时产生错误。
正交规范 (OSpec)
前述memset函数被编译器消除的案例展示了CISB将敏感数据移出安全边界的危害。此外,由于存在正交规范,编译器还容易破坏源代码的一些时间属性以及引入不安全的微架构副作用。如下图所示,for循环的无效执行是为了等待硬件初始化完成,而编译器的优化将导致代码没有给硬件初始化预留充分的等待时间,进而导致硬件错误,因此此处需要更新为显式的延迟执行。
而针对switch条件语句的优化也会因为CPU投机执行的特性不经意间引入安全问题。下述代码在优化之后,由于存在投机执行,CPU会利用空转时间提前执行一些将来可能用得上,也可能用不上的指令。如果指令执行完成后发现用不上,系统会抛弃计算结果。然而CPU恢复状态时并不会恢复CPU缓存的内容,因此优化实际上引入了潜在的安全威胁,允许攻击者利用基于缓存的侧信道攻击泄漏遗留在内存里的敏感信息。
用户调研
考虑到数量庞大的编译器规则和假设,编译器用户能否正确遵循编译器规则来避免引入CISB有待考究,本文通过用户调研从三个角度回答这个问题。
A1:程序员对CISB及其相关问题的了解和认知。
程序员在未定义行为(UB)、编译器无未定义行为假设(No-UB)、未定义行为导致的编译器引入漏洞(UB-CISB)之间存在认知差距,了解相关概念的比例分别为90.3%、48.4%、41.9%,说明大部分程序员只了解CISB相关问题的浅层概念,而缺乏对CISB安全问题的深入理解。
A2:程序员针对CISB的处理经验与处理难度估计。
调研发现程序员解决UB-CISB通常需要花费很多时间,超过一半的程序员要么花费2个小时以上的时间解决,要么根本没有办法解决CISB。
同时,大部分C程序员认为自己很难掌握和理解CISB的原理、修复或避免编写出引发UB-CISB的代码。如下图所示,大部分程序员认为UB-CISB相关的知识较难学习和应用。
A3:程序员对CISB问题的看法。
C程序员倾向于认为编译器应该对大多数CISB负责。当被要求对责任程度从1到9进行排名时(1表示完全是程序员的责任,5表示中立,9表示完全是编译器的责任),程序员平均将UB-CISB评为5.81,将OSpec的CISB评为5.87。
以上三个方面的量化结果表明:在目前的情况下指望程序员自己去防范CISB是不靠谱的。
当前防护手段
程序员/用户
为了避免受到CISB的影响,编译器用户通常选择(1)规范化源代码以避免潜在的漏洞语义,例如UB。(2)使用语言级或硬件级机制来控制编译器行为。(3)拒绝所有编译器优化。此外,作者认为指望编译器用户自己来防护CISB是有风险的,原因是(1)UB规则很难学习和理解。UB的C标准(ISOC17J2)中有多达200多个规则。(2)编译器优化可能会再次使编译器用户采取的防护措施失效,并且由于这个原因,作者观察到许多CISB为了修复打了不止一次补丁。
通过编译器辅助
用户可以利用某些编译器的编译选项开启针对CISB的防护。然而这些防护会受到性能开销的制约,也就是说,有效的缓解措施通常会严重损害性能。下图下半部分展示开启不同编译选项时编译器防御漏洞的有效性(如果编译策略能够避免漏洞产生,或者使得代码能在在运行时或编译时捕获bug,就认为该策略有效。)以及其额外性能开销(Overhead)。
自动化防护方法
Runtime sanitizer,可以在代码编译期间添加检查以捕获在运行时产生的安全问题。目前已有UBSanitizer(检测常见的未定义行为)、ThreadSanitizer(检测数据竞争)、TypeSanitizer(检测严格类型别名违规)等诸多工作。
形式化安全编译,使编译器能够在优化期间保留安全属性。
专门的安全分析。许多工作提供了特定的抽象模型或语义模式来识别此类安全问题。
未来研究的挑战
对于专门的安全分析,通过程序分析识别CISB最困难的部分是提供理想的模型来帮助识别bug是否与安全相关以及是否由编译器引入。此处的另一个挑战是,源码层次的分析不应仅考虑代码本身,还应考虑编译器的优化过程。此外,对于检测工作来说,误报和误报都是难以避免的。
对于Runtime sanitizer来说,对于非UB相关的CISB需要更加定制化的sanitizer,并且需要保证足够的代码覆盖率。
对于形式化安全编译,还未能在主流编程语言中真正应用。
贡献总结
本文(1)搜集了现实世界中数量众多的各类CISB,根据漏洞的根本原因、形成过程和安全影响分类,为安全社区贡献了高质量的CISB数据集。(2)进行了一项用户研究,显示C程序员通常缺乏并且难以理解CISB的相关知识。(3)从程序员、编译器和自动化防护工具角度开展调查,显示现有的CISB缓解措施依然存在的风险。(4)回顾当前CISB问题的研究进展,总结本文研究过程中获得的新知识,指出未来的研究方向。通过展示未来的挑战和机遇,作者希望这项研究能够为未来针对安全编译的研究提供动力和指导。
供稿:魏宏涛
审稿:顾康正、邬梦莹、洪赓
排版:边顾
戳“阅读原文”即可查看论文原文哦~
复旦白泽战队
一个有情怀的安全团队
还没有关注复旦白泽战队?
公众号、知乎、微博搜索:复旦白泽战队也能找到我们哦~