研究!rsc公司

关于编程的想法和链接,通过

RSS公司

我们的软件依赖性问题
发布于2019年1月23日星期三。PDF格式

几十年来,关于软件重用的讨论远比实际的软件重用更常见。如今,情况正好相反:开发人员每天都会重用他人编写的软件,以软件依赖性的形式,情况大多未经审查。

我自己的背景包括与谷歌的内部源代码系统,它将软件依赖性视为一流的概念,1并为Go编程语言中的依赖项。2

软件依赖性随之而来经常被忽视的严重风险。向简单、细粒度的软件重用的转变发生得如此之快我们还不了解选择的最佳实践并有效地使用依赖关系,或者甚至用于决定它们何时合适以及何时不合适。我写这篇文章的目的是提高对风险的认识并鼓励对解决方案进行更多调查。

什么是依赖关系?

在当今的软件开发世界中,附属国是要从程序中调用的附加代码。添加依赖项可避免重复已完成的工作:设计、编写、测试、调试和维护特定代码单位。在本文中,我们将该代码单元称为包裹;一些系统使用诸如库或模块之类的术语,而不是包。

采用外部编写的依赖关系是一种古老的做法:大多数程序员在职业生涯中都有过这样的经历必须经历手动下载和安装的步骤所需的库,如C的PCRE或zlib,或C++的Boost或Qt,或Java的JodaTime或JUnit。这些包包含经过调试的高质量代码这需要大量的专业知识来发展。对于需要这些包之一提供的功能的程序,手动下载、安装和更新的繁琐工作包裹比从头开始重新开发该功能更容易。但是再利用的固定成本很高意味着手动重新使用的软件包往往很大:一个小的包将更容易重新实现。

A类依赖关系管理器(有时称为包管理器)自动下载和安装依赖项包。作为依赖关系管理器使单个软件包更易于下载和安装,较低的固定成本使较小的包便于发布和重用。

例如,Node.js依赖项管理器NPM提供了访问超过750000个包。其中之一,转义字符串regexp,提供转义正则表达式的单个函数运算符输入。整个实施过程是:

var matchOperatorsRe=/[| \\{}()[\]^$+*?.]/克;module.exports=函数(str){if(typeofstr!=='string'){throw new TypeError('需要字符串');}return str.replace(matchOperatorsRe,'\\$&');};

在依赖项管理器之前,发布一个八行代码库这是不可想象的:开销太大,收益太少。但NPM已使间接费用几乎为零,其结果是几乎微不足道的功能可以打包和重用。2019年1月底转义字符串regexp包裹被近千人明确依赖其他NPM包,更不用说开发人员为自己编写的所有包了不要分享。

现在基本上每种编程语言都存在依赖关系管理器。Maven Central(Java),Nuget(.NET),Packagist(PHP),PyPI(Python),和RubyGems(Ruby)每个主机都有超过100000个程序包。这种细粒度、广泛的软件重用的到来是软件开发中最重要的转变之一在过去20年里。如果我们不更加小心,就会导致严重的问题。

可能会出什么问题?

本次讨论的一个包是您从互联网上下载的代码。将包作为依赖项添加将外包开发工作代码设计、编写、测试、调试和维护互联网上的其他人,你经常不认识的人。通过使用该代码,您公开了自己的程序依赖关系中的所有失败和缺陷。你的程序现在正在执行取决于在互联网上从这个陌生人那里下载的代码上。以这种方式呈现,听起来非常不安全。为什么会有人这样做?

我们这样做是因为很容易,因为它似乎有效,因为其他人也在这么做,最重要的是,因为它似乎是古老的惯例。但我们忽略了一些重要的差异。

几十年前,大多数开发人员已经信任他人编写他们所依赖的软件,例如操作系统和编译器。该软件是从已知来源购买的,通常有某种支持协议。仍然有可能出现错误或彻底的恶作剧,但至少我们知道我们在和谁打交道,而且通常拥有可用的商业或法律资源。

