QEMU/OVMF环境下UEFI HTTP(s)启动介绍
摘要
本文提供使用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
```
当然,这仍然不能工作,但至少第一个警告消失了。
相似文章
关于UEFI机器网络启动的一些通用笔记
一位系统管理员解释了如何通过屏蔽老旧浏览器User-Agent来阻止LLM训练爬虫,并为受影响用户提供了应对建议。
Bootimus — 一个自包含的PXE和HTTP启动服务器
Bootimus是一个自包含的PXE和HTTP启动服务器,使用Go语言编写,提供零配置、嵌入式iPXE,并支持超过50种发行版。它完全开源,基于Apache 2.0许可,无遥测功能。
在Proxmox VE中运行microVM的简便方法
介绍了pve-microvm,这是一个Debian软件包,它将QEMU的microvm机器类型集成到Proxmox VE中,实现了低于300毫秒的启动时间和硬件隔离,开销极小,支持多种客户操作系统。
它死了,吉姆!(UEFI CA 过期)
2011年的旧微软UEFI CA已经过期,但由于Debian及其他发行版的协调努力,新的双重签名shim二进制文件正在部署,以防止启动失败。
安全启动和CA证书轮换——给发行版的提醒
本文提醒Linux发行版注意微软用于安全启动的UEFI CA证书即将到期,介绍了新证书以及在缺少旧证书的较新硬件上可能出现的启动问题。