diff --git a/sys/fs/nfs/nfs.h b/sys/fs/nfs/nfs.h index be60c1ca7753..578145952677 100644 --- a/sys/fs/nfs/nfs.h +++ b/sys/fs/nfs/nfs.h @@ -96,12 +96,6 @@ #define NFSSESSIONHASHSIZE 20 /* Size of server session hash table */ #endif #define NFSSTATEHASHSIZE 10 /* Size of server stateid hash table */ -#ifndef NFSUSERHASHSIZE -#define NFSUSERHASHSIZE 30 /* Size of user id hash table */ -#endif -#ifndef NFSGROUPHASHSIZE -#define NFSGROUPHASHSIZE 5 /* Size of group id hash table */ -#endif #ifndef NFSCLDELEGHIGHWATER #define NFSCLDELEGHIGHWATER 10000 /* limit for client delegations */ #endif @@ -204,6 +198,18 @@ struct nfsd_idargs { int nid_usertimeout;/* User name timeout (minutes) */ u_char *nid_name; /* Name */ int nid_namelen; /* and its length */ + gid_t *nid_grps; /* and the list */ + int nid_ngroup; /* Size of groups list */ +}; + +struct nfsd_oidargs { + int nid_flag; /* Flags (see below) */ + uid_t nid_uid; /* user/group id */ + gid_t nid_gid; + int nid_usermax; /* Upper bound on user name cache */ + int nid_usertimeout;/* User name timeout (minutes) */ + u_char *nid_name; /* Name */ + int nid_namelen; /* and its length */ }; struct nfsd_clid { diff --git a/sys/fs/nfs/nfs_commonport.c b/sys/fs/nfs/nfs_commonport.c index bc5d9de9bd5c..a4a4b7bd0294 100644 --- a/sys/fs/nfs/nfs_commonport.c +++ b/sys/fs/nfs/nfs_commonport.c @@ -63,6 +63,7 @@ int nfs_numnfscbd = 0; int nfscl_debuglevel = 0; char nfsv4_callbackaddr[INET6_ADDRSTRLEN]; struct callout newnfsd_callout; +int nfsrv_lughashsize = 100; void (*nfsd_call_servertimer)(void) = NULL; void (*ncl_call_invalcaches)(struct vnode *) = NULL; @@ -79,6 +80,9 @@ SYSCTL_STRING(_vfs_nfs, OID_AUTO, callback_addr, CTLFLAG_RW, "NFSv4 callback addr for server to use"); SYSCTL_INT(_vfs_nfs, OID_AUTO, debuglevel, CTLFLAG_RW, &nfscl_debuglevel, 0, "Debug level for NFS client"); +TUNABLE_INT("vfs.nfs.userhashsize", &nfsrv_lughashsize); +SYSCTL_INT(_vfs_nfs, OID_AUTO, userhashsize, CTLFLAG_RDTUN, &nfsrv_lughashsize, + 0, "Size of hash tables for uid/name mapping"); /* * Defines for malloc @@ -445,9 +449,25 @@ nfssvc_call(struct thread *p, struct nfssvc_args *uap, struct ucred *cred) { int error = EINVAL; struct nfsd_idargs nid; + struct nfsd_oidargs onid; if (uap->flag & NFSSVC_IDNAME) { - error = copyin(uap->argp, (caddr_t)&nid, sizeof (nid)); + if ((uap->flag & NFSSVC_NEWSTRUCT) != 0) + error = copyin(uap->argp, &nid, sizeof(nid)); + else { + error = copyin(uap->argp, &onid, sizeof(onid)); + if (error == 0) { + nid.nid_flag = onid.nid_flag; + nid.nid_uid = onid.nid_uid; + nid.nid_gid = onid.nid_gid; + nid.nid_usermax = onid.nid_usermax; + nid.nid_usertimeout = onid.nid_usertimeout; + nid.nid_name = onid.nid_name; + nid.nid_namelen = onid.nid_namelen; + nid.nid_ngroup = 0; + nid.nid_grps = NULL; + } + } if (error) goto out; error = nfssvc_idname(&nid); diff --git a/sys/fs/nfs/nfs_commonsubs.c b/sys/fs/nfs/nfs_commonsubs.c index 0f6db3281026..11db5a5c783b 100644 --- a/sys/fs/nfs/nfs_commonsubs.c +++ b/sys/fs/nfs/nfs_commonsubs.c @@ -44,6 +44,8 @@ __FBSDID("$FreeBSD$"); #include +#include + /* * Data items converted to xdr at startup, since they are constant * This is kinda hokey, but may save a little time doing byte swaps @@ -68,6 +70,7 @@ int ncl_mbuf_mlen = MLEN; int nfsd_enable_stringtouid = 0; NFSNAMEIDMUTEX; NFSSOCKMUTEX; +extern int nfsrv_lughashsize; /* * This array of structures indicates, for V4: @@ -154,11 +157,14 @@ static int nfsrv_usercnt = 0; static int nfsrv_dnsnamelen; static u_char *nfsrv_dnsname = NULL; static int nfsrv_usermax = 999999999; -static struct nfsuserhashhead nfsuserhash[NFSUSERHASHSIZE]; -static struct nfsuserhashhead nfsusernamehash[NFSUSERHASHSIZE]; -static struct nfsuserhashhead nfsgrouphash[NFSGROUPHASHSIZE]; -static struct nfsuserhashhead nfsgroupnamehash[NFSGROUPHASHSIZE]; -static struct nfsuserlruhead nfsuserlruhead; +struct nfsrv_lughash { + struct mtx mtx; + struct nfsuserhashhead lughead; +}; +static struct nfsrv_lughash *nfsuserhash; +static struct nfsrv_lughash *nfsusernamehash; +static struct nfsrv_lughash *nfsgrouphash; +static struct nfsrv_lughash *nfsgroupnamehash; /* * This static array indicates whether or not the RPC generates a large @@ -177,7 +183,7 @@ static void nfsv4_wanted(struct nfsv4lock *lp); static int nfsrv_cmpmixedcase(u_char *cp, u_char *cp2, int len); static int nfsrv_getuser(int procnum, uid_t uid, gid_t gid, char *name, NFSPROC_T *p); -static void nfsrv_removeuser(struct nfsusrgrp *usrp); +static void nfsrv_removeuser(struct nfsusrgrp *usrp, int isuser); static int nfsrv_getrefstr(struct nfsrv_descript *, u_char **, u_char **, int *, int *); static void nfsrv_refstrbigenough(int, u_char **, u_char **, int *); @@ -2548,18 +2554,17 @@ nfsv4_uidtostr(uid_t uid, u_char **cpp, int *retlenp, NFSPROC_T *p) u_char *cp = *cpp; uid_t tmp; int cnt, hasampersand, len = NFSV4_SMALLSTR, ret; + struct nfsrv_lughash *hp; cnt = 0; tryagain: - NFSLOCKNAMEID(); - if (nfsrv_dnsname) { + if (nfsrv_dnsnamelen > 0) { /* * Always map nfsrv_defaultuid to "nobody". */ if (uid == nfsrv_defaultuid) { i = nfsrv_dnsnamelen + 7; if (i > len) { - NFSUNLOCKNAMEID(); if (len > NFSV4_SMALLSTR) free(cp, M_NFSSTRING); cp = malloc(i, M_NFSSTRING, M_WAITOK); @@ -2571,11 +2576,12 @@ nfsv4_uidtostr(uid_t uid, u_char **cpp, int *retlenp, NFSPROC_T *p) NFSBCOPY("nobody@", cp, 7); cp += 7; NFSBCOPY(nfsrv_dnsname, cp, nfsrv_dnsnamelen); - NFSUNLOCKNAMEID(); return; } hasampersand = 0; - LIST_FOREACH(usrp, NFSUSERHASH(uid), lug_numhash) { + hp = NFSUSERHASH(uid); + mtx_lock(&hp->mtx); + TAILQ_FOREACH(usrp, &hp->lughead, lug_numhash) { if (usrp->lug_uid == uid) { if (usrp->lug_expiry < NFSD_MONOSEC) break; @@ -2595,7 +2601,7 @@ nfsv4_uidtostr(uid_t uid, u_char **cpp, int *retlenp, NFSPROC_T *p) i = usrp->lug_namelen + nfsrv_dnsnamelen + 1; if (i > len) { - NFSUNLOCKNAMEID(); + mtx_unlock(&hp->mtx); if (len > NFSV4_SMALLSTR) free(cp, M_NFSSTRING); cp = malloc(i, M_NFSSTRING, M_WAITOK); @@ -2610,20 +2616,19 @@ nfsv4_uidtostr(uid_t uid, u_char **cpp, int *retlenp, NFSPROC_T *p) *cp++ = '@'; NFSBCOPY(nfsrv_dnsname, cp, nfsrv_dnsnamelen); } - TAILQ_REMOVE(&nfsuserlruhead, usrp, lug_lru); - TAILQ_INSERT_TAIL(&nfsuserlruhead, usrp, lug_lru); - NFSUNLOCKNAMEID(); + TAILQ_REMOVE(&hp->lughead, usrp, lug_numhash); + TAILQ_INSERT_TAIL(&hp->lughead, usrp, + lug_numhash); + mtx_unlock(&hp->mtx); return; } } - NFSUNLOCKNAMEID(); + mtx_unlock(&hp->mtx); cnt++; ret = nfsrv_getuser(RPCNFSUSERD_GETUID, uid, (gid_t)0, NULL, p); if (ret == 0 && cnt < 2) goto tryagain; - } else { - NFSUNLOCKNAMEID(); } /* @@ -2646,6 +2651,52 @@ nfsv4_uidtostr(uid_t uid, u_char **cpp, int *retlenp, NFSPROC_T *p) return; } +/* + * Get a credential for the uid with the server's group list. + * If none is found, just return the credential passed in after + * logging a warning message. + */ +struct ucred * +nfsrv_getgrpscred(struct ucred *oldcred) +{ + struct nfsusrgrp *usrp; + struct ucred *newcred; + int cnt, ret; + uid_t uid; + struct nfsrv_lughash *hp; + + cnt = 0; + uid = oldcred->cr_uid; +tryagain: + if (nfsrv_dnsnamelen > 0) { + hp = NFSUSERHASH(uid); + mtx_lock(&hp->mtx); + TAILQ_FOREACH(usrp, &hp->lughead, lug_numhash) { + if (usrp->lug_uid == uid) { + if (usrp->lug_expiry < NFSD_MONOSEC) + break; + if (usrp->lug_cred != NULL) { + newcred = crhold(usrp->lug_cred); + crfree(oldcred); + } else + newcred = oldcred; + TAILQ_REMOVE(&hp->lughead, usrp, lug_numhash); + TAILQ_INSERT_TAIL(&hp->lughead, usrp, + lug_numhash); + mtx_unlock(&hp->mtx); + return (newcred); + } + } + mtx_unlock(&hp->mtx); + cnt++; + ret = nfsrv_getuser(RPCNFSUSERD_GETUID, uid, (gid_t)0, + NULL, curthread); + if (ret == 0 && cnt < 2) + goto tryagain; + } + return (oldcred); +} + /* * Convert a string to a uid. * If no conversion is possible return NFSERR_BADOWNER, otherwise @@ -2664,6 +2715,7 @@ nfsv4_strtouid(struct nfsrv_descript *nd, u_char *str, int len, uid_t *uidp, int cnt, ret; int error = 0; uid_t tuid; + struct nfsrv_lughash *hp, *hp2; if (len == 0) { error = NFSERR_BADOWNER; @@ -2693,49 +2745,55 @@ nfsv4_strtouid(struct nfsrv_descript *nd, u_char *str, int len, uid_t *uidp, cnt = 0; tryagain: - NFSLOCKNAMEID(); - /* - * If an '@' is found and the domain name matches, search for the name - * with dns stripped off. - * Mixed case alpahbetics will match for the domain name, but all - * upper case will not. - */ - if (cnt == 0 && i < len && i > 0 && nfsrv_dnsname && - (len - 1 - i) == nfsrv_dnsnamelen && - !nfsrv_cmpmixedcase(cp, nfsrv_dnsname, nfsrv_dnsnamelen)) { - len -= (nfsrv_dnsnamelen + 1); - *(cp - 1) = '\0'; - } - - /* - * Check for the special case of "nobody". - */ - if (len == 6 && !NFSBCMP(str, "nobody", 6)) { - *uidp = nfsrv_defaultuid; - NFSUNLOCKNAMEID(); - error = 0; - goto out; - } - - LIST_FOREACH(usrp, NFSUSERNAMEHASH(str, len), lug_namehash) { - if (usrp->lug_namelen == len && - !NFSBCMP(usrp->lug_name, str, len)) { - if (usrp->lug_expiry < NFSD_MONOSEC) - break; - *uidp = usrp->lug_uid; - TAILQ_REMOVE(&nfsuserlruhead, usrp, lug_lru); - TAILQ_INSERT_TAIL(&nfsuserlruhead, usrp, lug_lru); - NFSUNLOCKNAMEID(); + if (nfsrv_dnsnamelen > 0) { + /* + * If an '@' is found and the domain name matches, search for + * the name with dns stripped off. + * Mixed case alpahbetics will match for the domain name, but + * all upper case will not. + */ + if (cnt == 0 && i < len && i > 0 && + (len - 1 - i) == nfsrv_dnsnamelen && + !nfsrv_cmpmixedcase(cp, nfsrv_dnsname, nfsrv_dnsnamelen)) { + len -= (nfsrv_dnsnamelen + 1); + *(cp - 1) = '\0'; + } + + /* + * Check for the special case of "nobody". + */ + if (len == 6 && !NFSBCMP(str, "nobody", 6)) { + *uidp = nfsrv_defaultuid; error = 0; goto out; } + + hp = NFSUSERNAMEHASH(str, len); + mtx_lock(&hp->mtx); + TAILQ_FOREACH(usrp, &hp->lughead, lug_namehash) { + if (usrp->lug_namelen == len && + !NFSBCMP(usrp->lug_name, str, len)) { + if (usrp->lug_expiry < NFSD_MONOSEC) + break; + hp2 = NFSUSERHASH(usrp->lug_uid); + mtx_lock(&hp2->mtx); + TAILQ_REMOVE(&hp2->lughead, usrp, lug_numhash); + TAILQ_INSERT_TAIL(&hp2->lughead, usrp, + lug_numhash); + *uidp = usrp->lug_uid; + mtx_unlock(&hp2->mtx); + mtx_unlock(&hp->mtx); + error = 0; + goto out; + } + } + mtx_unlock(&hp->mtx); + cnt++; + ret = nfsrv_getuser(RPCNFSUSERD_GETUSER, (uid_t)0, (gid_t)0, + str, p); + if (ret == 0 && cnt < 2) + goto tryagain; } - NFSUNLOCKNAMEID(); - cnt++; - ret = nfsrv_getuser(RPCNFSUSERD_GETUSER, (uid_t)0, (gid_t)0, - str, p); - if (ret == 0 && cnt < 2) - goto tryagain; error = NFSERR_BADOWNER; out: @@ -2758,18 +2816,17 @@ nfsv4_gidtostr(gid_t gid, u_char **cpp, int *retlenp, NFSPROC_T *p) u_char *cp = *cpp; gid_t tmp; int cnt, hasampersand, len = NFSV4_SMALLSTR, ret; + struct nfsrv_lughash *hp; cnt = 0; tryagain: - NFSLOCKNAMEID(); - if (nfsrv_dnsname) { + if (nfsrv_dnsnamelen > 0) { /* * Always map nfsrv_defaultgid to "nogroup". */ if (gid == nfsrv_defaultgid) { i = nfsrv_dnsnamelen + 8; if (i > len) { - NFSUNLOCKNAMEID(); if (len > NFSV4_SMALLSTR) free(cp, M_NFSSTRING); cp = malloc(i, M_NFSSTRING, M_WAITOK); @@ -2781,11 +2838,12 @@ nfsv4_gidtostr(gid_t gid, u_char **cpp, int *retlenp, NFSPROC_T *p) NFSBCOPY("nogroup@", cp, 8); cp += 8; NFSBCOPY(nfsrv_dnsname, cp, nfsrv_dnsnamelen); - NFSUNLOCKNAMEID(); return; } hasampersand = 0; - LIST_FOREACH(usrp, NFSGROUPHASH(gid), lug_numhash) { + hp = NFSGROUPHASH(gid); + mtx_lock(&hp->mtx); + TAILQ_FOREACH(usrp, &hp->lughead, lug_numhash) { if (usrp->lug_gid == gid) { if (usrp->lug_expiry < NFSD_MONOSEC) break; @@ -2805,7 +2863,7 @@ nfsv4_gidtostr(gid_t gid, u_char **cpp, int *retlenp, NFSPROC_T *p) i = usrp->lug_namelen + nfsrv_dnsnamelen + 1; if (i > len) { - NFSUNLOCKNAMEID(); + mtx_unlock(&hp->mtx); if (len > NFSV4_SMALLSTR) free(cp, M_NFSSTRING); cp = malloc(i, M_NFSSTRING, M_WAITOK); @@ -2820,20 +2878,19 @@ nfsv4_gidtostr(gid_t gid, u_char **cpp, int *retlenp, NFSPROC_T *p) *cp++ = '@'; NFSBCOPY(nfsrv_dnsname, cp, nfsrv_dnsnamelen); } - TAILQ_REMOVE(&nfsuserlruhead, usrp, lug_lru); - TAILQ_INSERT_TAIL(&nfsuserlruhead, usrp, lug_lru); - NFSUNLOCKNAMEID(); + TAILQ_REMOVE(&hp->lughead, usrp, lug_numhash); + TAILQ_INSERT_TAIL(&hp->lughead, usrp, + lug_numhash); + mtx_unlock(&hp->mtx); return; } } - NFSUNLOCKNAMEID(); + mtx_unlock(&hp->mtx); cnt++; ret = nfsrv_getuser(RPCNFSUSERD_GETGID, (uid_t)0, gid, NULL, p); if (ret == 0 && cnt < 2) goto tryagain; - } else { - NFSUNLOCKNAMEID(); } /* @@ -2874,6 +2931,7 @@ nfsv4_strtogid(struct nfsrv_descript *nd, u_char *str, int len, gid_t *gidp, int cnt, ret; int error = 0; gid_t tgid; + struct nfsrv_lughash *hp, *hp2; if (len == 0) { error = NFSERR_BADOWNER; @@ -2903,47 +2961,53 @@ nfsv4_strtogid(struct nfsrv_descript *nd, u_char *str, int len, gid_t *gidp, cnt = 0; tryagain: - NFSLOCKNAMEID(); - /* - * If an '@' is found and the dns name matches, search for the name - * with the dns stripped off. - */ - if (cnt == 0 && i < len && i > 0 && nfsrv_dnsname && - (len - 1 - i) == nfsrv_dnsnamelen && - !nfsrv_cmpmixedcase(cp, nfsrv_dnsname, nfsrv_dnsnamelen)) { - len -= (nfsrv_dnsnamelen + 1); - *(cp - 1) = '\0'; - } - - /* - * Check for the special case of "nogroup". - */ - if (len == 7 && !NFSBCMP(str, "nogroup", 7)) { - *gidp = nfsrv_defaultgid; - NFSUNLOCKNAMEID(); - error = 0; - goto out; - } - - LIST_FOREACH(usrp, NFSGROUPNAMEHASH(str, len), lug_namehash) { - if (usrp->lug_namelen == len && - !NFSBCMP(usrp->lug_name, str, len)) { - if (usrp->lug_expiry < NFSD_MONOSEC) - break; - *gidp = usrp->lug_gid; - TAILQ_REMOVE(&nfsuserlruhead, usrp, lug_lru); - TAILQ_INSERT_TAIL(&nfsuserlruhead, usrp, lug_lru); - NFSUNLOCKNAMEID(); + if (nfsrv_dnsnamelen > 0) { + /* + * If an '@' is found and the dns name matches, search for the + * name with the dns stripped off. + */ + if (cnt == 0 && i < len && i > 0 && + (len - 1 - i) == nfsrv_dnsnamelen && + !nfsrv_cmpmixedcase(cp, nfsrv_dnsname, nfsrv_dnsnamelen)) { + len -= (nfsrv_dnsnamelen + 1); + *(cp - 1) = '\0'; + } + + /* + * Check for the special case of "nogroup". + */ + if (len == 7 && !NFSBCMP(str, "nogroup", 7)) { + *gidp = nfsrv_defaultgid; error = 0; goto out; } + + hp = NFSGROUPNAMEHASH(str, len); + mtx_lock(&hp->mtx); + TAILQ_FOREACH(usrp, &hp->lughead, lug_namehash) { + if (usrp->lug_namelen == len && + !NFSBCMP(usrp->lug_name, str, len)) { + if (usrp->lug_expiry < NFSD_MONOSEC) + break; + hp2 = NFSGROUPHASH(usrp->lug_gid); + mtx_lock(&hp2->mtx); + TAILQ_REMOVE(&hp2->lughead, usrp, lug_numhash); + TAILQ_INSERT_TAIL(&hp2->lughead, usrp, + lug_numhash); + *gidp = usrp->lug_gid; + mtx_unlock(&hp2->mtx); + mtx_unlock(&hp->mtx); + error = 0; + goto out; + } + } + mtx_unlock(&hp->mtx); + cnt++; + ret = nfsrv_getuser(RPCNFSUSERD_GETGROUP, (uid_t)0, (gid_t)0, + str, p); + if (ret == 0 && cnt < 2) + goto tryagain; } - NFSUNLOCKNAMEID(); - cnt++; - ret = nfsrv_getuser(RPCNFSUSERD_GETGROUP, (uid_t)0, (gid_t)0, - str, p); - if (ret == 0 && cnt < 2) - goto tryagain; error = NFSERR_BADOWNER; out: @@ -3101,111 +3165,218 @@ APPLESTATIC int nfssvc_idname(struct nfsd_idargs *nidp) { struct nfsusrgrp *nusrp, *usrp, *newusrp; - struct nfsuserhashhead *hp; - int i; + struct nfsrv_lughash *hp_name, *hp_idnum, *thp; + int i, group_locked, groupname_locked, user_locked, username_locked; int error = 0; u_char *cp; + gid_t *grps; + struct ucred *cr; + static int onethread = 0; + static time_t lasttime = 0; if (nidp->nid_flag & NFSID_INITIALIZE) { - cp = (u_char *)malloc(nidp->nid_namelen + 1, - M_NFSSTRING, M_WAITOK); - error = copyin(CAST_USER_ADDR_T(nidp->nid_name), cp, - nidp->nid_namelen); - NFSLOCKNAMEID(); - if (nfsrv_dnsname) { - /* - * Free up all the old stuff and reinitialize hash lists. - */ - TAILQ_FOREACH_SAFE(usrp, &nfsuserlruhead, lug_lru, nusrp) { - nfsrv_removeuser(usrp); + cp = malloc(nidp->nid_namelen + 1, M_NFSSTRING, M_WAITOK); + error = copyin(CAST_USER_ADDR_T(nidp->nid_name), cp, + nidp->nid_namelen); + if (error != 0) { + free(cp, M_NFSSTRING); + goto out; } - free(nfsrv_dnsname, M_NFSSTRING); - nfsrv_dnsname = NULL; - } - TAILQ_INIT(&nfsuserlruhead); - for (i = 0; i < NFSUSERHASHSIZE; i++) - LIST_INIT(&nfsuserhash[i]); - for (i = 0; i < NFSGROUPHASHSIZE; i++) - LIST_INIT(&nfsgrouphash[i]); - for (i = 0; i < NFSUSERHASHSIZE; i++) - LIST_INIT(&nfsusernamehash[i]); - for (i = 0; i < NFSGROUPHASHSIZE; i++) - LIST_INIT(&nfsgroupnamehash[i]); + if (atomic_cmpset_acq_int(&nfsrv_dnsnamelen, 0, 0) == 0) { + /* + * Free up all the old stuff and reinitialize hash + * lists. All mutexes for both lists must be locked, + * with the user/group name ones before the uid/gid + * ones, to avoid a LOR. + */ + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_lock(&nfsusernamehash[i].mtx); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_lock(&nfsuserhash[i].mtx); + for (i = 0; i < nfsrv_lughashsize; i++) + TAILQ_FOREACH_SAFE(usrp, + &nfsuserhash[i].lughead, lug_numhash, nusrp) + nfsrv_removeuser(usrp, 1); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_unlock(&nfsuserhash[i].mtx); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_unlock(&nfsusernamehash[i].mtx); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_lock(&nfsgroupnamehash[i].mtx); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_lock(&nfsgrouphash[i].mtx); + for (i = 0; i < nfsrv_lughashsize; i++) + TAILQ_FOREACH_SAFE(usrp, + &nfsgrouphash[i].lughead, lug_numhash, + nusrp) + nfsrv_removeuser(usrp, 0); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_unlock(&nfsgrouphash[i].mtx); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_unlock(&nfsgroupnamehash[i].mtx); + free(nfsrv_dnsname, M_NFSSTRING); + nfsrv_dnsname = NULL; + } + if (nfsuserhash == NULL) { + /* Allocate the hash tables. */ + nfsuserhash = malloc(sizeof(struct nfsrv_lughash) * + nfsrv_lughashsize, M_NFSUSERGROUP, M_WAITOK | + M_ZERO); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_init(&nfsuserhash[i].mtx, "nfsuidhash", + NULL, MTX_DEF | MTX_DUPOK); + nfsusernamehash = malloc(sizeof(struct nfsrv_lughash) * + nfsrv_lughashsize, M_NFSUSERGROUP, M_WAITOK | + M_ZERO); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_init(&nfsusernamehash[i].mtx, + "nfsusrhash", NULL, MTX_DEF | + MTX_DUPOK); + nfsgrouphash = malloc(sizeof(struct nfsrv_lughash) * + nfsrv_lughashsize, M_NFSUSERGROUP, M_WAITOK | + M_ZERO); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_init(&nfsgrouphash[i].mtx, "nfsgidhash", + NULL, MTX_DEF | MTX_DUPOK); + nfsgroupnamehash = malloc(sizeof(struct nfsrv_lughash) * + nfsrv_lughashsize, M_NFSUSERGROUP, M_WAITOK | + M_ZERO); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_init(&nfsgroupnamehash[i].mtx, + "nfsgrphash", NULL, MTX_DEF | MTX_DUPOK); + } + /* (Re)initialize the list heads. */ + for (i = 0; i < nfsrv_lughashsize; i++) + TAILQ_INIT(&nfsuserhash[i].lughead); + for (i = 0; i < nfsrv_lughashsize; i++) + TAILQ_INIT(&nfsusernamehash[i].lughead); + for (i = 0; i < nfsrv_lughashsize; i++) + TAILQ_INIT(&nfsgrouphash[i].lughead); + for (i = 0; i < nfsrv_lughashsize; i++) + TAILQ_INIT(&nfsgroupnamehash[i].lughead); - /* - * Put name in "DNS" string. - */ - if (!error) { + /* + * Put name in "DNS" string. + */ nfsrv_dnsname = cp; - nfsrv_dnsnamelen = nidp->nid_namelen; nfsrv_defaultuid = nidp->nid_uid; nfsrv_defaultgid = nidp->nid_gid; nfsrv_usercnt = 0; nfsrv_usermax = nidp->nid_usermax; - } - NFSUNLOCKNAMEID(); - if (error) - free(cp, M_NFSSTRING); - goto out; + atomic_store_rel_int(&nfsrv_dnsnamelen, nidp->nid_namelen); + goto out; } /* * malloc the new one now, so any potential sleep occurs before * manipulation of the lists. */ - MALLOC(newusrp, struct nfsusrgrp *, sizeof (struct nfsusrgrp) + - nidp->nid_namelen, M_NFSUSERGROUP, M_WAITOK); + newusrp = malloc(sizeof(struct nfsusrgrp) + nidp->nid_namelen, + M_NFSUSERGROUP, M_WAITOK | M_ZERO); error = copyin(CAST_USER_ADDR_T(nidp->nid_name), newusrp->lug_name, nidp->nid_namelen); + if (error == 0 && nidp->nid_ngroup > 0 && + (nidp->nid_flag & NFSID_ADDUID) != 0) { + grps = malloc(sizeof(gid_t) * nidp->nid_ngroup, M_TEMP, + M_WAITOK); + error = copyin(CAST_USER_ADDR_T(nidp->nid_grps), grps, + sizeof(gid_t) * nidp->nid_ngroup); + if (error == 0) { + /* + * Create a credential just like svc_getcred(), + * but using the group list provided. + */ + cr = crget(); + cr->cr_uid = cr->cr_ruid = cr->cr_svuid = nidp->nid_uid; + crsetgroups(cr, nidp->nid_ngroup, grps); + cr->cr_rgid = cr->cr_svgid = cr->cr_groups[0]; + cr->cr_prison = &prison0; + prison_hold(cr->cr_prison); +#ifdef MAC + mac_cred_associate_nfsd(cr); +#endif + newusrp->lug_cred = cr; + } + free(grps, M_TEMP); + } if (error) { - free((caddr_t)newusrp, M_NFSUSERGROUP); + free(newusrp, M_NFSUSERGROUP); goto out; } newusrp->lug_namelen = nidp->nid_namelen; - NFSLOCKNAMEID(); + /* + * The lock order is username[0]->[nfsrv_lughashsize - 1] followed + * by uid[0]->[nfsrv_lughashsize - 1], with the same for group. + * The flags user_locked, username_locked, group_locked and + * groupname_locked are set to indicate all of those hash lists are + * locked. hp_name != NULL and hp_idnum != NULL indicates that + * the respective one mutex is locked. + */ + user_locked = username_locked = group_locked = groupname_locked = 0; + hp_name = hp_idnum = NULL; + /* * Delete old entries, as required. */ if (nidp->nid_flag & (NFSID_DELUID | NFSID_ADDUID)) { - hp = NFSUSERHASH(nidp->nid_uid); - LIST_FOREACH_SAFE(usrp, hp, lug_numhash, nusrp) { + /* Must lock all username hash lists first, to avoid a LOR. */ + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_lock(&nfsusernamehash[i].mtx); + username_locked = 1; + hp_idnum = NFSUSERHASH(nidp->nid_uid); + mtx_lock(&hp_idnum->mtx); + TAILQ_FOREACH_SAFE(usrp, &hp_idnum->lughead, lug_numhash, + nusrp) { if (usrp->lug_uid == nidp->nid_uid) - nfsrv_removeuser(usrp); + nfsrv_removeuser(usrp, 1); } - } - if (nidp->nid_flag & (NFSID_DELUSERNAME | NFSID_ADDUSERNAME)) { - hp = NFSUSERNAMEHASH(newusrp->lug_name, newusrp->lug_namelen); - LIST_FOREACH_SAFE(usrp, hp, lug_namehash, nusrp) { + } else if (nidp->nid_flag & (NFSID_DELUSERNAME | NFSID_ADDUSERNAME)) { + hp_name = NFSUSERNAMEHASH(newusrp->lug_name, + newusrp->lug_namelen); + mtx_lock(&hp_name->mtx); + TAILQ_FOREACH_SAFE(usrp, &hp_name->lughead, lug_namehash, + nusrp) { if (usrp->lug_namelen == newusrp->lug_namelen && !NFSBCMP(usrp->lug_name, newusrp->lug_name, - usrp->lug_namelen)) - nfsrv_removeuser(usrp); + usrp->lug_namelen)) { + thp = NFSUSERHASH(usrp->lug_uid); + mtx_lock(&thp->mtx); + nfsrv_removeuser(usrp, 1); + mtx_unlock(&thp->mtx); + } } - } - if (nidp->nid_flag & (NFSID_DELGID | NFSID_ADDGID)) { - hp = NFSGROUPHASH(nidp->nid_gid); - LIST_FOREACH_SAFE(usrp, hp, lug_numhash, nusrp) { + hp_idnum = NFSUSERHASH(nidp->nid_uid); + mtx_lock(&hp_idnum->mtx); + } else if (nidp->nid_flag & (NFSID_DELGID | NFSID_ADDGID)) { + /* Must lock all groupname hash lists first, to avoid a LOR. */ + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_lock(&nfsgroupnamehash[i].mtx); + groupname_locked = 1; + hp_idnum = NFSGROUPHASH(nidp->nid_gid); + mtx_lock(&hp_idnum->mtx); + TAILQ_FOREACH_SAFE(usrp, &hp_idnum->lughead, lug_numhash, + nusrp) { if (usrp->lug_gid == nidp->nid_gid) - nfsrv_removeuser(usrp); + nfsrv_removeuser(usrp, 0); } - } - if (nidp->nid_flag & (NFSID_DELGROUPNAME | NFSID_ADDGROUPNAME)) { - hp = NFSGROUPNAMEHASH(newusrp->lug_name, newusrp->lug_namelen); - LIST_FOREACH_SAFE(usrp, hp, lug_namehash, nusrp) { + } else if (nidp->nid_flag & (NFSID_DELGROUPNAME | NFSID_ADDGROUPNAME)) { + hp_name = NFSGROUPNAMEHASH(newusrp->lug_name, + newusrp->lug_namelen); + mtx_lock(&hp_name->mtx); + TAILQ_FOREACH_SAFE(usrp, &hp_name->lughead, lug_namehash, + nusrp) { if (usrp->lug_namelen == newusrp->lug_namelen && !NFSBCMP(usrp->lug_name, newusrp->lug_name, - usrp->lug_namelen)) - nfsrv_removeuser(usrp); + usrp->lug_namelen)) { + thp = NFSGROUPHASH(usrp->lug_gid); + mtx_lock(&thp->mtx); + nfsrv_removeuser(usrp, 0); + mtx_unlock(&thp->mtx); + } } - } - TAILQ_FOREACH_SAFE(usrp, &nfsuserlruhead, lug_lru, nusrp) { - if (usrp->lug_expiry < NFSD_MONOSEC) - nfsrv_removeuser(usrp); - } - while (nfsrv_usercnt >= nfsrv_usermax) { - usrp = TAILQ_FIRST(&nfsuserlruhead); - nfsrv_removeuser(usrp); + hp_idnum = NFSGROUPHASH(nidp->nid_gid); + mtx_lock(&hp_idnum->mtx); } /* @@ -3217,23 +3388,129 @@ nfssvc_idname(struct nfsd_idargs *nidp) newusrp->lug_expiry = NFSD_MONOSEC + 5; if (nidp->nid_flag & (NFSID_ADDUID | NFSID_ADDUSERNAME)) { newusrp->lug_uid = nidp->nid_uid; - LIST_INSERT_HEAD(NFSUSERHASH(newusrp->lug_uid), newusrp, - lug_numhash); - LIST_INSERT_HEAD(NFSUSERNAMEHASH(newusrp->lug_name, - newusrp->lug_namelen), newusrp, lug_namehash); - TAILQ_INSERT_TAIL(&nfsuserlruhead, newusrp, lug_lru); - nfsrv_usercnt++; + thp = NFSUSERHASH(newusrp->lug_uid); + mtx_assert(&thp->mtx, MA_OWNED); + TAILQ_INSERT_TAIL(&thp->lughead, newusrp, lug_numhash); + thp = NFSUSERNAMEHASH(newusrp->lug_name, newusrp->lug_namelen); + mtx_assert(&thp->mtx, MA_OWNED); + TAILQ_INSERT_TAIL(&thp->lughead, newusrp, lug_namehash); + atomic_add_int(&nfsrv_usercnt, 1); } else if (nidp->nid_flag & (NFSID_ADDGID | NFSID_ADDGROUPNAME)) { newusrp->lug_gid = nidp->nid_gid; - LIST_INSERT_HEAD(NFSGROUPHASH(newusrp->lug_gid), newusrp, - lug_numhash); - LIST_INSERT_HEAD(NFSGROUPNAMEHASH(newusrp->lug_name, - newusrp->lug_namelen), newusrp, lug_namehash); - TAILQ_INSERT_TAIL(&nfsuserlruhead, newusrp, lug_lru); - nfsrv_usercnt++; - } else - FREE((caddr_t)newusrp, M_NFSUSERGROUP); - NFSUNLOCKNAMEID(); + thp = NFSGROUPHASH(newusrp->lug_gid); + mtx_assert(&thp->mtx, MA_OWNED); + TAILQ_INSERT_TAIL(&thp->lughead, newusrp, lug_numhash); + thp = NFSGROUPNAMEHASH(newusrp->lug_name, newusrp->lug_namelen); + mtx_assert(&thp->mtx, MA_OWNED); + TAILQ_INSERT_TAIL(&thp->lughead, newusrp, lug_namehash); + atomic_add_int(&nfsrv_usercnt, 1); + } else { + if (newusrp->lug_cred != NULL) + crfree(newusrp->lug_cred); + free(newusrp, M_NFSUSERGROUP); + } + + /* + * Once per second, allow one thread to trim the cache. + */ + if (lasttime < NFSD_MONOSEC && + atomic_cmpset_acq_int(&onethread, 0, 1) != 0) { + /* + * First, unlock the single mutexes, so that all entries + * can be locked and any LOR is avoided. + */ + if (hp_name != NULL) { + mtx_unlock(&hp_name->mtx); + hp_name = NULL; + } + if (hp_idnum != NULL) { + mtx_unlock(&hp_idnum->mtx); + hp_idnum = NULL; + } + + if ((nidp->nid_flag & (NFSID_DELUID | NFSID_ADDUID | + NFSID_DELUSERNAME | NFSID_ADDUSERNAME)) != 0) { + if (username_locked == 0) { + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_lock(&nfsusernamehash[i].mtx); + username_locked = 1; + } + KASSERT(user_locked == 0, + ("nfssvc_idname: user_locked")); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_lock(&nfsuserhash[i].mtx); + user_locked = 1; + for (i = 0; i < nfsrv_lughashsize; i++) { + TAILQ_FOREACH_SAFE(usrp, + &nfsuserhash[i].lughead, lug_numhash, + nusrp) + if (usrp->lug_expiry < NFSD_MONOSEC) + nfsrv_removeuser(usrp, 1); + } + for (i = 0; i < nfsrv_lughashsize; i++) { + /* + * Trim the cache using an approximate LRU + * algorithm. This code deletes the least + * recently used entry on each hash list. + */ + if (nfsrv_usercnt <= nfsrv_usermax) + break; + usrp = TAILQ_FIRST(&nfsuserhash[i].lughead); + if (usrp != NULL) + nfsrv_removeuser(usrp, 1); + } + } else { + if (groupname_locked == 0) { + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_lock(&nfsgroupnamehash[i].mtx); + groupname_locked = 1; + } + KASSERT(group_locked == 0, + ("nfssvc_idname: group_locked")); + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_lock(&nfsgrouphash[i].mtx); + group_locked = 1; + for (i = 0; i < nfsrv_lughashsize; i++) { + TAILQ_FOREACH_SAFE(usrp, + &nfsgrouphash[i].lughead, lug_numhash, + nusrp) + if (usrp->lug_expiry < NFSD_MONOSEC) + nfsrv_removeuser(usrp, 0); + } + for (i = 0; i < nfsrv_lughashsize; i++) { + /* + * Trim the cache using an approximate LRU + * algorithm. This code deletes the least + * recently user entry on each hash list. + */ + if (nfsrv_usercnt <= nfsrv_usermax) + break; + usrp = TAILQ_FIRST(&nfsgrouphash[i].lughead); + if (usrp != NULL) + nfsrv_removeuser(usrp, 0); + } + } + lasttime = NFSD_MONOSEC; + atomic_store_rel_int(&onethread, 0); + } + + /* Now, unlock all locked mutexes. */ + if (hp_idnum != NULL) + mtx_unlock(&hp_idnum->mtx); + if (hp_name != NULL) + mtx_unlock(&hp_name->mtx); + if (user_locked != 0) + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_unlock(&nfsuserhash[i].mtx); + if (username_locked != 0) + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_unlock(&nfsusernamehash[i].mtx); + if (group_locked != 0) + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_unlock(&nfsgrouphash[i].mtx); + if (groupname_locked != 0) + for (i = 0; i < nfsrv_lughashsize; i++) + mtx_unlock(&nfsgroupnamehash[i].mtx); out: NFSEXITCODE(error); return (error); @@ -3243,15 +3520,29 @@ nfssvc_idname(struct nfsd_idargs *nidp) * Remove a user/group name element. */ static void -nfsrv_removeuser(struct nfsusrgrp *usrp) +nfsrv_removeuser(struct nfsusrgrp *usrp, int isuser) { + struct nfsrv_lughash *hp; - NFSNAMEIDREQUIRED(); - LIST_REMOVE(usrp, lug_numhash); - LIST_REMOVE(usrp, lug_namehash); - TAILQ_REMOVE(&nfsuserlruhead, usrp, lug_lru); - nfsrv_usercnt--; - FREE((caddr_t)usrp, M_NFSUSERGROUP); + if (isuser != 0) { + hp = NFSUSERHASH(usrp->lug_uid); + mtx_assert(&hp->mtx, MA_OWNED); + TAILQ_REMOVE(&hp->lughead, usrp, lug_numhash); + hp = NFSUSERNAMEHASH(usrp->lug_name, usrp->lug_namelen); + mtx_assert(&hp->mtx, MA_OWNED); + TAILQ_REMOVE(&hp->lughead, usrp, lug_namehash); + } else { + hp = NFSGROUPHASH(usrp->lug_gid); + mtx_assert(&hp->mtx, MA_OWNED); + TAILQ_REMOVE(&hp->lughead, usrp, lug_numhash); + hp = NFSGROUPNAMEHASH(usrp->lug_name, usrp->lug_namelen); + mtx_assert(&hp->mtx, MA_OWNED); + TAILQ_REMOVE(&hp->lughead, usrp, lug_namehash); + } + atomic_add_int(&nfsrv_usercnt, -1); + if (usrp->lug_cred != NULL) + crfree(usrp->lug_cred); + free(usrp, M_NFSUSERGROUP); } /* diff --git a/sys/fs/nfs/nfs_var.h b/sys/fs/nfs/nfs_var.h index ce771575a01f..e08835c12477 100644 --- a/sys/fs/nfs/nfs_var.h +++ b/sys/fs/nfs/nfs_var.h @@ -296,6 +296,7 @@ void nfsv4_setsequence(struct nfsmount *, struct nfsrv_descript *, int nfsv4_sequencelookup(struct nfsmount *, struct nfsclsession *, int *, int *, uint32_t *, uint8_t *); void nfsv4_freeslot(struct nfsclsession *, int); +struct ucred *nfsrv_getgrpscred(struct ucred *); /* nfs_clcomsubs.c */ void nfsm_uiombuf(struct nfsrv_descript *, struct uio *, int); diff --git a/sys/fs/nfs/nfsrvstate.h b/sys/fs/nfs/nfsrvstate.h index 972fffe15eb9..6d32244db513 100644 --- a/sys/fs/nfs/nfsrvstate.h +++ b/sys/fs/nfs/nfsrvstate.h @@ -48,23 +48,22 @@ LIST_HEAD(nfssessionhashhead, nfsdsession); /* * List head for nfsusrgrp. */ -LIST_HEAD(nfsuserhashhead, nfsusrgrp); -TAILQ_HEAD(nfsuserlruhead, nfsusrgrp); +TAILQ_HEAD(nfsuserhashhead, nfsusrgrp); #define NFSCLIENTHASH(id) \ (&nfsclienthash[(id).lval[1] % nfsrv_clienthashsize]) #define NFSSTATEHASH(clp, id) \ (&((clp)->lc_stateid[(id).other[2] % nfsrv_statehashsize])) #define NFSUSERHASH(id) \ - (&nfsuserhash[(id) % NFSUSERHASHSIZE]) + (&nfsuserhash[(id) % nfsrv_lughashsize]) #define NFSUSERNAMEHASH(p, l) \ (&nfsusernamehash[((l)>=4?(*(p)+*((p)+1)+*((p)+2)+*((p)+3)):*(p)) \ - % NFSUSERHASHSIZE]) + % nfsrv_lughashsize]) #define NFSGROUPHASH(id) \ - (&nfsgrouphash[(id) % NFSGROUPHASHSIZE]) + (&nfsgrouphash[(id) % nfsrv_lughashsize]) #define NFSGROUPNAMEHASH(p, l) \ (&nfsgroupnamehash[((l)>=4?(*(p)+*((p)+1)+*((p)+2)+*((p)+3)):*(p)) \ - % NFSGROUPHASHSIZE]) + % nfsrv_lughashsize]) struct nfssessionhash { struct mtx mtx; @@ -264,14 +263,14 @@ struct nfslockfile { * names. */ struct nfsusrgrp { - TAILQ_ENTRY(nfsusrgrp) lug_lru; /* LRU list */ - LIST_ENTRY(nfsusrgrp) lug_numhash; /* Hash by id# */ - LIST_ENTRY(nfsusrgrp) lug_namehash; /* and by name */ + TAILQ_ENTRY(nfsusrgrp) lug_numhash; /* Hash by id# */ + TAILQ_ENTRY(nfsusrgrp) lug_namehash; /* and by name */ time_t lug_expiry; /* Expiry time in sec */ union { uid_t un_uid; /* id# */ gid_t un_gid; } lug_un; + struct ucred *lug_cred; /* Cred. with groups list */ int lug_namelen; /* Name length */ u_char lug_name[1]; /* malloc'd correct length */ }; diff --git a/sys/fs/nfsserver/nfs_nfsdport.c b/sys/fs/nfsserver/nfs_nfsdport.c index 85b4466575af..edbcb355e074 100644 --- a/sys/fs/nfsserver/nfs_nfsdport.c +++ b/sys/fs/nfsserver/nfs_nfsdport.c @@ -2649,14 +2649,24 @@ nfsd_excred(struct nfsrv_descript *nd, struct nfsexstuff *exp, * Fsinfo RPC. If set for anything else, this code might need * to change.) */ - if (NFSVNO_EXPORTED(exp) && - ((!(nd->nd_flag & ND_GSS) && nd->nd_cred->cr_uid == 0) || - NFSVNO_EXPORTANON(exp) || - (nd->nd_flag & ND_AUTHNONE))) { - nd->nd_cred->cr_uid = credanon->cr_uid; - nd->nd_cred->cr_gid = credanon->cr_gid; - crsetgroups(nd->nd_cred, credanon->cr_ngroups, - credanon->cr_groups); + if (NFSVNO_EXPORTED(exp)) { + if (((nd->nd_flag & ND_GSS) == 0 && nd->nd_cred->cr_uid == 0) || + NFSVNO_EXPORTANON(exp) || + (nd->nd_flag & ND_AUTHNONE) != 0) { + nd->nd_cred->cr_uid = credanon->cr_uid; + nd->nd_cred->cr_gid = credanon->cr_gid; + crsetgroups(nd->nd_cred, credanon->cr_ngroups, + credanon->cr_groups); + } else if ((nd->nd_flag & ND_GSS) == 0) { + /* + * If using AUTH_SYS, call nfsrv_getgrpscred() to see + * if there is a replacement credential with a group + * list set up by "nfsuserd -manage-gids". + * If there is no replacement, nfsrv_getgrpscred() + * simply returns its argument. + */ + nd->nd_cred = nfsrv_getgrpscred(nd->nd_cred); + } } out: diff --git a/sys/nfs/nfssvc.h b/sys/nfs/nfssvc.h index 65b1681b58e6..a194ed51aea8 100644 --- a/sys/nfs/nfssvc.h +++ b/sys/nfs/nfssvc.h @@ -69,6 +69,7 @@ #define NFSSVC_SUSPENDNFSD 0x04000000 #define NFSSVC_RESUMENFSD 0x08000000 #define NFSSVC_DUMPMNTOPTS 0x10000000 +#define NFSSVC_NEWSTRUCT 0x20000000 /* Argument structure for NFSSVC_DUMPMNTOPTS. */ struct nfscl_dumpmntopts {