在 openSUSE 中借助全磁盘加密防范恶意设备

2025-07-26 | Fangzhou Liu | CC-BY-SA-4.0

在 openSUSE 中借助全磁盘加密防范恶意设备

openSUSE 如今有多种方式可配置全磁盘加密(FDE)安装。正如我们多次描述的那样(例如 这里这里这里),一种非常安全且简便的方法(通过 YaST2)是借助用户空间工具来实现。该解决方案基于 systemd 工具集,如 systemd-cryptenrollsystemd-pcrlocksystemd-cryptsetup 等,由内部的 sdbootutils 脚本进行协调。

使用这种 systemd 方法的主要优势之一是能够集成多种认证方式。除了在 initrd 阶段启动时要求输入的传统密码外,我们现在还可以使用证书、TPM2FIDO2 密钥来解锁系统。我们可以将其中一些方式结合起来,创建多个 LUKS2 密钥槽,例如,使用 TPM2 以无人值守的方式解锁设备,并将 FIDO2 密钥用作恢复机制。

说实话,TPM2 以及 TPM2+PIN 的组合,对用户而言是最实用的。正如我们在其他文章中所描述的,TPM2 是一种(有时是虚拟的)设备,它能够通过一种名为测量启动(measured boot)的机制来验证系统的健康状态。

简单来说的话,启动过程的每个阶段,从固件开始,都会在将执行权交给下一阶段之前,先加载并 “测量” 该阶段。例如,这意味着在启动过程的最后阶段,UEFI 固件会从磁盘将引导加载程序(可能是 shimsystemd-bootgrub2-bls)加载到内存中。随后,它会计算一个哈希值(通常是 SHA256),并命令 TPM2 对其内部寄存器(PCR)之一执行 “扩展”(extend)操作。

这种 “扩展” 是一种加密计算,它易于计算却无法复制。扩展操作针对 TPM2 的某个内部寄存器(PCR)进行,具体过程是:将该 PCR 的旧值与被测量组件的哈希值(同样采用 SHA256 算法)结合起来,计算出一个新的哈希值。这个新值会取代 PCR 当前的值,而这也是修改这些寄存器的唯一方式。其安全特性在于,从加密角度而言,强行向 PCR 写入预期值是不可能的,但计算最终值却十分容易。

这意味着,如果启动链过程中的所有组件(包括 UEFI 固件的所有阶段、固件配置、引导加载程序、命令行、内核乃至 initrd)都经过测量,那么我们就可以将最终的 PCR 值与预期值进行比对,从而判断系统是否是通过已知的正常软件和配置启动的,进而能立即发现启动链中的某个组件是否被非法篡改或入侵。

这是一项强大的特性,但更有趣的是,我们可以让机密信息仅在系统处于已知的安全状态时才能被访问。例如,我们可以使用 TPM2 对用于解锁加密磁盘的密钥进行加密(密封),同时设置一项策略:只有在使用同一 TPM2PCR 值与预期列表中的值匹配时,才能对该密钥进行解密(解封)。这些策略可以非常复杂,还能包含额外的密码、证书或其他验证项,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,那么 TPM2PCR 寄存器值将与解锁设备的策略匹配,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

分享帖子: