git: b567434a592e - main - stand: set st_dev/st_ino in the loader's ZFS stat for veriexec

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Tue, 18 Aug 2026 15:47:17 UTC
The branch main has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=b567434a592e1a35b20c5a39c4ccc2ea9e00601d

commit b567434a592e1a35b20c5a39c4ccc2ea9e00601d
Author:     Etienne Bonnand <etienne.bonnand@defenso.fr>
AuthorDate: 2026-06-18 16:37:31 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2026-08-18 15:47:12 +0000

    stand: set st_dev/st_ino in the loader's ZFS stat for veriexec
    
    The loader's ZFS implementation never set st_dev or st_ino in
    zfs_dnode_stat(). With an uninitialized struct stat, veriexec's device
    comparison in lib/libsecureboot/veopen.c read stack garbage and skipped
    the matching manifest entry, failing with a spurious "no entry" on ZFS
    root under UEFI Secure Boot.
    
    Rather than zeroing the device (which would break veriexec's ability to
    tell apart the same path on different datasets), populate st_dev and
    st_ino with the same intrinsic identifiers the kernel uses:
    
    - st_dev = the dataset's ds_fsid_guid (as the kernel does via
      dmu_objset_fsid_guid()/dsl_dataset_fsid_guid()), already read in
      zfs_mount_dataset() and now propagated through struct zfsmount.
    - st_ino = the object number resolved in zfs_lookup(), propagated
      through struct file (the loader's equivalent of the kernel's z_id).
    
    dev_t and ino_t are 64-bit on FreeBSD, so both are assigned directly
    with no hashing. A memset() at the top of zfs_dnode_stat() zeroes the
    remaining fields so they no longer hold stack garbage.
    
    Tested on 16.0-CURRENT (amd64), ZFS-on-GELI root: rebuilt and re-signed
    the EFI loader; the system boots under UEFI Secure Boot with
    mac_veriexec active.
    
    (boot1 segment was modified by kevans)
    
    PR:             295935
    Sponsored by:   Defenso
    
    Reviewed-by: kevans
    Pull-Request: https://github.com/freebsd/freebsd-src/pull/2271
---
 stand/efi/boot1/zfs_module.c |  5 ++--
 stand/libsa/zfs/zfs.c        |  8 ++++---
 stand/libsa/zfs/zfsimpl.c    | 54 +++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 56 insertions(+), 11 deletions(-)

diff --git a/stand/efi/boot1/zfs_module.c b/stand/efi/boot1/zfs_module.c
index f0cf28e8ce68..3c3e9f6a1015 100644
--- a/stand/efi/boot1/zfs_module.c
+++ b/stand/efi/boot1/zfs_module.c
@@ -205,7 +205,7 @@ load(const char *filepath, dev_info_t *devinfo, void **bufp, size_t *bufsize)
 		return (EFI_NOT_FOUND);
 	}
 