开源软件的现象,通过互联网免费分发,取代了许多早期购买的软件。当重用困难时,发布可重用代码包的项目更少。尽管他们的许可证通常被拒绝,任何“适销性和适用性的暗示保证特定目的,”这些项目树立了众所周知的声誉这往往是人们决定使用哪种产品的重要因素。信任我们的软件来源的商业和法律支持被声誉支持所取代。许多常见的早期套餐仍享有良好声誉:考虑BLAS(1979年出版)、Netlib(1987年)、libjpeg(1991年)、,LAPACK(1992)、HP STL(1994)和zlib(1995)。

依赖关系管理器缩小了这个开源代码重用模型的规模:现在,开发人员可以在以下粒度共享代码数十条线路的独立功能。这是一项重大的技术成就。有无数可用的软件包,编写代码可能会涉及到大量的代码,但商业、法律和声誉支持机制因为相信代码没有被执行。我们信任的代码越多,这样做的理由就越少。

可以查看采用不良依赖项的成本总之,在所有可能的不良结果中,每一个坏结果的代价乘以发生概率(风险)。

将使用依赖项的上下文决定坏结果的代价。在光谱的一端是一个个人爱好项目,最坏结果的代价接近零:你只是在玩,虫子除了浪费一些时间,甚至调试它们都会很有趣。所以风险概率几乎无关紧要:它被乘以零。另一方面是生产软件必须保持多年。在这里,一个bug的成本依赖性可能非常高:服务器可能会宕机,敏感数据可能泄露,客户可能会受到伤害,公司可能会倒闭。高故障成本使其更加重要估计并降低严重故障的风险。

无论预期成本如何,具有较大依赖性的体验建议一些方法评估并降低添加软件依赖项的风险。很可能需要更好的工具来帮助减少这些方法的成本,正如依赖关系管理器迄今为止所关注的那样降低下载和安装成本。

检查相关性

你不会雇佣一个你从未听说过的软件开发人员却一无所知。您将首先了解更多有关它们的信息:检查参考资料,进行面试,运行背景调查,等等。在你依赖你在网上找到的包裹之前,它同样谨慎先了解一下。

基本的检查可以给你一种感觉您尝试使用此代码时遇到问题的可能性。如果检查发现可能存在小问题,你可以采取措施做好准备,也可以避免。如果检查发现重大问题,最好不要使用该软件包:也许你会找到一个更合适的,或者你可能需要自己开发一个。记住,开源软件包是发布的作者希望他们会有用但不保证可用性或支持。在生产中断期间,您将是调试它的人。正如原始GNU通用公共许可证警告的那样,程序与您同在。如果程序被证明有缺陷,您将承担所有费用必要的维修、修理或纠正。”4

本节的其余部分概述了检查包时的一些注意事项并决定是否依赖它。

设计

包的文档是否清晰?API有明确的设计吗?如果作者能够向您、用户、,在文档中,这增加了他们在源代码中向计算机解释实现的可能性。为一个清晰、设计良好的API编写代码也更容易、更快,并且希望不太容易出错。作者是否记录了他们对客户端代码的期望为了使未来的升级兼容?(示例包括C++5然后出发6兼容性文档。)

代码质量

代码写得好吗?读一读。看起来作者是不是很谨慎、认真、始终如一?它看起来像要调试的代码吗?你可能需要。

开发您自己的系统方法来检查代码质量。例如,像用编译C或C++程序这样简单的事情启用了重要的编译器警告(例如,-墙壁)可以让您了解开发人员工作时要避免的严重程度各种未定义的行为。Go、Rust和Swift等最新语言使用不安全的要标记的关键字违反类型系统的代码;看看有多少不安全的代码。更高级的语义工具,如Infer7或SpotBugs8也很有帮助。林特斯没那么有用:你应该忽略死记硬背的建议关于大括号样式之类的主题,并将重点放在语义问题上。

对您可能不熟悉的开发实践保持开放的心态。例如,SQLite库作为一个200000行C源文件提供还有一个11000行的标题“合并”这些文件的绝对大小应该会引发一个初始的危险信号,但更仔细的调查会发现实际开发源代码,一个带有超过100个C源文件、测试和支持脚本。事实证明,单个文件分发是从原始源自动构建的而且对最终用户来说更容易,尤其是那些没有依赖关系管理器的用户。(编译后的代码也运行得更快,因为编译器可以看到更多的优化机会。)

