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>
