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

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

UEFI 填充程序加载器

shim 是一个普通的 EFI 应用程序,运行时会尝试打开并执行另一个应用程序。它最初会尝试通过标准 EFILoadImage()StartImage()调用来执行此操作。如果这些操作失败(例如,因为启用了安全启动并且二进制文件未使用适当的密钥签名),它将根据内置证书验证二进制文件。如果验证成功并且二进制文件或签名密钥未被列入黑名单,则 shim 将重新定位并执行二进制文件。

我一直在阅读以了解启用安全启动选项时验证过程如何发生:

vmlinuz *-generic 和 *-generic.efi.signed 之间的区别

安全启动究竟如何工作?

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

现在我可以说这个过程是这样的:

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

简单地说,机器的固件在 NVRAM 中有自己的密钥数据库来验证二进制文件,并且 shim 也有自己的密钥数据库来验证二进制文件?

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

答案1

Kaz Wolfe 的回答非常好,但我想强调并扩展几点......

上次我检查时,Shim 基本上提供了一种并行安全启动验证功能。它旨在由 GRUB 使用,GRUB 旨在启动非 EFI 程序的 Linux 内核。因此,Shim 以一种允许后续程序调用 Shim 来验证二进制文件是否已签名的方式向 EFI 注册。Shim 以以下两种方式之一进行注册:

  • Shim 的内置密钥-- 大多数 Shim 二进制文件(包括作为 Ubuntu 的一部分提供的二进制文件)都包含内置的安全启动密钥。Ubuntu 的 Shim 包含 Canonical 的公钥,用于验证 Ubuntu 的 GRUB 和 Linux 内核。因此,此密钥存储在 RAM 中,并且相当临时。Shim 的主要目的是使其后续程序(GRUB)能够执行安全启动类型验证——但 GRUB 本身并不真正进行安全启动验证,正如稍后所述。如果没有 Shim,Canonical 就需要依赖 Microsoft 来签署每个新版本的 GRUB 和每个新的 Linux 内核,这几乎是不切实际甚至不可能的。
  • 机器所有者密钥 (MOK)-- MOK 基本上是 Shim 内置密钥的扩展,但它们是供普通用户操作的。如果您想要启动未使用 Canonical 密钥签名的二进制文件,则可以使用 MOK。MOK 与固件内置的安全启动密钥一样,存储在 NVRAM 中;但它们更容易通过名为 MokManager 的程序添加到 NVRAM 中。将 MOK 放入 NVRAM 仍然很繁琐,大多数人都不愿意费心,而许多这样做的人会遇到问题;但这比完全控制您的安全启动子系统要容易得多,正如您引用的我的页面中所述(管理 Linux 的 EFI 引导加载程序:控制安全启动)。

大多数情况下,不使用 MOK;如果您想要双启动 Windows 和 Ubuntu,那么使用固件内置密钥和嵌入在 Ubuntu Shim 二进制文件中的密钥可能就足够了。如果您想添加另一个 Linux 发行版、编译自己的内核、使用 GRUB 以外的引导加载程序、使用第三方内核模块等,则可以使用 MOK。

除了这两个来源之外,固件中还内置有安全启动密钥。我不记得 Shim 是否使用这些密钥。它会隐含地如果它使用 EFILoadImage()StartImage()调用(它确实会这样做,但我还没有查看此答案的上下文),则使用它们。我记得当 GRUB 回调以查看内核是否已签名时,它自己的验证代码不会使用固件的安全启动密钥,但我可能记错了。

至于 Shim 如何集成到安全启动系统中,据我上次检查,它并没有集成。如果我没记错的话,为了启动其后续程序 (GRUB),Shim 实现了自己的二进制加载代码,该代码类似于 Tianocore UEFI 示例实现中代码的精简版。此代码调用 Shim 自己的安全启动验证代码,该代码根据其内置密钥和本地 MOK 列表检查二进制文件,以启动二进制文件。(它也可能使用固件自己的安全启动密钥,但我对此不确定。)一旦加载了 GRUB,它就会调用 Shim 的二进制验证函数来验证 Linux 内核,GRUB 会以自己的方式启动 Linux 内核(而不是 EFI 启动 EFI 程序的方式)。因此,Shim 并没有真正深入地集成到固件中;它只是将其一两个功能提供给后续程序,而LoadImage()EFIStartImage()功能保持不变。

