English

文章

keelOS: 虚拟机的拦路虎——Win11 需要 TPM 2.0

Windows 11 不认没有 TPM 2.0 的机器,虚拟机也一样。这篇讲 TPM 是什么、各家虚拟化平台怎么给虚拟机造一块"软 TPM",以及 keelOS 是怎么做的:尤其是在 keelOS 里,宿主系统重启时虚拟机不关机,TPM 的状态也得跟着活下来。

Windows 11 为什么非要 TPM

TPM(Trusted Platform Module)是主板上的一个小安全芯片,能做三件软件自己做不好的事:

  • 记录开机过程。固件、引导程序、内核一层层启动时,每一层先把下一层的哈希"扩展"进 TPM 的 PCR 寄存器(PCR0…PCR23)。PCR 只能扩展、不能改写,一次开机改过的东西会在 PCR 里留下痕迹。
  • 把密钥封在"这台机器、这次正常开机"上。BitLocker 把磁盘密钥交给 TPM 封存,只有 PCR 的值和封存时一样,TPM 才肯交出来。硬盘被拆走、引导被篡改,都解不开。
  • 证明身份。每块 TPM 出厂有一对背书密钥(EK)和证书,可以向远端证明"我是一块真 TPM"。Windows Hello、设备证明都用它。

Windows 11 把 TPM 2.0 列为硬性要求,安装程序开头就检查。以前可以改注册表(LabConfig)绕过去,新版 Windows 11 把这条路也堵了。所以虚拟机想跑 Windows 11,就得有一块像样的虚拟 TPM。

虚拟 TPM 由哪几块组成

给虚拟机造 TPM,要解决四件事:

  1. 接口:虚拟机看到的"硬件"。常用 CRB(Command Response Buffer):一页内存映射寄存器加一块命令缓冲区,外加 ACPI 的 TPM2 表告诉操作系统它在哪。
  2. 引擎:真正执行 TPM 命令的程序。开源世界基本都用 IBM 的 libtpms,外面包一层 swtpm 作为独立进程。
  3. 状态:永久状态(EK、种子、NV 存储)要存盘;易失状态(PCR、会话、已加载的密钥)只活在内存里。
  4. 生命周期:虚拟机重启时 TPM 要复位;虚拟机挂起、快照、迁移时,易失状态要跟着走。

别家怎么做

平台 接口 引擎在哪 挂起、迁移时带上 TPM 状态 状态加密
QEMU / libvirt / Proxmox TIS、CRB 外部 swtpm(数据 + 控制两个通道) 有 可选
Cloud Hypervisor CRB 外部 swtpm(数据 + 控制) 有复位 交给上层
VirtualBox 7 TIS、CRB 进程内 libtpms 有 随虚拟机加密
Hyper-V 自有 隔离的安全域(VSM) 有 强制
VMware 自有 hypervisor 内部 有 强制
bhyve(FreeBSD 14.3+) CRB 外部 swtpm,只接数据通道 没有 没有
keelOS CRB 外部 swtpm(数据 + 控制) 有,进挂起检查点 暂不加密

swtpm 有两条通道:数据通道传 TPM 命令本身;控制通道管 TPM 的"电源":上电复位(CMD_INIT)、停机、取消命令,以及把状态整块导出、导入(CMD_GET_STATEBLOB / SET_STATEBLOB)。QEMU 的热迁移就靠后者把 TPM 状态塞进迁移流。

起点:上游 bhyve 能用,但不够结实

keelOS 的虚拟机跑在 bhyve 上。FreeBSD 14.3 起,bhyve 有了 CRB 接口加 swtpm 后端,Windows 11 装得上。但读完代码,我们发现几处在边界情况下会出事:

  • 虚拟机发一条格式不对的 TPM 命令,TPM 的工作线程直接退出,此后这台虚拟机的 TPM 永久失效。
  • 虚拟机写一个只读寄存器、或者访问没实现的地址,bhyve 会把整台虚拟机停掉。真硬件只会忽略。
  • 后端出错时,虚拟机读到一个全 0 的"响应",而不是 TPM 错误码。
  • 从 swtpm 读响应只读一次,没按长度读完;swtpm 挂掉时 bhyve 直接退出。
  • 只接了数据通道:虚拟机重启时 TPM 不一定复位,挂起、恢复时 TPM 状态全丢。

keelOS 的特殊难题:宿主重启,虚拟机不关

keelOS 的结构和一般虚拟化平台不一样:一个很小的 hypervisor(hyp)从 UEFI 启动后常驻内存,FreeBSD 只是它上面一个可以随时重启的"root 分区",负责驱动、存储和设备模拟。root 重启(升级内核、换驱动)的时候,虚拟机只是暂停二三十秒:内存留在 hyp 里不动,设备状态写进检查点,新的 root 起来后再恢复,TCP 连接都不断。

问题在于 swtpm 也跑在 root 里。root 一重启,swtpm 进程就没了,TPM 的易失状态(PCR、会话、已加载的密钥)跟着消失。虚拟机醒来会发现 TPM "失忆":轻则 TPM 服务报错,重则 BitLocker 在下次开机时要恢复密钥。所以 keelOS 的 vTPM 有一条别家没有的硬要求:TPM 的易失状态必须进挂起检查点,恢复时灌进一个全新的 swtpm。

keelOS 是怎么实现的

keelOS vTPM 结构
keelOS vTPM 结构

