From 53b4ae3bf0f7e625d51fa263a5bd3859792d61e3 Mon Sep 17 00:00:00 2001 From: Rick Macklem Date: Tue, 7 Apr 2026 08:50:21 -0700 Subject: [PATCH] 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="" where 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 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 78e2fbb72bd..39d7cb447d6 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 @@ nfsv4_uidtostr(uid_t uid, u_char **cpp, int *retlenp) 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 @@ nfsrv_getgrpscred(struct ucred *oldcred) 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 @@ nfsv4_strtouid(struct nfsrv_descript *nd, u_char *str, int len, uid_t *uidp) 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 @@ nfsv4_gidtostr(gid_t gid, u_char **cpp, int *retlenp) 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 @@ nfsv4_strtogid(struct nfsrv_descript *nd, u_char *str, int len, gid_t *gidp) 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 @@ nfsrv_getuser(int procnum, uid_t uid, gid_t gid, char *name) 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 bd9807ca1ac..349fdecfc59 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 cc19ed6fa1d..858c52ec621 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 d5278612d8d..32dd7f3e997 100644 --- a/sys/nfs/nfs_diskless.c +++ b/sys/nfs/nfs_diskless.c @@ -54,6 +54,7 @@ #include #include #include +#include #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 @@ nfs_setup_diskless(void) 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);