4、Bootloader安全:GRUB2与shim,MOK管理,内核完整性校验

好,我们继续往下走。上一章聊了UEFI安全启动的硬件根基,那层信任锚点算是扎下去了。但说实话,光有固件层面的校验还不够——你想想看,固件验完Bootloader,Bootloader还得去加载内核,这中间要是被人动了手脚,前面全白干。

这一章,我们就来拆解Bootloader这一环。我个人习惯把Bootloader比作「信任传递的桥梁」——它得接住固件递过来的信任,再稳稳地交到内核手里。桥断了,整个启动链就塌了。

4.1 GRUB2:不只是个菜单

很多人对GRUB2的印象就是那个选系统的菜单界面。嗯,确实,它最常露脸的就是那个界面。但GRUB2真正的核心能力,是它作为第二阶段Bootloader,负责加载内核和initramfs,并且——关键来了——它自己也得被验过。

在UEFI安全启动的环境下,GRUB2的二进制文件(通常是grubx64.efi)必须经过签名。固件在启动它之前,会检查这个签名是否在DB(允许数据库)里。如果签名不对,或者压根没签名,机器直接拒绝启动。

核心要点:GRUB2本身是UEFI安全启动链上的第一个「可执行代码」环节。它必须被签名,且签名必须可信。

我在项目中遇到过一种情况:某团队自己编译了GRUB2,加了定制模块,结果忘了签名。刷到机器上,重启——黑屏,连错误提示都没有。后来查了半天,才发现是安全启动把未签名的grubx64.efi给拦了。从那以后,我每次编译完GRUB2,第一件事就是检查签名状态。

4.2 shim:那个「中间人」

这里有个现实问题:GRUB2是开源软件,社区维护,版本迭代快。如果每次GRUB2更新,都得让微软给它签一遍,那效率太低了。而且,有些Linux发行版有自己的签名证书,但微软的DB里不一定有。

于是就有了shim。shim是一个极简的Bootloader,它的唯一使命就是——被微软签名,然后去验证并加载GRUB2。

说白了,shim就是个「中间人」。它本身很小,代码量极少,审查起来容易。微软只给shim签名,然后shim再去验GRUB2的签名。GRUB2的签名可以用发行版自己的证书,只要这个证书的哈希被记录在shim的MOK(Machine Owner Key)列表里就行。

组件 签名方 验证方 说明
shim.efi 微软 UEFI固件 被固件直接验签,加载后接管控制权
GRUB2 (grubx64.efi) 发行版/自签名 shim shim验其签名,通过后才加载
内核 (vmlinuz) 发行版/自签名 GRUB2 GRUB2验内核签名,通过后才启动

这个链条你看明白了吗?信任从固件到shim,从shim到GRUB2,再从GRUB2到内核。每一环都验上一环的签名,环环相扣。

4.3 MOK管理:你自己的信任锚

MOK,全称Machine Owner Key。这玩意儿是干嘛的?简单说,它允许你自己管理哪些证书和哈希是可信的,而不需要去改UEFI固件里的DB。

为什么要搞这么一套?因为改DB太麻烦了。很多机器的固件设置界面里,安全启动相关的选项藏得很深,甚至有些OEM直接锁死了DB,不让你改。MOK就绕过了这个限制——它在shim层面维护了一个独立的信任列表。

MOK的管理工具是mokutil。我常用的几个命令:

# 查看当前已注册的MOK列表
mokutil --list-enrolled

# 查看待注册的MOK(新添加的还没生效)
mokutil --list-new

# 导入一个新MOK(需要重启后通过shim界面确认)
mokutil --import /path/to/my.der

# 删除一个MOK
mokutil --delete <hash-or-description>

小技巧:用mokutil --import添加MOK后,重启时会自动进入shim的MOK管理界面(蓝底白字)。你需要在那里手动确认「Enroll MOK」,输入密码,才能生效。这一步很多人会忽略,以为命令执行完就完了。

我曾经帮一个客户排查问题,他编译了自定义内核,也签了名,但死活启动不了。远程一看,MOK根本没注册成功——他以为mokutil --import完事大吉,结果重启后没去shim界面确认。嗯,这种坑我踩过一次就记住了。

4.4 内核完整性校验:最后一关

GRUB2加载内核之前,会做最后一件事:校验内核的签名。这一步用的是PE签名验证——因为Linux内核在UEFI环境下,本质上也是一个PE格式的EFI应用程序(vmlinuz.efi)。

GRUB2会检查内核文件的签名是否匹配它信任的证书。这个证书可以是MOK里的,也可以是DB里的。如果签名无效,GRUB2会拒绝加载内核,直接报错。

注意:内核完整性校验不光是验签名。GRUB2还会校验内核的测量值(如果启用了TPM),把内核的哈希值扩展到PCR寄存器里。这样,即使攻击者绕过了签名校验(比如利用了某些漏洞),TPM也能感知到内核被篡改过。

这里有个细节:GRUB2的verification模块负责签名校验。你可以通过check_signatures和enforce_signatures这两个变量来控制行为:

# 在GRUB2配置文件中设置
set check_signatures=enforce
# 或者更严格
set check_signatures=force

enforce模式下,所有未签名的文件都会被拒绝加载。force模式更狠,连已经加载的模块都得重新验一遍。我一般建议生产环境用enforce,开发环境可以暂时关掉,但上线前必须打开。

4.5 完整流程:一张图说清楚

说了这么多,不如一张图来得直观。下面这张SVG图,把整个Bootloader安全启动链画清楚了:

Bootloader安全启动链流程图 Bootloader安全启动链 UEFI固件 shim.efi 微软签名 GRUB2 (grubx64.efi) MOK验证 内核 (vmlinuz) 签名+哈希校验 操作系统启动 信任链传递 1. 固件验shim签名 2. shim验GRUB2签名 3. GRUB2验内核签名 4. 内核验initramfs 5. 内核验模块签名 6. 完整启动链闭环 MOK 注册 删除 查看 导入 导出

你看,从UEFI固件到shim,再到GRUB2,再到内核,最后到操作系统——每一层都做了签名验证。这就是所谓的「信任链传递」。任何一环断了,启动就会失败。这不是bug,这是feature——它保证了只有你信任的代码才能在你的机器上运行。

4.6 避坑指南

最后,分享几个我踩过的坑,希望能帮你少走弯路:

  • 签名算法不匹配:有些老版本的shim只支持SHA-256哈希,你用SHA-512签的GRUB2,shim认不出来。我建议统一用SHA-256,兼容性最好。
  • MOK证书格式:shim只认DER格式的X.509证书。别拿PEM格式往里塞,会报错。用openssl x509 -in cert.pem -outform DER -out cert.der转一下就行。
  • GRUB2模块签名:如果你加载了额外的GRUB2模块(比如lvm.mod、luks.mod),这些模块也得签名。否则在enforce模式下,GRUB2会拒绝加载它们。
  • 内核签名后别改:内核文件签名之后,哪怕你只改了一个字节,签名就失效了。我曾经手贱用strip命令去掉了内核的调试符号,结果启动失败——因为签名校验没通过。

我的习惯:每次更新内核或GRUB2之后,我都会用sbverify --list /boot/vmlinuz-xxx和pesign -S -i /boot/efi/EFI/grub/grubx64.efi检查一下签名状态。确认没问题了,再重启。这个习惯救过我很多次。

好了,Bootloader安全这块就聊到这儿。记住一句话:信任链的每一环,都得自己亲手验过。别偷懒,别侥幸。