3. 固件与Bootloader:UEFI固件初始化流程、Bootloader(如GRUB、shim)在安全启动中的角色

安全启动这条链,说白了就是从按下电源键到操作系统内核跑起来,每一步都要验签。哪一步断了,机器就黑在那。我排查过太多这种“断链”的案例,今天咱们就把UEFI固件初始化流程和Bootloader的角色彻底捋清楚。

3.1 UEFI固件初始化流程

UEFI固件启动,不是一上来就找硬盘的。它分几个阶段,每个阶段都有自己的签名数据库和校验逻辑。我个人习惯把流程拆成三块:SEC/PEI、DXE、BDS。

核心要点:UEFI安全启动的信任根(Root of Trust)在固件内部。从SEC阶段开始,每一段代码执行前都要验证上一段的签名。如果PEI阶段被篡改,DXE阶段根本不会跑。

3.1.1 SEC(安全验证)阶段

这是CPU复位后的第一条指令。SEC阶段做的事情很简单:验证PEI阶段的代码签名,然后跳转。我在项目中遇到过一台机器,SEC阶段直接卡死,最后发现是固件芯片的SPI Flash被腐蚀,读出来的签名全是0xFF。嗯,硬件问题往往最隐蔽。

3.1.2 PEI(EFI预初始化)阶段

PEI阶段负责初始化CPU、内存、芯片组。这个阶段会加载PEIM(PEI模块),每个PEIM都要验签。如果内存初始化失败,机器连报错的机会都没有——直接黑屏。你想想看,这时候连串口都还没起来,排查全靠示波器量SPI总线。

3.1.3 DXE(驱动执行环境)阶段

DXE阶段是UEFI固件最复杂的部分。它会加载各种驱动:SATA、NVMe、USB、网卡。每个驱动都是一个EFI文件,必须通过签名验证。我记得有一次,客户说换了块NVMe硬盘后安全启动就过不去。查了半天,发现是NVMe驱动固件里嵌了一个过期的签名证书。

3.1.4 BDS(启动设备选择)阶段

BDS阶段负责枚举启动设备,读取启动项,然后加载Bootloader。这里有个关键点:BDS会检查启动项的签名是否在db(允许签名数据库)里。如果不在,直接拒绝启动。

避坑指南:我曾经在BDS阶段踩过一个坑——UEFI固件默认只信任Microsoft签名的Bootloader。如果你用自编译的GRUB,必须先把你的证书导入db。否则BDS会直接报“Security Violation”。

3.2 Bootloader在安全启动中的角色

Bootloader是固件和操作系统之间的桥梁。在安全启动体系里,它既是“被验证者”,也是“验证者”。

为什么这么说?因为Bootloader既要被UEFI固件验签,又要负责验签操作系统内核。如果Bootloader本身被篡改,那它验签内核的结果就毫无意义。

3.2.1 GRUB的角色

GRUB是Linux世界最常用的Bootloader。在安全启动场景下,GRUB必须被签名。我建议你使用Microsoft签名的shim来引导GRUB,而不是直接签名GRUB。原因很简单:GRUB更新频繁,每次更新都要重新签名,太麻烦。

GRUB加载后,会读取配置文件grub.cfg。这里有个安全漏洞:如果grub.cfg没有被签名,攻击者可以修改它来加载恶意内核。所以,安全启动环境下,GRUB必须启用check_signatures和enforce_signatures选项。

# 在grub.cfg中启用签名验证
set check_signatures=enforce
export check_signatures

# 加载内核时自动验签
linux /vmlinuz-5.15.0-generic root=/dev/sda1
initrd /initrd.img-5.15.0-generic

警告:GRUB的check_signatures只验证内核和initrd的签名,不验证grub.cfg本身。如果你修改了grub.cfg,必须重新生成签名。否则GRUB会报“bad signature”错误。

3.2.2 shim的角色

shim是一个很小的Bootloader,由Microsoft签名。它的作用就是充当“中间人”——UEFI固件信任shim,shim信任GRUB。这样,你只需要维护shim和GRUB之间的信任关系,不用每次都找Microsoft签名。

shim的工作原理很简单:

  1. UEFI固件加载shim.efi,验证Microsoft签名。
  2. shim读取自己的签名数据库(MOK,Machine Owner Key)。
  3. shim验证GRUB的签名是否在MOK里。
  4. 如果验证通过,shim把控制权交给GRUB。

我在项目中遇到过shim版本不兼容的问题。shim 15.3和GRUB 2.06配合得很好,但shim 15.4改了内部接口,导致GRUB 2.04直接崩溃。所以,升级shim时一定要同步升级GRUB。

3.2.3 MOK(Machine Owner Key)管理

MOK是shim用来验证GRUB和内核的本地签名数据库。你可以通过mokutil工具管理MOK。

# 查看当前MOK列表
mokutil --list-enrolled

# 导入新的MOK(需要重启后确认)
mokutil --import /path/to/my.der

# 删除MOK
mokutil --delete <hash>

个人经验:MOK导入后,重启时会进入shim的MOK管理界面。你必须手动选择“Enroll MOK”并确认。很多工程师在这里卡住,以为导入失败。其实shim是故意这么设计的——防止恶意软件静默导入签名。

3.3 固件与Bootloader的信任链

把上面这些串起来,就是一条完整的信任链:

UEFI安全启动信任链 SEC阶段 PEI阶段 DXE阶段 BDS阶段 shim.efi GRUB Linux内核 信任链:固件硬件信任根 → SEC → PEI → DXE → BDS → shim → GRUB → 内核 每一环都验签,断一环就启动失败。排查时从后往前查,先看内核签名,再看GRUB,最后看固件。

这张图我画了很多次。每次排查安全启动问题,我都会对着这张图从右往左查——先看内核签名对不对,再看GRUB签名,最后看固件。这样效率最高。

3.4 常见故障与排查思路

故障现象 可能原因 排查方向
启动时黑屏,无任何输出 SEC/PEI阶段崩溃,或固件签名验证失败 检查SPI Flash完整性,用编程器读取固件
显示“Security Violation” Bootloader签名不在db或MOK中 检查Bootloader签名,重新导入MOK
GRUB菜单出现,但加载内核时报“bad signature” 内核或initrd签名无效 重新签名内核,或更新MOK
shim启动后直接重启 shim版本与GRUB不兼容 升级GRUB到与shim匹配的版本

注意:如果你在UEFI Shell下手动启动Bootloader,记得先执行SecureBootConfig查看当前安全启动状态。很多工程师忽略这一步,直接敲fs0:\EFI\grubx64.efi,结果报错后一头雾水。