Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第二章 · 搭建零风险实验环境:虚拟机 + 软件 TPM

上一章我们把信任链、PCR、度量这些概念过了一遍。这一章动手搭环境。整个搭建过程不碰任何真实 TPM 芯片,所有实验都在虚拟机里完成,玩坏了删掉重建就行。

本章目标

  • 说清楚为什么用“虚拟机 + 软件 TPM“而不是直接用真机
  • 检查宿主机前提,装齐软件包
  • virt-install 创建一台带 UEFI 固件和 TPM 2.0 的虚拟机
  • 验证虚拟机里的 TPM 能正常工作,并拍一个快照作为“后悔药“

背景知识:为什么用虚拟机 + swtpm

我学习的宿主机是一台 deepin Linux 笔记本,本身就有真实的 TPM 2.0 芯片:

$ ls /dev/tpm*
/dev/tpm0  /dev/tpmrm0
$ cat /sys/class/tpm/tpm0/tpm_version_major
2

按理说我可以直接在这台机器上做实验,但我没有。原因有三个。

第一,玩坏了代价太高。 TPM 实验会改 PCR、写 NVRAM、绑 LUKS 解锁。真机上翻车,轻则系统起不来,重则加密数据解锁不了。虚拟机玩坏了,删掉重建,几分钟的事。

第二,TPM 直通(passthrough)体验很差。 可能有人会想:把宿主机的真 TPM 直通给虚拟机,不就两全了?行不通。一个 TPM 设备同一时刻只能给一个系统用,直通给虚拟机后宿主机自己就失去 TPM 了,而且 PCI 资源直通涉及 IOMMU 分组、驱动解绑等一堆配置,折腾半天还没法在多虚拟机间共享。

第三,软件 TPM 是行业标准做法。 swtpm 是一个用软件完整实现 TPM 2.0 规范的模拟器,由 QEMU/libvirt 生态原生支持。Keylime(远程证明框架)、systemd 的测试套件,CI 里跑的都是 swtpm。对上层软件来说,虚拟机里的 swtpm 和真实的 TPM 2.0 芯片行为一致——内核看到的是同一个 /dev/tpm0,tpm2-tools 的每一条命令表现相同。我们学的是 TPM 的语义和行为,不是芯片电气特性,软件 TPM 完全够用。

注意:swtpm 模拟的是 TPM 的功能行为,不能用来评估真芯片的安全属性(比如防物理攻击)。生产环境的威胁模型另说,学习环境用它没有水分。

检查宿主机前提

只需要两样东西:KVM 支持和磁盘空间。

$ ls /dev/kvm
/dev/kvm

没有 /dev/kvm 的话,去 BIOS/UEFI 设置里把虚拟化(Intel VT-x 或 AMD-V)打开。

磁盘方面,虚拟机会用一个 qcow2 格式的磁盘文件,上限 40G,但 qcow2 按需增长,刚装完系统实际只占几个 G,宿主机留出 10G 以上富余就够了。

安装软件包

Debian / deepin / Ubuntu 系:

sudo apt update
sudo apt install -y virt-manager qemu-system-x86 qemu-utils \
    swtpm swtpm-tools ovmf tpm2-tools

各包的作用:

  • qemu-system-x86:虚拟机本体
  • virt-manager:图形管理界面,附带 virt-installvirsh 等命令行工具
  • qemu-utils:提供 qemu-img 等磁盘工具
  • swtpm / swtpm-tools:软件 TPM 模拟器及管理工具
  • ovmf:开源 UEFI 固件(Open Virtual Machine Firmware),后面详细说
  • tpm2-tools:TPM 2.0 命令行工具集,装一份在宿主机上没坏处

注意:其他发行版的对应包名——Arch:virt-manager qemu-full swtpm edk2-ovmf tpm2-tools;Fedora:virt-install qemu-kvm swtpm swtpm-tools edk2-ovmf tpm2-tools。装完后确认 swtpm 命令存在即可,libvirt 启动虚拟机时会自动拉起它,不需要你手动起服务。

创建虚拟机

本书实验用的系统是 Omarchy(基于 Arch,Limine 引导 + UKI 统一内核镜像 + LUKS 全盘加密),但你用任何支持 UEFI 安装的 Linux 发行版都行。先下载好安装 ISO,然后:

virt-install --connect qemu:///system \
  --name omarchy-tpm-lab \
  --memory 4096 --vcpus 4 \
  --disk size=40,format=qcow2,bus=virtio \
  --cdrom /path/to/os.iso \
  --boot loader=/usr/share/OVMF/OVMF_CODE_4M.fd,loader.readonly=yes,loader.type=pflash,loader_secure=no,nvram.template=/usr/share/OVMF/OVMF_VARS_4M.fd \
  --tpm backend.type=emulator,backend.version=2.0,model=tpm-crb \
  --network network=default,model=virtio \
  --graphics spice,listen=none \
  --video virtio \
  --osinfo detect=on,name=linux2022 \
  --noautoconsole

逐个解释关键参数:

  • --connect qemu:///system:连系统级 libvirt 守护进程(权限完整),而不是用户级的 qemu:///session
  • --disk size=40,format=qcow2,bus=virtio:40G 上限的 qcow2 磁盘,virtio 总线性能最好
  • --cdrom:指向你下载的安装 ISO,装完系统后 libvirt 会自动处理
  • --boot loader=...,nvram.template=...:显式指定 UEFI 固件,这是整条命令里最容易踩坑的部分,下面细讲
  • --tpm backend.type=emulator,backend.version=2.0,model=tpm-crb:核心参数。emulator 表示用 swtpm 模拟;2.0 指定 TPM 版本;tpm-crb 是 CRB(Command Response Buffer)接口模型,比老的 TIS 接口快,现代系统都认它
  • --graphics spice,listen=none:SPICE 显示协议,只监听本地套接字
  • --osinfo detect=on,name=linux2022:让 libvirt 按通用现代 Linux 优化默认配置(比如默认启用 virtio)
  • --noautoconsole:创建完不要自动弹控制台,我们手动连(原因见坑 2)

为什么必须 UEFI:本书后面所有内容——度量启动(Measured Boot)、PCR 7、Secure Boot——全部建立在 UEFI 固件之上。传统 BIOS 引导的虚拟机没有 UEFI 度量日志,实验 C 直接没法做。所以固件必须是 OVMF,这一点没有商量余地。

关于 Secure Bootloader_secure=no 表示 Secure Boot 默认关闭。这不是偷懒,是刻意安排——先关掉它,看清没有 Secure Boot 时度量在做什么,第八章再打开它,对比才深刻。

坑 1:为什么不用简单的 --boot uefi

virt-install 其实支持一个更省事的写法:--boot uefi,让它自动探测 OVMF 固件。网上很多教程就这么写。但它在 deepin 上翻车了:

ERROR    无法完成安装:'内部错误:process exited while connecting to monitor:
... 打开文件失败 /usr/share/OVMF/OVMF_VARS_4M.ms.fd: 没有那个文件或目录'