测试

代码有测试吗?你能跑吗?他们通过了吗?测试确定代码的基本功能是正确的,它们表明开发人员对保持正确性是认真的。例如,SQLite开发树有一个非常全面的测试套件有30000多个单独的测试用例以及解释测试策略的开发人员文档。9另一方面,如果测试很少或没有测试,或者测试失败,这是一个严重的危险信号:程序包的未来更改很可能会引入容易被发现的回归。如果坚持用自己编写的代码进行测试(是吗?),您应该坚持将代码中的测试外包给其他人。

假设测试存在、运行并通过,您可以收集更多通过使用运行时检测运行它们来获取信息如代码覆盖率分析、种族检测,10内存分配检查,和内存泄漏检测。

调试

查找包的问题跟踪器。是否有许多打开的错误报告?他们开业多久了?有很多修复的错误吗?最近有什么bug被修复了吗?如果你看到很多关于什么是真正的错误的公开问题,特别是如果它们已经开放了很长时间,这不是一个好兆头。另一方面,如果已解决的问题表明存在错误很少发现并及时修复,太棒了。

维护

查看包的提交历史记录。代码有效维护了多久?现在是否积极维护?已为扩展的时间量更有可能继续保持。有多少人负责这个包裹?许多包是开发人员的个人项目在业余时间创造和分享乐趣。其他则是数千小时工作的结果由一群付费开发人员提供。一般来说,后一种包更有可能及时修复错误、稳步改进和常规维护。

另一方面,有些代码确实“完成”了例如,NPM转义字符串regexp,如前所示,可能永远不需要再次修改。

用法

许多其他软件包依赖于此代码吗?依赖关系管理器通常可以提供有关使用情况的统计信息,或者你可以使用网络搜索来估计频率其他人写下了使用这个包的情况。更多的用户至少意味着更多的人代码运行良好,以及更快地检测新的错误。广泛使用还可以避免持续维护的问题:如果一个广泛使用的包失去了它的维护器,感兴趣的用户可能会向前迈进。

例如,PCRE、Boost或JUnit等库被广泛使用。这使得它更有可能——尽管这肯定不是保证的您可能会遇到的错误已经得到修复,因为其他人先撞上了他们。

安全

您会使用该包处理不受信任的输入吗?如果是这样的话,它是否对恶意输入具有鲁棒性?它有安全问题的历史吗列入国家脆弱性数据库(NVD)?11

例如,当Jeff Dean和我开始谷歌代码搜索12格雷普2006年公共源代码,流行的PCRE正则表达式库似乎是一个显而易见的选择。然而,在与谷歌安全团队的早期讨论中,我们了解到PCRE有缓冲区溢出等问题的历史,尤其是在它的解析器中。我们可以通过在NVD中搜索PCRE了解到同样的信息。这一发现并没有立即导致我们放弃PCRE,但它确实让我们更仔细地考虑测试和隔离。

许可

代码是否得到了适当的许可?它有执照吗?您的项目或公司是否可以接受许可证?GitHub上有一小部分项目没有明确的许可。您的项目或公司可能会对允许的依赖项许可证。例如,谷歌不允许使用根据类AGPL许可证(过于繁重)以及类WTFPL许可证(太模糊)。13

依赖关系

代码有自己的依赖关系吗?间接依赖中的缺陷对您的程序同样有害作为直接依赖中的缺陷。依赖项管理器可以列出所有可传递的依赖项对于给定的包装,理想情况下,每个包装都应按照本节中介绍。具有许多依赖项的包需要额外的检查工作,因为这些相同的依赖关系会带来额外的风险这需要评估。

