为rEFInd启用Secure Boot

不论如何,至少电脑没有变万元大砖头,可喜可贺!其实不太可能变砖。

注:如果你不想了解部分技术细节,你可以直接跳到本文的第四部分:上手操作。

操作引导需谨慎

为了写这篇文章,特意用官方工具升级了一下 rEFInd,结果成功把引导搞崩了,花了一个小时才救回来。

这件事情告诉我们:当你想要折腾引导这种系统关键组件,一定要保证自己有足够的知识从错误中恢复!虽然现在的计算机往往有充足的防呆设计,不太可能因为用户的正常操作变黑砖,但是在操作前一定要谨慎。

一个案例是:在根目录执行sudo rm -rf / --no-preserve-root可能导致一些设备变成黑砖,只能返厂上编程器救。怎么回事?不就是删除了硬盘上的文件,UEFI 还在,怎么会变黑砖呢?原来,存储在 NVRAM 里的EFI 变量是默认挂载在/sys/firmware/efi/efivars的,而且挂载为可读写,不然efibootmgr之类的工具怎么起作用呢?结果就是,不知不觉间,rm 把这些性命攸关的 EFI 变量都抹除了。当你以为这行命令只不过是清空硬盘,整死操作系统的时候,它其实在美滋滋地擦写你的固件。虽说 UEFI 规范要求这种情况下计算机应当可以继续启动(至少有一个恢复的机会),但是如果这台电脑的 UEFI 实现得非常糟糕,那么就等着傻眼吧!

为什么这么重要的东西竟然会挂载为可读写呢?不过某 linux 重要基础设施的维护人员似乎认为这无伤大雅。虽然大家一般不写 EFI 变量,但总有人是要写的。所谓:欲敲 sudo,必承其重!

可是,谁能想得到rm -rf竟会把固件整死了呢?

前因

人难免是爱折腾的。此前安装了第三方的引导rEFInd,换了比较好看的主题,总而言之摆脱了 grub 的大黑框(grub 当然也可以设置主题,但那是另一回事了)。rEFInd 的安装倒是没什么可说的,网上已经有很多人写过相关的博文了,不过大多数的教程都建议关闭安全启动(Secure Boot),因为安全启动下加载第三方引导会非常麻烦。这倒也无伤大雅,总而言之我就把安全启动禁用了。

然而好景不长。有时电脑莫名地卡死,长按关机键重启,在电脑启动时会看到“CMOS 校验出现错误”。所谓 CMOS,就是是存储 UEFI 配置信息的地方。校验出错,说明配置可能已经损坏,于是接下来便恢复默认值了。安全启动默认开启,于是 rEFInd 无法加载,grub 的大黑框又回来了。于是,我不得不进入 UEFI 配置,把安全启动关掉。

不过,这并称不上一种优雅的办法。实际上,有办法令 rEFInd 在安全启动下加载,而且相当简单,可惜国内的教程往往对此语焉不详,或者直接建议关掉安全启动。具体的做法,其实在 rEFInd 的官网上有很详细的介绍可供参阅。这份文档不但说明了如何在启用安全启动的情况下安装 rEFInd,而且很清晰地介绍了 Secure Boot 的概况。

关于安全启动

众所周知,早期的恶意软件都是在操作系统启动之后才可能加载的,所以早期的安全手段都注重系统启动后的防御。后来涌现了各种各样的 Bootkit,在系统启动之前运行,来一招釜底抽薪。Legacy BIOS 时代一个最经典的做法就是修改主引导记录(Master Boot Record, MBR)。当然,自从 UEFI 普及之后,就再也没有 MBR 这种东西了,然而类似的攻击方式却不断进化。总而言之,核心思想就是:恶意软件把自己伪装成操作系统,诱骗固件去加载。

Secure Boot 应运而生。其思想也非常简单,就是在固件加载引导程序时对其进行验证,仅加载可信的引导程序。在此不赘述证书、签名等艰深的密码学原理;简而言之,对一个程序进行数字签名之后,就可以用这个签名对应的密钥验证其是否经过篡改。