-	if ((err = zfs_lookup(&zmount, filepath, &dn)) != 0) {
+	if ((err = zfs_lookup(&zmount, filepath, &dn, NULL)) != 0) {
 		if (err == ENOENT) {
 			DPRINTF("Failed to find '%s' on pool '%s' (%d)\n",
 			    filepath, spa->spa_name, err);
@@ -216,7 +216,8 @@ load(const char *filepath, dev_info_t *devinfo, void **bufp, size_t *bufsize)
 		return (EFI_INVALID_PARAMETER);
 	}
 
-	if ((err = zfs_dnode_stat(spa, &dn, &st)) != 0) {
+	/* Only st_size is inspected here, so identity is irrelevant. */
+	if ((err = zfs_dnode_stat(spa, &dn, &st, 0, 0)) != 0) {
 		printf("Failed to stat '%s' on pool '%s' (%d)\n", filepath,
 		    spa->spa_name, err);
 		return (EFI_INVALID_PARAMETER);
diff --git a/stand/libsa/zfs/zfs.c b/stand/libsa/zfs/zfs.c
index ead772dc9c90..156c78d6869c 100644
--- a/stand/libsa/zfs/zfs.c
+++ b/stand/libsa/zfs/zfs.c
@@ -85,6 +85,7 @@ struct fs_ops zfs_fsops = {
 struct file {
 	off_t		f_seekp;	/* seek pointer */
 	dnode_phys_t	f_dnode;
+	uint64_t	f_objnum;	/* object number (st_ino) */
 	uint64_t	f_zap_type;	/* zap type for readdir */
 	uint64_t	f_num_leafs;	/* number of fzap leaf blocks */
 	zap_leaf_phys_t	*f_zap_leaf;	/* zap leaf buffer */
@@ -120,7 +121,7 @@ zfs_open(const char *upath, struct open_file *f)
 		return (ENOMEM);
 	f->f_fsdata = fp;
 
-	rc = zfs_lookup(mount, upath, &fp->f_dnode);
+	rc = zfs_lookup(mount, upath, &fp->f_dnode, &fp->f_objnum);
 	fp->f_seekp = 0;
 	if (rc) {
 		f->f_fsdata = NULL;
@@ -214,10 +215,11 @@ static int
 zfs_stat(struct open_file *f, struct stat *sb)
 {
 	struct devdesc *dev = f->f_devdata;
-	const spa_t *spa = ((struct zfsmount *)dev->d_opendata)->spa;
+	struct zfsmount *zm = dev->d_opendata;
 	struct file *fp = (struct file *)f->f_fsdata;
 
-	return (zfs_dnode_stat(spa, &fp->f_dnode, sb));
+	return (zfs_dnode_stat(zm->spa, &fp->f_dnode, sb, zm->fsid_guid,
+	    fp->f_objnum));
 }
 
 static int
diff --git a/stand/libsa/zfs/zfsimpl.c b/stand/libsa/zfs/zfsimpl.c
index ebeb7bea7041..b16d0ac406e1 100644
--- a/stand/libsa/zfs/zfsimpl.c
+++ b/stand/libsa/zfs/zfsimpl.c
@@ -48,6 +48,7 @@ struct zfsmount {
 	const spa_t		*spa;
 	objset_phys_t		objset;
 	uint64_t		rootobj;
+	uint64_t		fsid_guid;	/* mount's ds_fsid_guid */
 	STAILQ_ENTRY(zfsmount)	next;
 };
 
@@ -3432,7 +3433,8 @@ done:
  * and return its details in *objset
  */
 static int
-zfs_mount_dataset(const spa_t *spa, uint64_t objnum, objset_phys_t *objset)
+zfs_mount_dataset(const spa_t *spa, uint64_t objnum, objset_phys_t *objset,
+    uint64_t *fsid_guid)
 {
 	dnode_phys_t dataset;
 	dsl_dataset_phys_t *ds;
@@ -3449,6 +3451,17 @@ zfs_mount_dataset(const spa_t *spa, uint64_t objnum, objset_phys_t *objset)
 		return (EIO);
 	}
 
+	/*
+	 * Capture the dataset's intrinsic on-disk identifier so callers can
+	 * expose it as st_dev (matches the kernel, which derives the fsid from
+	 * dmu_objset_fsid_guid()/ds_fsid_guid).  ds_fsid_guid is a 56-bit ID
+	 * that may change to avoid collisions; ds_guid is a never-changing
+	 * 64-bit ID and would be the alternative if absolute stability over
+	 * time were required -- to be decided in review.
+	 */
+	if (fsid_guid != NULL)
+		*fsid_guid = ds->ds_fsid_guid;
+
 	return (0);
 }
 
@@ -3514,7 +3527,8 @@ zfs_mount_impl(const spa_t *spa, uint64_t rootobj, struct zfsmount *mount)
 		return (EIO);
 	}
 
-	if (zfs_mount_dataset(spa, rootobj, &mount->objset)) {
+	if (zfs_mount_dataset(spa, rootobj, &mount->objset,
+	    &mount->fsid_guid)) {
 		printf("ZFS: can't open root filesystem\n");
 		return (EIO);
 	}
@@ -3699,9 +3713,17 @@ zfs_spa_init(spa_t *spa)
 }
 
 static int
-zfs_dnode_stat(const spa_t *spa, dnode_phys_t *dn, struct stat *sb)
+zfs_dnode_stat(const spa_t *spa, dnode_phys_t *dn, struct stat *sb,
+    uint64_t fsid_guid, uint64_t objnum)
 {
 
+	/*
+	 * Zero the whole struct first so fields this function does not fill
+	 * (st_nlink, timestamps, st_blocks, ...) hold 0 rather than stack
+	 * garbage.  st_dev/st_ino are then overwritten with real values below.
+	 */
+	memset(sb, 0, sizeof(*sb));
+
 	if (dn->dn_bonustype != DMU_OT_SA) {
 		znode_phys_t *zp = (znode_phys_t *)dn->dn_bonus;
 
@@ -3750,6 +3772,15 @@ zfs_dnode_stat(const spa_t *spa, dnode_phys_t *dn, struct stat *sb)
 		free(buf);
 	}
 
+	/*
+	 * Populate the identity fields used by veriexec to tell apart the same
+	 * path on different datasets/filesystems.  dev_t and ino_t are 64-bit
+	 * on FreeBSD, so the dataset GUID and object number fit directly with
+	 * no hashing or truncation.
+	 */
+	sb->st_dev = (dev_t)fsid_guid;
+	sb->st_ino = (ino_t)objnum;
+
 	return (0);
 }
 
@@ -3814,7 +3845,8 @@ struct obj_list {
  * Lookup a file and return its dnode.
  */
 static int
-zfs_lookup(const struct zfsmount *mount, const char *upath, dnode_phys_t *dnode)
+zfs_lookup(const struct zfsmount *mount, const char *upath,
+    dnode_phys_t *dnode, uint64_t *objnum_out)
 {
 	int rc;
 	uint64_t objnum;
@@ -3900,7 +3932,8 @@ zfs_lookup(const struct zfsmount *mount, const char *upath, dnode_phys_t *dnode)
 		element[q - p] = 0;
 		p = q;
 
-		if ((rc = zfs_dnode_stat(spa, &dn, &sb)) != 0)
+		/* Only st_mode is inspected here, so identity is irrelevant. */
+		if ((rc = zfs_dnode_stat(spa, &dn, &sb, 0, 0)) != 0)
 			goto done;
 		if (!S_ISDIR(sb.st_mode)) {
 			rc = ENOTDIR;
@@ -3925,7 +3958,8 @@ zfs_lookup(const struct zfsmount *mount, const char *upath, dnode_phys_t *dnode)
 		/*
 		 * Check for symlink.
 		 */
-		rc = zfs_dnode_stat(spa, &dn, &sb);
+		/* Only st_mode is inspected here, so identity is irrelevant. */
+		rc = zfs_dnode_stat(spa, &dn, &sb, 0, 0);
 		if (rc)
 			goto done;
 		if (S_ISLNK(sb.st_mode)) {
@@ -3972,6 +4006,14 @@ zfs_lookup(const struct zfsmount *mount, const char *upath, dnode_phys_t *dnode)
 	}
 
 	*dnode = dn;
+	/*
+	 * objnum tracks the object number of the dnode we just resolved (the
+	 * loader's equivalent of the kernel's z_id/db_object); hand it back so
+	 * the file can expose it as st_ino.  Callers that do not need it pass
+	 * NULL.
+	 */
+	if (objnum_out != NULL)
+		*objnum_out = objnum;
 done:
 	STAILQ_FOREACH_SAFE(entry, &on_cache, entry, tentry)
 		free(entry);