x86_64_v2: OVMF ships x86-64-v3 code (noarch, not rebuilt) — no UEFI guest can boot #1

Open
opened 2026-08-10 07:28:59 +00:00 by kyamaguchi · 1 comment

AlmaLinux 10's edk2-ovmf is a noarch package, so it is not rebuilt for the
x86_64_v2 variant
— the x86_64_v2 repositories serve the same binary as the
standard x86_64 ones. That binary contains BMI1 instructions, so on a CPU that
only implements x86-64-v2 the firmware triple-faults immediately after SEC and the
guest never boots.

The x86_64_v2 variant exists to support pre-v3 CPUs, but its UEFI firmware does not
run on them, which makes UEFI virtualization unusable on such hosts.

ipxe-roms-qemu has the same problem for the same reason (also noarch); details
at the end. The two compound: fixing only edk2-ovmf still leaves every libvirt guest
hanging, because libvirt always attaches a NIC and OVMF always loads its option ROM.

The failure is silent — nothing on the guest serial console, nothing in dmesg, no
SELinux AVC. The VNC console just shows Guest has not initialized the display (yet)
while the vCPU spins at 100%.

Affected environment

  • AlmaLinux 10.2, x86_64_v2 variant (repos are ?arch=x86_64_v2)
  • edk2-ovmf-20251114-5.el10_2.2.noarch
  • qemu-kvm-18:10.1.0-16.el10_2.2.alma.1.x86_64_v2, libvirt 11.10.0
  • CPU: Intel Celeron J4125 (Goldmont Plus). No avx, avx2, bmi1, bmi2,
    abm/lzcnt, fma, f16c. ld.so --help reports x86-64-v2 (supported) as the
    highest level.

Any x86-64-v2-only CPU should reproduce (Goldmont / Goldmont Plus / Silvermont based
Atom, Celeron and Pentium Silver; early Nehalem/Westmere-era parts, etc.).

Steps to reproduce — no v2-only hardware required

TCG lets you subtract CPU features, so this reproduces on any machine:

cp /usr/share/edk2/ovmf/OVMF_VARS.fd /tmp/vars.fd
qemu-kvm -nodefaults -machine q35,smm=off -m 1024 -display none \
  -accel tcg -cpu max,avx=off,avx2=off,bmi1=off,bmi2=off,abm=off,fma=off,f16c=off \
  -drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/ovmf/OVMF_CODE.fd \
  -drive if=pflash,format=raw,file=/tmp/vars.fd \
  -debugcon file:/tmp/ovmf.log -global isa-debugcon.iobase=0x402

/tmp/ovmf.log fills with nothing but the SEC entry banner, repeated forever:

SecCoreStartupWithStack(0xFFFCC000, 0x820000)
SecCoreStartupWithStack(0xFFFCC000, 0x820000)
...            (32170 iterations in a 40 s run)

Remove bmi1=off and the same command reaches the UEFI boot manager normally.
bmi1=off alone is sufficient to reproduce; bmi2=off or abm=off alone are not.

Evidence

Firmware / accelerator matrix, same host, same disk:

Firmware Accel / CPU Result
edk2-ovmf el10_2.2 KVM, -cpu host (J4125) SEC reset loop
edk2-ovmf el10_2.2 KVM, -cpu Nehalem / host-model SEC reset loop
edk2-ovmf el10_2.2 TCG, -cpu max boots
edk2-ovmf el10_2.2 TCG, -cpu max,bmi1=off SEC reset loop
edk2-ovmf from el9 (20241117-8.el9) KVM, -cpu host (J4125) boots
SeaBIOS (seabios-bin) KVM, -cpu host (J4125) boots — KVM itself is fine

The x86_64_v2 repository ships the identical file as the standard x86_64
repository, confirming that no separate v2 build exists:

2981261a5991538ef2c9026a85aae2806fcd59712f76d45fbf7699a8208d1966  OVMF_CODE.fd  (x86_64_v2 repo)
2981261a5991538ef2c9026a85aae2806fcd59712f76d45fbf7699a8208d1966  OVMF_CODE.fd  (x86_64 repo)

Root cause