许多开发人员从未查看过完整的可传递列表他们的代码依赖性,不知道他们依赖什么。例如,2016年3月,NPM用户社区发现许多受欢迎的项目,包括Babel、Ember和React,都依赖于在名为左通道,由一个8行函数体组成。他们发现这一点的时候的作者左通道从NPM中删除了该包,无意中破坏了大多数Node.js用户的构建。14而且左通道在这方面也不例外。例如,30%的NPM上发布了750000个程序包至少间接地依赖转义字符串regexp.改编Leslie Lamport对分布式系统的观察,依赖关系管理器可以很容易地创造一种情况,在这种情况下,你没有即使知道存在,也会导致您自己的代码无法使用。

测试相关性

检查过程应包括运行包自己的测试。如果包裹通过检查,您决定项目取决于此,下一步应该是编写专注于功能的新测试您的应用程序需要。这些测试通常以简短的独立程序开始编写以确保您能够理解包的API它做了你想做的事。(如果你不能或它没有,现在回头!)因此,值得付出额外的努力来改变这些程序可以针对包的新版本运行的自动化测试。如果你发现了一个bug并有可能进行修复,您希望能够重新运行这些特定于项目的测试很容易,以确保修复不会破坏其他任何东西。

尤其值得练习可能的问题领域基本检查。对于代码搜索,我们从过去的经验中了解到PCRE有时执行某些正则表达式搜索需要很长时间。我们最初的计划是为“简单”和“复杂”正则表达式搜索。我们运行的首批测试之一是基准测试,比较pcregrep公司和其他几个格雷普实现。当我们发现,对于一个基本测试用例,pcregrep公司最快的格雷普可用,我们开始重新考虑使用PCRE的计划。尽管我们最终完全放弃了PCRE,这个基准仍然保留在我们的代码库中。

抽象依赖关系

取决于一个包裹是一个你可能会做出的决定稍后再访问。也许更新将把包带向一个新的方向。也许会发现严重的安全问题。也许会有更好的选择。由于所有这些原因,这是值得的努力以便于将项目迁移到新的依赖项。

如果该包将在项目源代码中的多个位置使用,迁移到新的依赖项需要更改所有这些不同的源位置。更糟糕的是,如果包将在您自己项目的API中公开,迁移到新的依赖项需要调用API的所有代码的更改,你可能无法控制。为了避免这些成本,有必要定义自己的接口,以及实现接口使用依赖项。请注意,包装器应仅包括您的项目需要从依赖关系中获得什么,并不是依赖关系所提供的一切。理想情况下,这允许您稍后替换另一个同样合适的依赖项,只更改包装器。迁移项目内测试以使用新接口测试接口和包装器实现并且可以轻松测试任何潜在的替代品对于依赖项。

对于代码搜索,我们开发了一个摘要Regexp公司定义了任何正则表达式引擎。然后我们在PCRE周围写了一个薄薄的包装实现该接口。间接方法使测试备用库变得容易,它阻止了我们意外地引入知识将PCRE内部构件添加到源代码树的其余部分中。这反过来确保了它将很容易切换如果需要,可以添加到不同的依赖项。

隔离依赖项

隔离依赖关系也可能合适在运行时,以限制其中的错误可能造成的损坏。例如,Google Chrome允许用户在浏览器中添加依赖扩展代码。当Chrome于2008年推出时,它引入了关键功能(现在在所有浏览器中都是标准功能)在单独的操作系统过程。15写得不好的扩展中的可利用漏洞因此无法自动访问整个内存浏览器本身的可以阻止进行不适当的系统调用。16对于代码搜索,在我们完全放弃PCRE之前,我们的计划是至少隔离PCRE解析器在类似的沙箱中。今天,另一个选项是基于轻量级虚拟机监控程序的沙箱比如gVisor。17隔离依赖项降低了运行该代码的相关风险。

即使有了这些示例和其他非现成选项,可疑代码的运行时隔离仍然太困难,而且很少进行。真正的隔离需要一种完全无记忆的语言,没有逃逸舱口进入非类型代码。这不仅在完全不安全的语言(如C和C)中具有挑战性++也可以使用提供受限不安全操作的语言,包括JNI时类似Java,或者类似Go、Rust和Swift包括“不安全”功能时。即使在JavaScript这样的内存安全语言中,代码通常可以访问远远超出其需要的内容。2018年11月,NPM包的最新版本事件流,它为JavaScript事件提供了功能性流API,发现包含经过模糊处理的恶意代码两个半月前添加的。该代码从Copay移动应用程序的用户那里获取了大量比特币钱包,正在访问与处理完全无关的系统资源事件流。18这类问题的许多可能防御措施之一将更好地限制可以访问的依赖项。

避免依赖性

如果依赖关系看起来风险太大而你找不到一种隔离它的方法,最好的答案可能是完全避免它,或者至少避免那些你认为最有问题的部分。

例如,当我们更好地了解相关风险和成本时通过PCRE,我们的谷歌代码搜索计划得到了发展从“直接使用PCRE”到“使用PCRE但沙盒解析器”“编写一个新的正则表达式解析器,但保留PCRE执行引擎,”“编写一个新的解析器并将其连接到另一个更高效的开源执行引擎。”后来我们也重写了执行引擎,这样就不会留下依赖关系,我们开源了结果:RE2。19

如果您只需要依赖关系的一小部分,复制你需要的东西可能是最简单的(当然,保留适当的版权和其他法律声明)。您正在承担修复错误、维护等方面的责任,但你也完全脱离了更大的风险。Go开发者社区有一句谚语:“一点复制总比一点依赖要好。”20

升级依赖项

长期以来,关于软件的传统观点是“如果它没有坏,就不要修复它。”升级带来了引入新错误的机会;如果没有相应的奖励,比如你需要一个新功能-为什么要冒险?此分析忽略了两个成本。首先是最终升级的成本。在软件中,进行代码更改的难度并不是线性扩展的:做十个小小的改变,工作量更小,更容易纠正而不是做出一个相当大的改变。第二个是费力发现已经固定的错误的成本。特别是在安全环境中,已知的错误被积极利用,你等待的每一天都是攻击者可以闯入的另一天。

例如,考虑一下高管们所叙述的2017年Equifax在详细的国会证词中。213月7日,Apache Struts中的一个新漏洞被披露,并发布了补丁版本。3月8日,Equifax收到US-CERT关于需要更新的通知Apache Struts的任何使用。Equifax分别于3月9日和3月15日运行源代码和网络扫描;这两次扫描都没有找到一组特定的公共网络服务器。5月13日,攻击者发现了Equifax安全团队无法找到的服务器。他们利用Apache Struts漏洞破坏Equifax的网络然后窃取详细的个人和财务信息约1.48亿人在接下来的两个月里。Equifax终于在7月29日发现了漏洞并于9月4日公开披露。截至9月底,Equifax的首席执行官、首席信息官和CSO均已辞职,国会正在进行调查。

Equifax的经验表明尽管依赖项管理器知道它们在构建时使用的版本,你需要其他安排来追踪这些信息通过生产部署过程。对于Go语言,我们正在自动尝试在每个二进制文件中包含一个版本清单,以便部署进程可以扫描二进制文件中需要升级的依赖项。Go还在运行时提供这些信息,以便服务器可以查询已知错误的数据库并自行报告当他们需要升级时监控软件。

及时升级很重要,但升级意味着向项目中添加新代码,这意味着更新你对风险的评估基于新版本使用依赖项。至少,您需要浏览显示从当前版本到升级版本,或者至少阅读发行说明,确定升级代码中最可能出现的问题。如果有很多代码正在更改,因此差异很难理解,这也是您可以合并到您的风险评估更新。

您还需要重新运行您编写的测试特定于您的项目,确保升级后的软件包至少是合适的作为早期版本。重新运行包自己的测试也是有意义的。如果包有自己的依赖项,您的项目的配置完全可能使用这些依赖项的不同版本(旧的或新的)。运行包自己的测试可以快速识别问题特定于您的配置。

同样,升级不应该是完全自动的。您需要验证升级的版本是否适合您的环境,然后再部署它们。22

如果升级过程包括重新运行您已经为依赖项编写了集成和鉴定测试,这样您就可以在新问题进入生产之前发现它们,那么,在大多数情况下,延迟升级比快速升级风险更大。

