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

附录 A · 踩坑实录

这本书里每个实验都是真跑过的,坑也是真踩过的。这里把它们集中收录,每条按“现象 → 原因 → 解法 → 教训“展开。有些是 TPM 特有的,有些是虚拟化和 Linux 系统管理的老坑在新场景里复发——但教训大多通用。

收录标准只有一条:这个坑真的浪费过我的时间。那些“看文档就能避免“的问题不在此列。

1. virt-install 报“打开文件失败“:deepin 缺 OVMF_VARS_4M.ms.fd

现象:在 deepin 宿主机上 virt-install --boot uefi 创建虚拟机,直接报错退出,提示无法打开固件变量文件(OVMF_VARS_4M.ms.fd)。

原因:libvirt 不是直接去找固件文件的,它先读发行版提供的固件描述符(/usr/share/qemu/firmware/*.json),按优先级选中一个变体。deepin 打包的 ovmf 提供的描述符里,带 .ms 后缀(Microsoft 签名键预置版)的变体排在前,但软件包里恰恰没装这个文件——描述符指向一个不存在的文件。

解法:别让它自动探测,显式指定 loader 和 nvram 模板:

virt-install ... \
  --boot loader=/usr/share/OVMF/OVMF_CODE_4M.fd,loader.readonly=yes,loader.type=pflash,nvram.template=/usr/share/OVMF/OVMF_VARS_4M.fd

(具体路径以 dpkg -L ovmf 的实际输出为准。)

如果换发行版后遇到类似问题,排查顺序是:先看 /usr/share/qemu/firmware/ 下有哪些描述符,再逐个检查里面引用的文件是否真的存在于磁盘上——多半是描述符和实际文件对不上。

教训:发行版打包和 libvirt 的期望对不上时,自动探测只会给你一个含糊的错误。显式指定比祈祷自动探测选对要可靠。

2. virt-viewer:只能通过 libvirt 使用 –attach 连接显示器

现象virt-viewer --connect qemu:///system <虚拟机名> 起不来,报“只能通过 libvirt 使用 –attach 连接显示器“之类的错误。

原因:virt-viewer 默认想直连 QEMU 的显示通道,但 libvirt 管理的虚拟机不走这条路。

解法:加上 --attach

virt-viewer --connect qemu:///system --attach <虚拟机名>

或者干脆装 virt-manager,图形界面里双击虚拟机名就行。

教训:报错信息里往往已经写了答案,先把错误原文读完整再动手。这条看起来是废话,但当时我就是扫了一眼“连接显示器失败“就开始乱试,漏掉了紧跟其后的“use –attach“提示——那五个字就是解法本身。

3. 无 tty 环境下 sudo 要密码

现象:在某些脚本化或受限 shell 环境里跑 sudo,它想从终端读密码但没有终端可用,直接失败。

解法:两个出路——

# 方案一:走 polkit,弹 GUI 授权框(桌面环境适用)
pkexec <命令>

# 方案二:用 SSH_ASKPASS 把密码从别处喂给 sudo
SSH_ASKPASS=/path/to/askpass.sh SSH_ASKPASS_REQUIRE=force sudo -A <命令>

教训sudo 的密码输入路径比想象中多,-A(走 askpass 程序)、-S(从 stdin 读)、-n(非交互,没缓存凭据就直接失败)的区别值得看一遍。写自动化脚本时优先 -n——它失败得干脆,不会卡住等一个永远等不到的终端输入。

4. heredoc 抢 stdin,sudo -S 拿不到密码

现象:改造 initramfs 的脚本里用 heredoc 写配置文件,脚本里还有 sudo -S 要从 stdin 读密码。结果 heredoc 把 stdin 占走了,sudo -S 拿到的密码不对,后续步骤静默失败——配置文件根本没写进去。差一点就以“旧钩子 + 新 cmdline“的中间态重启,那大概率起不来。

原因:heredoc 会重定向整个命令块或管道的 stdin,嵌在其中的 sudo -S 读到的不是你以为的密码来源。而且这种失败不炸出明显错误,配置写入步骤“看起来执行了“。

解法:把密码读取和文件写入拆开,密码先读进变量或用 sudo -v 提前缓存凭据,heredoc 里不再碰 sudo;写完后立刻 cat 产物确认内容。

更稳的做法是给这类脚本加一个固定收尾:把所有改动过的文件列出来逐个 catdiff,确认每个都符合预期再允许重启。多十秒钟,少一次进不去系统的惊魂。

教训:两条。第一,每一步改完都验证产物,不要相信“命令没报错“;第二,改造类操作最危险的是中间态——一半生效一半没生效,比全失败更难排查。

5. UKI 的 0600 root 权限:objcopy 提取 initrd 静默失败

现象:从 UKI 里用 objcopy 提取 initrd 段,命令跑完没有任何报错,但输出文件是空的。

原因:UKI 文件(/efi/EFI/Linux/*.efi)默认权限 0600 属 root,普通用户读不了。而命令里习惯性地加了 2>/dev/null,“Permission denied“被吞得干干净净。

解法:提取前加 sudo,或者干脆先把 UKI 拷到临时目录改权限再操作。更重要的是:调试期别随手 2>/dev/null

顺带一提,UKI 设成 0600 不是打包失误,是有意的:里面可能嵌着包含敏感信息的 initrd 或命令行参数。理解这一点后,“需要 sudo“就不是麻烦而是提示——这个文件本来就不该被随便读。

教训:先确认输入有效,再分析输出。空输出文件不是“提取失败“的现象,而是“根本没读到输入“的现象——如果上来就盯着 objcopy 的参数找茬,会浪费很久。

6. tpm2_verifysignature 的版本差异

现象:按记忆写的 tpm2_verifysignature --format=plain ... 在新版本上报参数错误。

原因:tpm2-tools 5.8 起,--format=plain 被标记为 deprecated,而且对 openssl 产生的裸签名需要显式给 --scheme=rsassa。老教程里的写法在新版行为变了。

解法

tpm2_verifysignature -c key.ctx -g sha256 --scheme=rsassa -m msg.bin -s sig.bin -t ticket.bin

教训:tpm2-tools 版本之间参数变动不少,--help 比记忆可靠,也比网上抄来的老命令可靠。装完先看版本:tpm2_verifysignature --version。遇到参数报错时,第一步是 man--help 对照当前版本的写法,而不是怀疑自己记错了——很可能就是版本变了。

tpm2_verifysignature --help | grep -E 'scheme|format'
# 当前版本里 --format 还在但标了 deprecated,--scheme 才是正路

这本书所有 tpm2-tools 命令都在 5.8 上验证过;如果你用的是别的版本,附录 B 的命令照抄之前也建议先对一遍 --help

7. “LUKS 密码输错还能进系统“的真相

现象:测试 LUKS 解锁时故意输错密码,结果系统还是进了桌面。第一反应是“加密没生效?“

原因:busybox 的 encrypt 钩子允许重试 3 次,输错后并没有失败退出,而是又给了提示——后来(可能连自己都没意识到)输对了,或者后续解锁路径成功了。系统能进桌面本身就说明根分区被解开了,加密当然生效。

解法:不猜,看证据。进系统后:

lsblk
# 根分区应该挂在 /dev/mapper/luks-xxx 这类 dm-crypt 设备上
cryptsetup status luks-xxx

看到 LUKS 设备确实处于 active 状态,“加密没生效“的猜想就不攻自破。反过来想,如果根分区直接挂在 /dev/vda2 而没有 mapper 层,那才是真的出问题了。

这条坑还教会我一个排查习惯:涉及“加密生没生效““绑定起没起作用“这类安全性判断时,永远去看系统状态的实际证据,不要用“我观察到的行为像不像“来推断——人的观察在这种事上非常不可靠。

教训:对反常现象下结论之前,先找一条能直接证伪的证据。lsblk 的设备层级比感觉可靠。

8. Arch 全系内核没有 CONFIG_IMA

现象:照着第九章做 IMA 实验,/sys/kernel/security/ima/ 目录根本不存在。

原因:IMA 需要内核编译时开启 CONFIG_IMA,而 Arch 官方内核(包括 linux、linux-lts、linux-zen)都没开这个选项。不是配置错了,是内核里压根没有这个功能。

解法:动手前先确认内核支持:

zcat /proc/config.gz | grep CONFIG_IMA
# 或者
grep IMA /boot/config-$(uname -r)

想用就得自己编译内核(或换支持 IMA 的发行版内核),配合 ima_appraise 等启动参数。

教训:凡是依赖内核特性的实验,第一步永远是确认当前内核编没编进去。这一步五秒钟,省下的排查时间以小时计。同理适用于其他内核特性依赖:/proc/config.gz(或 /boot/config-*)是你判断“功能不存在“和“配置不对“的分界线。

总结

八条坑看下来,反复出现的主题就三个:

  1. 静默失败是头号敌人2>/dev/null、heredoc 抢 stdin、重试机制——坑都不是“报错看不懂“,而是“该报的错没报“。调试期让错误都暴露出来。
  2. 显式优于自动。固件文件、签名 scheme、内核特性——自动探测和老经验都会背叛你,显式指定和 --help 不会。
  3. 每步验证产物。改造类操作的危险在中间态,写完看一眼、挂完看一眼、重启前再看一眼。

这些教训没有一条是 TPM 特有的——这本身就是个好消息:你在本书实验里练出的排查习惯,放回日常 Linux 运维里一样好用。