虚拟机只看到 CRB 寄存器;bhyve 同时接 swtpm 的数据通道和控制通道。

1. 把 bhyve 的 TPM 修结实

我们没有另起炉灶,而是在 bhyve 自己的 CRB 实现上修:格式不对的命令回一个标准的 TPM 错误(TPM_RC_FAILURE),工作线程继续跑;没实现的寄存器读回全 1、写入忽略,和真硬件一样,不再让虚拟机停机;响应按头部的长度读完;swtpm 出问题时把错误交给虚拟机里的驱动处理,bhyve 照常运行。我们实测过:把 swtpm 杀掉,虚拟机里读 PCR 只会得到一个 TPM 错误,虚拟机本身不受影响。

2. 接上 swtpm 的控制通道

bhyve 多了一个参数 tpm.ctrl=<socket>。每次 bhyve 启动,先经控制通道给 TPM"上电":停机、把缓冲区大小设成和 CRB 一样(3968 字节,否则 TPM 会以为自己能收 4096 字节的命令)、再发 CMD_INIT。bhyve 的每次启动都对应虚拟机的一次开机,所以虚拟机重启时 TPM 必然复位,PCR 从零开始重新度量。

3. TPM 状态进检查点

挂起时,bhyve 先等正在执行的 TPM 命令做完,再把 CRB 寄存器、数据缓冲区、PPI 页,以及经控制通道导出的三块 TPM 状态(永久、易失、保存状态)写进检查点。恢复时顺序反过来:

STOP → SET_STATEBLOB(permanent) → SET_STATEBLOB(volatile)
     → SET_STATEBLOB(savestate) → INIT

libtpms 看到有易失状态,就从那里接着跑,而不是冷启动。对虚拟机来说,TPM 从来没断过。

4. 管理程序替你打理 swtpm

在 keelOS 的管理程序里,给虚拟机的 config.json 加一行 "tpm": true(命令行是 keel vm create --tpm),剩下的都自动:

  • 第一次开机时用 swtpm_setup 生成 TPM 的身份:EK、EK 证书、平台证书,只开 SHA-256 这一组 PCR。
  • 每次 bhyve 启动前(开机、虚拟机重启、恢复)起一个新的 swtpm;swtpm 带 terminate 参数,bhyve 一退出它就跟着退出,不会留下孤儿进程。
  • TPM 的永久状态放在虚拟机自己的目录 tpm/ 里。这个目录在虚拟机的 ZFS 数据集上,快照、复制、备份都自然带上它。
  • 没开 TPM 的虚拟机完全不受影响:参数、检查点格式都和以前一样。

实测结果

测试都在真机上做(开发机,FreeBSD 14.5 root,swtpm 0.10.2):

  • 通过:Linux 虚拟机看到 /dev/tpm0,固件的度量启动事件日志也在。
  • 通过:往 PCR16 写入一个值后挂起,换一个全新的 swtpm 再恢复:PCR0、PCR7、PCR16 和挂起前一模一样,之后还能继续写。
  • 通过:虚拟机自己重启:PCR16 归零,PCR0、PCR7 重新度量,结果和上次开机相同。
  • 通过:swtpm 中途被杀:虚拟机里只得到 TPM 错误,虚拟机照常运行。
  • 通过:Windows 11 25H2 安装程序自己的 TPM 2.0、Secure Boot 硬件检查,没用任何绕过手段。
  • 通过:Windows 11 里 TPM 就绪,BitLocker 用 TPM 保护器(PCR 0、2、4、11)加密系统盘。
  • 通过:Windows 11 重启:TPM 自动解封,没有要 BitLocker 恢复密钥。
  • 通过:Windows 11 挂起再恢复(swtpm 换成新进程):暂停 3.7 秒,之后 TPM 和 BitLocker 照常。

还没做的:TPM 状态文件加密(个人和小办公室场景下,状态文件和磁盘在同一台机器上,加密能防的情形有限,先不做);Secure Boot 预置微软密钥;Windows 里"清除 TPM"这类 PPI 请求要到下次开机由固件执行,目前 bhyve 每次开机都是新进程,这类请求会丢,上游也一样。

顺带解决的几个 Windows 11 问题

  • 只用了一半的 CPU。bhyve 默认一个 vCPU 算一个处理器插槽,Windows 11 专业版最多用两个插槽,4 个 vCPU 只起了 2 个,24H2 在多插槽下还特别慢。keel 新建的虚拟机现在默认把所有 vCPU 放进一个插槽。
  • 重启时卡死。bhyve 模拟的 e1000 网卡在驱动屏蔽中断后不撤回中断线,Windows 关机卸载网卡驱动时变成中断风暴。keel 的 bhyve 已经修好。
  • 多出一个"未知设备"。TPM 的事件日志要走 QEMU 的 fw_cfg,Windows 没有它的驱动。keel 像 QEMU 一样把这个节点标成"不在界面上显示"。

小结

vTPM 本身不新,QEMU、Hyper-V、VMware 早就有。keelOS 要解决的是自己结构带来的那一步:宿主系统可以随时重启,虚拟机不停,TPM 也不能失忆。做法是沿用业界最成熟的 swtpm 加控制通道,把 TPM 状态放进 keelOS 本来就有的挂起检查点里。对用户来说,这只是配置里的一行 "tpm": true。


keelOS 开发笔记。设计和调研细节见仓库 docs/26(vTPM 调研与实现)。

← 文章 · 为什么选 keelOS