安全关键型升级的窗口尤其短。在Equifax事件发生后,法医安全小组发现攻击者(可能是不同的攻击者)的证据成功利用了Apache Struts3月10日,仅三天,受影响服务器上的漏洞在公开披露后,但他们只运行了一个哇米命令。

注意您的依赖关系

即使完成了所有这些工作,你仍然没有完成照顾你的依赖。继续监控他们很重要,甚至可能重新评估您使用它们的决定。

首先,确保您继续使用您认为是的特定软件包版本。现在,大多数依赖关系管理器使其变得简单甚至自动化记录预期源代码的加密散列对于给定的包版本然后在重新下载包时检查该散列在另一台计算机或测试环境中。这可以确保您的构建使用您检查和测试的依赖项源代码相同。这些支票阻止了事件流攻击者,如前所述,从静默插入已发布版本3.3.5中的恶意代码。相反,攻击者必须创建一个新版本3.3.6,并等待人们升级(不仔细查看更改)。

还必须注意新的间接依赖性:升级可以很容易地引入新包现在项目的成功取决于此。他们也值得你关注。在以下情况下事件流,恶意代码是隐藏在不同的包中,平面图流,哪个是新的事件流版本已添加为新的依赖项。

爬行依赖性也会影响项目的大小。谷歌Sawzall开发期间23-JIT’ed系列作者在不同时期发现的日志处理语言主解释器二进制文件不仅包含Sawzall的JIT还有(未使用的)PostScript、Python和JavaScript解释器。每次,罪魁祸首都是未使用的依赖项由Sawzall所依赖的某个图书馆宣布,再加上谷歌的构建系统消除了开始使用新依赖项所需的任何手动操作。。这种错误是Go语言使导入未使用的包成为编译时错误。

升级是重新考虑使用正在更改的依赖项的决定的自然时机。定期重新访问以下依赖项也很重要不是改变。没有安全问题或其他漏洞需要修复,这看起来似乎合理吗?这个项目被放弃了吗?也许是时候开始计划取代这种依赖了。

重新检查每个依赖项的安全历史记录也很重要。例如,Apache Struts公开了不同的主要远程代码执行2016年、2017年和2018年的漏洞。即使您有一个运行它的所有服务器的列表及时更新,这一记录可能会让你重新考虑使用它。

结论

软件重用终于到来了,我并不想低估它的好处:它带来了巨大的积极变化适用于软件开发人员。尽管如此,我们接受了这种转变完全考虑潜在后果。信任依赖关系的旧理由越来越不合理与此同时,我们拥有比以往更多的依赖性。

对特定依赖项的批判性检查我在这篇文章中概述了大量的工作并且仍然是例外而非规则。但我怀疑是否有开发人员对于每个可能的新依赖项,都要努力做到这一点。我只为我自己的依赖项的子集做了其中的一个子集。大多数时候,整个决策都是“让我们看看会发生什么”太多时候,任何超过这一点的事情似乎都太费力了。

但Copay和Equifax攻击明确警告当今我们使用软件依赖关系的方式中存在的真正问题。我们不应该忽视这些警告。我提出三项广泛的建议。

  1. 认识到问题所在。如果没有别的,我希望这篇文章已经说服了你认为这里有一个问题值得解决。我们需要很多人集中精力解决这个问题。

  2. 建立今天的最佳实践。我们需要建立管理依赖关系的最佳实践使用今天可用的。这意味着制定评估、降低和跟踪风险的流程,从最初的采用决定到生产使用。事实上,正如一些工程师擅长测试一样,可能我们需要专门管理依赖关系的工程师。

  3. 为明天开发更好的依赖性技术。依赖关系管理器基本上消除了下载并安装依赖项。未来的开发工作应侧重于降低使用所需的评估和维护类型依赖关系。例如,包发现站点可能会查找让开发人员分享他们的发现的更多方法。构建工具至少应该使运行包自己的测试变得容易。更具侵略性,构建工具和包管理系统也可以协同工作允许包作者针对所有公共客户端测试新更改他们的API。语言还应该提供简单的方法来隔离可疑包。

有很多好的软件。让我们一起研究如何安全地重用它。

