Shim如何验证安全启动中的二进制文件?

UEFI shim loader shim是一个简单的EFI应用程序,当运行时,它会尝试打开和执行另一个应用程序。它最初会通过标准的EFI LoadImage()StartImage()调用来尝试执行。如果这些调用失败(例如因为启用了安全启动并且二进制文件未使用适当的密钥进行签名),则它将根据内置证书验证二进制文件。如果验证成功,并且二进制文件或签名密钥未被列入黑名单,则shim将重新定位并执行该二进制文件。
我一直在阅读以了解在启用安全启动选项时验证过程是如何进行的: vmlinuz *-generic和*-generic.efi.signed之间的区别 Secure Boot实际上是如何工作的?

管理Linux的EFI引导加载程序:控制安全启动

现在我可以告诉你,这个过程是这样的:

机器的固件首先运行Shim。现在Shim必须运行引导加载程序。我不明白的是Shim如何验证二进制文件?例如,上面引用的段落指出,Shim尝试通过标准的EFI LoadImage()StartImage()调用来启动其他应用程序,如果失败了,Shim会尝试从内置证书中验证二进制文件。这个内置证书是属于Shim的吗?基本上,这就是为什么Shim被称为机器所有者密钥管理器(MOK)吗?因为它有自己的密钥数据库来验证二进制文件。

简单来说,机器的固件有自己的NVRAM密钥数据库来验证二进制文件,而Shim有自己的密钥数据库来验证二进制文件。

在引导加载程序经过验证并执行之后,引导加载程序会在哪里查找需要引导的已签名内核的密钥,比如从固件的密钥数据库中?

2个回答

Kaz Wolfe的回答很好,但我想强调并扩展几个要点...
据我最后了解,Shim基本上提供了一种类似的并行安全启动验证功能。它被设计用于由非EFI程序启动的Linux内核的GRUB。因此,Shim以一种使得后续程序可以调用Shim进行二进制文件签名验证的方式在EFI中注册自身。Shim有两种方式进行验证:
  • Shim的内置密钥 -- 大多数Shim二进制文件(包括Ubuntu提供的那个)都包含了一个内置的安全引导密钥。Ubuntu的Shim包含了Canonical的公钥,用于验证Ubuntu的GRUB和Linux内核。这个密钥因此存储在RAM中,相对而言是临时的。Shim的主要目的是使其后续程序(GRUB)能够执行安全引导类型的验证--但实际上,GRUB并没有做安全引导的验证,稍后会详细描述。如果没有Shim,Canonical将需要依赖Microsoft为每个新版本的GRUB和每个新的Linux内核进行签名,这在实践中几乎不可能。
  • 机器所有者密钥(MOKs) -- MOKs基本上是Shim内置密钥的扩展,但它们是用于普通用户操作的。如果您想要启动未使用Canonical密钥签名的二进制文件,可以使用MOKs。与固件内置的安全引导密钥类似,MOKs存储在NVRAM中。但是,通过一个叫做MokManager的程序,很容易将MOKs添加到NVRAM中。将MOKs添加到NVRAM仍然相当繁琐,大多数人不会费心去做,而且许多人在这方面遇到问题;但这比完全控制您的安全引导子系统要容易,详细介绍了您所引用的页面(Managing EFI Boot Loaders for Linux: Controlling Secure Boot)。
