Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 08 Jun 2026 13:56:14 +0000
From:      bugzilla-noreply@freebsd.org
To:        bugs@FreeBSD.org
Subject:   [Bug 295935] stand/libsa: fstat() returns an uninitialized struct stat, causing spurious veriexec "no entry" on ZFS root under Secure Boot
Message-ID:  <bug-295935-227@https.bugs.freebsd.org/bugzilla/>

index | next in thread | raw e-mail

https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=295935

            Bug ID: 295935
           Summary: stand/libsa: fstat() returns an uninitialized struct
                    stat, causing spurious veriexec "no entry" on ZFS root
                    under Secure Boot
           Product: Base System
           Version: 16.0-CURRENT
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Only Me
          Priority: ---
         Component: bin
          Assignee: bugs@FreeBSD.org
          Reporter: etienne.bonnand@defenso.fr

Product: Base System
Component: bin   (boot loader / stand)
Version: 16.0-CURRENT

--- Summary ---

With a verified-boot loader (LOADER_VERIEXEC / LOADER_VERIEXEC_VECTX) and a ZFS
root, the loader fails to verify the kernel against a correct, signed manifest:

    Verified /boot/kernel/manifest signed by <key>
    Unverified /boot/kernel/kernel: no entry

The manifest contains a correct, properly formatted entry for the kernel whose
SHA-256 matches the on-disk file. The same configuration works on UFS.

--- Root cause ---

lib/libsecureboot/veopen.c, fingerprint_info_lookup() filters manifest entries
by device number:

    if (fip->fi_dev != 0 && fip->fi_dev != dev)
        continue;

fi_dev is the st_dev of the manifest file (fingerprint_info_add(), veopen.c
~124); dev is the st_dev of the verified file (fstat() in
fingerprint_info_lookup(), veopen.c ~206-207).

Both come from struct stat objects that are never zero-initialized:

  - lib/libsecureboot/veopen.c declares "struct stat st;" at lines 198 and 422
    with no initialization.
  - stand/libsa/fstat.c:35 — fstat() passes the caller's struct stat straight
    to fo_stat without memset'ing it.
  - stand/libsa/zfs/zfsimpl.c — zfs_dnode_stat() only sets st_mode (3708),
    st_uid, st_gid and st_size; it never assigns st_dev.

On ZFS, st_dev therefore retains stack garbage. fi_dev and dev are read from
two
different uninitialized stack frames, so they almost always differ. The
"fi_dev != 0" guard does not help, because fi_dev is garbage too (rarely
exactly
0). The only matching manifest entry is skipped, fingerprint_info_lookup()
returns NULL, and verify_fd() emits "no entry" (veopen.c ~430).

UFS is unaffected: stand/libsa/ufs.c:869 sets
st_dev = (dev_t)(fp->f_fs->fs_id[0] ^ fp->f_fs->fs_id[1]), so manifest and
target share the same value and the comparison passes.

The UNIT_TEST paths in veopen.c force both fi_dev and dev to 0, which indicates
the device comparison was already known to be unreliable outside a real kernel.

--- Reproduction ---

1. ZFS-on-root (zroot/ROOT/default over GELI), amd64.
2. Build the EFI loader with WITH_BEARSSL, WITH_LOADER_VERIEXEC,
   WITH_LOADER_VERIEXEC_VECTX, WITH_LOADER_EFI_SECUREBOOT.
3. Generate and sign a /boot/kernel manifest (kernel + modules), enroll the
   signing certificate, enable UEFI Secure Boot.
4. At the loader prompt: unload; load /boot/kernel/kernel
5. The manifest signature verifies, but the kernel lookup fails with "no
entry".
   The manifest line is byte-for-byte correct (hexdump: "kernel
sha256=<hash>\n",
   single 0x20 separator, no CRLF, hash matches sha256 of the kernel).

--- Suggested fix ---

The underlying defect is that the loader's stat path returns a struct stat with
uninitialized fields. Zero the struct in stand/libsa/fstat.c before it is
populated, so filesystems that do not set every field (the standalone ZFS
implementation does not set st_dev) leave those fields as 0 rather than stack
garbage:

    int
    fstat(int fd, struct stat *sb)
    {
        ...
        memset(sb, 0, sizeof(*sb));    /* ensure unset fields are 0, not
garbage */
        ...
    }

With st_dev == 0 on both sides, the existing "fi_dev != 0" guard in veopen.c
neutralizes the device comparison, and verification proceeds based on path
matching and the signed hash (where the actual security guarantee lies). This
fixes the symptom without modifying libsecureboot, and is a general robustness
improvement: a struct stat should never be returned with uninitialized fields.

An alternative is to have zfs_dnode_stat() set st_dev to a stable per-dataset
value (as UFS does with fs_id), but a correct stable value is less obvious in
the standalone ZFS code, and the memset addresses the broader class of
uninitialized-field issues.

--- Environment ---

FreeBSD 16.0-CURRENT, amd64, Lenovo ThinkPad T14s Gen 6.
Root: ZFS (zroot/ROOT/default) over GELI. UEFI Secure Boot enabled, custom
signing key enrolled in db.

-- 
You are receiving this mail because:
You are the assignee for the bug.

home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?bug-295935-227>