第二章 · 搭建零风险实验环境:虚拟机 + 软件 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-install、virsh等命令行工具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 Boot:loader_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
...
(输出很长,这里只留几行示意,你的具体数值会和我的不同。)
两个观察点:
tpm_version_major输出2,说明内核认到了 TPM 2.0,swtpm 工作正常。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 不能重置、只能单向演进“这条规则是不是真的。