在大多数情况下,MOKs是不被使用的;如果你想要在Windows和Ubuntu之间进行双启动,使用固件内置的密钥和嵌入在Ubuntu的Shim二进制文件中的密钥应该就足够了。只有当你想要添加另一个Linux发行版、编译自己的内核、使用非GRUB的引导加载程序、使用第三方内核模块等情况下,你才需要使用MOKs。
除了这两个来源外,固件中还内置了安全启动密钥。我不记得Shim是否使用这些密钥。如果它使用EFI的LoadImage()和StartImage()调用(它确实使用了,但我还没有对此回答的上下文进行审查),那么它将隐式地使用这些密钥。据我记忆,它自己的验证代码在GRUB回调以检查内核是否已签名时并不使用固件的安全启动密钥,但我可能记错了。
关于Shim如何集成到Secure Boot系统中,据我上次检查的情况,它并没有。如果我没记错的话,为了启动其后续程序(GRUB),Shim实现了自己的二进制加载代码,类似于Tianocore UEFI示例实现中的简化版本。该代码调用Shim自己的Secure Boot验证代码,通过对二进制文件与内置密钥和本地MOK列表进行比对来启动二进制文件。(它可能还会使用固件自己的Secure Boot密钥,但我不能确定。)一旦GRUB被加载,它会调用Shim的二进制验证函数来验证Linux内核,而GRUB以自己的方式启动内核(而不是EFI启动EFI程序的方式)。因此,Shim并没有深度集成到固件中;它只是使其中一两个功能可供后续程序使用,而LoadImage()StartImage() EFI函数保持不变。
EFI确实提供了替换或补充正常EFI系统调用的方法,一些工具也使用了这些方法。例如,PreLoader程序是一个类似于Shim的工具,它更深入地集成到固件中;它使用了EFI系统调用来修补损坏或过时的函数,以修改StartImage()函数,使其能够同时检查UEFI安全启动密钥和MOK。PreLoader已经逐渐被淘汰;它的开发人员和Shim的开发人员合作,将重点放在Shim上,而不是PreLoader作为标准的Linux安全启动工具。据我所知,Shim没有采用PreLoader更深层次的UEFI集成;然而,我很久没有仔细查看代码了,所以可能有些信息已经过时。话虽如此...
我的rEFInd引导管理器使用了我从PreLoader程序中获取的代码,以便将Shim的二进制验证代码“粘合”到UEFI的正常验证子系统中。因此,在有rEFInd的情况下,任何尝试使用LoadImage()StartImage()调用来启动EFI程序的操作都会首先调用Shim的身份验证代码,如果失败,则调用标准的UEFI安全启动身份验证。gummiboot/systemd-boot引导管理器也是类似的做法。这两个程序之所以这样做,是因为它们通过EFI stub加载器启动Linux内核,这意味着它们依赖于EFI的LoadImage()StartImage()调用。这与GRUB不同,GRUB是一个完整的引导加载程序,以自己的方式启动Linux内核,因此GRUB不需要这些EFI系统调用来识别Shim的密钥或本地MOK列表。
希望这能帮助澄清问题,但我不确定是否有效。所有这些工作的细节非常混乱,而且我已经很久没有详细处理它们了,所以我的思路可能没有那么有条理。

说得没错,如果安全启动的目的是为了让我们更安全,那么它的复杂性就是多余的。幸运的是,我的固件允许我根据自己的意愿添加和操作密钥,但由于某些原因,我仍然会禁用安全启动,因为一些内核模块没有自己的密钥。 - direprobs
当我检查数据库密钥时,令人惊讶地发现了一个规范签名。也许这个规范密钥用于验证Grub?根据您的回答,我理解UFEI的系统调用可能可以修补吗? - direprobs
2Canonical的密钥包含在一些计算机的固件中,但这是很罕见的。(我个人只在ASUS P8 H77-I主板上看到过。)这样的计算机可以直接启动Ubuntu的GRUB,而无需Shim,并且启用了Secure Boot。您可以自己签名第三方内核模块-有关方法,请参阅我在此问题的回答。是的,许多EFI系统调用可以通过EFI程序进行修补。 - Rod Smith
@Rob Smith 我有一台华硕电脑。 - direprobs

正如你正确推断的那样,SHIM将首先尝试从LoadImage()StartImage()加载。然后,EFI将通过内部SecureBoot机制验证签名是否匹配。如果LoadImage()返回EFI_SECURITY_VIOLATION,系统将尝试从内部证书回退加载stage2(在本例中为GRUB2)。
这个证书在编译时被嵌入到系统中,这是由Canonical完成的。可以使用binwalk或类似的工具从SHIM中提取这个证书
实际上,这使得SecureBoot能够在缓存中存储一个经过验证的shim签名,然后shim可以验证GRUB是否使用上述证书进行了签名。如果是的话,GRUB成功启动。
SHIM 将尽可能使用系统密钥 - 这就是为什么首先使用 LoadImage()StartImage()。只有在这些方法无效时,SHIM 才会尝试使用自己的内部证书加载 stage2。你可以在这里看到这段代码 hereverify_buffer 的一部分),它在 handle_image 链的某个部分被调用。
整个验证链如下所示:
  1. 验证系统哈希值和 MOK 列表
  2. 确保二进制文件 not blacklisted
  3. 尝试通过 MOK/BIOS 白名单 检查二进制文件
  4. 根据构建密钥和 SHIM 自身的内部密钥进行内部签名检查。

同样重要的是,MOK管理器不是MOK数据库本身。后者由EFI固件维护,它接收命令以添加/删除制造商在刷写过程中所需的内容,以及操作系统(或者在这种情况下,shim)。shim只存储一个非常短的编译密钥列表,以允许引导-其他所有操作由EFI固件处理。


只有在不起作用的情况下,SHIM才会尝试使用自己的内部证书加载stage2。那么可以这样说,SHIM通过代表另一个二进制文件提供其证书来愚弄UEFI吗?然而,尽管UEFI被愚弄了,SHIM仍会执行自己的密钥检查。换句话说,有两个检查步骤:(1)首先是UEFI密钥验证,如果失败了,(2)SHIM会向UEFI提供自己的证书,并使用SHIM内置的静态密钥进行自己的密钥验证,而不使用UEFI的密钥。 - direprobs
我真的无法理解SHIM在做什么。据我所知,SHIM通过使用自己的证书来欺骗UEFI,然后任意执行它找到的(已签名的)内容。或者至少,这是我目前对它的理解。 - Kaz Wolfe