git: 53b4ae3bf0f7 - main - nfs_diskless: Fix handling of nfsuserd case for NFSv4

From: Rick Macklem <rmacklem_at_FreeBSD.org>
Date: Tue, 07 Apr 2026 15:53:23 UTC
The branch main has been updated by rmacklem:

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

commit 53b4ae3bf0f7e625d51fa263a5bd3859792d61e3
Author:     Rick Macklem <rmacklem@FreeBSD.org>
AuthorDate: 2026-04-07 15:50:21 +0000
Commit:     Rick Macklem <rmacklem@FreeBSD.org>
CommitDate: 2026-04-07 15:50:21 +0000

    nfs_diskless: Fix handling of nfsuserd case for NFSv4
    
    Commit 8b9775912cbc added support for an NFSv4 mounted
    root file system, but only if the NFSv4 configuration
    used id numbers in the strings.
    
    This patch adds support for the case where the NFSv4
    configuration uses name<-->id mappings via nfsuserd(8)
    by priming the mapping cache with just enough entries
    so that it works until the nfsuserd(8) is running.
    They are listed in nfs_prime_userd[] in
    sys/fs/nfs/nfs_commonsubs.c.
    
    The entries in nfs_prime_userd[] are also wired into
    the kernel's cache for name<-->id mappings when nfsuserd(8)
    starts up.  This is necessary, since an upcall to the
    nfsuserd(8) daemon for a mapping when looking up the
    path to the passwd/group database files (/etc) will
    hang the system, due to a vnode lock being held on
    the entry in the path which blocks nfsuserd(8) from
    accessing files.
    
    To enable this case, the following must be put in the
    NFS root file system's /boot/loader.conf:
    boot.nfsroot.options="nfsv4"
    boot.nfsroot.user_domain="<user.domain>"
    where <user.domain> must be the same as nfsuserd
    uses (usually set via the -domain flag).
    If boot.nfsroot.user_domain does not exist or is
    the empty string, ids is strings is configured.
    
    MFC after:      1 week
    Requested by:   Dan Shelton <dan.f.shelton@gmail.com>
    Fixes:  8b9775912cbc ("nfs_diskless: Add support for an NFSv4 root fs")
---
 sys/fs/nfs/nfs_commonsubs.c | 76 +++++++++++++++++++++++++++++++++++++++------
 sys/fs/nfs/nfsid.h          | 13 ++++++++
 sys/fs/nfs/nfsrvstate.h     |  1 +
 sys/nfs/nfs_diskless.c      | 44 ++++++++++++++++++++++++++
 4 files changed, 124 insertions(+), 10 deletions(-)

diff --git a/sys/fs/nfs/nfs_commonsubs.c b/sys/fs/nfs/nfs_commonsubs.c
index 78e2fbb72bdb..39d7cb447d6a 100644
--- a/sys/fs/nfs/nfs_commonsubs.c
+++ b/sys/fs/nfs/nfs_commonsubs.c
@@ -70,6 +70,7 @@ struct nfsreqhead nfsd_reqq;
 int nfsrv_lease = NFSRV_LEASE;
 int ncl_mbuf_mlen = MLEN;
 int nfsrv_doflexfile = 0;
+bool nfs_nfsv4root = false;
 NFSNAMEIDMUTEX;
 NFSSOCKMUTEX;
 extern int nfsrv_lughashsize;
@@ -80,6 +81,21 @@ extern struct nfsdevicehead nfsrv_devidhead;
 extern struct nfsstatsv1 nfsstatsv1;
 extern uint32_t nfs_srvmaxio;
 
+/*
+ * Define just enough NFSv4 id<-->name mappings to make things work
+ * until the nfsuserd(8) is running.
+ * XXX These name/ids must be kept the same as what is in /etc/passwd
+ *     and /etc/group.
+ */
+struct nfs_prime_userd nfs_prime_userd[] = {
+	{ NFSID_INITIALIZE,	UID_NOBODY,	GID_NOGROUP,	NULL },
+	{ NFSID_ADDUID,		UID_ROOT,	GID_NOGROUP,	"root" },
+	{ NFSID_ADDUID,		UID_BIN,	GID_NOGROUP,	"bin" },
+	{ NFSID_ADDGID,		UID_NOBODY,	GID_WHEEL,	"wheel" },
+	{ NFSID_ADDGID,		UID_NOBODY,	GID_OPERATOR,	"operator" },
+	{ 0,			0,		0,		NULL },
+};
+
 NFSD_VNET_DEFINE(int, nfsd_enable_stringtouid) = 0;
 NFSD_VNET_DEFINE(struct nfssockreq, nfsrv_nfsuserdsock);
 NFSD_VNET_DEFINE(nfsuserd_state, nfsrv_nfsuserd) = NOTRUNNING;
