QEMU/OVMF环境下UEFI HTTP(s)启动介绍

Hacker News Top 工具

摘要

本文提供使用QEMU和OVMF设置UEFI HTTP和HTTPS启动的教程,强调需要随机数生成器设备,并展示最小配置。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/12 17:55

# 使用Qemu/OVMF进行UEFI HTTP(S)启动介绍 来源:https://blog.yadutaf.fr/2026/06/12/introduction-to-uefi-https-boot-qemu-ovmf/ 历史上,网络启动的标准方案是PXE。PXE基于DHCP和TFTP。它配置起来很棘手,要使其高可用就更难了,而且面对这种明文无签名的协议,安全方面也只能听天由命。 现代Web早已标准化了带有TLS证书的HTTPS,用于服务器认证、完整性和机密性。此外,高可用性设置在HTTPS方面已经是一个已解决的问题。更好的是,加密层使得通过互联网启动变得实用,而无需立即面对中间人攻击的威胁——这在TFTP下是轻而易举的(记住,开头的“t”代表“Trivial”,而不是“Secure”)。 好消息是,大多数基于UEFI的现代系统都支持通过HTTP(S)启动。 在这篇文章中,我们将直接从官方网站启动 `netboot.xyz` (https://netboot.xyz/) 的 `snponly` 变体。准备好迎接HTTPS带来的乐趣吧。 除非另有说明,所有测试均在Ubuntu 26.04上执行,使用的是系统提供的1:10.2.1+ds-1ubuntu3版本的Qemu和2025.11-3ubuntu7版本的OVMF软件包。请注意,因为稍后会在本文中说明的原因,旧版本实际上可能效果更好🙃。 ### 从简单案例开始:通过DHCP发现HTTP启动 (理所当然地)怀疑HTTPS变体会是一个难啃的骨头,我在这趟旅程开始时进行了一个初步测试,一开始就绕开了证书信任问题和其他古怪之处。 启动固件的URL是:`http://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi`。 这里的目标是展示一个最小的设置,以便更容易地将其集成到你自己的环境中。我们将使用一个基于用户态SLIRP网络、非root权限运行的Qemu虚拟机,并且没有额外的设备,例如存储设备。整个系统将在内存中运行。 让我们从一个基于以下条件的第一轮迭代开始: - OVMF固件,为了简单起见,禁用安全启动 - 一块网卡,它将模拟一个DHCP服务器,并将UEFI指向HTTP启动目标 - 在控制台输出 ``` 1qemu-system-x86_64 \ 2 -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ 3 -nic user,bootfile=http://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi \ 4 -nographic ``` 这看起来很有希望,但它无法通过网络启动: ``` 1BdsDxe: No bootable option or device was found. 2BdsDxe: Press any key to enter the Boot Manager Menu. ``` 这是因为OVMF中的网络栈需要一个随机数生成器设备才能工作。仔细想想,这几乎是显而易见的。网络栈的每一层几乎都需要随机性,从以太网冲突避免到TLS,再到DHCP本身。 通过向Qemu命令行添加以下标志,可以非常简单地启用它: 任何其他能提供随机数生成器的配置更改也同样有效。例如,可以启用KVM并使用`-cpu host`,这将允许访问CPU的随机数生成指令。 虽然有点显而易见,但由于缺少错误日志,如果没有帮助,这很难推断出来。在这种情况下,我是在向免费的Claude提问后得到帮助的。 我想知道如何才能通过归纳/演绎来推断出这一点,而不是通过(非常有帮助的)LLM来暴力破解。事实证明,这个依赖关系在 `NetworkPkg/Library/DxeNetLib/DxeNetLib.inf` 的 `[Depex]` 部分中声明了: ``` 1[Depex] 2 gEfiRngProtocolGuid ``` 在其内部,这将EFI随机数生成协议的GUID推送到依赖栈上,这样任何链接了 `DxeNetLib` 的EFI软件包在运行时调度器评估依赖关系时,都会隐式地要求它。 为了将来参考(对我自己而言),可以通过在 `OvmfPkg/OvmfPkgX64.dsc` 中启用 `gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel` 的 “DEBUG_DISPATCH” 标志,在运行时从调试日志中找出依赖关系。准备好迎接一个基于GUID的调试世界吧! (如果你对暴力破解方法感到沮丧,更愿意看到基于日志的方法,请关注HTTPS部分。) 有了这个,搞定! ``` 1>>Start PXE over IPv4. 2 Station IP address is 10.0.2.15 3 4 Server IP address is 10.0.2.2 5 NBP filename is http://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi 6 NBP filesize is 0 Bytes 7 PXE-E23: Client received TFTP error from server. 8BdsDxe: failed to load Boot0002 "UEFI PXEv4 (MAC:525400123456)" from PciRoot(0x0)/Pci(0x3,0x0)/MAC(525400123456,0x1)/IPv4(0.0.0.0,0x0,DHCP,0.0.0.0,0.0.0.0,0.0.0.0): Not Found 9 10>>Start PXE over IPv6. 11 PXE-E16: No valid offer received. 12BdsDxe: failed to load Boot0003 "UEFI PXEv6 (MAC:525400123456)" from PciRoot(0x0)/Pci(0x3,0x0)/MAC(525400123456,0x1)/IPv6(0000:0000:0000:0000:0000:0000:0000:0000,0x0,Static,0000:0000:0000:0000:0000:0000:0000:0000,0x40,0000:0000:0000:0000:0000:0000:0000:0000): Not Found 13 14>>Start HTTP Boot over IPv4.... 15 Station IP address is 10.0.2.15 16 17 URI: http://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi 18 File Size: 310784 Bytes 19 Downloading...100%BdsDxe: loading Boot0004 "UEFI HTTPv4 (MAC:525400123456)" from PciRoot(0x0)/Pci(0x3,0x0)/MAC(525400123456,0x1)/IPv4(0.0.0.0,0x0,DHCP,0.0.0.0,0.0.0.0,0.0.0.0)/Uri() 20BdsDxe: starting Boot0004 "UEFI HTTPv4 (MAC:525400123456)" from PciRoot(0x0)/Pci(0x3,0x0)/MAC(525400123456,0x1)/IPv4(0.0.0.0,0x0,DHCP,0.0.0.0,0.0.0.0,0.0.0.0)/Uri() 21iPXE initialising devices... 22autoexec.ipxe... ok 23 24 25 26iPXE 2.0.0+ (g36e8c) -- Open Source Network Boot Firmware -- https://ipxe.org 27Features: DNS HTTP HTTPS iSCSI NFS TFTP VLAN AoE EFI Menu 28netboot.xyz - v3.x 29Hit the m key to open failsafe menu... ``` 我有没有提到达到这一步大约需要1分15秒?🐌 ### 通过配置加速HTTP启动 仅仅为了达到我们感兴趣的那一次启动尝试,就花费一分钟,这实在太慢了,完全不是可以接受的目标。既然现在能工作了,我们可以尝试让它变快。好消息是,这相对容易。 UEFI网络栈会依次尝试: 1. IPv4 PXE:IPv4部分有效,但HTTP URL无效,期望的是一个TFTP引用。 2. IPv6 PXE:由于未配置IPv6,没有任何东西能工作。 3. IPv4 HTTP:最终能工作了。 4. IPv6 HTTP:这将是接下来的尝试。 理想情况下,我们需要一种机制来指示OVMF避免在传统的PXE上浪费时间。 幸运的是,Qemu有一种通过 `-fw_cfg` 标志向固件传递选项的方法,并且(部分)可用的OVMF设置文档甚至可以在以下地址找到:https://github.com/tianocore/edk2/blob/master/OvmfPkg/RUNTIME_CONFIG.md 从文档中,我们可以找到很好的候选者: - `opt/org.tianocore/IPv4PXESupport` - `opt/org.tianocore/IPv6PXESupport` 我们甚至可以看到 `etc/edk2/https/cacerts`。这可能在下一部分派上用场。谁知道呢? 将它们整合在一起,最终的HTTP启动Qemu命令行如下: ``` 1qemu-system-x86_64 \ 2 -device virtio-rng-pci \ 3 -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ 4 -nic user,bootfile=http://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi \ 5 -fw_cfg name=opt/org.tianocore/IPv4PXESupport,string=no \ 6 -fw_cfg name=opt/org.tianocore/IPv6PXESupport,string=no \ 7 -nographic ``` 这将在大约5秒内启动到 `netboot.xyz`,这要容易接受得多。 ### 另一种HTTP启动方式:通过UEFI变量 我们已经达到了一个能工作但难以移植到真实硬件的状态。在真实硬件上,我们没有这些方便的OVMF运行时可调参数。我们拥有的是UEFI变量。我们能否用UEFI变量取代Qemu的花招来实现同样的效果?理想情况下,我们需要在虚拟机首次启动前预生成变量,以达到与先前方法相同的效果。 (好吧,好吧,采用这种替代方法的真正原因是为了稍微平滑一下通往HTTPS变体的道路。) 理想情况下,将变量放在一个人类可读的配置文件中是最理想的解决方案。等等,我说人类可读了吗?是的,Qemu从10.0版本开始就有了这个功能,我们现在用的是10.2.1。参见 https://github.com/tianocore/edk2/blob/master/OvmfPkg/QEMU_PV_VARS.md。 当然,这个选项并未在Ubuntu的OVMF构建中启用,而且我不想重新构建OVMF。这太麻烦了。(记住,我们才刚走到简单的HTTP步骤。) 幸运的是,来自 https://gitlab.com/kraxel/virt-firmware 的 `virt-fw-vars` 可以将一个“下次启动URI”直接注入到 `OVMF_VARS_4M.fd` EFI变量存储中: ``` 1# 安装固件工具 2sudo apt install python3-virt-firmware 3 4# 在全新的变量存储中注入“下次启动条目” 5virt-fw-vars --input /usr/share/OVMF/OVMF_VARS_4M.fd --set-boot-uri http://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi --output ./OVMF_VARS_4M.fd 6 7# 启动虚拟机 8qemu-system-x86_64 \ 9 -device virtio-rng-pci \ 10 -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \ 11 -drive if=pflash,format=raw,file=./OVMF_VARS_4M.fd \ 12 -nic user \ 13 -nographic ``` 通过这种方式,我们可以用两种方法通过HTTP启动虚拟机: 1. 第一种,使用DHCP bootfile。 2. 第二种,通过构建下次启动UEFI变量。 准备好为HTTPS添加“S”了吗? ### UEFI HTTPS启动:仅仅是在URL后加个“s”那么简单吗? 当然,至少对其中一种方法来说,将“http://”替换为“https://”就这么简单? 并非如此。 基于DHCP/bootfile的方法在大约1分钟的超时后失败,并显示以下消息: ``` 1>>Start HTTP Boot over IPv4..... 2 Error: Could not retrieve NBP file size from HTTP server. 3 4 Error: Server response timeout. 5BdsDxe: failed to load Boot0004 "UEFI HTTPv4 (MAC:525400123456)" from PciRoot(0x0)/Pci(0x3,0x0)/MAC(525400123456,0x1)/IPv4(0.0.0.0,0x0,DHCP,0.0.0.0,0.0.0.0,0.0.0.0)/Uri(): Not Found ``` 至少变量存储变体失败得更快,大约5秒: ``` 1>>Start HTTP Boot over IPv4.... 2 Station IP address is 10.0.2.15 3 4 URI: https://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi 5 6 Error: Could not retrieve NBP file size from HTTP server. 7 8 Error: Unexpected network error. ``` 这些错误消息都没有可操作性。这里唯一有价值的线索是,变量存储变体至少成功获取了IP地址并找到了下次启动的URL。至于基于DHCP的方法失败的原因,我实在不知道。 这显然不仅仅是添加“https”中的“s”那么简单。 ### 日志追踪 EDK II/OVMF固件可以在一个特定的虚拟串行端口上方便地生成调试日志。幸运的是,Gerd Hoffmann(恰好也是我们之前使用的 `virt-fw-vars` 的作者/维护者)最近在他的博客上记录了正确的操作方法:https://www.kraxel.org/blog/2025/10/firmware-logging/。 让我们这样做,将 `-device isa-debugcon,iobase=0x402,chardev=fw -chardev file,id=fw,path=debug.log` 添加到Qemu命令行,并检查日志。 以下是完整输出: 是的,你没看错。没有错误。什么都没有。 这是因为这只适用于OVMF的DEBUG版本,而我们使用的是RELEASE版本。这说得通,但Ubuntu并没有提供这样的构建版本。我们别无选择,只能自己重新构建。 为此,我选择了简单的路径。我获取了软件包源码,并复用了 `debian/rules` Makefile。Deb软件包可能是个麻烦的家伙,但正确地构建OVMF也是一次有趣的经历。如果其他人已经完成了正确的设置并找到了好的构建标志组合,我肯定不会再怀疑他们。 ``` 1sudo apt install devscripts 2sudo apt build-dep ovmf 3apt-get source ovmf 4cd edk2-2025.11 5make -f debian/rules build-ovmf-no-secboot BUILD_TYPE=DEBUG ``` 上面重要的部分是 `BUILD_TYPE=DEBUG`。这是Debian打包特定的变量,但它随后会被按需转发给OVMF构建系统。 让我们将这个固件插入到Qemu命令行中,然后看看进展如何: ``` 1# 初始化变量存储(总是需要,因为这只是下次启动) 2virt-fw-vars --input /usr/share/OVMF/OVMF_VARS_4M.fd --set-boot-uri https://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi --output ./OVMF_VARS_4M.fd 3 4# 使用调试固件和标志启动 5qemu-system-x86_64 \ 6 -device virtio-rng-pci \ 7 -drive if=pflash,format=raw,readonly=on,file=edk2-2025.11/debian/build/ovmf/no-secboot/Build/OvmfX64/DEBUG_GCC5/FV/OVMF_CODE.fd \ 8 -drive if=pflash,format=raw,file=./OVMF_VARS_4M.fd \ 9 -nic user \ 10 -device isa-debugcon,iobase=0x402,chardev=fw \ 11 -chardev file,id=fw,path=debug.log \ 12 -nographic ``` 有日志了! 让我们用‘tls’过滤它们: ``` 1Loading driver at 0x0000672A000 EntryPoint=0x00006730B3B TlsAuthConfigDxe.efi 2Loading driver at 0x00006397000 EntryPoint=0x0000646255F TlsDxe.efi 3TLS Certificate is not found on the system! 4TlsDoHandshake SSL_HANDSHAKE_ERROR State=0x4 SSL_ERROR_SSL 5TlsDoHandshake ERROR 0xA000086=L14:R86 tls_post_process_server_certificate(): ``` 显而易见的问题就在中间:我们没有提供任何可信任的CA证书,并且OVMF不像网络浏览器那样自带自己的证书列表。 ### 提供一组可信任的CA证书 事后看来,这部分出奇地简单。我们之前看到的 `RUNTIME_CONFIG.md` (https://github.com/tianocore/edk2/blob/master/OvmfPkg/RUNTIME_CONFIG.md) 中的第一个控制钮恰好是 `etc/edk2/https/cacerts`。诚然,我们将不得不再次使用一些 `fw_cfg` 的技巧,但这也可以(也许?)通过设置 `TlsCaCertificate` UEFI变量来完成(参见 TlsAuthConfigLib.c (https://github.com/tianocore/edk2/blob/master/OvmfPkg/Library/TlsAuthConfigLib/TlsAuthConfigLib.c))。 这个变量不是UEFI规范的一部分,我觉得这很令人费解,因为提供一份受控的可信任CA证书列表在安全启动环境中看起来是一个非常合理的需求。至少,EFI签名的主要防御层仍然存在。但是,我真心好奇现实中的固件实现是如何处理这个问题的(而且我没有疯狂到在我的主笔记本上测试它)。 实际上,关于这个变量的少数资料似乎分散在edk2中的两个文本文件中。两者都暗示了生成有效证书包的不同方法,分别针对不同的用例: - OvmfPkg/RUNTIME_CONFIG.md (https://github.com/tianocore/edk2/blob/master/OvmfPkg/RUNTIME_CONFIG.md) 建议使用 `virt-fw-sigdb` 来生成一个定制列表,只包含特定的可信任证书。 - 另一方面,OvmfPkg/README (https://github.com/tianocore/edk2/blob/master/OvmfPkg/README) 建议使用 `p11-kit` 来转换系统可信任的整个证书列表并原样复用。 我们采用粗暴的方法,转换整个包并再试一次: ``` 1# 将系统证书包转换为适合OVMF的格式 2p11-kit extract --format=edk2-cacerts --filter=ca-anchors --overwrite --purpose=server-auth cacerts.bin 3 4# 将其连接到Qemu命令行中 5virt-fw-vars --input /usr/share/OVMF/OVMF_VARS_4M.fd --set-boot-uri https://boot.netboot.xyz/ipxe/netboot.xyz-snponly.efi --output ./OVMF_VARS_4M.fd 6qemu-system-x86_64 \ 7 -device virtio-rng-pci \ 8 -drive if=pflash,format=raw,readonly=on,file=edk2-2025.11/debian/build/ovmf/no-secboot/Build/OvmfX64/DEBUG_GCC5/FV/OVMF_CODE.fd \ 9 -drive if=pflash,format=raw,file=./OVMF_VARS_4M.fd \ 10 -nic user \ 11 -fw_cfg name=etc/edk2/https/cacerts,file=cacerts.bin \ 12 -device isa-debugcon,iobase=0x402,chardev=fw \ 13 -chardev file,id=fw,path=debug.log \ 14 -nographic ``` 当然,这仍然不能工作,但至少第一个警告消失了。

相似文章

Bootimus — 一个自包含的PXE和HTTP启动服务器

Hacker News Top

Bootimus是一个自包含的PXE和HTTP启动服务器,使用Go语言编写,提供零配置、嵌入式iPXE,并支持超过50种发行版。它完全开源,基于Apache 2.0许可,无遥测功能。

在Proxmox VE中运行microVM的简便方法

Lobsters Hottest

介绍了pve-microvm,这是一个Debian软件包,它将QEMU的microvm机器类型集成到Proxmox VE中,实现了低于300毫秒的启动时间和硬件隔离,开销极小,支持多种客户操作系统。

它死了,吉姆!(UEFI CA 过期)

Lobsters Hottest

2011年的旧微软UEFI CA已经过期,但由于Debian及其他发行版的协调努力,新的双重签名shim二进制文件正在部署,以防止启动失败。