跳转至

Google Security Blog – 正确对待 Linux 内核安全

  • 原文:Linux Kernel Security Done Right
  • 作者:Kees Cook
    Software Engineer, Google Open Source Security Team
  • 许可证:未知
  • 译者:Hanjingxue Boling
  • 日期:2021-08-04

借用现代计算机生态系统和 20 世纪 60 年代美国汽车工业之间的一个很好的比喻,Linux 内核运行良好:在高速公路上行驶时,你不会被油和汽油喷到脸上,而且你很快就能到达你想去的地方。然而,在遇到故障时,汽车可能最终起火,飞出悬崖。

随着我们接近 Linux 发布 30 周年,Linux 仍然是计算机历史上最大的合作开发项目。围绕着 Linux 的巨大社区使它能够做一些令人惊奇的事情并平稳地运行。然而,我们仍然缺少的是足够的关注,以确保 Linux 也能失效安全。代码的健壮性和安全性之间有很强的联系:让任何 bug 更难表现出来,就更难表现出安全缺陷。但这并不是故事的终点。当缺陷表现出来时,有效地处理它们是很重要的。

与其只从每次一个 bug 的角度出发,先发制人的行动可以阻止 bug 产生不良影响。由于 Linux 是用 C 语言编写的,它将继续有一长串的相关问题。Linux 必须被设计成采取积极主动的措施来抵御它自己的风险。汽车有安全带,不是因为我们想撞车,而是因为它能保证突发情况下乘客的人身安全。

尽管每个人都希望有一个安全的内核在他们的电脑、手机、汽车或星际直升机上运行,但并不是每个人都有能力为此做些什么。上游的内核开发者可以修复错误,但无法控制下游厂商选择纳入其产品的内容。最终用户可以选择他们的产品,但通常不能控制什么错误被修复,也不能控制使用什么内核(这本身就是一个问题)。归根结底,供应商有责任保持他们产品的内核安全。

要解决什么问题?

追踪和修复不同的错误的统计数据是令人清醒的。稳定的内核版本(“只修复错误”)每周都包含近 100 个新的修复。面对如此高的变化率,供应商可以选择忽略所有的修复,只挑出 "重要的 "修复,或者面临把所有修复都并入产品的艰巨任务。

01

什么都不修复?

随着恶意软件、僵尸网络和针对有缺陷的软件的国家监控大量出现,很明显,忽视所有修复是错误的 “解决方案”。不幸的是,这是供应商非常普遍的立场,他们认为他们的设备只是一个物理产品,而不是一个必须定期更新的混合产品/服务。

修复重要的缺陷?

在无所作为的失职和修复一切的沉重负担之间,传统的供应商的选择是只挑 “重要” 的修复。但是,什么才是 “重要的” ,甚至是相关的?仅仅由

实施一个修复是否需要开发人员时间来决定。

普遍的看法是,根据 Mitre CVE 列表来选择要修复的漏洞,假定所有重要的缺陷(以及因此要修复的缺陷)都有一个相关的 CVE 。然而,考虑到缺陷的数量及其对特定系统的适用性,并不是所有的安全缺陷都有 CVE ,而且也没有及时分配。证据显示,对于 Linux 的 CVE ,超过 40% 在 CVE 被分配之前就已经被修复了,平均延迟到修复后三个月以上。有些修复措施在数年后仍未被确认其安全影响。除此之外,与产品相关的错误甚至可能不属于 CVE 的范畴。最后,上游开发者对 CVE 分配并不感兴趣;他们把有限的时间花在修复错误上。

依靠精选修复某些漏洞的供应商几乎可以保证会错过别人正在积极修复的重要漏洞,这几乎比什么都不做更糟糕,因为它造成了安全更新得到适当处理的假象。

修复全部的缺陷!

那么,供应商该怎么做呢?答案很简单,尽管很痛苦:持续更新到最新的内核版本,无论是主要版本还是稳定版本。跟踪主要版本意味着获得安全改进和错误修复,而稳定版本只是错误修复。例如,尽管现代安卓手机的内核是基于两到四年前的主要版本,但值得庆幸的是,安卓供应商现在确实在追踪稳定版内核。因此,即使在较新的主要内核中加入的功能会被遗漏,但所有最新的稳定内核修复都是存在的。

进行持续的内核更新(主要的或稳定的)可以理解为在一个组织内面临巨大的阻力,因为害怕退步 – 更新会破坏产品吗?答案通常是供应商不知道,或者更新的频率比他们测试的时间短。但是,更新的问题不在于内核可能导致回退;而在于供应商没有足够的测试覆盖率和自动化来知道答案。测试必须优先于个别修复。

使之成为现实

