- Shell 71.7%
- Makefile 14.9%
- Python 11%
- Dockerfile 2.4%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
build/version.sh with the shared rule and its test (make test-version). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XvgVC2H8Wa7QukpNzTJia9 |
||
| build | ||
| test | ||
| .gitignore | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| LICENSE.md | ||
| Makefile | ||
| NOTICE.md | ||
| README.md | ||
Wise Foundry Iron firmware (iron-fw)
iron-fw builds the UEFI firmware that Wise Foundry Iron uses for virtual
machines whose EFI variables live in the host variable store: libvirt's
<varstore/>, which is QEMU's uefi-vars-x64 device ("host UEFI variables").
With a host variable store, the firmware keeps no variables in flash. It is a single read-only image that QEMU maps into memory, and QEMU itself holds the variables (Secure Boot keys, boot entries) in a JSON file per machine. Secure Boot keys are enforced by QEMU on the host rather than by code in the guest, so Secure Boot works without SMM and without a writable pflash device.
Alpine Linux 3.24, Iron's base OS, packages QEMU's uefi-vars module
(qemu-hw-uefi-vars) but its ovmf package has no firmware built for it
(QEMU_PV_VARS). iron-fw builds that firmware from upstream edk2, the same way
Fedora builds its OVMF.qemuvars.fd, and generates the variable-store
templates with virt-firmware.
Building
Requirements: podman (or docker) and make. The whole build runs rootless
in a Debian builder container (build/Containerfile): gcc, nasm, acpica-tools,
python3 and git live there, not on the host.
make # print the targets and variables (the default goal)
make firmware # build out/iron-fw/ (builds the builder image first)
make test # boot it with Alpine 3.24's QEMU (see Testing)
make clean # remove out/
make distclean # also remove .cache/ (edk2 checkouts, virt-firmware, pip cache)
The build's version is YY.MM.PATCH (build/version.sh): YY.MM is this
repository's branch when it is release/YY.MM, else (dev, any other branch,
a detached HEAD, no git) the year and month it runs in (UTC); PATCH is 0
unless given (make PATCH=1); VERSION=YY.MM.PATCH overrides both. It goes into the build container as
IRON_FW_VERSION and into the firmware version string.
A first build fetches edk2 and its submodules and takes a few minutes; later
builds of the same edk2 release reuse the checkout in .cache/edk2/<ref>/.
The edk2 build log is out/work/edk2-build.log.
What the build fetches
- edk2: by default the newest stable release, resolved at build time
with
git ls-remote --tags https://github.com/tianocore/edk2as the highestedk2-stableYYYYMMtag (release candidates such asedk2-stable202608-rc1are ignored). The top-level git submodules are fetched with it (OpenSSL, Brotli, libfdt, MbedTLS, ...); the OpenSSL submodule is then moved to a pinned commit (see Embedded OpenSSL security patch). - virt-firmware: by default the newest release on PyPI, installed into a
virtual environment in
.cache/. It is a build tool only: it writes the JSON variable stores and is not part of the output. - dbx updates: Microsoft's revocation lists from
microsoft/secureboot_objects,
pinned by commit and sha256 in
build/build.sh(see Secure Boot keys).
Pinning
| Variable | Default | Example |
|---|---|---|
EDK2_REF |
newest edk2-stable* tag |
make firmware EDK2_REF=edk2-stable202608, or a full commit id |
EDK2_REPO |
https://github.com/tianocore/edk2 |
a mirror |
VIRT_FIRMWARE_VERSION |
newest on PyPI | make firmware VIRT_FIRMWARE_VERSION=26.9 |
EDK2_TARGET |
RELEASE |
DEBUG (logs to the QEMU debug port 0x402) |
LOGO |
logo/logo.bmp if it exists |
make firmware LOGO=~/art/iron.bmp |
OPENSSL_COMMIT |
f4dc4d58… (OpenSSL 3.5.8) |
a reviewed full commit id |
BUILD_JOBS |
number of CPUs | make firmware BUILD_JOBS=4 |
Every build records what it actually used in usr/share/iron-fw/VERSION, so a
build of "latest" can be reproduced by passing those values back.
To update the dbx lists, change DBX_BASE and the two *_SHA256 values in
build/build.sh to a newer commit of microsoft/secureboot_objects
(PostSignedObjects/DBX/amd64/DBXUpdate.bin and
PostSignedObjects/SignedByKEK2023/dbx_x64.efiauth2). Fedora follows the same
files with its make-dbxupdate.sh.
Embedded OpenSSL security patch
The tested edk2-stable202608 baseline references OpenSSL 3.5.7. The builder
checks out OpenSSL 3.5.8 commit f4dc4d58b48d346a8270183f89acf826d459b0ca in
that submodule before compiling, independently of the EDK2 gitlink.
OPENSSL_COMMIT accepts a reviewed full commit id override; it is passed
through the Makefile (make firmware OPENSSL_COMMIT=...). The firmware VERSION
manifest records the actual source commit and version. Host APK updates do not
update this embedded library. Firmware compilation and both variable-store boot
smoke tests must pass after changing this pin.
Outputs
out/iron-fw/ is a root-filesystem overlay: Iron's image build copies it
verbatim onto the rootfs.
| Path | Contents |
|---|---|
usr/share/iron-fw/OVMF.qemuvars.fd |
The firmware, 4 MiB: the whole FV/OVMF.fd of OvmfPkg/OvmfPkgX64.dsc, X64, RELEASE |
usr/share/iron-fw/vars.blank.json |
Empty variable store (virt-fw-vars --output-json) |
usr/share/iron-fw/vars.secboot.json |
Secure Boot enabled, Microsoft keys enrolled, current dbx |
usr/share/iron-fw/VERSION |
Build manifest: edk2 tag and commit, OpenSSL submodule commit and version, virt-firmware version, dbx sources, logo, build date, sha256 of every file |
usr/share/qemu/firmware/90-iron-ovmf-qemuvars-x64-sb-enrolled.json |
QEMU firmware descriptor: SB enabled, keys enrolled (vars.secboot.json) |
usr/share/qemu/firmware/91-iron-ovmf-qemuvars-x64-sb.json |
QEMU firmware descriptor: SB supported, not enrolled (vars.blank.json) |
Build options
The firmware is built with the options of Fedora's [build.ovmf.qemu.vars]
(edk2-build.fedora): -D options from ovmf.common
(NETWORK_HTTP_BOOT_ENABLE, NETWORK_IP6_ENABLE, NETWORK_TLS_ENABLE,
NETWORK_ISCSI_ENABLE, NETWORK_ALLOW_HTTP_CONNECTIONS, TPM2_ENABLE,
TPM2_CONFIG_ENABLE, TPM1_ENABLE=FALSE, CAVIUM_ERRATUM_27456), ovmf.4m
(FD_SIZE_4MB, DEBUG_TO_MEM) and ovmf.qemu.vars (QEMU_PV_VARS,
SECURE_BOOT_ENABLE, BUILD_SHELL=FALSE), and the PCDs of nx.strict (strict
NX memory protection) and la57 (5-level paging). PcdFirmwareVersionString
is iron-fw-<version>-<edk2 ref> and PcdFirmwareReleaseDateString the date of
the edk2 commit, as Fedora's edk2-build.py sets them.
One difference: Fedora builds this image as DEBUG (its build config has no
tgts, so edk2-build.py's default applies), which is why its descriptors carry
verbose-dynamic. iron-fw builds RELEASE by default, which prints no debug
messages, and its descriptors leave verbose-dynamic out. EDK2_TARGET=DEBUG
builds the Fedora variant, descriptors included.
Secure Boot keys
vars.secboot.json is made like Fedora's, except for the platform key:
virt-fw-vars --output-json vars.secboot.json \
--set-dbx DBXUpdate-2011.x64.bin --add-dbx DBXUpdate-2023.x64.bin \
--enroll-microsoft --secure-boot
Fedora uses --enroll-redhat, which enrolls Red Hat's certificate as PK.
iron-fw is not a Red Hat product, so it uses --enroll-microsoft, the only
enrollment in virt-firmware that uses no vendor key besides Microsoft's:
- PK: Microsoft's 2023 OEM platform key (
Windows OEM Devices PK), with the Microsoft owner GUID, as Hyper-V and other Microsoft-keyed platforms use. - KEK: Microsoft Corporation KEK CA 2011 and KEK 2K CA 2023.
- db: Microsoft Windows Production PCA 2011 and Windows UEFI CA 2023, Microsoft Corporation UEFI CA 2011 and UEFI CA 2023 (third party: shim, and so most Linux distributions), and Microsoft Option ROM UEFI CA 2023.
- dbx: Microsoft's revocation lists: the last update signed with KEK CA
2011 (
PostSignedObjects/DBX/amd64/DBXUpdate.bin) sets dbx, and the current update signed with KEK 2K CA 2023 (PostSignedObjects/SignedByKEK2023/dbx_x64.efiauth2) is appended. These are byte for byte the files Fedora ships asDBXUpdate-20260630.2011.x64.binandDBXUpdate-20260901.2023.x64.bin.
Because PK is Microsoft's, only Microsoft can sign KEK updates for these
machines; db and dbx updates signed with the Microsoft KEK apply as usual. To
enroll a different PK (for example one of your own), replace
--enroll-microsoft in build/build.sh with --enroll-cert <cert> or
--enroll-generate <name>; virt-fw-vars --help lists the options.
How Iron uses it
Iron's image build (iron/build/mkrootfs.sh) copies out/iron-fw/ onto its
root filesystem; iron/Makefile mounts ../iron-fw/out/iron-fw (or
IRON_FW_DIR) read-only for it. Any image other than a dev one
(IRON_VARIANT=dev) refuses to build without the firmware; a dev image warns
and keeps every UEFI machine on the flash store. The files land next to
Alpine's qemu-hw-uefi-vars package (QEMU's uefi-vars module,
/usr/lib/qemu/hw-uefi-vars.so). Iron's agent names the files directly in the
libvirt domains it writes (internal/vm/domain.go): a machine with the host
variable store gets
<os firmware='efi'>
<loader type='rom' format='raw'>/usr/share/iron-fw/OVMF.qemuvars.fd</loader>
<varstore template='/usr/share/iron-fw/vars.secboot.json'/> <!-- or vars.blank.json -->
</os>
and libvirt copies the template to the machine's own JSON store on first start.
The descriptors in usr/share/qemu/firmware/ let libvirt pick the firmware by
feature (host-uefi-vars, secure-boot, enrolled-keys) for domains that do
not name a loader. Iron reports the host variable store as available only when
the firmware, both templates and the QEMU module are all present.
Boot logo
OVMF shows a logo, centred on the screen, while it starts. It comes from the edk2 tree:
- The image is
MdeModulePkg/Logo/Logo.bmp(upstream: 193 x 58, 8 bits per pixel with a palette). MdeModulePkg/Logo/Logo.idfdeclares it (#image IMG_LOGO Logo.bmp), andMdeModulePkg/Logo/LogoDxe.infbuilds it, withLogo.c, into the LogoDxe driver as an HII image resource.Logo.cpublishes it through theEDKII_PLATFORM_LOGO_PROTOCOLwithEdkiiPlatformLogoDisplayAttributeCenterand offset 0,0.OvmfPkg/OvmfPkgX64.dsclistsMdeModulePkg/Logo/LogoDxe.infas a component andOvmfPkg/OvmfPkgX64.fdfplaces it in the DXE firmware volume (INF MdeModulePkg/Logo/LogoDxe.inf).- At boot, OVMF's
PlatformBootManagerAfterConsole(OvmfPkg/Library/PlatformBootManagerLib/BdsPlatform.c) callsBootLogoEnableLogo()(MdeModulePkg/Library/BootLogoLib), which draws the image in the middle of the current graphics mode: x = (screen width - logo width) / 2, y = (screen height - logo height) / 2. It is not scaled; an image larger than the screen is not drawn. The same image is published to the operating system in the ACPI BGRT table, so a Linux splash (Plymouth themes that use the firmware logo) or Windows shows it too. The screen around it is black.
To use your own logo, put it at logo/logo.bmp (or pass LOGO=path) and run
make firmware. The build validates it with build/bmpcheck.py, copies it over
MdeModulePkg/Logo/Logo.bmp in its edk2 checkout (restoring the stock file
first on every build) and records its size and sha256 in VERSION. The file
must suit edk2's BMP converter (BaseTools/Source/Python/AutoGen/GenC.py,
BmpImageDecoder):
- Windows BMP with a 40-byte
BITMAPINFOHEADER(no V4/V5 headers), bottom-up. - Uncompressed (
BI_RGB), 1, 4 or 8 bits per pixel with a palette, or 24 bits per pixel. No 32-bit, no alpha channel, no RLE. - Width and height at most 65535, and nothing after the pixel data.
- Keep it reasonably small. The DXE firmware volume that holds it is
0xE80000 bytes uncompressed (about 7 MiB free in the first iron-fw build) and is
LZMA-compressed into the main volume (
FVMAIN_SIZE, 0x348000 bytes, about 1.5 MiB free). A picture that does not fit makes the build fail; the build log (out/work/edk2-build.log) reports theDXEFVandFVMAIN_COMPACTfill levels. Something up to a few hundred pixels wide fits easily, and a small logo looks the same on any resolution. - Transparent areas are not supported; use a black background to blend with the screen.
With ImageMagick, a suitable file is
magick iron.png -background black -alpha remove -type TrueColor BMP3:logo/logo.bmp
# or, 8 bits per pixel: magick iron.png -background black -alpha remove -colors 256 -type Palette BMP3:logo/logo.bmp
python3 build/bmpcheck.py logo/logo.bmp
Testing
make test boots the firmware built by make firmware in an alpine:3.24
container with Alpine's QEMU and its uefi-vars-x64 device, under TCG and
without disks, once with each template (30 seconds each; SMOKE_SECONDS
changes that for test/smoke.sh run by hand, but make test does not pass it
into the container). It passes when the serial console shows the firmware at work and
QEMU has written boot entries back to the machine's JSON store without losing
any template variable. The logs and stores are kept in out/test/.
License
iron-fw's own files (build scripts, descriptors, documentation) are licensed
under the Apache License, Version 2.0 (LICENSE.md). The firmware it builds is
edk2 with its bundled components under their own licenses; see NOTICE.md.