Page MenuHomeFreeBSD

freebsd-update: automatically update EFI and BIOS bootloaders on install
Needs ReviewPublic

Authored by ronald.pagani.jr_dev.us.com on Aug 19 2026, 3:40 PM.
Tags
None
Referenced Files
F173029529: D58990.id186344.diff
Wed, Sep 23, 12:08 AM
Unknown Object (File)
Sat, Sep 19, 5:15 PM
Unknown Object (File)
Sat, Sep 19, 12:45 PM
Unknown Object (File)
Thu, Sep 17, 7:15 AM
Unknown Object (File)
Wed, Sep 16, 2:12 AM
Unknown Object (File)
Wed, Sep 16, 12:57 AM
Unknown Object (File)
Tue, Sep 15, 8:56 PM
Unknown Object (File)
Tue, Sep 15, 5:04 PM

Details

Summary

freebsd-update only updates files under the root filesystem. The EFI System Partition (ESP) is a separate FAT32 partition that the UEFI firmware reads directly. After a major version upgrade, the old loader.efi on the ESP may be incompatible with the new /boot/lua scripts — since 14.1 removed the lua_path==nil compatibility shim in core.lua, this results in a hard boot failure. Running zpool upgrade with a stale ESP loader makes the system completely unbootable.

This patch adds efi_bootloader_update.sh, installed to /usr/libexec/, and hooks it into the install_run phase of freebsd-update. After install_files completes, the script uses boot-scoped ESP discovery: BootCurrent NVRAM identifies the active boot disk; root filesystem disk(s) are determined via zpool status (ZFS) or mount --libxo json (UFS). ZFS vdev names using diskid/gptid paths are resolved to real device names via gpart rawuuid cross-reference. ESP and partition discovery uses gpart show --libxo json throughout with a text-mode fallback for releases where --libxo is unavailable. The union of both sets is used as candidates, ensuring mirror members are all updated while unrelated ESPs (other OSes, other FreeBSD installations) are never touched. A split-media guard restricts candidates to the BootCurrent disk when it does not overlap with the root filesystem disks.

For each candidate ESP the script:

  • Mounts it (or reuses an existing mount)
  • Updates /EFI/FreeBSD/loader.efi, creating the directory if absent
  • Updates /EFI/BOOT/BOOTx64.efi (or arch equivalent) only if it fingerprints as a FreeBSD loader — multi-OS safe. EFI binaries carrying a Secure Boot signature are not overwritten (uefisign -V check, fail-safe, must return 'unsigned' to proceed with update).
  • On amd64 14.3+/15.x: copies /boot/loader_ia32.efi to EFI/BOOT/BOOTia32.efi when present, fingerprint-gated. Fresh install (no existing BOOTia32.efi) requires EFI_INSTALL_IA32=1; update of an existing file is unconditional.
  • Creates an NVRAM boot entry if none exists, with guards:
    • Skips if the fallback binary already fingerprints as FreeBSD
    • Skips if efibootmgr returns no parseable BootOrder
    • Preserves existing BootOrder entry position Non-fatal if efibootmgr or EFI Runtime Services are unavailable.
  • Unmounts the ESP

For BIOS freebsd-boot partitions (scoped to root filesystem disks):

  • Writes gptzfsboot or gptboot as appropriate

MBR partition schemes are supported. kern.cam.nda.nvd_compat=1 device aliases (nvd/nda) are resolved via realpath before mount table comparison. Graceful fallback on platforms without EFI Runtime Services. Supports FreeBSD 13.x, 14.x, and 15-CURRENT.

Opt-out: set UpdateBootloader no in /etc/freebsd-update.conf to disable all
bootloader updates. Set UpdateBootloaderNVRAM no to skip NVRAM writes while
still updating ESP files (for fleet environments where NVRAM entries are
managed externally via BMC, Ansible, iDRAC, etc.).
Env override: EFI_NVRAM_UPDATE=0 skips NVRAM for a single invocation.