还有一个问题:如何可能支持所有持续更新所需的工作?事实证明,这是一个简单的资源分配问题,而且比想象中的更容易完成:下游的冗余资源可以转移到更大的上游协作。

更多的工程师更早地修复错误

由于供应商使用旧的内核和向后移植现有的修复,他们的工程资源正在做多余的工作。例如,与其让 10 家公司各派一名工程师独立地回传相同的修复程序,不如将这些开发人员的时间转移到上游工作上,这样每个人就可以为 Linux 生态系统修复 10 个单独的错误。这将有助于解决日益积压的错误。仅仅看一下潜在的内核安全缺陷的一个来源,syzkaller 仪表板显示,目前公开的错误数量接近 900 个,即使每年有约 400 个被修复,每年也会增长约 100 个。

更多的工程师参与代码审查

除了事后积压的 bug 之外,更多关注上游代码审查将有助于从一开始就阻止 bug 的引入,其好处不仅仅是抓到的直接 bug 。能力强的代码审查带宽是一种有限的资源。如果没有足够的人专门负责上游代码审查和子系统维护任务,整个内核开发过程就会出现瓶颈。

Linux 长期的健壮性取决于开发者,但特别是有效的内核维护者。尽管业界在努力培训新的开发者,但传统上这只是以他们能得到的 “功能驱动的” 工作为理由。但是,只关注产品的时间表最终会使 Linux 陷入 “公地悲剧”。扩大维护者的数量可以避免此事。幸运的是,新的维护者的 “渠道” 是直截了当的。

维护者的建立不仅来自于他们对子系统技术的深度了解,也来自于他们对其他开发者的指导和代码审查的经验。培训新的审查员必须成为常态,其动机是使上游审查成为工作的一部分。今天的审查员成为明天的维护者。如果每个主要的内核子系统都多了四个专门的维护者,我们的生产力就会翻倍

更多的工程师参与测试和基础设施

除了更多的审查员,改善 Linux 的开发工作流程对于扩大每个人的贡献能力至关重要。 Linux 的 “只用电子邮件” 的工作流程已显老态,但更多的自动化补丁跟踪持续集成模糊处理覆盖率测试的上游开发将使开发过程明显更有效率。

此外,与其在内核发布后进行测试,不如在开发过程中进行测试更为有效。当针对未发布的内核版本(例如 linux-next)进行测试并向上游报告时,开发者可以立即得到关于缺陷的反馈。在缺陷被真正发布之前,就可以开发出修复方法;尽早修复一个缺陷总是比晚些时候更容易

这种 “上游优先” 的产品内核开发和测试方法是非常有效的。谷歌在 Chrome OS 和安卓系统上这样做已经有一段时间了,而且在行业内几乎是唯一的。这意味着功能开发是针对最新的内核进行的,而设备的测试也同样尽可能接近最新的上游内核,所有这些都避免了重复的 “机构内部” 努力。

更多的工程师参与安全和工具链开发

除了被动地处理个别漏洞和现有的维护需求外,还需要主动地消除整类缺陷,这样开发人员就不会再引入这些类型的漏洞。如果我们能阻止同类安全漏洞再次出现,为什么还要一年修复 10 次?

在过去的几年里,各种脆弱的语言特性和内核 API 已经被取消或取代(例如,VLAsswitch fallthroughaddr_limit)。然而,仍然有很多工作要做。最耗时的方面之一是在 Linux 的 2500 万行代码中进行这些通常是侵入性的、对上下文敏感的改变所涉及的重构工作。

除了内核代码本身,编译器和工具链也需要增加更多的防御性功能(例如,变量归零CFI消毒器)。由于工具链在技术上处于内核的 “外部” ,它的开发工作经常被不适当地忽视和投入不足。代码安全的负担需要尽可能地转移到工具链上,以释放人类在其他领域的工作。在最先进的方面,我们必须确保 Linux 可以用 Rust 这样的内存安全语言编写。

别再等了

如果你没有使用最新的内核,你就没有最新增加的安全防御措施(包括错误修复)。面对新发现的缺陷,这使得系统的安全性比它们本来的要低。即使通过仔细的系统设计、适当的威胁建模和其他标准的安全实践,风险的大小也会随着时间的推移而迅速增长,使供应商不得不计算出他们可以容忍用户接触多老的内核。除非答案是 “放弃我们的用户” ,否则工程资源必须集中在上游,通过持续部署最新的内核版本来缩小差距。

根据我们最保守的估计,Linux 内核及其工具链目前至少有 100 名工程师的人力资源空缺,所以要靠大家把他们的开发者人才聚集到上游来。这是唯一能确保以合理的长期成本平衡安全的解决方案。