尽管如此,EFI 确实提供了替代或补充正常 EFI 系统调用的方法,一些工具也确实使用了这些方法。例如,PreLoader 程序是一种执行与 Shim 类似操作的工具,它将自身更深入地集成到固件中;它使用旨在修补损坏或过时功能的 EFI 系统调用进行修改,StartImage()以便检查两个都常见的 UEFI 安全启动键MOK。PreLoader 几乎已经过时了;它的开发人员和 Shim 的开发人员合作将重点放在 Shim 而不是 PreLoader 上,将其作为标准的 Linux 安全启动工具。据我所知,Shim 尚未采用 PreLoader 更深层次的 UEFI 集成;但是,我已经有一段时间没有仔细查看代码了,所以我可能对此不太了解。话虽如此……

我自己的 rEFInd 启动管理器使用我从 PreLoader 程序中获取的代码,以便将 Shim 的二进制验证代码“粘贴”到 UEFI 的正常验证子系统中。因此,在 rEFInd 的情况下,任何使用LoadImage()和调用 Shim 身份验证代码启动 EFI 程序的尝试StartImage()都会首先调用 Shim 身份验证代码,如果失败,则第二次调用标准 UEFI 安全启动身份验证。gummiboot/systemd-boot 启动管理器执行类似操作。这两个程序之所以这样做,是因为它们通过其 EFI 存根加载器启动 Linux 内核,这意味着它们依赖于 EFILoadImage()StartImage()调用。这与 GRUB 形成对比,GRUB 是一个完整的启动加载器,以自己的方式启动 Linux 内核,因此 GRUB 不需要这些 EFI 系统调用来识别 Shim 的密钥或本地 MOK 列表。

我希望这能帮助澄清一些事情,但我不确定会不会。所有这些工作的细节相当混乱,而且我已经有一段时间没有详细处理它们了,所以我自己的想法并不像它们应该的那样有条理。

答案2

正如你正确推断的那样,SHIM 将尝试从LoadImage()StartImage()首先。然后,EFI 将验证签名是否匹配(通过使用内部 SecureBoot 机制)。如果LoadImage()返回EFI_SECURITY_VIOLATION,系统将尝试从内部证书回退到加载阶段 2(在本例中为 GRUB2)。

该证书在编译时嵌入到系统中,在本例中由 Canonical 完成。本证书binwalk可以使用或类似的实用程序从 SHIM 中提取。

实际上,这允许 SecureBoot 在缓存中存储经过验证的签名shim,然后反过来允许shim验证 GRUB 是否使用上述证书签名。如果是,则 GRUB 成功启动。

SHIM 会尽可能使用系统密钥 - 这就是为什么LoadImage()StartImage()被首先使用。只有当它不起作用时,SHIM 才会尝试使用自己的内部证书加载 stage2。您可以看到此代码这里(的一部分verify_buffer),这在链的一部分中被调用handle_image

整个验证链如下所示:

  1. 验证系统哈希和 MOK 列表
  2. 确保二进制文件未列入黑名单
  3. 尝试通过以下方式检查二进制文件MOK/BIOS 白名单
  4. 对照构建密钥和 SHIM 自己的内部密钥定义的内部签名进行检查。

同样重要的是,MOK 管理器不是 MOK 数据库本身。后者由 EFI 固件维护,它接受制造商在刷新期间添加/删除内容的命令以及操作系统(或在本例中为shim)。shim仅存储上述编译密钥的极短列表以允许启动 - 其余一切都需要由 EFI 固件处理。

相关内容