3. UEFI 安全启动架构:固件、数据库与启动策略
好,我们进入第三章。这一章是安全启动的骨架——UEFI 架构本身。说白了,你得先知道房子怎么盖的,才能谈怎么修漏水。
UEFI 安全启动不是凭空冒出来的。它是 UEFI 规范里定义的一套固件级验证机制。我刚开始接触时也觉得这东西很玄乎,后来拆开一看,其实就是几个数据库加一段启动逻辑。嗯,咱们一层层剥开。
3.1 UEFI 固件组成
UEFI 固件,你可以把它理解成主板上的一个小型操作系统。它负责在 CPU 上电后、操作系统加载前,把硬件初始化好,然后找到启动设备。
它的核心组件包括:
- SEC (Security Phase):安全阶段,CPU 上电后第一个执行的代码。负责建立临时栈,验证后续阶段的签名。我见过有些板卡在这里就挂了,因为 PEI 阶段的签名没通过。
- PEI (Pre-EFI Initialization):预初始化阶段。主要做内存初始化。这时候内存还没完全可用,代码在缓存里跑。
- DXE (Driver Execution Environment):驱动执行环境。内存可用了,各种驱动(SATA、NVMe、USB)开始加载。安全启动的数据库也是在这个阶段被读取和验证的。
- BDS (Boot Device Selection):启动设备选择。根据你设置的启动顺序,去加载操作系统引导程序。
- TSL (Transient System Load):临时系统加载。比如你按 F12 进启动菜单,或者直接加载 bootmgfw.efi。
- RT (Runtime):运行时。操作系统接管后,UEFI 只提供一些运行时服务,比如设置变量、重启。
关键点:安全启动的验证点主要卡在 DXE 和 BDS 阶段。DXE 阶段验证驱动签名,BDS 阶段验证引导加载程序签名。任何一个环节没通过,启动就中断了。
我画了一张图,帮你把整个流程串起来:
3.2 安全启动数据库
安全启动的核心,是四个数据库。它们存储在 UEFI 的 NVRAM 里,断电不丢失。我习惯把它们比作一个门禁系统:
- PK (Platform Key):平台密钥。这是最高权限,相当于门禁系统的总管理员。只有持有 PK 私钥的人,才能修改 PK 本身或 KEK。
- KEK (Key Exchange Key):密钥交换密钥。相当于门禁系统的授权管理员。KEK 的持有者可以修改 DB 和 DBX。
- DB (Signature Database):签名数据库。白名单。里面存放的是被信任的签名或证书哈希。只有签名匹配 DB 里条目的 EFI 程序才能运行。
- DBX (Revoked Signature Database):吊销签名数据库。黑名单。即使签名在 DB 里,如果也在 DBX 里,照样不让跑。
避坑指南:我曾经遇到过一台设备,刷了新版固件后安全启动一直失败。查了半天,发现是 DBX 里多了一条旧版引导加载程序的吊销记录。新固件自带的引导加载程序恰好被这条记录匹配上了。解决办法?要么更新引导加载程序,要么在固件设置里清空 DBX(不推荐,有安全风险)。
这四个数据库的关系,可以用一张图说清楚:
3.3 启动策略与验证流程
有了数据库,还得有规则。安全启动的验证策略,说白了就是三步走:
- 检查 DBX:先看这个 EFI 程序的签名或哈希是否在吊销列表里。如果在,直接拒绝,启动失败。
- 检查 DB:如果不在 DBX 里,再看是否在 DB 白名单里。如果在,允许运行。
- 检查 Setup Mode:如果既不在 DBX 也不在 DB,固件会看当前是否处于 Setup Mode(设置模式)。如果是,允许运行并提示用户是否添加签名;如果不是,直接拒绝。
注意:Setup Mode 是安全启动的“后门”。在 Setup Mode 下,你可以用 KEK 私钥签名新的 DB 条目。但一旦退出 Setup Mode 进入 User Mode(用户模式),没有有效签名的 EFI 程序就再也跑不起来了。我见过有人为了省事,长期把设备放在 Setup Mode 下跑——嗯,那安全启动就形同虚设了。
验证流程的伪代码大概是这样:
// 安全启动验证伪代码
EFI_STATUS SecureBootVerify(EFI_FILE *image) {
// 1. 检查 DBX
if (IsInDBX(image->signature)) {
return EFI_SECURITY_VIOLATION; // 被吊销,拒绝
}
// 2. 检查 DB
if (IsInDB(image->signature)) {
return EFI_SUCCESS; // 白名单,允许
}
// 3. 检查 Setup Mode
if (IsSetupMode()) {
// 提示用户是否添加签名
return EFI_SUCCESS; // 允许,但记录日志
}
// 4. 都不满足,拒绝
return EFI_SECURITY_VIOLATION;
}
你可能会问:为什么先查 DBX 再查 DB?顺序重要吗?
重要。你想,如果一个签名同时出现在 DB 和 DBX 里,那应该以哪个为准?规范明确说了:DBX 优先。这是为了防止有人通过往 DB 里添加已被吊销的签名来绕过安全启动。我当年第一次看规范时也忽略了这点,后来在测试中才发现顺序问题。
3.4 数据库的存储与操作
这四个数据库都存储在 UEFI NVRAM 变量里。变量名是固定的:
| 数据库 | UEFI 变量名 | GUID | 权限 |
|---|---|---|---|
| PK | PK |
8be4df61-93ca-11d2-aa0d-00e098032b8c |
仅 PK 自身可修改 |
| KEK | KEK |
8be4df61-93ca-11d2-aa0d-00e098032b8c |
PK 可修改 |
| DB | db |
d719b2cb-3d3a-4596-a3bc-dad00e67656f |
KEK 可修改 |
| DBX | dbx |
d719b2cb-3d3a-4596-a3bc-dad00e67656f |
KEK 可修改 |
每个数据库条目可以是以下三种格式之一:
- SHA-256 哈希:直接存储 EFI 程序镜像的哈希值。匹配最快,但更新固件后需要重新添加。
- X.509 证书:存储证书的 SHA-256 哈希。只要是用该证书签名的 EFI 程序都能通过验证。灵活性高,我比较推荐这种方式。
- EFI_CERT_TYPE_PKCS7_GUID:存储完整的 PKCS#7 签名。用得少,主要用于吊销场景。
我的习惯:在生产环境中,我倾向于用 X.509 证书来管理 DB。这样如果某个驱动需要更新,只要用同一张证书重新签名就行,不用动数据库。但要注意证书过期问题——我曾经吃过这个亏,证书过期后所有用该证书签名的驱动都跑不起来了。
3.5 启动策略的三种模式
UEFI 规范定义了三种安全启动模式:
| 模式 | 描述 | 典型场景 |
|---|---|---|
| Setup Mode | PK 变量为空。任何人都可以修改数据库。固件会提示用户安装 PK。 | 出厂首次启动、固件重置后 |
| User Mode | PK 已安装。只有持有 PK 私钥才能修改 PK 和 KEK;持有 KEK 私钥才能修改 DB/DBX。 | 正常生产环境 |
| Audit Mode | 类似 User Mode,但签名验证失败时不会阻止启动,只会记录日志。用于调试。 | 开发和测试阶段 |
Audit Mode 是个好东西。我调试安全启动问题时,第一件事就是切到 Audit Mode。这样既能收集签名验证失败的日志,又不会影响系统启动。等找到问题根因,再切回 User Mode 修复。