人为因素
第一个部署是批量部署,而不是连续部署。在这种模式下,Infer每晚将在整个Facebook Android代码库上运行一次,然后生成一个漏洞列表。之后,分析人员会手动查看这些漏洞,并将它们分配给对应的专业人员予以解决。
但在实际测试中,这个解决漏洞的流程却问题多多。当我们给开发人员分配了20-30个漏洞,但是没有一个被得到解决。我们一直在努力将误报率降低到我们认为低于20%的水平,但是修复率,即开发人员解决的所报告的漏洞比例却是零。
接下来,我们在diff时间打开Infer。同样,工程师们的反应同样令人震惊:修复率飙升至70%以上。同样的程序分析,同样的误报率,在diff部署时具有更大的影响。
虽然这种情况令Infer团队的静态分析专家感到惊讶,但Facebook的开发人员并不感到惊讶。他们向我们提供的解释可以概括如下:
差异部署地址的一个问题是上下文切换的脑力活动(mental effort),如果开发人员正在解决一个漏洞,而他们面对的是一个关于另一个漏洞的报告,那么他们必须更换第一个漏洞的脑力上下文并切换到第二个漏洞,这可能是耗时且具有破坏性的。通过以木马的身份参与代码审查,上下文切换问题在很大程度就可以得到解决:此时程序员使用评审工具与人工评审人员讨论他们的代码,脑力上下文已经切换这也说明了及时性是多么重要,如果一个木马要在差异上运行一个小时或更长时间,那么它可能来不及有效地参与进来。
差异部署的第二个问题是相关性,当在代码库中发现漏洞时,将其分配给合适的修复人员可能并不重要。在极端情况下,离开公司的人也可能会引发这个漏洞。此外,即使你认为自己找到了熟悉代码库的人,该漏洞也可能与他们过去或当前的工作无关。但是,如果我们对引入漏洞的差异进行评论,那么它就是一个相当好的(但并非完美的)机会。
脑力活动的上下文切换一直是心理学研究的主题,它与相关性的重要性一样,是Facebook工程师给我们留下的智慧。
对于Facebook来说,我们正在积极致力于将 diff 技术(差异时间)应用于其他测试技术。另外,我们还支持学者研究用于差异时间报告的增量模糊测试和符号执行技术。
进程间漏洞
Infer发现的许多漏洞涉及跨越多个进程或文件的推理。,以OpenSSL的一个例子为例说明:
apps/ca.c:2780: NULL _ DEREFERENCE pointer 'revtm' last assigned on line 2778 could be null and is dereferenced at line 2780, column 6 2778. revtm = X509 _ gmtime _ adj (NULL, 0); 2779. 2780. i = revtm->length + 1;
问题是进程X509 _ gmtime _ adj()在某些情况下可以返回null。总的来说,Infer找到的漏洞跟踪有61个步骤,而null的来源,对X509 _ gmtime _ adj()的调用会深入五个进程并最终在调用深度4处遇到null返回。这个漏洞是一个我们向OpenSSL报告的15个漏洞之一,目前这些漏洞都已经修复。
Infer通过执行组合推理发现了这个漏洞,它允许覆盖进程间漏洞,同时仍然可以扩展到数百万个程序行数(Lines of Code,LOC)。它推导出一个近似于X509 _ gmtime _ adj行为的前置或后置条件规范,然后在对其调用进行推理时使用该规范。规范将0作为返回值之一,这将触发漏洞。
在2017年,我们查看了几个类别中的漏洞修复情况,发现超过50%的修复程序是针对进程间跟踪漏洞。如果我们只部署本地分析,进程间的漏洞将会被遗漏。
并发性
Facebook 于2017年10将其开发的 RacerD 进行了开源,代码包含在静态代码分析工具 Infer 的代码库中。据 Facebook 介绍,Infer 静态分析平台在开源的前10个月内在 Android 代码库中捕获了超过1000种多线程问题。有一种软件漏洞是开发复杂软件项目开发者的噩梦,那就是代码中的竞争条件引发的软件漏洞,而RacerD 可以帮助开发者检查并预防竞争条件引发的软件漏洞。
目前,RacerD的研发者们计划将部分Facebook的Android应用程序从串行架构转换为多线程架构。现在必须在并发上下文中使用为单线程体系结构编写的数百个类,因为转换可能会引入并发漏洞。他们要求提供进程间功能,因为Android UI排列在树中,每个节点有一个类。竞争可以通过进程间调用链(有时跨越多个类)发生,而突变几乎从未发生在顶层,因为这将遗漏很多竞争条件引发的软件漏洞。
该分析检查Java程序中的数据竞争,即两个并发内存访问,其中一个是写入。图2(顶部)中的示例说明:如果我们在此代码上运行Infer,则不会发现任何漏洞。未受保护的读取和受保护的写入不会因为它们位于同一线程而竞争。但是,如果其中包含其他竞争方法,那么Infer将报告RacerD,如图2所示。
捕捉了Android应用程序中使用的常见安全模式
自2014年以来,Facebook的开发人员已经解决了由Infer标记的100000多个问题。 Infer的大部分影响来自差异时间部署,但是它也运行批处理来跟踪主数据中的问题,修补程序中解决的问题以及其他定期计划。
截至2018年3月,RacerD数据竞争检测器已经发现超过2500个修复程序。它支持将Facebook的Android应用程序从单线程转换为多线程架构,方法是搜索潜在的数据竞争,而无需程序员插入注释来说明内存是由什么锁保护。这种转换提高了滚动条的性能,并且在谈到分析器的作用时,Facebook的Android工程师Benjamin Jaeger表示:
“如果没有Infer,新闻Feed中的多线程将不会成立。”
截至2018年3月,并发性分析的修正率约为50%,低于之前的常规diff分析。
总的来说,Infer可以报告30多种类型的漏洞,从深入的进程间检查到简单的本地检查都有它的身影。并发支持在过去一年中修复了数百个“app not-responding”漏洞,另外,Infer最近还实现了一项安全性分析(“污点”分析),该分析已应用于Java和C ++代码,该思路借鉴了Zoncolan的想法。
关于Zoncolan
开发和采用Hack的最初原因之一是能够对Facebook的核心代码库进行更强大的分析。Zoncolan是我们构建的静态分析工具,用于查找可能导致Hack代码库中的安全性或隐私冲突的代码和数据路径。
图3中的代码是Zoncolan分析的一个漏洞示例。如果第21行的member_id变量包含value ../../users/ delete_user/,则可以将此表单重定向到Facebook上的任何其他表单。在提交表单时,它将调用https://face-book.com/groups/add_member/../../users/delete_user/的请求,该请求将删除用户的帐户。图3中漏洞的根本原因是攻击者控制了<form>元素的action字段中使用的member_id变量的值,Zoncolan遵循不可信数据(例如用户输入)的进程间流程到代码库的敏感部分。虚拟调用确实使进程间分析变得困难,因为该工具通常不知道对象的精确类型。为了避免丢失路径以及漏洞,Zoncolan必须考虑调用可能解析到的所有可能函数。
Zoncolan阻止的漏洞示例,它可能导致攻击者删除用户帐户。攻击者可以在第5行提供一个输入,从而在第20行重定向到Facebook上的任何其他表单。
Zoncolan的初始设计始于安全工程师提供给我们的SEV列表,对于每个漏洞,我们都要想一下:“我们怎么能用静态分析来捕获它?”,由于编程语言或安全框架阻止了它们的递归,这些历史漏洞中的大多数不再相关,例如,XHP的广泛采用使得通过构建无xss Web页面成为可能。而被遗漏的漏洞则涉及不可信数据的进程间流程,直接或间接地加入某些含有高级权限的API。使用静态污点流程分析可以自动检测此类漏洞,该分析可以跟踪来自某些不可信源的数据如何到达或影响到达代码库的某些敏感部分的数据。
当安全工程师发现一个新的漏洞时,我们会评估该类漏洞是否适合静态分析。如果是,我们对新规则进行复原设计,并与工程师的反馈进行迭代,以便优化误报率的分析结果。当我们相信规则足够好时,它就会在运行中的所有Zoncolan运行中启用。我们采用标准的Facebook App Security严重性框架,它将每个漏洞关联到一个影响级别,范围从1到5不等,安全影响级别为3或更高被认为是严重的。
扩展分析
Zoncolan的一个主要挑战是将Zoncolan扩展到超过1亿个LOC代码的代码库。由于我们设计了一种新的并行、组合、非均匀静态分析策略,所以Zoncolan在24核服务器上在不到30分钟的时间内就完成对代码库的全面分析。
为此,Zoncolan还专门构建了一个依赖图,它将方法与潜在的调用者联系起来。它使用此图来安排各个方法的并行分析。在相互递归方法的情况下,调度程序迭代方法的分析,直到它稳定下来,也就是说,不再发现更多的流程。另外,合适的操作符(在静态分析文字中称为宽度)可以确保迭代的收敛。值得一提的是,即使在学术界已经建立了污点分析的概念,我们也必须开发新算法来扩展代码库的大小。
图4提供了Zoncolan部署模型的图形表示。这个部署模型优化了漏洞检测,目的是支持Facebook的安全性:Zoncolan主分析查找新发现的漏洞的所有现有实例。 Zoncolandiff分析可以避免在代码库中(重新)引入漏洞。
Zoncolan的部署策略
Zoncolan会定期分析整个Facebook Hack代码库以更新主列表。目标受众是安全工程师执行安全审查。在主分析中,我们公开了所有发现的警报。安全工程师对给定项目或给定类别的所有现有警报都感兴趣。他们通过仪表板对警报进行分类,仪表板可以按项目、代码位置、数据源或目标、跟踪的长度或功能进行筛选。当安全工程师发现漏洞时,Zoncolan会提供有关如何使代码安全的指导。当警报是误报时, Zoncolan的开发人员会及时作出解释。然后,Zoncolan开发人员会改进该工具以提高分析的精确度。在对漏洞类别进行了广泛测试后,Zoncolan团队与App安全团队会一起评估是否可以推广diff分析。升级通常涉及通过根据例如进程间跟踪的长度、端点的可见性(外部或内部?)等对输出进行筛选,从而改进信号。截至发稿时,大约1/3的Zoncolan类别已启用diff分析。
如果diff分析引入新的安全漏洞,Zoncolan会分析每个Hack代码修改并进行警报。目标受众是:diff的作者和审稿人(非安全专家的Facebook软件工程师),以及随叫随到轮换的安全工程师。在适当的时候,随叫随到的工程师验证报告的警报,阻止差异,并提供以安全的方式编写代码的支持。对于信号非常高的类别,Zoncolan扮演安全机器人的角色:它绕过随叫随到的工程师验证,直接对diff进行评论。它提供了关于安全漏洞的详细说明,以及如何利用它,并包括对过去事件的引用,例如SEV。
最后,请注意,部署模型可以扩展安全修复程序,而不会降低Zoncolan实现的整体覆盖范围(也就是说,不会遗漏漏洞):如果Zoncolan确定新问题不足以在diff上自动进行自动命令,但需要由专家查看,它将其推送到待命队列。如果警报没有产生这些削减,那么问题将在差异提交后的Zoncolan主分析中结束。
Zoncolan已经在Face-book部署了两年多,先是给安全工程师,然后是软件工程师。它已经阻止了数千个漏洞被引入Facebook的代码库。图5将Zoncolan在6个月内预防的SEV数量(例如严重程度为3到5的漏洞)与安全工程师采用的传统程序(例如手工代码审查/渗透测试和漏洞数量报告)进行了比较,由下图可以看出,在Facebook上,Zoncolan比手动安全检查或漏洞赏金报告获得更多的SEV。目前,我们发现的43.3%的严重安全漏洞是通过Zoncolan检测到的。
在6个月的时间内,Zoncolan在漏洞审查方面与传统方法的对比
根据安全影响级别,图6中的图表显示了Zoncolan在部署的不同阶段发现的操作漏洞的分布情况。由于主分析启用了最大数量的类别,因此它是最大的存储桶也就不足为奇了。然而,当限制为SEV时,diff分析在很大程度上取代了主分析,在差异时间可以防止211个严重的漏洞,而在主分析上可以检测到122个漏洞。总的来说, Zoncolan发现的漏洞的比例接近了80%。
在六个月内修复的所有漏洞的分布,基于Zoncolan的部署和发现的漏洞严重性(深色意味着更严重)
另外,我们还使用传统的安全程序来测量被遗漏的漏洞(即存在Zoncolan类别的漏洞),但这些传统的工具未能报告它们。到目前为止,使用传统的程序,我们已经遗漏了大约11个被遗漏的漏洞,其中一些是由工具中的漏洞或不完整的建模引起的。
组合性和抽象
支撑我们分析的技术特征是组合性和抽象性,组合性的概念来自于语言语义:如果一个复合短语的含义是根据其部分的含义和组合它们的方式来定义的,那么这个语义就是组合的。同样的想法可以应用于程序分析。如果一个复合程序的分析结果是根据其各部分的分析结果及其组合方法来定义的,则程序分析是组合的。在程序分析中应用组合性时,有两个关键问题:
1. 如何简洁地表示一个进程的意义?
2. 如何有效地结合意义?
对于第一个问题,我们需要通过抽象出进程的全部行为并只关注与分析相关的属性来近似组件的含义。例如,对于安全性分析,当输入参数包含用户控制的字符串时,安全工程师才感兴趣。更正式地说,静态分析的设计者定义了一个适当的数学结构,称为抽象域。静态分析的设计依赖于足够精确的抽象域来捕获感兴趣的属性,并且足够粗糙,以使问题在计算上易于处理。在分析文献中,“进程意义的抽象”通常被称为进程摘要。
而对于第二个问题,主要取决于为表示摘要而选择的特定抽象域。有关Infer和Zoncolan支持的抽象的更多信息,以及有关递归、定点和分析算法的简要信息,请翻看上文。值得讨论的是,为什么组合分析与精心设计的抽象域可以扩展?因为每个进程只需要访问几次,代码库中的许多进程可以独立分析,从而为并行性提供了机会。