工具书类

  1. Rachel Potvin和Josh Levenberg,“为什么谷歌在单个存储库中存储数十亿行代码,”ACM通信59(7)(2016年7月),第78-87页。https://doi.org/10.1145/2854146 (⇡)
  2. Russ Cox,“Go&Versioning”,2018年2月。https://research.swtch.com/vgo网址 (⇡)
  3. Ken Thompson,“关于信托的思考”ACM通信27(8)(1984年8月),第761-763页。https://doi.org/10.1145/358198.358210 (⇡)
  4. GNU项目,“GNU通用公共许可证,版本1”,1989年2月。https://www.gnu.org/licenses/old-licenses/gpl-1.0.html (⇡)
  5. Titus Winters,“SD-8:标准库兼容性”,C++标准文档,2018年8月。https://isocpp.org/std/standing-documents/sd-8-standard-library-compatibility (⇡)
  6. 围棋项目,“围棋1和围棋项目的未来”,2013年9月。https://golang.org/doc/go1公司 (⇡)
  7. Facebook,“Infer:一个在Java和C/C++/Objective-C代码发布之前检测其错误的工具。”https://fbenfer.com网站/ (⇡)
  8. “SpotBugs:查找Java程序中的错误。”https://spotbugs.github.io/ (⇡)
  9. D.Richard Hipp,“SQLite是如何测试的。”https://www.sqlite.org/testing.html (⇡)
  10. Alexander Potapenko,“Testing Chromium:ThreadSanitizer v2,a next-gen data race detector”,2014年4月。https://blog.chromium.org/2014/04/testing-chromium-threadsanitizer-v2.html (⇡)
  11. NIST,“国家脆弱性数据库——搜索和统计”https://nvd.nist.gov/vuln/search网站 (⇡)
  12. Russ Cox,“正则表达式与三角索引的匹配,或谷歌代码搜索的工作原理”,2012年1月。https://swtch.com网址/~rsc/regexp/regexp4.html (⇡)
  13. 谷歌,“谷歌开源:使用第三方许可”https://opensource.google.com/docs/thirdparty/licenses/禁止 (⇡)
  14. Nathan Willis,“单一故障节点”,LWN,2016年3月。https://lwn.net/文章/681410/ (⇡)
  15. Charlie Reis,“多进程体系结构”,2008年9月。https://blog.chromium.org/2008/09/multi-process-architecture.html (⇡)
  16. Adam Langley,《Chromium的seccomp沙盒》,2009年8月。https://www.imperialviolet.org/2009/08/26/seccomp.html (⇡)
  17. 尼古拉斯·拉卡斯(Nicolas Lacasse),“开源gVisor,沙盒容器运行时”,2018年5月。https://cloud.google.com/blog/products/gcp/open-sourceing-gvisor-a-sandboxed-container-runtime (⇡)
  18. Adam Baldwin,“事件流事件详情”,2018年11月。https://blog.npmjs.org/post/180565383195/details-about-the-event-stream事件 (⇡)
  19. Russ-Cox,“RE2:正则表达式匹配的原则方法”,2010年3月。https://opensource.googleblog.com/2010/03/re2-principled-approach-to-regular.html (⇡)
  20. 罗布·派克(Rob Pike),“Go Proverbs”,2015年11月。https://go-proverbs.github.io/ (⇡)
  21. 美国众议院监督和政府改革委员会,“Equifax数据泄露”,多数党工作人员报告,第115届国会,2018年12月。https://reconomics-oversight.house.gov/wp-content/uploads/2018/12/Equifax-Report.pdf (⇡)
  22. Russ Cox,“围棋版本化原则”,新加坡GopherCon,2018年5月。https://www.youtube.com/watch?v=F8nrpe0XWRg (⇡)
  23. Rob Pike、Sean Dorward、Robert Griesemer和Sean Quinlan,“解释数据:与Sawzall的平行分析”科学规划杂志第13卷(2005年)。https://doi.org/10.1155/2005/962135 (⇡)

尾波

发布了此帖子的一个版本在里面ACM队列(2019年3月至4月),然后ACM通信(2019年8月),标题为“幸存的软件依赖性”