diff options
Diffstat (limited to 'sys/fs/nfs/nfs_commonsubs.c')
-rw-r--r-- | sys/fs/nfs/nfs_commonsubs.c | 683 |
1 files changed, 487 insertions, 196 deletions
diff --git a/sys/fs/nfs/nfs_commonsubs.c b/sys/fs/nfs/nfs_commonsubs.c index 6e0204b8..5b8bca3 100644 --- a/sys/fs/nfs/nfs_commonsubs.c +++ b/sys/fs/nfs/nfs_commonsubs.c @@ -44,6 +44,8 @@ __FBSDID("$FreeBSD$"); #include <fs/nfs/nfsport.h> +#include <security/mac/mac_framework.h> + /* * 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 *); @@ -2541,18 +2547,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); @@ -2564,11 +2569,12 @@ tryagain: 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; @@ -2588,7 +2594,7 @@ tryagain: 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); @@ -2603,20 +2609,19 @@ tryagain: *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(); } /* @@ -2640,6 +2645,52 @@ tryagain: } /* + * 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 * return 0. @@ -2657,6 +2708,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; @@ -2686,49 +2738,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: @@ -2751,18 +2809,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); @@ -2774,11 +2831,12 @@ tryagain: 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; @@ -2798,7 +2856,7 @@ tryagain: 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); @@ -2813,20 +2871,19 @@ tryagain: *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(); } /* @@ -2867,6 +2924,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; @@ -2896,47 +2954,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: @@ -3094,111 +3158,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) { + 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; + } + 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); + /* - * Free up all the old stuff and reinitialize hash lists. + * Put name in "DNS" string. */ - TAILQ_FOREACH_SAFE(usrp, &nfsuserlruhead, lug_lru, nusrp) { - nfsrv_removeuser(usrp); - } - 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]); - - /* - * Put name in "DNS" string. - */ - if (!error) { 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); } /* @@ -3210,23 +3381,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); @@ -3236,15 +3513,29 @@ out: * Remove a user/group name element. */ static void -nfsrv_removeuser(struct nfsusrgrp *usrp) +nfsrv_removeuser(struct nfsusrgrp *usrp, int isuser) { - - 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); + struct nfsrv_lughash *hp; + + 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); } /* |