排查过程是这样的:--boot uefi 时,libvirt 会去读 /usr/share/qemu/firmware/*.json 里的固件描述符,按特性匹配选一个。它选中了带 .ms 后缀的变体(OVMF_VARS_4M.ms.fd,微软签名版,为 Secure Boot 预置了密钥)。问题是 deepin 的 ovmf 包根本没打包这个文件,于是 QEMU 启动时打不开固件 NVRAM,直接挂掉。

解法就是上面命令里那样,绕开自动探测,把 loader(固件代码,只读)和 nvram.template(NVRAM 模板,每虚拟机可写)两个文件路径都显式写死。写之前可以确认一下它们确实存在:

$ ls /usr/share/OVMF/
OVMF_CODE_4M.fd  OVMF_VARS_4M.fd  ...

:这个教训值得记住——发行版打包的内容和 libvirt 的期望不一致时,自动探测选出来的东西可能根本不存在。显式指定比自动探测可靠,报错信息也更好懂。

坑 2:连不上控制台

虚拟机创建好之后会自己启动进入安装界面,但如果你直接敲 virt-viewer omarchy-tpm-lab,大概率会收到:

根据错误信息,只能通过 libvirt 使用 --attach 连接显示器

正确姿势:

virt-viewer --connect qemu:///system --attach omarchy-tpm-lab

或者干脆打开 virt-manager 图形界面,双击虚拟机名字,效果一样。接下来就是在安装界面里正常装系统,分区、LUKS 加密、引导器这些按发行版的安装流程走即可。

装好系统后的第一件事:验证 TPM,拍快照

系统装好、重启进系统之后,先做两件事。

验证 TPM 在线:

$ cat /sys/class/tpm/tpm0/tpm_version_major
2
$ tpm2_pcrread
sha1:
  0 : 0xD2432A6266FB4F7C129003242D452DDEF729674C
  1 : 0x53E70DD1F90743A8CFF06256E0CFACF0C4F2AFBB
  ...
sha256:
  0 : 0x2853F792C876EA7AEBAAC4D86002BDF3A10C97C05A0830985F869F807BBD02E3
  1 : 0x51825B1F11600E6E4B89B32A54EA5E20E16C66D50E8CBE28A0719BC355E23821
  2 : 0x3D458CFE55CC03EA1F443F1562BEEC8DF51C75E14A9FCF9A7234A13F198E7969
  3 : 0x3D458CFE55CC03EA1F443F1562BEEC8DF51C75E14A9FCF9A7234A13F198E7969
  ...

(输出很长,这里只留几行示意,你的具体数值会和我的不同。)

两个观察点:

  1. tpm_version_major 输出 2,说明内核认到了 TPM 2.0,swtpm 工作正常。
  2. tpm2_pcrread 里 PCR 0-9 左右已经有值,而且不为全零。这说明还没等我们做任何事,固件和引导器已经在往 PCR 里写度量了——这就是第一章说的度量启动,它一直在发生,只是平时没人看。如果所有 PCR 都是全零,那才说明有问题。

拍快照:

virsh snapshot-create-as omarchy-tpm-lab clean-install "全新安装" --disk-only --atomic

这个快照就是后悔药。后面任何实验把系统搞挂了,一条命令回到刚装完的状态:

virsh snapshot-revert omarchy-tpm-lab clean-install

建议每章实验开始前都确认一下自己能回到这个点。

思考:快照只管虚拟机的磁盘。swtpm 的状态存在宿主机 /var/lib/libvirt/swtpm/ 下对应的目录里,随虚拟机的 libvirt 快照一起保存和恢复(--atomic 保证这个操作是原子的)。这就是为什么整套方案能做到“零风险“——连 TPM 的内部状态都有备份。

关于发行版的选择

本书所有命令都是在 Omarchy(Arch 系)里跑的。你用 Ubuntu、Fedora、Debian 的虚拟机跟着做也完全没问题,第三章到第五章的实验(PCR、Seal、度量日志)在任何发行版上命令都一样,只要装好 tpm2-tools

唯一的差异点在第六章:LUKS 绑定 TPM 后需要改造 initramfs(初始内存文件系统,initial ram filesystem),Arch 系用 mkinitcpio,Debian/Ubuntu 用 initramfs-tools,Fedora/RHEL 用 dracut,三家的钩子和配置方式都不一样。到时候我会给出各家的差异说明。

常用管理命令速查

virsh list --all                      # 列出所有虚拟机(含关机的)
virsh start omarchy-tpm-lab           # 开机
virsh shutdown omarchy-tpm-lab        # 优雅关机
virsh destroy omarchy-tpm-lab         # 强制断电(相当于拔电源,慎用)
virsh snapshot-list omarchy-tpm-lab   # 查看快照

小结

这一章我们确定了“虚拟机 + swtpm“的实验方案:安全、可重建、和真芯片行为一致,也是业界标准做法。宿主机上装好 KVM、QEMU、swtpm、OVMF 和 tpm2-tools 之后,用一条 virt-install 命令创建了带 UEFI 固件和 TPM 2.0 的虚拟机,踩过了 OVMF 固件文件缺失和控制台连接两个坑,最后在虚拟机里确认 TPM 在线、PCR 已有度量值,并拍下了 clean-install 快照。

环境已经就绪。下一章实验 A,我们直接对 PCR 下手:手动 extend 一个值进去,亲眼看哈希链怎么滚动,验证“PCR 不能重置、只能单向演进“这条规则是不是真的。