假设有一台运行 Windows 的电脑。在 UEFI 下,操作系统的启动器其实是一个个 efi 应用程序(.efi 文件)。对于 Winodws 来说,往往是 bootmgfw.efi,这个程序由微软进行签名。然后,在每台电脑的**签名数据库(Signature Database, db)**中,都保存着微软的密钥。使用这个密钥验证操作系统加载器,就可以保证其没有受到恶意软件的篡改。

如果微软的系统引导器出现了可供利用的严重安全漏洞怎么办?那么,它的 hash 或者签名就会被添加到禁止数据库(Forbidden Database, dbx)中。如果固件发现 dbx 中的密钥可以验证程序,或者其中保存的 hash 与程序相匹配,那么固件就会拒绝加载该程序,并抛出EFI_SECURITY_VIOLATION错误。

由上可知,我们有时是有增删 db 或 dbx 的需求的。可是我们该怎么保证对 db 和 dbx 的修改也是可信的呢?当然是用另一个密钥来验证。这个密钥叫做密钥交换密钥(Key Exchange Key, KEK)。顾名思义,KEK 主要是用来修改 db 和 dbx 中的密钥的。如果没有 KEK 的签名,那么就不能修改 db 和 dbx。KEK 可以有多个,一般来说包含了微软的密钥和计算机厂商的密钥。

最后,谁来确保这些 KEK 都是可信的呢?答案是计算机厂商。它们控制着平台密钥(Platform Key, PK),它是整个可信系统的根源。每台电脑只能有一个 PK,这个 PK 拥有至高无上的权限。如果将 PK 修改成自己的密钥,那么就可以完全控制安全启动。

事情到这里就很清晰了。首先,厂商在生产计算机时,会把自己的密钥作为 PK,这是安全启动的基石。然后,便可以用 PK 来设置 KEK。KEK 一般都包含微软密钥,还可能包含计算机厂商自己的密钥。最后,微软或者厂商可以在执行系统更新的时候,使用 KEK 修改 db 或者 dbx,以将已知的恶意程序加入 dbx 的黑名单,或者将新的密钥加入白名单。

目前为止事情看起来很美好。可是,这其中却浮现了一个大问题:linux 世界被完全排除在外了。实际上,安全启动就就是微软和 Intel 牵头做出来的,自然会首先考虑自家系统。可是如果我想运行 Windows 以外的操作系统,该怎么办呢?db 中包含了微软的两个密钥,一个用来给微软自己的东西签名,另一个则用来给第三方程序签名。只要交 99 美元,就可以用微软的第三方密钥签名,想签多少签多少。

然而争论并没有减少。且不论由微软给 linux 签名是否合适,签名的费用该由谁来出呢?linux 内核和 grub 都可能会进行频繁的更新,每次都要由微软重新签名,这在实践上是很难接受的。而且,用户自己编译的 linux 该怎么办呢?

目前有两种解决方案:Shim 和 Preloader,其中 Shim 是主流。Shim 脱胎于 fedora 的一个项目,基本的原理是:由微软为 Shim 签名,然后由 Shim 验证并加载其他 linux 发行版厂商的签名。Shim 的版本应当是很稳定的,所以不需要频繁地找微软进行签名。

shim 使用下列密钥验证启动镜像:

shimx64.efi 默认加载同目录下的 grubx64.efi。

那么,要为 rEFInd 启用安全启动就很简单了。rEFInd 二进制是由作者 Rod Smith 进行签名的,你可以直接将他的密钥导入 MOK。或者,也可以用你自己的密钥进行签名。

上手操作

在开始之前,我认为我有必要提醒你:同一件事,不同的设备的具体操作往往是非常不同的,尤其是固件这种与硬件联系非常紧密的东西。我下方给出的截图,只能代表我这一台设备的情况。尽管如此,大体的流程应当是相似的。不过,以防万一,请你先熟悉你的设备厂商提供的固件界面,以及出现问题之后的恢复手段。

操作需谨慎!

准备 shim

首先要介绍EFI 分区,也就是所谓的ESP(EFI System Partition)。我们前面提过,电脑上电之后,首先加载 UEFI 固件。UEFI 已经是一个功能齐备的系统了,在上面可以运行 EFI 应用程序(.efi 文件)。操作系统的启动器,其实就是一个 EFI 应用程序。这些 EFI 应用程序都存储在 EFI 分区中。EFI 分区必须使用 FAT 文件系统,因为 FAT 足够简单而无需额外的复杂的文件系统驱动。现在的机器上,EFI 分区可能是唯一 FAT 格式的分区。

