好锁定 vs. 坏锁定
公司希望销售用户无法完全控制的产品,而内核开发者希望用户成为最高权威,这两者之间一直存在一系列冲突。有时,这些冲突以旨在锁定内核的安全补丁的形式manifest。它们是锁定内核以防御外部攻击者吗?还是锁定内核以防止任何人(包括设备所有者用户)进行更改?
David Howells 最近从 linux-next 推出了一个补丁,并提交以包含在主源代码树中。正如他所说,该补丁“为 EFI 安全启动添加了内核锁定支持”。补丁中包含的一份手册页上写道:
内核锁定功能旨在阻止对正在运行的内核镜像的直接和间接访问,尝试防止未经授权修改内核镜像,并防止访问位于内核内存中的安全和加密数据,同时仍然允许加载驱动程序模块。
这个补丁引发了一场奇怪的辩论,但现在已经很熟悉了。Matthew Garrett 最终成为该补丁的主要支持者,他一直在技术层面上为其辩护,但 Linus Torvalds 认为这些技术层面毫无意义且不诚实,掩盖了一个秘密议程,其中包括帮助像 Microsoft 这样的公司阻止用户更改自己的系统。
Andy Lutomirski 是 Matthew 为该补丁辩护的另一位批评者。辩论兜兜转转,Linus 和 Andy 试图让 Matthew 承认他们认为他拥有的真实动机,而 Matthew 试图给出充分的理由说明该补丁应该进入内核。事情变得难堪。
James Morris 最初接受了这个补丁,计划将其发送给 Linus 以供纳入,而 Andy 审查了代码。在他的评论中,Andy 说该补丁的目标没有明确说明。他说,为了进行代码审查,他将假设目标是防止 root 用户读取内核内存或故意破坏内核。
但是,他认为这些不是内核的适当目标,即使是 UEFI 安全启动 内核。他说,“内核应该尝试摆脱 UEFI 安全启动应该暗示令人讨厌的限制的想法。这真的很烦人,而且我一直不清楚它有什么好处。” 他特别指出,阻止 root 用户访问内核内存的想法是这些令人讨厌的限制之一。
Kees Cook 回复了他的补丁的总体理由。他说:
这是关于在 uid-0 和 ring-0 之间划清界限。这些区别中最有力的是很久以前使用签名模块做出的。但这还不够,因为 uid-0 有很多方法可以读取或写入内核内存。我对它的期望是合理地填补所有剩余的漏洞。
他还不同意 Andy 对“令人讨厌的限制”的反感。事实上,Kees 认为即使在 UEFI 安全启动之外,也需要实施这些类型的安全措施。他说,“不仅仅是安全启动需要这个。例如,Chrome OS 的静态信任根和启动固件不是 UEFI,但它希望启用此功能。”
但是,Andy 一直以“令人讨厌的限制”等委婉的术语来表达他的批评——他真正的反对意见是他觉得这段代码试图做的不仅仅是它声称的,特别是要使公司能够生产用户无法修改的 Linux 设备。
Andy 说,即使他愿意承认阻止写入对于这个补丁来说是个好主意,他仍然看不到阻止读取的价值——特别是像 kprobes 和 perf 这样的调试工具的读取。他希望看到一个真正有效的用例来证明该补丁的功能是合理的,然后才批准它。
David 为这些功能提供了自己的理由。首先,他说,“为了能够通过 kexec 传递安全启动模式,你必须确保内核镜像没有被破坏,以免有人在引导加载程序中将你的签名密钥列入黑名单。”
为了捍卫限制内存读取和写入,他还补充说,“如果有人可以读取你的内核镜像,他们就可以窃取你用来加密文件系统的加密密钥。”
然而,Andy 并不买账这些理由,并且似乎觉得它们只是借口,试图避免承认该补丁的真实目的。但是,他试图反驳已陈述的论点:就通过 kexec 传递安全启动模式而言,他说,“我不明白这与 kexec 有什么关系。并且‘有人在引导加载程序中将你的密钥列入黑名单’听起来像是一个政治问题,而不是一个技术问题。”
就攻击者窃取密钥以读取加密文件系统而言,Andy 没有看到实际的攻击。他说:
假设我是一个坏人,正在攻击某人的笔记本电脑。如果我只有正常的 uid != 0 访问权限,那么这些补丁没有任何效果。相反,我们谈论的是一个攻击者,他以某种方式能够成为全局 root 并绕过所有 LSM 限制,但尚未获得内核代码执行权限。确实,你的补丁使简单地从主内存中读取 dm-crypt 加密密钥变得更加困难。但是 root 可以通过许多其他方式攻击磁盘加密。
他继续列举了这些方式:
他们可以通过添加服务或用户帐户或故意错误配置某些内容来持久性地危害机器。他们可以直接读取磁盘的全部内容。他们可以修改 initrd,以便在下次机器重启并且用户输入密码时,攻击者获得密钥(除非涉及 TPM,但是在标准发行版上正确设置 *那个* 是困难或不可能的)。我甚至不确定设法成为 root 的攻击者为什么想要你的磁盘加密密钥。除非攻击者使其攻击具有持久性,否则该密钥毫无价值,但是,如果攻击者可以安装持久的用户级后门,那么他们可以像读取密文一样轻松地读取磁盘上的明文。
在随后的帖子中,Andy 在某种程度上澄清了他的立场,也许试图迫使他的对手承认他们的真实论点。他说他并不反对所有“锁定”概念,但他觉得当前的补丁超出了为系统提供实际保护的范围。它实施了他认为没有提供实际安全性的安全功能。Andy 说,最好让人们澄清该补丁的意图锁定的确切类型,以便证明它确实有价值。他评论说,“现在有一系列检查‘锁定’的补丁,并且似乎禁用了某些让人感到不安的东西。这不是设计安全功能的好方法。”
与此同时,Matthew 在此时加入了讨论,反对 Andy 对攻击者在引导加载程序中将用户的密钥列入黑名单的反对意见。Andy 将其描述为一个政治问题而不是技术问题,但 Matthew 回复说,“允许用户任意访问 ring 0 的内核只是一个功能过多的引导加载程序。在这种情况下,你为什么要安全启动?”
可以说,这就是真正辩论开始的地方。Andy 回复说,没有 Matthew 的额外锁定代码的安全启动的价值是“获得信任链”。他举了一个例子:
我可以配置一个系统,其中包含一些存储在 UEFI 身份验证变量中的公钥,这样系统将只启动签名镜像。该签名镜像反过来可以加载签名(或哈希或以其他方式验证)的内核和经过验证的 initramfs。例如,initramfs 可以从经过验证(使用 dm-verity 或类似方法)的文件系统中运行完整的系统。现在,持久性地攻击这个系统非常困难。
Andy 最终表达了他对当前补丁的性质的怀疑,他说:
如果我不得不猜测一个使这个补丁集有效的动机,那就是 Microsoft 和各种签名 Linux 引导加载程序的供应商之间存在着不安的休战。这种休战可能需要签名的引导加载程序不要有意地发布一个允许非物理存在的用户链式加载 Windows 的系统。
链式加载是指一个引导加载程序加载另一个引导加载程序,然后继续启动系统。例如,在具有 Linux 和 Windows 的双启动系统中,GRUB 引导加载程序通常会加载 Windows 引导加载程序以启动 Windows。
Linus 在此时加入了讨论。他回复了 Matthew 关于允许任意访问 ring 0 会将内核变成一个臃肿的引导加载程序的说法。Linus 指出,“也许你 *不想要* 安全启动,但它被有议程的人强加给你了?”
对此,Matthew 建议用户可以在这种情况下关闭该功能。那时,Linus 失去了耐心,也许是厌倦了各种相互推诿的论点。他说:
所以你问了一个问题,然后当你得到答案时,你说“那就不要那样做”。
事实是,一些硬件非常积极地推动安全启动。这与某种“锁定”模式 *毫无关系*。
你为什么要将两者混为一谈?这是最初的问题。你用另一个问题回答了你的问题。人们回答了你的问题。
现在回答最初的问题,该死的。
Matthew 回复说:
安全启动确保固件只会加载签名的引导加载程序。如果签名的引导加载程序加载的内核实际上是一个未签名的引导加载程序,那么使用安全启动就没有意义了 - 你应该直接将其关闭,因为它没有给你任何有意义的安全性。Andy 的例子给出了一个场景,通过充分约束你的 *用户空间*,你可以接近获得相同的保证,但这涉及到你拥有只读文件系统,并使你离拥有一台通用计算机更远。
如果你不想要安全启动,请将其关闭。如果你想要安全启动,请使用一个以实际提高你的安全性的方式运行的内核。
Al Viro 认为这是在回避真正的问题。他回复说:
这假设你可以关闭那些狗屎。在制造商安装了不允许关闭 [安全启动] 的固件的硬件上,[安全启动] 是一种必须解决的错误功能。使关闭更加困难可能会提高 [安全启动] 对上述制造商的价值,但对其他人有什么好处?
而且,Linus 也直言不讳地对 Matthew 说:
胡说八道。
我可能想知道我正在运行 *我的* 内核,但一旦情况如此,我就信任它。
事实上,我倾向于比一些随机的供应商密钥更信任它。你也应该这样。
你的整个论点从根本上是垃圾。这是迪士尼那种垃圾。那时是垃圾,现在也是垃圾。
这也是垃圾,原因很简单:安全启动可能很难关闭。有时“关闭”意味着“你只需要添加你自己的密钥”。
是的,至少在 x86 硬件上,MS 实际上在某个时候有规定,它必须是可以关闭的东西。但这条规则显然在 ARM 上不成立。
说真的。你听起来像是在鹦鹉学舌地重复某些党的路线,而不是像你在回答实际问题。
所以再说一遍:你为什么要将这两个问题混为一谈?
如果你想要锁定,没问题,启用它。但是这他妈的与你是否拥有安全启动有什么关系?
Matthew 继续重申他的立场,他说:
如果你不相信你的自签名内核会对你的安全模型构成威胁,那就太好了!构建它时不要启用它。但是,如果你构建了一个没有此锁定功能的内核,并用 Red Hat 的签名密钥对其进行了签名,那么任何人都可以使用 Red Hat 的引导加载程序链和该内核来破坏信任第三方签名密钥的任何机器上的安全启动链(即,基本上所有机器)。
关于 MS 在 ARM 上没有关于安全启动必须具有“关闭”开关的规则,Matthew 回复说,“正确 - 没有要求它在 ARM 上是可以禁用的东西,但是由于 Microsoft 无论如何都不会为 ARM 签署任何第三方代码,因此这对本次讨论没有任何影响。”
在讨论进行到这个阶段左右,James(最初接受了该补丁并计划将其推送给 Linus)表示,基于这场辩论,他撤回了他的批准,并且最终不会将代码推送给 Linus。
但是讨论仍在继续,双方都没有取得任何进展。在某个时候,Linus 采取了锁定会使内核更难调试的策略。他说:
我不希望我的内核根据它启动方式中的一些非常深奥的细节而表现不同。这从根本上是错误的。
这真的很难理解吗?
这样看:也许锁定会破坏某些应用程序,因为该应用程序做了一些奇怪的事情。我收到了关于这种情况发生的报告,碰巧报告者正在运行与我相同的发行版,所以我尝试使用他完全相同的内核配置,但它对我来说有效。
记者碰巧运行了一个启用了安全启动的发行版内核,而我显然没有,这完全是不明显的。
看到问题所在了吗?将这些东西神奇地联系在一起是一个坏主意。
当人们问你为什么要这样做时,你还没有提出一个实际的回应。
相反,你只是问人们为什么他们在乎,或者告诉人们不要启用它。
说真的,Matthew,当它们彼此之间没有任何关系时,以神奇的方式将事物联系在一起是错误的。
所以不。答案很简单“不要将这两件事联系在一起”。
而且该死的,如果你将它们联系在一起,你最好有一个充分的理由。到目前为止,你的理由实际上一直是“为什么不呢?”,并试图让别人来向你解释为什么不。
Matthew,从一开始,这种方法就是错误的。你有责任解释你为什么要将它们联系在一起,而不是让别人一遍又一遍地向你解释它们彼此之间没有任何关系。
在您给出将这两个功能联系在一起的真正诚实的理由之前,这场讨论就结束了。不要再“为什么不呢?”这种废话了。
在他的下一篇文章中,Linus 补充说:“旁注:我怀疑原因是类似于‘存在政治原因’。但是该死的,如果是这种情况,那么应该记录和解释这些原因,而不是在人们询问为什么会这样时用‘为什么不呢?’来回答。”
Matthew 提供了他对他的立场的另一种解释:
1) 安全启动旨在允许构建一个启动链,该链仅运行用户认为可信的 ring 0 代码。
2) 因此,在未经用户明确同意的情况下允许任意用户代码在 ring 0 中运行与安全启动的目标不兼容。
3) 此补丁集提供了一种机制来更改内核的行为,使其更难以让任意用户代码在未经用户明确同意的情况下在 ring 0 中运行。
4) 提供一种机制来在旨在限制对 ring 0 访问的上下文中自动启用此行为是合理的,因为否则很难实现 (1) 中的目标。
实现 (1) 的替代方法依赖于严格约束用户空间 - 例如,ChromeOS 目前没有施加这些限制,但也不允许用户运行任意应用程序(你被困在 Chrome 或 Android 沙箱中)。因此,如果目标是在平台处于这种状态时实现 (1),那么更合理的替代方案是什么?
Linus 逐点回复。
对于 Matthew 的第 1 点,Linus 说这不一定是安全启动的意图。他说,“对于某些人来说,这可能是 *一种* 意图。但对于实际用户来说,这不是先验的意图。”
对于第 2 点,Linus 说,“这些目标不是 *用户* 的目标。现在诚实点。通常不是用户强烈要求它。如果用户实际上想要它,并且正在要求它,他可以启用它。独立于用户通常无法控制的安全启动。”
对于第 3 点,Linus 说 Matthew 描述的保护已经存在,即限制内核仅由 root 用户加载模块,或仅加载签名模块。不同之处在于“它们不会根据你是否使用安全启动启动而神奇地启用自己(或禁用自己)。”
对于第 4 点,Linus 回复:
不。看看为什么它 *不* 合理,如已多次解释的那样。
根据一些微妙且通常是无意的启动行为细节来神奇地更改内核行为是完全愚蠢的。
如果它是“检查内核模块签名”检查,那将是愚蠢的。这同样愚蠢。
说真的,听听你自己的论点。如果它们对于检查内核模块签名没有意义,那么为什么它们对于像锁定这样的东西有意义呢?
这两件事完全独立。
我和你谈完了。你不听,而且你在重复毫无意义的虚假论点。
我绝不会合并任何类似的东西。
最终,Linus 说:
Matthew,继续和你谈话根本不值得。
我只是不会拉取这种垃圾,而你让那些你说服去做愚蠢事情的供应商只能怪他们自己。
你显然有一个议程,并且不愿意听取反对你愚蠢选择的论点。
尽管如此,Linus 还是继续与 Matthew 辩论,并在某个时候也说,“你把整个‘不安全启动’意味着‘微不足道的规避’推得太过了。到了彻底谎言的地步。”
最终,在讨论过程中,Linus 给出了他对 Matthew 真实论点的估计。他说:
我觉得听到“我们希望一直启用它,但已知它会破坏一些我们无法在软件中修复的异常硬件案例,并且我们想要 *某种* 方式来禁用它,这需要明确且经过验证的用户干预才能做到这一点,而禁用安全启动是我们能想到的最简单的hack”更令人信服。
看到了吗?没有废话。只是直言不讳地谈论人们决定采用这种特定联系的 *实际* 原因,并承认这是一种 hack,但也清楚地说明了 hack 的原因。
现在,我仍然不一定同意这是最好的选择,但当以这些术语陈述时,我至少理解了为什么将该选项选为合理的选项,并且它极大地改变了讨论,并且(至少对我而言)使其更易于接受。
因为只要解释只是一些“你必须使用安全启动,否则你已经失败了,进一步的安全毫无意义”的胡说八道魔法思维,我立即就会说“不,这听起来完全是假的,而且它使测试和覆盖率变得更糟,我们已经在没有这种安全启动联系的情况下做了其他非常相似的事情。”
这种类型的讨论对于内核开发来说是不寻常的,但对于这种特定类型的补丁来说并非如此。试图将代码偷偷塞进内核,使供应商能够阻止用户控制自己的系统,总是倾向于涉及双方完全各说各话。Linus 和 Andy 无法让 Matthew 以他们想要的方式解决问题,而 Matthew 也无法说服 Linus 和 Andy 他的技术解释是真实且合法的。与此同时,像 Kees 这样的人可能出于正当理由支持该补丁,但只是在关于什么是真正安全性的哲学方面与 Linus 存在分歧。因此,尽管出于不同的原因,他们最终与 Matthew 并肩作战。
我没有完全捕捉到这个摘要中的一个有趣的元素是,Linus 在已经声明他认为 Matthew 的论点是虚伪的,甚至是彻头彻尾的谎言,并且他永远不会接受提交的补丁之后,仍然继续回应 Matthew 的技术要点的方式。为什么他继续辩论,即使他对辩论持如此消极的看法?Linus 可能是正在寻找一个“完美的论点”——一个显而易见的技术解释,即使 Matthew 也必须放弃所有伪装,承认他的立场是错误的。或者 Linus 和 Matthew 都不是在互相说话,而是每个人都有更广泛的受众。也许 Matthew 真正在对他的雇主说话,表明他会遵守规定。也许 Linus 也在真正对 Matthew 的雇主以及其他像他们这样的人说话,表明他看穿了他们的努力,并且不会被愚弄。
或者,也许我太快地相信了 Linus 和 Andy 的指责,而实际上,Matthew 只是提出了一个他真诚地认为会对用户有所帮助的安全增强功能。
注意:如果您在上面被提及并希望在评论部分上方发布回复,请将包含您的回复文本的消息发送至 ljeditor@linuxjournal.com。