在 openSUSE 中借助全磁盘加密防范恶意设备
2025-07-26 | Fangzhou Liu | CC-BY-SA-4.0

openSUSE 如今有多种方式可配置全磁盘加密(FDE)安装。正如我们多次描述的那样(例如 这里、这里 或 这里),一种非常安全且简便的方法(通过 YaST2)是借助用户空间工具来实现。该解决方案基于 systemd
工具集,如 systemd-cryptenroll
、systemd-pcrlock
和 systemd-cryptsetup
等,由内部的 sdbootutil
s 脚本进行协调。
使用这种 systemd
方法的主要优势之一是能够集成多种认证方式。除了在 initrd
阶段启动时要求输入的传统密码外,我们现在还可以使用证书、TPM2
或 FIDO2
密钥来解锁系统。我们可以将其中一些方式结合起来,创建多个 LUKS2
密钥槽,例如,使用 TPM2
以无人值守的方式解锁设备,并将 FIDO2
密钥用作恢复机制。
说实话,TPM2
以及 TPM2+PIN
的组合,对用户而言是最实用的。正如我们在其他文章中所描述的,TPM2
是一种(有时是虚拟的)设备,它能够通过一种名为测量启动(measured boot
)的机制来验证系统的健康状态。
简单来说的话,启动过程的每个阶段,从固件开始,都会在将执行权交给下一阶段之前,先加载并 “测量” 该阶段。例如,这意味着在启动过程的最后阶段,UEFI 固件会从磁盘将引导加载程序(可能是 shim
、systemd-boot
或 grub2-bls
)加载到内存中。随后,它会计算一个哈希值(通常是 SHA256
),并命令 TPM2
对其内部寄存器(PCR
)之一执行 “扩展”(extend)操作。
这种 “扩展” 是一种加密计算,它易于计算却无法复制。扩展操作针对 TPM2 的某个内部寄存器(PCR
)进行,具体过程是:将该 PCR
的旧值与被测量组件的哈希值(同样采用 SHA256
算法)结合起来,计算出一个新的哈希值。这个新值会取代 PCR
当前的值,而这也是修改这些寄存器的唯一方式。其安全特性在于,从加密角度而言,强行向 PCR
写入预期值是不可能的,但计算最终值却十分容易。
这意味着,如果启动链过程中的所有组件(包括 UEFI
固件的所有阶段、固件配置、引导加载程序、命令行、内核乃至 initrd
)都经过测量,那么我们就可以将最终的 PCR
值与预期值进行比对,从而判断系统是否是通过已知的正常软件和配置启动的,进而能立即发现启动链中的某个组件是否被非法篡改或入侵。
这是一项强大的特性,但更有趣的是,我们可以让机密信息仅在系统处于已知的安全状态时才能被访问。例如,我们可以使用 TPM2
对用于解锁加密磁盘的密钥进行加密(密封),同时设置一项策略:只有在使用同一 TPM2
且 PCR
值与预期列表中的值匹配时,才能对该密钥进行解密(解封)。这些策略可以非常复杂,还能包含额外的密码、证书或其他验证项,TPM2
必须在验证通过后才能解封密钥。
借助这种机制,在 systemd
工具的支持下,当系统处于健康状态时,我们无需输入密码即可解锁加密磁盘。这里的 “健康” 是指,通过加密方式确保启动过程中使用的代码和配置与预期一致,例如,内核命令行中未被植入 init=/bin/bash
这样的恶意指令,内核或 initrd
也未被替换为存在漏洞的版本。
在 openSUSE 中,我们对该模型进行了集成,使得系统更新(包括引导加载程序或内核更新)时,sdbootutil
会自动生成新的预期 PCR
值(这些值被视为安全的)。这意味着 TPM2
策略会相应更新,并在下次启动时生效,从而确保自动解锁能够成功。若出现异常情况,即实际 PCR
值与预期不符,用户则需要输入存储在另一个 LUKS2
密钥槽中的密码来解锁设备,进而对系统进行审计和验证。
设计中的潜在缺陷
如前所述,使用 TPM2
无疑能提升安全等级,但它并非终极解决方案 —— 安全性始终是一种渐近式的逼近(没有绝对的安全)。
几年前,有人针对 Windows BitLocker 的全磁盘加密方案提出了一种 物理攻击方式。BitLocker 同样采用了类似前文所述的 TPM2
使用逻辑,但它在与设备通信时未采用加密会话。攻击者通过拦截 SPI 总线,就可能获取用于解锁磁盘的密码。systemd
从这一案例中吸取教训,很早就采用了加密会话来应对此类风险;不过,若用于解封密钥的策略还要求用户输入 PIN 码或密码,这类攻击也能被避免 —— 此时,TPM2
只有在 PCR
值符合预期且用户提供的密码正确时,才能解封密钥。需要说明的是,SPI 嗅探攻击对 Clevis(一种加密框架)同样可能 奏效。
但最近又出现了一种新的公开 攻击方式,它能直接影响上述原始设计,且实施难度远低于前一种攻击(说明:我们内部几个月前就独立发现了这一攻击方式,并早已采取了相应的应对措施)。
该文章描述了这种攻击的实施方式:通过检查 initrd
中用于挂载加密设备的文件系统 UUID 来实现。相关信息存储在 initrd
内的 /etc/crypttab
中,其内容大致如下:
systemd-cryptsetup attach cr_root /dev/disk/by-uuid/$UUID 'none' 'tpm2-device=auto'
如果启动过程中使用的是预期的固件、配置文件、内核和 initrd
,那么 TPM2
的 PCR
寄存器值将与解锁设备的策略匹配,TPM2
会解封密封的密钥,磁盘随之解锁,根文件系统切换(switch root)成功,启动过程将在根文件系统(rootfs)中继续进行。
但如果原始驱动器被替换成了另一个加密设备,而这个设备具有相同的 UUID
(毕竟 UUID 是公开信息),会发生什么呢?此时,PCR
寄存器的值仍会处于正确状态(因为测量启动中,前一阶段会先测量下一阶段再移交执行权,替换驱动器不会影响此前的测量结果)。接下来,systemd-cryptsetup
会尝试用 TPM2
成功解封的密钥去解锁这个设备,结果自然是解锁失败。这个恶意设备的 LUKS2
头部里或许也有一个 TPM2
密钥槽,但毫无疑问,它既无法用这个 TPM2
解锁,也无法用那个(用户的)秘密密码解锁。
这种情况下,systemd-cryptsetup
会尝试用 TPM2
解封的密钥解锁设备,结果必然失败。随后,它会要求用户输入密码以解锁设备,而攻击者此时可以输入一个能解锁该恶意设备的密码。根文件系统切换会顺利完成,但启动过程将在假根文件系统(fake rootfs
) 中继续——存储在该文件系统中的程序可以向 TPM2
发送请求(此时 TPM2
仍保存着正确的 PCR
值),其中一个请求可能是根据当前策略解封密钥。而这一次(和之前的正常流程一样),TPM2
会同意将密钥交给这个恶意程序。至此,攻击成功。
针对这种攻击,当然有相应的解决办法。
一种办法是再次采用 TPM2+PIN
的组合,而非仅使用 TPM2
,这与应对嗅探攻击的解决思路一致。在这种情形下,首次调用 systemd-cryptsetup
会失败,系统会要求用户输入密码来解锁设备。但此时,恶意程序无法让 TPM2
依据当前策略解封设备。尽管 PCR
值是匹配的,但该策略还要求输入只有真正用户才知晓的秘密 PIN 码或密码,要是没有这个密码,解封操作就会失败,密钥也能得以安全保存。
另一种解决办法是在某种程度上使策略失效,即在根文件系统切换(switch root)之前,对部分相关的 PCR
寄存器进行扩展操作,这样一来,之后该策略就无法再被应用。systemd-cryptsetup
可以通过在 /etc/crypttab
中设置 measure-pcr=yes
来自动实现这一点。启用这个选项后,会使用卷密钥(只有知道某些设备密钥才能提取出的一种秘密信息)对 PCR15
进行扩展。要让这个解决方案生效,需要把 PCR15
纳入当前策略中,其预期值设为默认的 0x000..00
。一旦黑客用自己提供的密码打开了恶意设备,PCR15
就会被自动扩展,其值将不再是 0x000..00
,从而在根文件系统切换之前使该策略失效。
但这个方案对我们而言并不适用。日常使用中,用户需要更新系统(例如更新内核),此时需计算新策略替代旧策略。由于 systemd-pcrlock
会将策略存储在 TPM2
的非易失性 RAM 插槽(NVIndex
)中,我们需要保护它不被其他进程替换。为此,systemd
会将一个恢复 PIN 码存储在另一个 NVIndex
中,且该 PIN 码由同一策略密封!若因策略失效导致无法自动恢复密钥,系统会要求用户输入恢复 PIN 码——若策略频繁失效,更新过程会变得十分繁琐。
最后,另一种解决思路是:若检测到设备并非预期的那一个,就终止启动过程。可在 initrd
中新增一个服务,让它在根文件系统切换前的最后一刻运行;一旦发现存储根文件系统(rootfs)的设备不符预期,就立即终止启动(例如让系统停机)。
为此,PCR15
仍然是一个不错的解决方案。它包含了一个秘密(卷密钥)的测量值,这个秘密只有真正的用户才能知晓,攻击者无法复制。理想情况下,我们可以为 PCR15
创建一个预期值,让这个服务将实际值与预期值进行比较,如果两者不同,就可以停止启动过程。
这正是 sdbootutil
中的 measure-pcr-validator 服务 所做的事情。sdbootutil
首先为所有在 initrd
期间被打开的加密设备生成一个预期值,并检查 /etc/crypttab
中是否存在正确的标记。为了能够访问卷密钥,该工具需要根密码,因此这个预期值只在真正需要的时候才会更新,例如添加新的加密设备时。这个预期值由存储在主机中的私钥签名,作为一项额外的安全措施,但由于公钥也存储在 ESP
中,说实话这并没有增加太多安全性。
额外的 measure-pcr-generator
服务会规范加密设备的打开顺序,因为这个顺序对于生成唯一可能的 PCR15
值至关重要。如果只有一个加密设备,测量顺序无关紧要,但如果有三个(例如 rootfs
、/home 分区
和 swap 交换分区
),就可能产生六个不同且有效的 PCR15
值。
最后一步是,initrd
中的 dracut-pcr-signature
服务会从 ESP
导入预期值、签名和公钥,这样measure-pcr-validator
服务就能验证签名并比较 PCR
值。
这样就大功告成了!
这种方法与新的 systemd-validatefs
在文件系统层面所做的工作有些类似。
原文:Protecting against rogue devices in openSUSE with Full Disk Encryption,作者:Alberto Planas