edk2.spec does export the distro flags:

export EXTRA_OPTFLAGS="%{optflags}"

but EXTRA_OPTFLAGS is only consumed by BaseTools/Source/C/Makefiles/header.makefile,
i.e. the host build tools. The firmware itself is compiled with EDK2's own flag set
(tools_def.template), which contains no -march. An actual firmware compile line
from the build log:

gcc -g -Os -fshort-wchar -fno-builtin -fno-strict-aliasing -Wall -Werror \
    -include AutoGen.h -fno-common -fstack-protector -ffunction-sections \
    -fdata-sections -m64 "-DEFIAPI=__attribute__((ms_abi))" \
    -maccumulate-outgoing-args -mno-red-zone -mcmodel=small -fpie \
    -flto -mno-mmx -mno-sse ...

So the compiler's built-in default decides the ISA baseline of the shipped binary,
and on a RHEL-10-baseline builder that default is x86-64-v3.

Note that EDK2 does disable SIMD explicitly (-mno-mmx -mno-sse), because firmware
cannot rely on FPU/SIMD state — but nothing guards against general-purpose ISA
extensions such as BMI1
, which the compiler emits freely whenever the default
-march allows it. That is exactly the gap here.

Combined with BuildArch: noarch on the firmware subpackage, the result is a package
labelled architecture-independent whose payload is x86_64 machine code built for
whatever the builder's compiler defaulted to.

A plain rebuild fixes it — no spec or source change needed

Rebuilding the unmodified SRPM on an x86_64_v2 AlmaLinux 10 host is sufficient,
because that host's GCC defaults to -march=x86-64-v2:

$ gcc -Q --help=target | grep -- '-march='
  -march=                     x86-64-v2
dnf download --source --enablerepo=appstream-source edk2-ovmf
dnf builddep -y edk2-20251114-5.el10_2.2.src.rpm
rpmbuild --rebuild edk2-20251114-5.el10_2.2.src.rpm

Result — a different binary that boots on the v2-only CPU under KVM:

stock (v3): 2981261a5991538ef2c9026a85aae2806fcd59712f76d45fbf7699a8208d1966
local (v2): 42a534960cda4f02082eb378b7def35634133e7fb241d167748f411d74cebfdd

Suggested fix

  1. Short term — rebuild edk2 on the x86_64_v2 builders and ship that binary in
    the x86_64_v2 repositories, even though the subpackage is noarch.
  2. Longer term / upstream — pin the ISA baseline explicitly in the spec (e.g.
    -march=x86-64 or -march=x86-64-v2) instead of inheriting the builder's compiler
    default. A firmware image shipped as noarch should not silently depend on which
    machine built it.
  3. Audit — any other noarch package carrying compiled x86_64 payloads is suspect.
    seabios-bin / seavgabios-bin appeared to work in our testing, but the whole class
    should be checked.

Same problem in ipxe-roms-qemu

ipxe-roms-qemu-20240119-5.gitde8a0821.el10.noarch ships efi-virtio.rom (the iPXE EFI
driver) with the same defect. ipxe.spec does not pass %{optflags} at all — it just
runs make %{?_smp_mflags} NO_WERROR=1 V=1 GITVERSION=... — so iPXE's own flags plus the
compiler default apply, and the subpackage is noarch too.

With working firmware but the stock ROM, OVMF faults while loading the option ROM of a
virtio-net device (1af4:1000):