@@ -233,6 +249,7 @@ static int nfsrv_skipace(struct nfsrv_descript *nd, acl_type_t, int *acesizep);
 static void nfsv4_wanted(struct nfsv4lock *lp);
 static uint32_t nfsv4_filesavail(struct statfs *, struct mount *);
 static int nfsrv_getuser(int procnum, uid_t uid, gid_t gid, char *name);
+static bool nfs_in_prime(int flag, uid_t uid, gid_t gid);
 static void nfsrv_removeuser(struct nfsusrgrp *usrp, int isuser);
 static int nfsrv_getrefstr(struct nfsrv_descript *, u_char **, u_char **,
     int *, int *);
@@ -3667,7 +3684,8 @@ tryagain:
 		mtx_lock(&hp->mtx);
 		TAILQ_FOREACH(usrp, &hp->lughead, lug_numhash) {
 			if (usrp->lug_uid == uid) {
-				if (usrp->lug_expiry < NFSD_MONOSEC)
+				if (!usrp->lug_wired &&
+				    usrp->lug_expiry < NFSD_MONOSEC)
 					break;
 				/*
 				 * If the name doesn't already have an '@'
@@ -3759,7 +3777,8 @@ tryagain:
 		mtx_lock(&hp->mtx);
 		TAILQ_FOREACH(usrp, &hp->lughead, lug_numhash) {
 			if (usrp->lug_uid == uid) {
-				if (usrp->lug_expiry < NFSD_MONOSEC)
+				if (!usrp->lug_wired &&
+				    usrp->lug_expiry < NFSD_MONOSEC)
 					break;
 				if (usrp->lug_cred != NULL) {
 					newcred = crhold(usrp->lug_cred);
@@ -3859,7 +3878,8 @@ tryagain:
 		TAILQ_FOREACH(usrp, &hp->lughead, lug_namehash) {
 			if (usrp->lug_namelen == len &&
 			    !NFSBCMP(usrp->lug_name, str, len)) {
-				if (usrp->lug_expiry < NFSD_MONOSEC)
+				if (!usrp->lug_wired &&
+				    usrp->lug_expiry < NFSD_MONOSEC)
 					break;
 				hp2 = NFSUSERHASH(usrp->lug_uid);
 				mtx_lock(&hp2->mtx);
@@ -3936,7 +3956,8 @@ tryagain:
 		mtx_lock(&hp->mtx);
 		TAILQ_FOREACH(usrp, &hp->lughead, lug_numhash) {
 			if (usrp->lug_gid == gid) {
-				if (usrp->lug_expiry < NFSD_MONOSEC)
+				if (!usrp->lug_wired &&
+				    usrp->lug_expiry < NFSD_MONOSEC)
 					break;
 				/*
 				 * If the name doesn't already have an '@'
@@ -4081,7 +4102,8 @@ tryagain:
 		TAILQ_FOREACH(usrp, &hp->lughead, lug_namehash) {
 			if (usrp->lug_namelen == len &&
 			    !NFSBCMP(usrp->lug_name, str, len)) {
-				if (usrp->lug_expiry < NFSD_MONOSEC)
+				if (!usrp->lug_wired &&
+				    usrp->lug_expiry < NFSD_MONOSEC)
 					break;
 				hp2 = NFSGROUPHASH(usrp->lug_gid);
 				mtx_lock(&hp2->mtx);
@@ -4282,6 +4304,23 @@ out:
 	return (error);
 }
 
+/* Check to see if the uid/gid is in the nfs_prime_userd list. */
+static bool
+nfs_in_prime(int flag, uid_t uid, gid_t gid)
+{
+	int i;
+
+	for (i = 0; nfs_prime_userd[i].flag != 0; i++) {
+		if ((nfs_prime_userd[i].flag & flag) == NFSID_ADDUID &&
+		    nfs_prime_userd[i].uid == uid)
+			return (true);
+		if ((nfs_prime_userd[i].flag & flag) == NFSID_ADDGID &&
+		    nfs_prime_userd[i].gid == gid)
+			return (true);
+	}
+	return (false);
+}
+
 /*
  * This function is called from the nfssvc(2) system call, to update the
  * kernel user/group name list(s) for the V4 owner and ownergroup attributes.
@@ -4305,7 +4344,11 @@ nfssvc_idname(struct nfsd_idargs *nidp)
 	}
 	if (nidp->nid_flag & NFSID_INITIALIZE) {
 		cp = malloc(nidp->nid_namelen + 1, M_NFSSTRING, M_WAITOK);
-		error = copyin(nidp->nid_name, cp, nidp->nid_namelen);
+		error = 0;
+		if ((nidp->nid_flag & NFSID_SYSSPACE) == 0)
+			error = copyin(nidp->nid_name, cp, nidp->nid_namelen);
+		else
+			NFSBCOPY(nidp->nid_name, cp, nidp->nid_namelen);
 		if (error != 0) {
 			free(cp, M_NFSSTRING);
 			goto out;
@@ -4403,8 +4446,12 @@ nfssvc_idname(struct nfsd_idargs *nidp)
 	 */
 	newusrp = malloc(sizeof(struct nfsusrgrp) + nidp->nid_namelen,
 	    M_NFSUSERGROUP, M_WAITOK | M_ZERO);
-	error = copyin(nidp->nid_name, newusrp->lug_name,
-	    nidp->nid_namelen);
+	error = 0;
+	if ((nidp->nid_flag & NFSID_SYSSPACE) == 0)
+		error = copyin(nidp->nid_name, newusrp->lug_name,
+		    nidp->nid_namelen);
+	else
+		NFSBCOPY(nidp->nid_name, newusrp->lug_name, nidp->nid_namelen);
 	if (error == 0 && nidp->nid_ngroup > 0 &&
 	    (nidp->nid_flag & NFSID_ADDUID) != 0) {
 		grps = NULL;
@@ -4522,7 +4569,11 @@ nfssvc_idname(struct nfsd_idargs *nidp)
 		newusrp->lug_expiry = NFSD_MONOSEC + nidp->nid_usertimeout;
 	else
 		newusrp->lug_expiry = NFSD_MONOSEC + 5;
+	newusrp->lug_wired = false;
 	if (nidp->nid_flag & (NFSID_ADDUID | NFSID_ADDUSERNAME)) {
+		if (nfs_nfsv4root && nfs_in_prime(NFSID_ADDUID, nidp->nid_uid,
+		    nidp->nid_gid))
+			newusrp->lug_wired = true;
 		newusrp->lug_uid = nidp->nid_uid;
 		thp = NFSUSERHASH(newusrp->lug_uid);
 		mtx_assert(&thp->mtx, MA_OWNED);
@@ -4532,6 +4583,9 @@ nfssvc_idname(struct nfsd_idargs *nidp)
 		TAILQ_INSERT_TAIL(&thp->lughead, newusrp, lug_namehash);
 		atomic_add_int(&NFSD_VNET(nfsrv_usercnt), 1);
 	} else if (nidp->nid_flag & (NFSID_ADDGID | NFSID_ADDGROUPNAME)) {
+		if (nfs_nfsv4root && nfs_in_prime(NFSID_ADDGID, nidp->nid_uid,
+		    nidp->nid_gid))
+			newusrp->lug_wired = true;
 		newusrp->lug_gid = nidp->nid_gid;
 		thp = NFSGROUPHASH(newusrp->lug_gid);
 		mtx_assert(&thp->mtx, MA_OWNED);
@@ -4580,7 +4634,8 @@ nfssvc_idname(struct nfsd_idargs *nidp)
 				TAILQ_FOREACH_SAFE(usrp,
 				    &NFSD_VNET(nfsuserhash)[i].lughead, lug_numhash,
 				    nusrp)
-					if (usrp->lug_expiry < NFSD_MONOSEC)
+					if (!usrp->lug_wired &&
+					    usrp->lug_expiry < NFSD_MONOSEC)
 						nfsrv_removeuser(usrp, 1);
 			}
 			for (i = 0; i < nfsrv_lughashsize; i++) {
@@ -4610,7 +4665,8 @@ nfssvc_idname(struct nfsd_idargs *nidp)
 				TAILQ_FOREACH_SAFE(usrp,
 				    &NFSD_VNET(nfsgrouphash)[i].lughead, lug_numhash,
 				    nusrp)
-					if (usrp->lug_expiry < NFSD_MONOSEC)
+					if (!usrp->lug_wired &&
+					    usrp->lug_expiry < NFSD_MONOSEC)
 						nfsrv_removeuser(usrp, 0);
 			}
 			for (i = 0; i < nfsrv_lughashsize; i++) {
diff --git a/sys/fs/nfs/nfsid.h b/sys/fs/nfs/nfsid.h
index bd9807ca1acc..349fdecfc596 100644
--- a/sys/fs/nfs/nfsid.h
+++ b/sys/fs/nfs/nfsid.h
@@ -61,6 +61,19 @@ struct nfsd_idargs {
 #define	NFSID_SYSSPACE		0x0200
 
 #if defined(_KERNEL) || defined(KERNEL)
+/*
+ * Define just enough NFSv4 id<-->name mappings to make things work
+ * until the nfsuserd(8) is running.
+ * XXX These name/ids must be kept the same as what is in /etc/passwd
+ *     and /etc/group.
+ */
+struct nfs_prime_userd {
+	int	flag;
+	uid_t	uid;
+	gid_t	gid;
+	char	*nam;
+};
+
 int nfssvc_idname(struct nfsd_idargs *);
 #endif
 
diff --git a/sys/fs/nfs/nfsrvstate.h b/sys/fs/nfs/nfsrvstate.h
index cc19ed6fa1d2..858c52ec6218 100644
--- a/sys/fs/nfs/nfsrvstate.h
+++ b/sys/fs/nfs/nfsrvstate.h
@@ -317,6 +317,7 @@ struct nfsusrgrp {
 	} lug_un;
 	struct ucred		*lug_cred;	/* Cred. with groups list */
 	int			lug_namelen;	/* Name length */
+	bool			lug_wired;	/* Wired into cache */
 	u_char			lug_name[1];	/* malloc'd correct length */
 };
 #define	lug_uid		lug_un.un_uid
diff --git a/sys/nfs/nfs_diskless.c b/sys/nfs/nfs_diskless.c
index d5278612d8d9..32dd7f3e997f 100644
--- a/sys/nfs/nfs_diskless.c
+++ b/sys/nfs/nfs_diskless.c
@@ -54,6 +54,7 @@
 #include <nfs/nfsproto.h>
 #include <nfsclient/nfs.h>
 #include <nfs/nfsdiskless.h>
+#include <fs/nfs/nfsid.h>
 
 #define	NFS_IFACE_TIMEOUT_SECS	10 /* Timeout for interface to appear. */
 
@@ -70,6 +71,9 @@ struct nfs_diskless	nfs_diskless = { { { 0 } } };
 struct nfsv3_diskless	nfsv3_diskless = { { { 0 } } };
 int			nfs_diskless_valid = 0;
 
+extern struct nfs_prime_userd nfs_prime_userd[];
+extern bool nfs_nfsv4root;
+
 /*
  * Validate/sanity check a rsize/wsize parameter.
  */
@@ -292,11 +296,51 @@ match_done:
 				return;
 			}
 		} else {
+			struct nfsd_idargs nid;
+			int ret;
+
 			/*
 			 * For NFSv4, the file handle is derived from the
 			 * boot.nfsroot.path during mounting by NFSv4.
 			 */
 			nd3->root_fhsize = 0;
+			nfs_nfsv4root = true;
+
+			/*
+			 * Prime the id<-->name mappings just enough to
+			 * make things work until the nfsuserd(8) daemon
+			 * is started, if the nfsuserd_domain is set to a
+			 * non-empty string.
+			 */
+			if ((cp = kern_getenv("boot.nfsroot.user_domain")) !=
+			    NULL) {
+				for (cnt = 0; *cp != '\0' &&
+				    nfs_prime_userd[cnt].flag != 0; cnt++) {
+					nid.nid_flag =
+					    nfs_prime_userd[cnt].flag |
+					    NFSID_SYSSPACE;
+					if (nfs_prime_userd[cnt].flag ==
+					    NFSID_INITIALIZE) {
+						nid.nid_name = cp;
+						nid.nid_usermax = 10;
+					} else {
+						nid.nid_name =
+						    nfs_prime_userd[cnt].nam;
+						nid.nid_usertimeout = 3600;
+					}
+					nid.nid_namelen = strlen(nid.nid_name);
+					nid.nid_uid = nfs_prime_userd[cnt].uid;
+					nid.nid_gid = nfs_prime_userd[cnt].gid;
+					nid.nid_ngroup = 0;
+					nid.nid_grps = NULL;
+					ret = nfssvc_idname(&nid);
+					if (ret != 0)
+						printf("nfs_diskless: "
+						    "nfssvc_idname failed %d\n",
+						    ret);
+				}
+				freeenv(cp);
+			}
 		}
 		if ((cp = kern_getenv("boot.nfsroot.path")) != NULL) {
 			strncpy(nd3->root_hostnam, cp, MNAMELEN - 1);