PR: 279829
See also: D45890 (Warner Losh's complementary loader version-check patch)

Test Plan

sh tests/run_tests.sh
335/335 passing on FreeBSD 14.0-RELEASE-p11 and 15.1-RELEASE-p3
(amd64, EFI+BIOS, ZFS).

Revision-7 additions: efi_is_signed output parsing (4 unit tests:
error output triggers fail-safe, no write, dst unchanged, diagnostic
warning emitted); BootOrder preservation hardening (3 unit tests:
_new_num detection failure warns, both verify attempts fail warns,
retry succeeds no warning).

Revision-6 additions: uefisign fail-safe (3 unit tests: absent returns
0/skip, _efi_copy_wrote=0, dst unchanged).

Revision-5 additions: Secure Boot guard (4 unit tests), EFI_INSTALL_IA32
gate (1 integration test), UpdateBootloaderNVRAM/EFI_NVRAM_UPDATE=0
(2 unit tests).

Live run on FreeBSD 14.0-RELEASE-p11 amd64: two reboots clean,
Boot0004 NVRAM entry active (from revision-1; system carries forward
across all revisions).

Dry-run validated on FreeBSD 14.0-RELEASE-p11 and 15.1-RELEASE-p3:
guard paths, gpart --libxo fallback (14.0), gpart --libxo json (15.1),
loader_ia32.efi absent path (14.0), Guard 1 firing (BOOTx64.efi
fingerprinted as FreeBSD). 15.1 dry-run: Windows Boot Manager present
in BootOrder on separate disk (Boot0000 not on root disk); Guard 1
correctly skips NVRAM creation. uefisign not called in dry-run path
(efi_safe_copy exits early before signed-destination check when
EFI_DRY_RUN=1).

ia32 path validated via dry-run on Intel Pocket PC (Atom Z3736F,
Bay Trail, 32-bit UEFI, 14.3-RELEASE, eMMC): Windows-owned
BOOTia32.efi correctly preserved; NVRAM skipped (/dev/efi absent).

Full hardware coverage matrix (20+ environments, amd64 + aarch64):
https://github.com/RZA-SF/freebsd-patch-for-bug279829/blob/main/docs/hardware-reports.md

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Skipped
Unit
Tests Skipped

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
usr.sbin/freebsd-update/efi_bootloader_update.sh
248

I should have also shown:

# zpool status
  pool: zoptb
 state: ONLINE
  scan: scrub repaired 0B in 00:00:54 with 0 errors on Mon May  5 23:40:50 2025
config:

	NAME           STATE     READ WRITE CKSUM
	zoptb          ONLINE       0     0     0
	  gpt/OptBzfs  ONLINE       0     0     0

errors: No known data errors
# ls -lodT /dev/gpt/OptB*
crw-r-----  1 root operator - 0xe8 Aug 13 12:04:56 2026 /dev/gpt/OptBefi
crw-r-----  1 root operator - 0xe9 Aug 13 12:04:57 2026 /dev/gpt/OptBswp364
crw-r-----  1 root operator - 0xea Aug 13 12:04:56 2026 /dev/gpt/OptBzfs
# gpart show -pl /dev/nda2
=>        40  2930277095    nda2  GPT  (1.4T)
          40      532480  nda2p1  OptBefi  (260M)
      532520        2008          - free -  (1004K)
      534528  1073741824  nda2p2  OptBswp364  (512G)
  1074276352  1845493760  nda2p3  OptBzfs  (880G)
  2919770112     8388608  nda2p4  HyperVswp4  (4.0G)
  2928158720     2118415          - free -  (1.0G)
261

My context has:

# awk '$2 == "/" && $1 !~ /^#/ { print $1; exit }' /etc/fstab 2>/dev/null
/dev/gpt/PBaseUFS

So gpt/PBaseUFS after /dev/ removal and later would get:

# gpart show gpt/PBaseUFS
gpart: No such geom: gpt/PBaseUFS.
usr.sbin/freebsd-update/efi_bootloader_update.sh
288

These may not be intended for coverage, but I'll note the MBR case of using "gpart add -t '!12'" (fat32 lba) for the EFI partitions:

/usr/src/release/tools/arm.subr has:

arm_create_disk() {
. . .
        if [ "${PART_SCHEME}" = "GPT" ]; then
                chroot ${CHROOTDIR} gpart add -t efi -l efi -a 512k -s ${FAT_SIZE} ${mddev}
                chroot ${CHROOTDIR} gpart add -t freebsd-ufs -l rootfs -a 64k ${mddev}
        fi
        if [ "${PART_SCHEME}" = "MBR" ]; then
                chroot ${CHROOTDIR} gpart add -t '!12' -a 512k -s ${FAT_SIZE} ${mddev}
                chroot ${CHROOTDIR} gpart set -a active -i 1 ${mddev}
                chroot ${CHROOTDIR} gpart add -t freebsd ${mddev}
                chroot ${CHROOTDIR} gpart create -s bsd ${mddev}${BSDLABEL_SUFFIX}
                chroot ${CHROOTDIR} gpart add -t freebsd-ufs -a 64k -b 64k ${mddev}${BSDLABEL_SUFFIX}
        fi

        # Create the EFI and UFS filesystems
        chroot ${CHROOTDIR} newfs_msdos -L efi -F ${FAT_TYPE} /dev/${mddev}${EFIPART_SUFFIX}
. . .

I'll note: /usr/src/release/arm64/RPI.conf:PART_SCHEME="MBR"
is still in use. For releases, it is the file system that always has a -L efi in this armv7 and embedded aarch64 area. (Manually set up MBR contexts may have a wider range of varation?)

Note: my examples show things that are possible for installations based on examples I have (or had). They may be all "no plan/reason to support for freebsd-update for EFI updating". If true, I'm fine with that. At least such likely would end up being explicitly indicated now.

So I generally like this. But I think that there's a number of simplifying assumptions that can be made.
My comments about 'unreliable' are based on running a large fleet for $WORK and being the boot loader person.
As well as fighting bugs over the years.
I've tried to offer better alternatives, though.
Plus two or three tries at writing this.

usr.sbin/freebsd-update/efi_bootloader_update.sh
30

Please consider omitting the boilerplate.

102

So this likely doesn't work.
We have a bootia32.efi for booting 64-bit kernels, and you'd need to carefully sniff out this case.

138

No.

159

I'm not sure this exists on !x86 platforms...

172

This is unreliable. mount --libxo json is easier to parse.
Any spaces will cause problems.

239

This breaks with zpools that were created using diskids.

288

This assumes that we're booting off the same disk that the zpool is on. That's often not the case.

329

This works only for GPT.
And it fails if there's any other partitions that are mounted using the diskid.

410

newvers.sh generates
bootprog_info="FreeBSD/${3} ${2}, Revision ${r}\\n"
where ${3} is the arch and ${2} is EFI, etc

502
# efibootmgr --esp
/dev/diskid/DISK-5430A0ALTMAJp1

gives you this directly.

I'm not sure why you are updating the bootmgr entry. It tends to be somewhat unreliable.
I added --esp to find the right name you'd need to update....

usr.sbin/freebsd-update/efi_bootloader_update.sh
502

Windows Dev Kit 2023 (aarch64) {my currently handy aarch64 context]:

# efibootmgr --esp
efibootmgr: Can't get BootCurrent: Function not implemented

Thank you both for the thorough review!

I've gone through all the comments carefully. Here is my understanding of the issues and proposed resolutions — please correct me where I've misread the intent.

Issue 7 (line 287) — ESP disk discovery architecture:
This is the core concern. The current approach derives boot disks from ZFS pool membership, which fails when the ESP lives on a dedicated disk separate from the pool. My proposed fix is to replace this with a scan of all disks via sysctl kern.disks, using gpart show to find EFI-typed partitions on each, and fingerprinting to confirm FreeBSD ownership before updating. This is the approach used by the loaders-update port. It also resolves the diskid issue (line 238) and the GPT-only device construction issue (line 328) since disk names come from the kernel's disk list rather than being derived from pool topology.

Is this what you had in mind, or were you thinking of a different mechanism? (e.g. reading the active EFI boot variable).

Line 171 — mount parsing:
Will replace both mount | awk call sites with mount --libxo json.

Line 137 — chroot:
To clarify: the existing jail check (security.jail.jailed) — should stay, correct?

Line 101 — bootia32.efi:
I didn't realize this but now I understand FreeBSD ships bootia32.efi for booting 64-bit kernels on hardware with 32-bit EFI. Is there a reliable way to detect this case at runtime, or would you prefer the script simply update BOOTia32.efi if it already exists on the ESP and fingerprints as a FreeBSD loader — consistent with the existing fallback logic?

Line 409 — bootprog_info fingerprint:
Thanks for the format string. Will update the fingerprint check to match FreeBSD/<arch> EFI, Revision pattern from bootprog_info. Will verify against a real binary first.

Line 501 — efibootmgr reliability:
The NVRAM step is already non-fatal with a manual fallback instruction. Will add --esp <device> once the device path is known from the scan-all-disks approach. Note that markmi reports efibootmgr --esp returns "Function not implemented" on his aarch64 hardware (Windows Dev Kit 2023) — so the non-fatal wrapper and manual fallback remain important.

markmi — GPT label paths in fstab (lines 247, 260):
Confirmed real bug. With the scan-all-disks approach, EFI partition discovery moves to sysctl kern.disks directly, so efi_ufs_boot_disks is no longer in the EFI path. However, it remains in the BIOS bootcode path — we need to know which disks are in the root filesystem before writing bootcode, since writing to an unrelated disk would be wrong. The GPT label fix (realpath resolution before suffix stripping) applies there.

markmi — MBR !12 (line 287):
Planning to add MBR support alongside the scan-all-disks fix. The efi_discover_all_esps function will detect !12 (FAT32 LBA) and !ef (EFI System) typed partitions on MBR disks in addition to efi on GPT. The fingerprint check is the safety gate — if the mounted FAT32 doesn't contain a FreeBSD loader, it is skipped silently, which handles the U-Boot/RPi case without EDK2 correctly.

usr.sbin/freebsd-update/efi_bootloader_update.sh
30

Sure thing.

239

Yes, actually this seemed like something the would change things quite a bit -- the main architectural issue. The current approach derives boot disks from ZFS pool membership, which fails when the ESP is on a dedicated boot device separate from the pool.

I revisited this and my proposed fix is to scan all disks via sysctl kern.disks, find EFI-typed partitions via gpart show, fingerprint to confirm FreeBSD ownership, and update only those. This is what the loaders-update port does and it's idempotent. It also resolves the diskid issue simultaneously since we're no longer deriving device names from pool topology.

What happens on systems with a bunch of disks... on a system with many spindles, this calls gpart show on every disk. gpart show is fast, but is that overhead acceptable in your view, or would you prefer a narrower heuristic?

Something on a related github page: "MBR (non-GPT) disks Not supported (BIOS-only, legacy)"

Turns out that the "BIOS-only" is false: official 64-bit RPi* releases are U-Boot UEFI based and FreeBSD release builds use MBR for that 64-bit aarch64 support. Various RPi* are MBR only (without the bootcode.bin file on a microsd card, anyway) and the RPi4B related EEPROM ones depend on how recent the EEPROM content is. (Different types of "legacy" than was likely intended.) armv7 is also this way as I remember.

[Note: I can and do use GPT USB media but accept the tradeoffs, such as needing a microsd card to be involved or not using old EEPROM content.]

Not supporting MBR at all may well stay true but, if so, more context may need to be mentioned/reported in that case.

usr.sbin/freebsd-update/efi_bootloader_update.sh
502

You need efirt in the kernel.
We should use this and maybe just fail if it's not avsilable. You can't reliably know otherwise. The complexity of guessing isn't worth it, imho, if /boot/efi isn't mounted or in /etc/fstab. Something that's 95% with something as critical as boot blocks is too unreliable

usr.sbin/freebsd-update/efi_bootloader_update.sh
502

For reference for the aarch64 boot media (installation is an official pkgbase distribution of main):

# strings /boot/kernel/kernel | grep -i efirt | less
nexus/efirtc
/home/pkgbuild/worktrees/main/sys/dev/efidev/efirt.c
(td->td_pflags & TDP_EFIRT) == 0
Total number of faults that occurred during EFIRT calls
(td->td_pflags & TDP_EFIRT) != 0
/home/pkgbuild/worktrees/main/sys/arm64/arm64/efirt_machdep.c
Print fault  information upon trap from EFIRT calls: 0 - never, 1 - once, 2 - always
efirtc
efirt
efirtc_nexus
options EFIRT
device  efirtc
print_efirt_faults
cnt_efirt_faults
usr.sbin/freebsd-update/efi_bootloader_update.sh
502

So far the small set of aarch64 systems that I have access to have had no examples of the --esp usage work, each producing one of the following messages (booting the same FreeBSD OS media moved across systems):

efibootmgr: Can't get BootCurrent: Function not implemented
efibootmgr: Can't get BootCurrent: No such file or directory
efibootmgr: Can't convert to unix path

The systems trieid are: the Honeycomb LX2160A UEFI/ACPI (Solidrun EDK2 based), the Windows Dev Kit 2023 UEFI/ACPI, as well as a RPI5 (via a draft/partial EDK2 UEFI/ACPI) and a RPi4B (U-Boot UEFI FreeBSD port, likely a rather old instance). The U-Boot UEFI context is what referenced "no such" and the WinDevKit23 is what referenced "not implemented". I've no evidence if what I'm seeing generalizes fairly commonly to aarch64.

Something on a related github page: "MBR (non-GPT) disks Not supported (BIOS-only, legacy)"

Turns out that the "BIOS-only" is false: official 64-bit RPi* releases are U-Boot UEFI based and FreeBSD release builds use MBR for that 64-bit aarch64 support. Various RPi* are MBR only (without the bootcode.bin file on a microsd card, anyway) and the RPi4B related EEPROM ones depend on how recent the EEPROM content is. (Different types of "legacy" than was likely intended.) armv7 is also this way as I remember.

[Note: I can and do use GPT USB media but accept the tradeoffs, such as needing a microsd card to be involved or not using old EEPROM content.]

Not supporting MBR at all may well stay true but, if so, more context may need to be mentioned/reported in that case.

There are changes in my revision-2 branch, which also include your feedback for the aarch64 environments, and ongoing updates for the test suite. The above was cited from the initial commit README.

If there are some cases where it makes more sense to not support, I agree that might be something to discuss further. My motivation was to make this as comprehensive and inclusive as possible.

ronald.pagani.jr_dev.us.com edited the summary of this revision. (Show Details)
ronald.pagani.jr_dev.us.com edited the test plan for this revision. (Show Details)

This is a complete revision-2 resubmission. The previous diff was the initial
submission; everything below is new.

Boot-scoped ESP discovery (replaces scan-all-disks)

  • BootCurrent NVRAM path (primary): efibootmgr -v parses the active boot entry PARTUUID — inline HD(GPT,...) or dp: sub-line format — and matches it against gpart list to identify the exact disk and partition the firmware booted from.
  • Root filesystem disk(s) (fallback / union): zpool status for ZFS (mirrors, RAIDz, diskid/gptid/GEOM-label vdev names, device-node labels via glabel status fallback); mount --libxo json for UFS.
  • Union + dedup + overlap check: only ESPs on disks plausibly related to the running system are updated.
  • Split-media guard (R-14): when BootCurrent identifies a boot disk with no overlap with root filesystem disks, candidates are restricted to the boot disk only — prevents updating a portable or shared ESP belonging to other systems.
  • EFIRT guard: when /dev/efi is absent (i386, armv7, riscv64, custom kernels), falls back to root-disk discovery; NVRAM step skipped gracefully.

Review items resolved

  • #1 (imp): removed verbose BSD-2-Clause boilerplate; SPDX + copyright only
  • #4 (imp): machdep.bootmethod OID absent on aarch64/armv7 — assume UEFI
  • #5 (imp): mount --libxo json replaces mount | awk throughout
  • #6 (imp): diskid/gptid pool vdev names resolved via realpath
  • #7 (imp): ESP not on zpool — covered by BootCurrent PARTUUID path
  • #8 (imp): GPT-only mount assumptions removed
  • #9 (imp): bootprog_info fingerprint: primary strings | grep 'FreeBSD/[^ ]+ EFI,'; 2-of-3 heuristic fallback
  • M1 (markmi): armv7 bootarm.efi architecture mapping added
  • M3 (markmi): UFS GPT label paths (gpt/PBaseUFS) resolved via realpath
  • M4 (markmi): GPT labels in gpart show output do not break field parsing

MBR support

  • Detects fat32lba, fat32, efi partition types; also !12/!ef fallback
  • MBR device paths use s-suffix (/dev/da0s1) vs. GPT p-suffix
  • Covers ARM SBCs (RPi, WDK23) running UEFI via U-Boot on MBR-partitioned storage
  • Safety gate: MBR FAT32 partition only written if efi_is_freebsd_loader passes

Bug fixes from hardware validation (R-06 through R-14)

  • R-06: efibootmgr -v PARTUUID on dp: sub-line, not inline with HD(GPT,...)
  • R-07: ZFS gpt/LABEL vdev awk filter excluded label-named vdevs; fixed with in_config flag and $1 != pool guard
  • R-08: GEOM label /dev/gpt/X as device node — realpath unchanged; fixed with glabel status fallback in ZFS and UFS root disk paths
  • R-09: Three MBR bugs: ufs/* label not matched; sed stripped trailing s from label names; fat32lba type not recognized
  • R-10: Dry-run space check measured root filesystem free space instead of ESP; blank-ESP notice appeared unconditionally even when ESP was pre-mounted
  • R-11: efi_esp_mountpoint did not detect ESP mounted via msdosfs GEOM label (/dev/msdosfs/EFI) — "Device busy" on RPi3B/RPi4B MBR installs
  • R-12: _efi_esp_is_real flag: dry-run space/blank-ESP guards fired even when operating on a real pre-mounted ESP, producing contradictory output
  • R-13: MBR summary line used pN suffix instead of sN; WARN showed template path instead of actual path
  • R-14: Split-media guard: BootCurrent disk not in root disk set → restrict candidates to boot disk only

Loader backward compatibility

Source inspection of stand/common/bootinfo.c, stand/lua/core.lua, and
sys/sys/linker.h confirms: newer loaders (15.x, 16-CURRENT) can boot older
kernels (13.x, 14.x). core.loaderTooOld() tests whether the loader is too
old for current Lua scripts — not whether the kernel is too old for the loader.
No kernel-side version gate exists. Documented in design.md §6.7.

Open items

  • #2 (imp): bootia32.efi — source /boot/loader_ia32.efi absent on 13.x. Documented with resolution path in design.md §6.8; deferred.

Test suite

286 assertions across 43 files (unit, integration, error conditions).
Verified on FreeBSD 14.0-RELEASE-p11 and 15.1-RELEASE-p2.
Eight rounds of hardware validation across six platforms (amd64, Honeycomb
LX2160A, WDK23, RPi4B, RPi5, RPi3B) contributed by markmi.
Full reports: https://github.com/RZA-SF/freebsd-patch-for-bug279829/blob/main/docs/hardware-reports.md

usr.sbin/freebsd-update/efi_bootloader_update.sh
262

The script no longer contains the usage of the /dev/ removed string for this as far as I know.

Likely the above can be declared done.

(I do not seem to be able to delete my inline comments --or edit them-- when they become irrelevant.)

I'll note that efi_bootloader_update.sh could be considered to be of direct use for folks using pkgbase: pkgbase does not deal with updating the FreeBSD loaders in the ESP.

(Nor does pkgbase automatically do a reboot between a kernel upgrade and the matching world upgrade --or prompt then stop for such. freebsd-update can prompt and stop for such.)

Side note, Just for reference:
Nothing deals with updating the aarch64 or armv7 dtb/ materials in the ESP that use FreeBSD-builds of Linux based *.dtb files. Not everything has/uses such FreeBSD built files. RPi* use files from a FreeBSD port instead, as well.

ronald.pagani.jr_dev.us.com added inline comments.
usr.sbin/freebsd-update/efi_bootloader_update.sh
102

I am procuring an old Atom-based box today to work out the ia32 boot loader w/ 64-bit kernel situation. I am cautiously optimistic and looking forward to sharing this.

ronald.pagani.jr_dev.us.com added inline comments.
usr.sbin/freebsd-update/efi_bootloader_update.sh
159

Addressing comments from the original submission (prior to revision-2) -- Good catch regarding machdep.bootmethod and its absence is now handled explicitly. Should the sysctl call fail or return empty, the function assumes UEFI and logs it at verbose level. The rationale (also commented in the code) is that for arm64, armv7, and riscv64, BIOS boot doesn't exist as a concept, so absence of the OID unambiguously implies UEFI. Also, now with inclusion of mount --libxo json for reliable structured output throughout, things are cleaned up and no references to procfs.

502

@markmi_dsl-only.net did we cover these with the revision-2 changes?

Referencing notes from the testing results here:

https://github.com/RZA-SF/freebsd-patch-for-bug279829/blob/main/docs/hardware-reports.md#marklmi

usr.sbin/freebsd-update/efi_bootloader_update.sh
502

@markmi_dsl-only.net did we cover these with the revision-2 changes?
. . .

I had sent the outputs from:

# ~/efi_bootloader_update.sh --dry-run --verbose

for each of my test contexts but that was in the "[commit c02bb46, not dry-runs]" Emails.

usr.sbin/freebsd-update/efi_bootloader_update.sh
502

Ok, I'll go and validate whether these are resolved with revision-2 (which is what the current patch diff represents). I probably should update with a new diff, since I've tested AWS Graviton instances using revision-3.

ronald.pagani.jr_dev.us.com added inline comments.
usr.sbin/freebsd-update/efi_bootloader_update.sh
102

Unfortunately, the Cherry Trail hardware (MINIX Neo Z83-4) wasn't sufficient (updated firmware, so 64-bit UEFI). A Bay Trail based Intel compute stick is on the way (with Atom Z3735F, 64-bit) and it has been confirmed to have 32-bit UEFI. The ia32 case remains documented as a known limitation (design.md §6.8) and will be addressed in a follow-on revision.

172

Resolved with revision-2 >> D58990?id=184773

239

Resolved with revision-2 >> D58990?id=184773

248

Resolved with revision-2 >> D58990?id=184773

261

Resolved with revision-2 >> D58990?id=184773

288

Resolved with revision-2 >> D58990?id=184773

410

Resolved with revision-2 >> D58990?id=184773

502

Confirmed resolved with revision-2 >> D58990?id=184773. efibootmgr calls are non-fatal; the script falls back to root-disk scanning when BootCurrent is unavailable. All four of @markmi_dsl-only.net's aarch64 systems (Honeycomb LX2160A, WDK2023, RPi4B, RPi5) validated successfully, and documented in hardware-reports.md.

ronald.pagani.jr_dev.us.com edited the summary of this revision. (Show Details)
ronald.pagani.jr_dev.us.com edited the test plan for this revision. (Show Details)
ronald.pagani.jr_dev.us.com added a subscriber: se.

This is revision-3. The previous diff was revision-2 (D58990?id=184773).

Reliability improvements (R-15 through R-17)

  • R-15: diskid/gptid vdev resolution via rawuuid cross-reference. Revision-2 resolved diskid/gptid vdev names via realpath(), which requires the /dev symlink to exist at runtime. R-15 adds a rawuuid fallback: gpart list is queried for partition rawuuids and matched against zpool status vdev UUIDs to identify the real device name when the symlink is absent.
  • R-16: gpart show --libxo json replaces tabular text parsing at all four call sites. Eliminates field-position fragility when GPT partition labels are present.
  • R-17: FreeBSD 13.x compatibility. gpart show --libxo json key names differ between 13.x and 14.x+; R-17 detects the format and applies the correct keys.

Open items

  • #2 (imp): bootia32.efi — investigation to continue with Bay Trail hardware featuring 32-bit UEFI firmware. Deferred to a future revision; documented in design.md §6.8.

Test suite

291 assertions across 44 files (unit, integration, error conditions).
Verified on FreeBSD 13.5-RELEASE, 14.0-RELEASE-p11, and
15.1-RELEASE-p3.
EC2 AWS validation: 21 environments (amd64, FreeBSD 13.5–15.1,
nitro/xen, NVMe/paravirtual). All passed.
Full reports: https://github.com/RZA-SF/freebsd-patch-for-bug279829/blob/main/docs/hardware-reports.md

ronald.pagani.jr_dev.us.com edited the summary of this revision. (Show Details)
ronald.pagani.jr_dev.us.com edited the test plan for this revision. (Show Details)

This is revision-4. The previous diff was revision-3 (D58990?id=185468).

ia32 EFI support, NVRAM write safety, and reliability fixes

  • ia32 EFI (#2, imp): /boot/loader_ia32.efi is present on FreeBSD 14.3+ amd64. The script now copies it to EFI/BOOT/BOOTia32.efi using the same fingerprint-gated safe-copy logic as the 64-bit fallback. Silently skipped on 13.x and 14.0-14.2 where the source is absent. NVRAM management is skipped on 32-bit UEFI firmware (efibootmgr unavailable when /dev/efi is absent); one-time manual setup documented in contrib/howto-ia32-uefi-nvram-windows.md. Validated on an Intel Pocket PC (Atom Z3736F, Bay Trail, 32-bit UEFI, 14.3-RELEASE, eMMC) via dry-run.
  • Fingerprint pattern fix: efi_is_freebsd_loader tightened from 'FreeBSD/[^ ]+ EFI' to 'FreeBSD/[^ ]+ EFI[ ,]'. FreeBSD 14.0 changed bootprog_info from 'FreeBSD/amd64 EFI, Revision 1.1' to 'FreeBSD/amd64 EFI loader, Revision 1.1'. The new pattern covers both formats and the ia32 variant.
  • NVRAM BootOrder position preservation: when a FreeBSD fallback entry already exists in BootOrder, its ordinal position is recorded and the new dedicated entry is inserted at the same position rather than prepending to slot 0. Preserves user-configured boot priority on multi-OS systems.
  • Option 5 NVRAM guards (informed by Emrion, GitHub issue #3): Guard 1: skip NVRAM entry creation when BOOTx64.efi/BOOTia32.efi already fingerprints as a FreeBSD loader. Guard 2: skip creation when efibootmgr returns no parseable BootOrder (avoids writes to firmware with broken SetVariable implementations). [1]
  • nvd/nda symlink fix (Emrion, GitHub issue #4): efi_esp_mountpoint adds a realpath third fallback to resolve kern.cam.nda.nvd_compat=1 device aliases (nvd0p1 => nda0p1) before comparing against the mount table. Previously, direct and glabel matches both failed on such systems.
  • gpart --libxo fallback scope corrected: verbose message and comment previously claimed "(FreeBSD 13.x)"; dry-run traces on 14.0-RELEASE-p11 and 14.3-RELEASE confirm the fallback fires there too. Version claim removed.

Open items

  • #3 (Emrion / imp): NVRAM write policy: Whether freebsd-update should write NVRAM entries at all versus a separate remediation tool. Guard 1 + Guard 2 implement a conservative default; design.md §4.5 documents the trade-offs. Seeking imp's input.

Test suite

315 assertions across 47 files (unit, integration, error conditions).
Verified on FreeBSD 14.0-RELEASE-p11 and 15.1-RELEASE-p3 (amd64,
EFI+BIOS, ZFS).
Full reports: https://github.com/RZA-SF/freebsd-patch-for-bug279829/blob/main/docs/hardware-reports.md

[1] https://github.com/RZA-SF/freebsd-patch-for-bug279829/issues/3

usr.sbin/freebsd-update/efi_bootloader_update.sh
102

Implemented in revision-4 (D58990?id=186193).

efi_bootloader_update.sh now copies /boot/loader_ia32.efi to EFI/BOOT/BOOTia32.efi on amd64 systems running 14.3-RELEASE or later (where the ia32 loader is shipped). The copy is fingerprint-gated -- it only runs if BOOTia32.efi is absent or already carries a FreeBSD fingerprint; a foreign BOOTia32.efi (e.g. Windows) is left untouched.

Feature scope: amd64 only; 14.3-RELEASE / 15.x only (loader absent on 13.x and 14.0). Validated via dry-run on an Intel Pocket PC (Z3736F, Bay Trail, 32-bit UEFI): correct detection and expected copy behaviour confirmed.

quentin.thebault_defenso.fr added inline comments.
usr.sbin/freebsd-update/efi_bootloader_update.sh
962

Replacing a signed bootloader with an unsigned one on a Secure Boot-enabled system will prevent it from booting. We have no way to know how the system administrator manages their cryptographic keys so I believe the most sensible course of action is not to touch any signed loader.

uefisign -V ${dst} can be used to check for an existing signature.

usr.sbin/freebsd-update/freebsd-update.8
221

If implementing the uefisign check, please make it explicit here in the documentation too.

This revision now requires changes to proceed.Wed, Sep 9, 8:00 AM
ronald.pagani.jr_dev.us.com edited the summary of this revision. (Show Details)
ronald.pagani.jr_dev.us.com edited the test plan for this revision. (Show Details)

This is revision-5. This includes important updates to revision-4.
The previous diff was revision-4 (D58990?id=186193).

What changed in revision-4 (ia32 EFI support, NVRAM write safety,
and reliability fixes)

  • ia32 EFI (#2, imp): /boot/loader_ia32.efi is present on FreeBSD 14.3+ amd64. The script now copies it to EFI/BOOT/BOOTia32.efi using the same fingerprint-gated safe-copy logic as the 64-bit fallback. Silently skipped on 13.x and 14.0-14.2 where the source is absent. NVRAM management is skipped on 32-bit UEFI firmware (efibootmgr unavailable when /dev/efi is absent); one-time manual setup documented in contrib/howto-ia32-uefi-nvram-windows.md. Validated on an Intel Pocket PC (Atom Z3736F, Bay Trail, 32-bit UEFI, 14.3-RELEASE, eMMC) via dry-run.
  • Fingerprint pattern fix: efi_is_freebsd_loader tightened from 'FreeBSD/[^ ]+ EFI' to 'FreeBSD/[^ ]+ EFI[ ,]'. FreeBSD 14.0 changed bootprog_info from 'FreeBSD/amd64 EFI, Revision 1.1' to 'FreeBSD/amd64 EFI loader, Revision 1.1'. The new pattern covers both formats and the ia32 variant.
  • NVRAM BootOrder position preservation: when a FreeBSD fallback entry already exists in BootOrder, its ordinal position is recorded and the new dedicated entry is inserted at the same position rather than prepending to slot 0. Preserves user-configured boot priority on multi-OS systems.
  • Option 5 NVRAM guards (informed by Emrion, GitHub issue #3): Guard 1: skip NVRAM entry creation when BOOTx64.efi/BOOTia32.efi already fingerprints as a FreeBSD loader. Guard 2: skip creation when efibootmgr returns no parseable BootOrder (avoids writes to firmware with broken SetVariable implementations). [1]
  • nvd/nda symlink fix (Emrion, GitHub issue #4): efi_esp_mountpoint adds a realpath third fallback to resolve kern.cam.nda.nvd_compat=1 device aliases (nvd0p1 => nda0p1) before comparing against the mount table. Previously, direct and glabel matches both failed on such systems.
  • gpart --libxo fallback scope corrected: verbose message and comment previously claimed "(FreeBSD 13.x)"; dry-run traces on 14.0-RELEASE-p11 and 14.3-RELEASE confirm the fallback fires there too. Version claim removed.

What was added in as revision-5

Secure Boot guard, EFI_INSTALL_IA32 gate, and freebsd-update.conf
NVRAM opt-out

  • Secure Boot signature guard (quentin.thebault, revision-3 inline comment): efi_safe_copy now calls efi_is_signed (uefisign -V) before overwriting an existing EFI binary. If the destination carries a Secure Boot signature the copy is skipped and a warning is emitted; the signed binary is left unchanged and the update continues to the next file (fail-open). Four new unit tests cover the signed/unsigned matrix.
  • EFI_INSTALL_IA32 opt-in gate: fresh BOOTia32.efi installation (no existing file) now requires EFI_INSTALL_IA32=1. Updating an existing BOOTia32.efi proceeds unconditionally. Prevents unexpected file creation on amd64 systems where bsdinstall has not placed the file; 32-bit UEFI hardware (BOOTia32.efi already present) is unaffected.
  • UpdateBootloaderNVRAM in freebsd-update.conf (quentin.thebault, revision-3 man page comment): new persistent configuration option lets fleet operators set UpdateBootloaderNVRAM no to skip NVRAM entry management while still updating ESP files. Appropriate when NVRAM entries are managed externally (BMC, Ansible, iDRAC, etc.) or when firmware SetVariable is unreliable. Setting UpdateBootloader no disables all bootloader updates including NVRAM (unchanged from revision-4). For one-off runs EFI_NVRAM_UPDATE=0 still works as before. freebsd-update.8 updated to document both options.

Open items

  • #3 (Emrion / imp): NVRAM write policy: whether freebsd-update should write NVRAM entries at all versus a separate remediation tool. Guard 1 + Guard 2 implement a conservative default; UpdateBootloaderNVRAM no in freebsd-update.conf now provides a persistent fleet-wide opt-out. design.md §4.5 documents the trade-offs. Seeking imp's input on whether the default should change.

Test suite

325 assertions across 48 files (unit, integration, error conditions).
Verified on FreeBSD 14.0-RELEASE-p11 and 15.1-RELEASE-p3 (amd64,
EFI+BIOS, ZFS).
Full reports: https://github.com/RZA-SF/freebsd-patch-for-bug279829/blob/main/docs/hardware-reports.md

[1] https://github.com/RZA-SF/freebsd-patch-for-bug279829/issues/3

usr.sbin/freebsd-update/efi_bootloader_update.sh
936

That sounds dangerous. We should only proceed when we are able to confirm that the loader is not signed. If uefisign(8) is not available or does not work for some reason, we cannot assert that, so we should not be touching the file.

ronald.pagani.jr_dev.us.com added inline comments.
usr.sbin/freebsd-update/efi_bootloader_update.sh
962

Implemented in revision-5 (D58990?id=186318).

efi_is_signed() calls uefisign -V on the destination before any write. If the destination carries a Secure Boot signature, efi_safe_copy skips the copy and emits a warning; the signed binary is left unchanged and the update continues to the next file. Fail-open: if uefisign is absent or returns an unexpected exit code, the copy proceeds normally.

usr.sbin/freebsd-update/freebsd-update.8
221

Done in revision-5 (D58990?id=186318). The install command description in freebsd-update.8 now explicitly notes that EFI binaries carrying a Secure Boot signature are not overwritten.

ronald.pagani.jr_dev.us.com added inline comments.
usr.sbin/freebsd-update/efi_bootloader_update.sh
936

That's a very reasonable point. If it's not available (which would be very odd), or could not determine whether it's signed, we don't copy. This will be revision-6.

ronald.pagani.jr_dev.us.com edited the summary of this revision. (Show Details)
ronald.pagani.jr_dev.us.com edited the test plan for this revision. (Show Details)

This is revision-6. This includes an important update for uefisign.
The previous diff was revision-5 (D58990?id=186318), and
revision-4 (D58990?id=186193) was a significant update.

What changed in revision-4 (ia32 EFI support, NVRAM write safety,
and reliability fixes)

  • ia32 EFI (#2, imp): /boot/loader_ia32.efi is present on FreeBSD 14.3+ amd64. The script now copies it to EFI/BOOT/BOOTia32.efi using the same fingerprint-gated safe-copy logic as the 64-bit fallback. Silently skipped on 13.x and 14.0-14.2 where the source is absent. NVRAM management is skipped on 32-bit UEFI firmware (efibootmgr unavailable when /dev/efi is absent); one-time manual setup documented in contrib/howto-ia32-uefi-nvram-windows.md. Validated on an Intel Pocket PC (Atom Z3736F, Bay Trail, 32-bit UEFI, 14.3-RELEASE, eMMC) via dry-run.
  • Fingerprint pattern fix: efi_is_freebsd_loader tightened from 'FreeBSD/[^ ]+ EFI' to 'FreeBSD/[^ ]+ EFI[ ,]'. FreeBSD 14.0 changed bootprog_info from 'FreeBSD/amd64 EFI, Revision 1.1' to 'FreeBSD/amd64 EFI loader, Revision 1.1'. The new pattern covers both formats and the ia32 variant.
  • NVRAM BootOrder position preservation: when a FreeBSD fallback entry already exists in BootOrder, its ordinal position is recorded and the new dedicated entry is inserted at the same position rather than prepending to slot 0. Preserves user-configured boot priority on multi-OS systems.
  • Option 5 NVRAM guards (informed by Emrion, GitHub issue #3): Guard 1: skip NVRAM entry creation when BOOTx64.efi/BOOTia32.efi already fingerprints as a FreeBSD loader. Guard 2: skip creation when efibootmgr returns no parseable BootOrder (avoids writes to firmware with broken SetVariable implementations). [1]
  • nvd/nda symlink fix (Emrion, GitHub issue #4): efi_esp_mountpoint adds a realpath third fallback to resolve kern.cam.nda.nvd_compat=1 device aliases (nvd0p1 => nda0p1) before comparing against the mount table. Previously, direct and glabel matches both failed on such systems.
  • gpart --libxo fallback scope corrected: verbose message and comment previously claimed "(FreeBSD 13.x)"; dry-run traces on 14.0-RELEASE-p11 and 14.3-RELEASE confirm the fallback fires there too. Version claim removed.

What was added in as revision-5

Secure Boot guard, EFI_INSTALL_IA32 gate, and freebsd-update.conf
NVRAM opt-out

  • Secure Boot signature guard (quentin.thebault, revision-3 inline comment): efi_safe_copy now calls efi_is_signed (uefisign -V) before overwriting an existing EFI binary. If the destination carries a Secure Boot signature the copy is skipped and a warning is emitted; the signed binary is left unchanged and the update continues to the next file. Four new unit tests cover the signed/unsigned matrix. Note: the fail-open behavior for absent uefisign has been replaced with fail-safe in revision-6.
  • EFI_INSTALL_IA32 opt-in gate: fresh BOOTia32.efi installation (no existing file) now requires EFI_INSTALL_IA32=1. Updating an existing BOOTia32.efi proceeds unconditionally. Prevents unexpected file creation on amd64 systems where bsdinstall has not placed the file; 32-bit UEFI hardware (BOOTia32.efi already present) is unaffected.
  • UpdateBootloaderNVRAM in freebsd-update.conf (quentin.thebault, revision-3 man page comment): new persistent configuration option lets fleet operators set UpdateBootloaderNVRAM no to skip NVRAM entry management while still updating ESP files. Appropriate when NVRAM entries are managed externally (BMC, Ansible, iDRAC, etc.) or when firmware SetVariable is unreliable. Setting UpdateBootloader no disables all bootloader updates including NVRAM (unchanged from revision-4). For one-off runs EFI_NVRAM_UPDATE=0 still works as before. freebsd-update.8 updated to document both options.

What was added as revision-6 (uefisign fail-safe)

  • Secure Boot guard fail-safe (quentin.thebault, D58990 inline comment on revision-5): revision-5 was fail-open — if uefisign was absent from PATH, efi_safe_copy proceeded normally on the assumption that the destination was unsigned. quentin.thebault correctly identified this as dangerous: if the destination carries a Secure Boot signature and uefisign is absent, the script would silently overwrite a signed binary with an unsigned one, breaking Secure Boot.

    Revision-6 is fail-safe. efi_is_signed now returns a 3-way result: 0 (signed), 1 (confirmed unsigned), 2 (indeterminate: uefisign not in PATH, detected via exit code 127, the POSIX shell convention for "command not found"). efi_safe_copy treats case 2 as a skip: it emits a warning and leaves the destination unchanged. Case 1 (confirmed unsigned) proceeds as before.

    Three new unit tests cover the indeterminate path: uefisign absent returns 0 (skip, fail-safe), _efi_copy_wrote=0 (no write counted), destination content unchanged.

Open items

  • #3 (Emrion / imp): NVRAM write policy: whether freebsd-update should write NVRAM entries at all versus a separate remediation tool. Guard 1 + Guard 2 implement a conservative default; UpdateBootloaderNVRAM no in freebsd-update.conf now provides a persistent fleet-wide opt-out. design.md §4.5 documents the trade-offs. Seeking imp's input on whether the default should change.

    Test suite

328 assertions across 48 files (unit, integration, error conditions).
Verified on FreeBSD 14.0-RELEASE-p11 and 15.1-RELEASE-p3 (amd64,
EFI+BIOS, ZFS).
Full reports: https://github.com/RZA-SF/freebsd-patch-for-bug279829/blob/main/docs/hardware-reports.md

[1] https://github.com/RZA-SF/freebsd-patch-for-bug279829/issues/3

ronald.pagani.jr_dev.us.com added inline comments.
usr.sbin/freebsd-update/efi_bootloader_update.sh
936

Implemented in revision-6 (D58990?id=186344).

efi_is_signed() now returns a three-way result:

0 — signed (uefisign -V succeeded)
1 — confirmed unsigned (uefisign ran, no signature found)
2 — indeterminate (uefisign not in PATH, detected via exit 127)

efi_safe_copy handles all three cases. Return code 2 now skips the copy with a warning instead of proceeding. Replacing a potentially signed binary when we cannot verify its state is unsafe; uefisign is present in FreeBSD base so exit 127 indicates a non-standard environment rather than a normal unsigned system.

usr.sbin/freebsd-update/efi_bootloader_update.sh
936

This is fragile: a return code of 1 from uefisign(8) currently does not distinguish a failure from a verified but unsigned file. I reported that here: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=298383

I propose to add the following patch as a prerequisite to this one, so reduce the chance of something going wrong: https://reviews.freebsd.org/D59580

usr.sbin/freebsd-update/efi_bootloader_update.sh
936

Confirmed the exit code ambiguity via source review and testing on FreeBSD 14.0-RELEASE-p11 and 15.1-RELEASE-p3. All failure paths (errx(1, ...)) share exit code 1 with the "not signed" case.

However, "file not signed" is a hardcoded literal at child.c:248 — the only path that produces it. All error paths produce distinct output ("MZ header not found", "No such file or directory", PE parsing errors, etc.). This is verifiable from source and confirmed on hardware.

Proposed fix for revision-7: update efi_is_signed() to capture stderr and grep for "file not signed" specifically. Any other output lands in fail-safe (skip + warn). This resolves the ambiguity you identified without requiring D59580 as a prerequisite, while remaining forward-compatible with D59580 when it lands.

D59580 for sure is a worthwhile improvement to uefisign independently and I support it landing on its own merits.

I'm going to note one possibly unintended consequence of earlier test activities. It happened on the amd64 system used in testing.

[Updated notes: Turns out my description below was of the Boot Overrides lists. There is a also a Boot Option Priorities list. In the priorities list, FreeBSD had been automatically added as first. I was able to change the priority order from the UEFI UI.]

Not only did the update add a FreeBSD boot selection, it also became the default boot selection. Previously the default was to boot Windows 11 Pro --something that had been true since the system was first configured. No prior activity had disturbed that default status. It now takes manual intervention to have Windows 11 Pro updates/upgrades continue after each reboot during the sequence.

I'll note that the FreeBSD entry showed up in the UEFI UI as a new last entry it the list it presents when selecting manually. Previously Windows 11 Pro was not in the last position but was the default.

For reference: X670-P WIFI

Thank you @markmi_dsl-only.net for the detailed feedback and follow-up. This has been a valuable investigation and helped identify both a firmware-specific edge case and a silent failure path in the implementation.

I'm going to note one possibly unintended consequence of earlier test activities. It happened on the amd64 system used in testing.

[Updated notes: Turns out my description below was of the Boot Overrides lists. There is a also a Boot Option Priorities list. In the priorities list, FreeBSD had been automatically added as first. I was able to change the priority order from the UEFI UI.]

Not only did the update add a FreeBSD boot selection, it also became the default boot selection. Previously the default was to boot Windows 11 Pro --something that had been true since the system was first configured. No prior activity had disturbed that default status. It now takes manual intervention to have Windows 11 Pro updates/upgrades continue after each reboot during the sequence.

The NVRAM entry creation was triggered because no existing FreeBSD boot entry was detected for the current ESP. This is the exceptional case. efi_ensure_nvram_entry only runs when no FreeBSD entry is found in NVRAM. On systems where a FreeBSD entry already exists, the function detects it and returns immediately without touching BootOrder. For environments where NVRAM management is undesirable, UpdateBootloaderNVRAM no in freebsd-update.conf disables NVRAM entry management entirely while ESP file updates continue normally. This option has been present since revision-5 and it is the recommended configuration for systems where boot entries are managed externally, or where firmware behavior around NVRAM writes is unpredictable.

I'll note that the FreeBSD entry showed up in the UEFI UI as a new last entry it the list it presents when selecting manually. Previously Windows 11 Pro was not in the last position but was the default.

For reference: X670-P WIFI

When creating a new entry, the script reads the existing BootOrder before proceeding. Per the UEFI specification, BootOrder[0] is the implicit boot default. There is no separate "default" variable. The script captures the full existing BootOrder, creates the FreeBSD entry (which efibootmgr -c necessarily prepends to BootOrder), then issues efibootmgr -o to restore the original sequence. All original entries are preserved in the rebuild, not just the previous default entry. This only applies to the ESP on the booted system's disk where a FreeBSD-fingerprinted loader is found. Whether the firmware honors the restored BootOrder is outside what the script can observe or control.

In this case, the X670-P WiFi firmware presents two separate lists: a Boot Options Order (manual selection) and a Priority Order (unattended default). This is non-standard. On standard UEFI/EDK2 systems there is only one list, and efibootmgr's BootOrder is the priority order. On this firmware, it is not possible to determine from the OS side which of the two lists efibootmgr's BootOrder maps to. If it maps to the selection list, the Priority Order is entirely outside the script's reach, which would explain why the Priority Order ended up with FreeBSD first despite the restore completing successfully.

The next revision will address the silent failure path. After issuing efibootmgr -o, the script will read back BootOrder and verify the original first entry is still in place. If not, it retries once. If the write is still not reflected after two attempts, the script explicitly warns that BootOrder may not have been preserved and prints the exact efibootmgr -o restore command. This eliminates the silent failure path where the entry was created but the order correction went undetected.

ronald.pagani.jr_dev.us.com edited the test plan for this revision. (Show Details)

This is revision-7. This includes updates to efi_is_signed and BootOrder
preservation hardening. The previous diff was revision-6
(D58990?id=186344), which replaced the fail-open uefisign behavior with
a fail-safe. Prior revision-5 (D58990?id=186318), and
revision-4 (D58990?id=186193) were significant updates.

What changed in revision-4 (ia32 EFI support, NVRAM write safety,
and reliability fixes)

  • ia32 EFI (#2, imp): /boot/loader_ia32.efi is present on FreeBSD 14.3+ amd64. The script now copies it to EFI/BOOT/BOOTia32.efi using the same fingerprint-gated safe-copy logic as the 64-bit fallback. Silently skipped on 13.x and 14.0-14.2 where the source is absent. NVRAM management is skipped on 32-bit UEFI firmware (efibootmgr unavailable when /dev/efi is absent); one-time manual setup documented in contrib/howto-ia32-uefi-nvram-windows.md. Validated on an Intel Pocket PC (Atom Z3736F, Bay Trail, 32-bit UEFI, 14.3-RELEASE, eMMC) via dry-run.
  • Fingerprint pattern fix: efi_is_freebsd_loader tightened from 'FreeBSD/[^ ]+ EFI' to 'FreeBSD/[^ ]+ EFI[ ,]'. FreeBSD 14.0 changed bootprog_info from 'FreeBSD/amd64 EFI, Revision 1.1' to 'FreeBSD/amd64 EFI loader, Revision 1.1'. The new pattern covers both formats and the ia32 variant.
  • NVRAM BootOrder position preservation: when a FreeBSD fallback entry already exists in BootOrder, its ordinal position is recorded and the new dedicated entry is inserted at the same position rather than prepending to slot 0. Preserves user-configured boot priority on multi-OS systems.
  • Option 5 NVRAM guards (informed by Emrion, GitHub issue #3): Guard 1: skip NVRAM entry creation when BOOTx64.efi/BOOTia32.efi already fingerprints as a FreeBSD loader. Guard 2: skip creation when efibootmgr returns no parseable BootOrder (avoids writes to firmware with broken SetVariable implementations). [1]
  • nvd/nda symlink fix (Emrion, GitHub issue #4): efi_esp_mountpoint adds a realpath third fallback to resolve kern.cam.nda.nvd_compat=1 device aliases (nvd0p1 => nda0p1) before comparing against the mount table. Previously, direct and glabel matches both failed on such systems.
  • gpart --libxo fallback scope corrected: verbose message and comment previously claimed "(FreeBSD 13.x)"; dry-run traces on 14.0-RELEASE-p11 and 14.3-RELEASE confirm the fallback fires there too. Version claim removed.

What was added as revision-5 (Secure Boot guard, EFI_INSTALL_IA32
gate, and freebsd-update.conf NVRAM opt-out)

  • Secure Boot signature guard (quentin.thebault, revision-3 inline comment): efi_safe_copy now calls efi_is_signed (uefisign -V) before overwriting an existing EFI binary. If the destination carries a Secure Boot signature the copy is skipped and a warning is emitted; the signed binary is left unchanged and the update continues to the next file. Four new unit tests cover the signed/unsigned matrix. Note: the fail-open behavior for absent uefisign has been replaced with fail-safe in revision-6.
  • EFI_INSTALL_IA32 opt-in gate: fresh BOOTia32.efi installation (no existing file) now requires EFI_INSTALL_IA32=1. Updating an existing BOOTia32.efi proceeds unconditionally. Prevents unexpected file creation on amd64 systems where bsdinstall has not placed the file; 32-bit UEFI hardware (BOOTia32.efi already present) is unaffected.
  • UpdateBootloaderNVRAM in freebsd-update.conf (quentin.thebault, revision-3 man page comment): new persistent configuration option lets fleet operators set UpdateBootloaderNVRAM no to skip NVRAM entry management while still updating ESP files. Appropriate when NVRAM entries are managed externally (BMC, Ansible, iDRAC, etc.) or when firmware SetVariable is unreliable. Setting UpdateBootloader no disables all bootloader updates including NVRAM (unchanged from revision-4). For one-off runs EFI_NVRAM_UPDATE=0 still works as before. freebsd-update.8 updated to document both options.

What was added as revision-6 (uefisign fail-safe)

  • Secure Boot guard fail-safe (quentin.thebault, D58990 inline comment on revision-5): revision-5 was fail-open -- if uefisign was absent from PATH, efi_safe_copy proceeded normally on the assumption that the destination was unsigned. quentin.thebault correctly identified this as dangerous: if the destination carries a Secure Boot signature and uefisign is absent, the script would silently overwrite a signed binary with an unsigned one, breaking Secure Boot.

    Revision-6 is fail-safe. efi_is_signed now returns a 3-way result: 0 (signed), 1 (confirmed unsigned), 2 (indeterminate: uefisign not in PATH, detected via exit code 127, the POSIX shell convention for "command not found"). efi_safe_copy treats case 2 as a skip: it emits a warning and leaves the destination unchanged. Case 1 (confirmed unsigned) proceeds as before.

    Three new unit tests cover the indeterminate path: uefisign absent returns 0 (skip, fail-safe), _efi_copy_wrote=0 (no write counted), destination content unchanged.

What was added as revision-7 (efi_is_signed output parsing,
BootOrder preservation hardening)

  • efi_is_signed output parsing (quentin.thebault, D58990 inline comment on revision-6): revision-6 disambiguated uefisign exit code 1 only by presence/absence of the binary (exit 127 = absent, fail-safe). Exit code 1 is also used for non-PE input, parse failures, and other error paths, making the exit-code-only approach unreliable for distinguishing "file not signed" from other errors.

    Revision-7 adds stderr output parsing. _out=$(uefisign -V "$file" 2>&1) captures output before checking the exit code. A non-0 non-127 exit with output matching "file not signed" returns 1 (confirmed unsigned). Any other non-0 non-127 output emits a diagnostic warning and returns 2 (fail-safe). "file not signed" is a hardcoded literal at child.c:248 in the uefisign source, distinct from all error paths. A code comment references D59580; output parsing will be replaced with clean exit codes when D59580 lands.

    mock_framework.sh default uefisign mock updated to emit "uefisign: file not signed" so existing tests exercise the confirmed-unsigned path. Four new unit tests cover: error output triggers fail-safe, no write, dst unchanged, diagnostic warning emitted.
  • BootOrder preservation hardening (markmi, D58990 review): markmi reported that after running the script on an ASUS X670-P WiFi, FreeBSD became the unattended boot default despite Windows having been the prior default. Investigation found silent failure paths in the BootOrder restore step.

    After efibootmgr -o, the script now reads back BootOrder and verifies BootOrder[0] matches the original default. If not reflected, it retries once (handles transient firmware delays). After two failed attempts it warns that BootOrder was not preserved, names the new and previous defaults, and prints the exact efibootmgr -o restore command. _new_num detection failure and efibootmgr -o failure also now warn with restore commands.

    The X670-P WiFi presents two separate firmware lists (Boot Options Order and Priority Order). The Priority Order is managed independently of the UEFI BootOrder variable; this is non-standard behavior outside the script's reach. The hardening ensures failures visible to the script are surfaced rather than silent. Three new unit tests cover _new_num detection failure, both verify attempts failing, and retry succeeding.

Open items

  • D59580 (quentin.thebault): proposed uefisign exit code additions to distinguish "file not signed" from error paths at the exit code level. The output parsing in revision-7 is the interim solution; it will be replaced with clean exit code checks when D59580 lands.

Test suite

335 assertions across 48 files (unit, integration, error conditions).
Verified on FreeBSD 14.0-RELEASE-p11 and 15.1-RELEASE-p3 (amd64,
EFI+BIOS, ZFS).
Full reports: https://github.com/RZA-SF/freebsd-patch-for-bug279829/blob/main/docs/hardware-reports.md

[1] https://github.com/RZA-SF/freebsd-patch-for-bug279829/issues/3