!!!! X64 Exception Type - 06(#UD - Invalid Opcode)  CPU Apic ID - 00000000 !!!!
RIP  - 000000003EA1856E, CS  - 0000000000000038, RFLAGS - 0000000000010246
...
!!!! Find image based on IP(0x3EA1856E) 1af41000.efidrv
     (ImageBase=000000003E9F2000, EntryPoint=000000003E9F9B6E) !!!!

The vCPU then spins at a fixed RIP (CpuDeadLoop()), which is why the guest looks
"hung" rather than "crashed". This is easy to mistake for a libvirt bug: bare qemu-kvm
without a NIC boots fine, while every libvirt guest hangs.

A plain rpmbuild --rebuild of the unmodified ipxe SRPM on a v2 host fixes it too:

stock (v3): 0d846f8f53ac7911f854b4898a723c87f2d3daa408be1317d857d1764d5c3a1f  efi-virtio.rom
local (v2): e7195b6fe12fa9c6e17b3a88d023da34c2ef9b1b676d27ea80b79f284aafa821  efi-virtio.rom
romfile= Result (same OVMF build, v2-only CPU, everything else identical)
stock efi-virtio.rom #UD inside 1af41000.efidrv, then CpuDeadLoop()
locally rebuilt efi-virtio.rom [Bds] Entry...>>Start PXE over IPv4. — iPXE runs normally

I am happy to file that as a separate issue against rpms/ipxe if you prefer them
tracked separately.

Workarounds for users on v2-only hardware

  • Rebuild both packages locally as shown above, or
  • Extract OVMF_CODE.fd / OVMF_VARS.fd from the EL9 edk2-ovmf package and point the
    domain at them, and
  • Disable the NIC option ROM in the guest (<rom enabled='no'/>, i.e. romfile=""),
    which is harmless unless PXE/HTTP boot is needed.
AlmaLinux 10's `edk2-ovmf` is a `noarch` package, so it is **not rebuilt for the `x86_64_v2` variant** — the `x86_64_v2` repositories serve the same binary as the standard `x86_64` ones. That binary contains **BMI1 instructions**, so on a CPU that only implements `x86-64-v2` the firmware triple-faults immediately after SEC and the guest never boots. The `x86_64_v2` variant exists to support pre-v3 CPUs, but its UEFI firmware does not run on them, which makes UEFI virtualization unusable on such hosts. `ipxe-roms-qemu` has the **same problem for the same reason** (also `noarch`); details at the end. The two compound: fixing only `edk2-ovmf` still leaves every libvirt guest hanging, because libvirt always attaches a NIC and OVMF always loads its option ROM. The failure is silent — nothing on the guest serial console, nothing in `dmesg`, no SELinux AVC. The VNC console just shows `Guest has not initialized the display (yet)` while the vCPU spins at 100%. ### Affected environment - AlmaLinux 10.2, **`x86_64_v2` variant** (repos are `?arch=x86_64_v2`) - `edk2-ovmf-20251114-5.el10_2.2.noarch` - `qemu-kvm-18:10.1.0-16.el10_2.2.alma.1.x86_64_v2`, `libvirt 11.10.0` - CPU: Intel Celeron J4125 (Goldmont Plus). No `avx`, `avx2`, `bmi1`, `bmi2`, `abm`/`lzcnt`, `fma`, `f16c`. `ld.so --help` reports `x86-64-v2 (supported)` as the highest level. Any `x86-64-v2`-only CPU should reproduce (Goldmont / Goldmont Plus / Silvermont based Atom, Celeron and Pentium Silver; early Nehalem/Westmere-era parts, etc.). ### Steps to reproduce — no v2-only hardware required TCG lets you subtract CPU features, so this reproduces on any machine: ```sh cp /usr/share/edk2/ovmf/OVMF_VARS.fd /tmp/vars.fd qemu-kvm -nodefaults -machine q35,smm=off -m 1024 -display none \ -accel tcg -cpu max,avx=off,avx2=off,bmi1=off,bmi2=off,abm=off,fma=off,f16c=off \ -drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/ovmf/OVMF_CODE.fd \ -drive if=pflash,format=raw,file=/tmp/vars.fd \ -debugcon file:/tmp/ovmf.log -global isa-debugcon.iobase=0x402 ``` `/tmp/ovmf.log` fills with nothing but the SEC entry banner, repeated forever: ``` SecCoreStartupWithStack(0xFFFCC000, 0x820000) SecCoreStartupWithStack(0xFFFCC000, 0x820000) ... (32170 iterations in a 40 s run) ``` Remove `bmi1=off` and the same command reaches the UEFI boot manager normally. **`bmi1=off` alone is sufficient to reproduce**; `bmi2=off` or `abm=off` alone are not. ### Evidence Firmware / accelerator matrix, same host, same disk: | Firmware | Accel / CPU | Result | |---|---|---| | `edk2-ovmf` el10_2.2 | KVM, `-cpu host` (J4125) | SEC reset loop | | `edk2-ovmf` el10_2.2 | KVM, `-cpu Nehalem` / `host-model` | SEC reset loop | | `edk2-ovmf` el10_2.2 | TCG, `-cpu max` | boots | | `edk2-ovmf` el10_2.2 | TCG, `-cpu max,bmi1=off` | SEC reset loop | | `edk2-ovmf` **from el9** (`20241117-8.el9`) | KVM, `-cpu host` (J4125) | boots | | SeaBIOS (`seabios-bin`) | KVM, `-cpu host` (J4125) | boots — KVM itself is fine | The `x86_64_v2` repository ships the *identical file* as the standard `x86_64` repository, confirming that no separate v2 build exists: ``` 2981261a5991538ef2c9026a85aae2806fcd59712f76d45fbf7699a8208d1966 OVMF_CODE.fd (x86_64_v2 repo) 2981261a5991538ef2c9026a85aae2806fcd59712f76d45fbf7699a8208d1966 OVMF_CODE.fd (x86_64 repo) ``` ### Root cause `edk2.spec` does export the distro flags: ```spec export EXTRA_OPTFLAGS="%{optflags}" ``` but `EXTRA_OPTFLAGS` is only consumed by `BaseTools/Source/C/Makefiles/header.makefile`, i.e. the **host build tools**. The firmware itself is compiled with EDK2's own flag set (`tools_def.template`), which contains **no `-march`**. An actual firmware compile line from the build log: ``` gcc -g -Os -fshort-wchar -fno-builtin -fno-strict-aliasing -Wall -Werror \ -include AutoGen.h -fno-common -fstack-protector -ffunction-sections \ -fdata-sections -m64 "-DEFIAPI=__attribute__((ms_abi))" \ -maccumulate-outgoing-args -mno-red-zone -mcmodel=small -fpie \ -flto -mno-mmx -mno-sse ... ``` So **the compiler's built-in default decides the ISA baseline of the shipped binary**, and on a RHEL-10-baseline builder that default is `x86-64-v3`. Note that EDK2 *does* disable SIMD explicitly (`-mno-mmx -mno-sse`), because firmware cannot rely on FPU/SIMD state — but nothing guards against **general-purpose ISA extensions such as BMI1**, which the compiler emits freely whenever the default `-march` allows it. That is exactly the gap here. Combined with `BuildArch: noarch` on the firmware subpackage, the result is a package labelled architecture-independent whose payload is x86_64 machine code built for whatever the builder's compiler defaulted to. ### A plain rebuild fixes it — no spec or source change needed Rebuilding the **unmodified** SRPM on an `x86_64_v2` AlmaLinux 10 host is sufficient, because that host's GCC defaults to `-march=x86-64-v2`: ``` $ gcc -Q --help=target | grep -- '-march=' -march= x86-64-v2 ``` ```sh dnf download --source --enablerepo=appstream-source edk2-ovmf dnf builddep -y edk2-20251114-5.el10_2.2.src.rpm rpmbuild --rebuild edk2-20251114-5.el10_2.2.src.rpm ``` Result — a different binary that boots on the v2-only CPU under KVM: ``` stock (v3): 2981261a5991538ef2c9026a85aae2806fcd59712f76d45fbf7699a8208d1966 local (v2): 42a534960cda4f02082eb378b7def35634133e7fb241d167748f411d74cebfdd ``` ### Suggested fix 1. **Short term** — rebuild `edk2` on the `x86_64_v2` builders and ship that binary in the `x86_64_v2` repositories, even though the subpackage is `noarch`. 2. **Longer term / upstream** — pin the ISA baseline explicitly in the spec (e.g. `-march=x86-64` or `-march=x86-64-v2`) instead of inheriting the builder's compiler default. A firmware image shipped as `noarch` should not silently depend on which machine built it. 3. **Audit** — any other `noarch` package carrying compiled x86_64 payloads is suspect. `seabios-bin` / `seavgabios-bin` appeared to work in our testing, but the whole class should be checked. ### Same problem in `ipxe-roms-qemu` `ipxe-roms-qemu-20240119-5.gitde8a0821.el10.noarch` ships `efi-virtio.rom` (the iPXE EFI driver) with the same defect. `ipxe.spec` does not pass `%{optflags}` at all — it just runs `make %{?_smp_mflags} NO_WERROR=1 V=1 GITVERSION=...` — so iPXE's own flags plus the compiler default apply, and the subpackage is `noarch` too. With working firmware but the stock ROM, OVMF faults while loading the option ROM of a virtio-net device (`1af4:1000`): ``` !!!! X64 Exception Type - 06(#UD - Invalid Opcode) CPU Apic ID - 00000000 !!!! RIP - 000000003EA1856E, CS - 0000000000000038, RFLAGS - 0000000000010246 ... !!!! Find image based on IP(0x3EA1856E) 1af41000.efidrv (ImageBase=000000003E9F2000, EntryPoint=000000003E9F9B6E) !!!! ``` The vCPU then spins at a fixed `RIP` (`CpuDeadLoop()`), which is why the guest looks "hung" rather than "crashed". This is easy to mistake for a libvirt bug: bare `qemu-kvm` without a NIC boots fine, while **every** libvirt guest hangs. A plain `rpmbuild --rebuild` of the unmodified `ipxe` SRPM on a v2 host fixes it too: ``` stock (v3): 0d846f8f53ac7911f854b4898a723c87f2d3daa408be1317d857d1764d5c3a1f efi-virtio.rom local (v2): e7195b6fe12fa9c6e17b3a88d023da34c2ef9b1b676d27ea80b79f284aafa821 efi-virtio.rom ``` | `romfile=` | Result (same OVMF build, v2-only CPU, everything else identical) | |---|---| | stock `efi-virtio.rom` | `#UD` inside `1af41000.efidrv`, then `CpuDeadLoop()` | | locally rebuilt `efi-virtio.rom` | `[Bds] Entry...` → `>>Start PXE over IPv4.` — iPXE runs normally | I am happy to file that as a separate issue against `rpms/ipxe` if you prefer them tracked separately. ### Workarounds for users on v2-only hardware - Rebuild both packages locally as shown above, or - Extract `OVMF_CODE.fd` / `OVMF_VARS.fd` from the EL9 `edk2-ovmf` package and point the domain at them, and - Disable the NIC option ROM in the guest (`<rom enabled='no'/>`, i.e. `romfile=""`), which is harmless unless PXE/HTTP boot is needed.
Owner

Hi.
Could you please check if it helps you?
edk2 - https://build.almalinux.org/build/74889
ipxe - https://build.almalinux.org/build/75251

You can add it as repo to your system

[AlmaLinux-10-x86_64_v2-74889-br]
baseurl = https://build.almalinux.org/pulp/content/builds/AlmaLinux-10-x86_64_v2-74889-br/
enabled = 1
name = AlmaLinux-10-x86_64_v2-74889-br

[AlmaLinux-10-x86_64_v2-75251-br]
baseurl = https://build.almalinux.org/pulp/content/builds/AlmaLinux-10-x86_64_v2-75251-br/
enabled = 1
name = AlmaLinux-10-x86_64_v2-75251-br
Hi. Could you please check if it helps you? edk2 - [https://build.almalinux.org/build/74889](https://build.almalinux.org/build/74889) ipxe - [https://build.almalinux.org/build/75251](https://build.almalinux.org/build/75251) You can add it as repo to your system ``` [AlmaLinux-10-x86_64_v2-74889-br] baseurl = https://build.almalinux.org/pulp/content/builds/AlmaLinux-10-x86_64_v2-74889-br/ enabled = 1 name = AlmaLinux-10-x86_64_v2-74889-br [AlmaLinux-10-x86_64_v2-75251-br] baseurl = https://build.almalinux.org/pulp/content/builds/AlmaLinux-10-x86_64_v2-75251-br/ enabled = 1 name = AlmaLinux-10-x86_64_v2-75251-br ```
eabdullin self-assigned this 2026-08-12 14:29:35 +00:00
Sign in to join this conversation.
No Label
No Milestone
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: rpms/edk2#1
No description provided.