一般来说,在 linux 系统中,EFI 分区被挂载在/boot/efi目录下,但是需要 root 权限才能访问。如果你找不到这个目录,那么你有可能需要手动挂载。

而 rEFInd 的典型安装路径是/boot/efi/EFI/refind。在这里,你可以看到 rEFInd 本体refind.efi。但是,正如上文所述,为了实现安全启动的大业,我们还需要一个 shim 程序shimx64.efi,以及一个 MOK 管理工具mmx64.efi(或者叫做MokManager.efi)。大多数发行版应该自带了 shim,你可以在 EFI 目录下找一找。这个 shim 应当是由微软签名的。如果找不到,你可以尝试在发行版官网搜索一下。有些发行版可以通过包管理器安装 shim,一般来说名字类似于shim-signed。例如,对于 ubuntu:

sudo apt install shim-signed

当你准备妥当 shim 和 mokmanager 之后,把他们复制到 rEFInd 安装目录下。

注意:请不要使用 shim 0.1,因为这个版本不支持启动 rEFInd。实际上,我不建议你使用 shim 的任何早期版本,因为它们有大量的已知安全漏洞,而且有些过早的版本已经被纳入了 dbx。

同时,如果你使用版本比较新的 shim,那么你也需要使用版本比较新的 rEFInd。这是因为新版本的 shim 引入了一个名为**Secure Boot Advanced Targeting(SBAT)**的安全机制,而更早的 rEFInd 不支持这一机制,会被拒绝加载。

你还需要检查一下在/boot/efi/EFI/refind/keys/目录下是否有一个名为refind.cer的文件。这是用于验证 rEFInd 作者 Rod Smith 签名的密钥,我们需要把它导入 MOK。如果没有的话,你需要去 rEFInd 官网下载。

准备录入 MOK

我们还需要两个命令行工具:efibootmgrmokutil

efibootmgr 一般是包含在各大发行版之中的。然而,你需要自行安装 mokutil。大多数发行版的包管理器仓库应当都有以上两种工具。

准备就绪之后,运行:

sudo mokutil -i /boot/efi/EFI/refind/keys/refind.cer

这行命令将/boot/efi/EFI/refind/keys/refind.cer加入准备注册的密钥。命令执行时会要求你设置一个密码,这是一个一次性密码,用于重启后录入 MOK。要注册 MOK,我们不能在已经启动的电脑上操作,因此我们需要重启,使用 mmx64.efi 操作。mokutil 已经帮我们做了设置,下次重启时就会进入 MokManager 界面。然后,你需要输入刚才设置的密码,以证明这一操作是由你进行的。

进行 MOK 录入

重启后,你会直接进入一个蓝色页面,要求你按任意键以开始录入 MOK。请不要对这样的页面感到害怕,虽然它们的设计原始而且有点吓人,但是本质上讲它们是为了人的方便而设计的工具。仔细看看给出的选项,一步步操作即可。

如果你的操作没有问题,你将会看到如下的菜单:

mmx64.efi

按上下键移动光标,选择 Enroll MOK 这一项,回车。如果你的菜单没有这一项,你也可以选择 Enroll key from disk,在 EFI 分区中找到你需要录入的密钥。

如果你愿意,你可以选择 View Key 以查看密钥信息。然后选择 Continue,继续录入。

mmx64.efi

接下来会弹出窗口,询问你是否继续。选择 Yes。

mmx64.efi

然后输入密码,回车。

mmx64.efi

于是录入就完成了。选择 Reboot 重启。如果你卡在了任何一个步骤希望重来,可以按下 Ctrl+Alt+Delete 组合键重启。

mmx64.efi

设置启动项

最后,你需要使用 efibootmgr 将 shimx64.efi 设置为首个启动项,然后把 refind.efi 改名为 grubx64.efi。完成之后,你可以尝试启用 Secure Boot,检验设备能否正常启动。