Page MenuHomeFreeBSD

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

Authored by ronald.pagani.jr_dev.us.com on Wed, Aug 19, 3:40 PM.
Tags
None
Referenced Files
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
Unknown Object (File)
Tue, Sep 15, 7:49 AM
Unknown Object (File)
Tue, Sep 15, 12:37 AM
Unknown Object (File)
Mon, Sep 14, 6:22 AM
Unknown Object (File)
Sat, Sep 12, 7:18 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
328/328 passing on FreeBSD 14.0-RELEASE-p11 and 15.1-RELEASE-p3
(amd64, EFI+BIOS, ZFS).

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), loader_ia32.efi absent
path (14.0), Guard 1 firing (BOOTx64.efi fingerprinted as FreeBSD).
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

Is it deliberate that there is coverage of armv7 and its bootarm.efi ?

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

Do chroot contexts need something analogous to the above jail avoidance?

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

Hmm:

# zfs get -H -o value name / 2>/dev/null
zoptb/ROOT/main-amd64
# zpool status zoptb/ROOT/main-amd64 2>/dev/null
# 
# bectl list -a
BE/Dataset/Snapshot                             Active Mountpoint Space Created

main-amd64
  zoptb/ROOT/main-amd64                         NR     /          20.1G 2025-10-14 21:25

previous-main-amd64
  zoptb/ROOT/previous-main-amd64                -      -          8K    2026-07-30 21:08
    zoptb/ROOT/main-amd64@2026-07-30-21:08:47-0 -      -          4.67G 2026-07-30 21:08
# gpart show -p
. . .
=>        40  2930277095    nda2  GPT  (1.4T)
          40      532480  nda2p1  efi  (260M)
      532520        2008          - free -  (1004K)
      534528  1073741824  nda2p2  freebsd-swap  (512G)
  1074276352  1845493760  nda2p3  freebsd-zfs  (880G)
  2919770112     8388608  nda2p4  freebsd-swap  (4.0G)
  2928158720     2118415          - free -  (1.0G)
. . .
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