Index: head/sys/kern/kern_loginclass.c =================================================================== --- head/sys/kern/kern_loginclass.c (revision 273762) +++ head/sys/kern/kern_loginclass.c (revision 273763) @@ -1,229 +1,255 @@ /*- * Copyright (c) 2011 The FreeBSD Foundation * All rights reserved. * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Processes may set login class name using setloginclass(2). This * is usually done through call to setusercontext(3), by programs * such as login(1), based on information from master.passwd(5). Kernel * uses this information to enforce per-class resource limits. Current * login class can be determined using id(1). Login class is inherited * from the parent process during fork(2). If not set, it defaults * to "default". * * Code in this file implements setloginclass(2) and getloginclass(2) * system calls, and maintains class name storage and retrieval. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include -#include #include #include #include #include #include #include +#include #include #include static MALLOC_DEFINE(M_LOGINCLASS, "loginclass", "loginclass structures"); LIST_HEAD(, loginclass) loginclasses; /* * Lock protecting loginclasses list. */ -static struct mtx loginclasses_lock; -MTX_SYSINIT(loginclasses_init, &loginclasses_lock, "loginclasses lock", MTX_DEF); +static struct rwlock loginclasses_lock; +RW_SYSINIT(loginclasses_init, &loginclasses_lock, "loginclasses lock"); void loginclass_hold(struct loginclass *lc) { refcount_acquire(&lc->lc_refcount); } void loginclass_free(struct loginclass *lc) { int old; old = lc->lc_refcount; if (old > 1 && atomic_cmpset_int(&lc->lc_refcount, old, old - 1)) return; - mtx_lock(&loginclasses_lock); - if (refcount_release(&lc->lc_refcount)) { - racct_destroy(&lc->lc_racct); - LIST_REMOVE(lc, lc_next); - mtx_unlock(&loginclasses_lock); - free(lc, M_LOGINCLASS); - + rw_wlock(&loginclasses_lock); + if (!refcount_release(&lc->lc_refcount)) { + rw_wunlock(&loginclasses_lock); return; } - mtx_unlock(&loginclasses_lock); + + racct_destroy(&lc->lc_racct); + LIST_REMOVE(lc, lc_next); + rw_wunlock(&loginclasses_lock); + + free(lc, M_LOGINCLASS); } /* + * Look up a loginclass struct for the parameter name. + * loginclasses_lock must be locked. + * Increase refcount on loginclass struct returned. + */ +static struct loginclass * +loginclass_lookup(const char *name) +{ + struct loginclass *lc; + + rw_assert(&loginclasses_lock, RA_LOCKED); + LIST_FOREACH(lc, &loginclasses, lc_next) + if (strcmp(name, lc->lc_name) == 0) { + loginclass_hold(lc); + break; + } + + return (lc); +} + +/* * Return loginclass structure with a corresponding name. Not * performance critical, as it's used mainly by setloginclass(2), * which happens once per login session. Caller has to use * loginclass_free() on the returned value when it's no longer * needed. */ struct loginclass * loginclass_find(const char *name) { - struct loginclass *lc, *newlc; + struct loginclass *lc, *new_lc; if (name[0] == '\0' || strlen(name) >= MAXLOGNAME) return (NULL); - newlc = malloc(sizeof(*newlc), M_LOGINCLASS, M_ZERO | M_WAITOK); - racct_create(&newlc->lc_racct); + rw_rlock(&loginclasses_lock); + lc = loginclass_lookup(name); + rw_runlock(&loginclasses_lock); + if (lc != NULL) + return (lc); - mtx_lock(&loginclasses_lock); - LIST_FOREACH(lc, &loginclasses, lc_next) { - if (strcmp(name, lc->lc_name) != 0) - continue; + new_lc = malloc(sizeof(*new_lc), M_LOGINCLASS, M_ZERO | M_WAITOK); + racct_create(&new_lc->lc_racct); + refcount_init(&new_lc->lc_refcount, 1); + strcpy(new_lc->lc_name, name); - /* Found loginclass with a matching name? */ - loginclass_hold(lc); - mtx_unlock(&loginclasses_lock); - racct_destroy(&newlc->lc_racct); - free(newlc, M_LOGINCLASS); - return (lc); + rw_wlock(&loginclasses_lock); + /* + * There's a chance someone created our loginclass while we + * were in malloc and not holding the lock, so we have to + * make sure we don't insert a duplicate loginclass. + */ + if ((lc = loginclass_lookup(name)) == NULL) { + LIST_INSERT_HEAD(&loginclasses, new_lc, lc_next); + rw_wunlock(&loginclasses_lock); + lc = new_lc; + } else { + rw_wunlock(&loginclasses_lock); + racct_destroy(&new_lc->lc_racct); + free(new_lc, M_LOGINCLASS); } - /* Add new loginclass. */ - strcpy(newlc->lc_name, name); - refcount_init(&newlc->lc_refcount, 1); - LIST_INSERT_HEAD(&loginclasses, newlc, lc_next); - mtx_unlock(&loginclasses_lock); - - return (newlc); + return (lc); } /* * Get login class name. */ #ifndef _SYS_SYSPROTO_H_ struct getloginclass_args { char *namebuf; size_t namelen; }; #endif /* ARGSUSED */ int sys_getloginclass(struct thread *td, struct getloginclass_args *uap) { int error = 0; size_t lcnamelen; struct proc *p; struct loginclass *lc; p = td->td_proc; PROC_LOCK(p); lc = p->p_ucred->cr_loginclass; loginclass_hold(lc); PROC_UNLOCK(p); lcnamelen = strlen(lc->lc_name) + 1; if (lcnamelen > uap->namelen) error = ERANGE; if (error == 0) error = copyout(lc->lc_name, uap->namebuf, lcnamelen); loginclass_free(lc); return (error); } /* * Set login class name. */ #ifndef _SYS_SYSPROTO_H_ struct setloginclass_args { const char *namebuf; }; #endif /* ARGSUSED */ int sys_setloginclass(struct thread *td, struct setloginclass_args *uap) { struct proc *p = td->td_proc; int error; char lcname[MAXLOGNAME]; struct loginclass *newlc; struct ucred *newcred, *oldcred; error = priv_check(td, PRIV_PROC_SETLOGINCLASS); if (error != 0) return (error); error = copyinstr(uap->namebuf, lcname, sizeof(lcname), NULL); if (error != 0) return (error); newlc = loginclass_find(lcname); if (newlc == NULL) return (EINVAL); newcred = crget(); PROC_LOCK(p); oldcred = crcopysafe(p, newcred); newcred->cr_loginclass = newlc; p->p_ucred = newcred; PROC_UNLOCK(p); #ifdef RACCT racct_proc_ucred_changed(p, oldcred, newcred); #endif loginclass_free(oldcred->cr_loginclass); crfree(oldcred); return (0); } void loginclass_racct_foreach(void (*callback)(struct racct *racct, void *arg2, void *arg3), void *arg2, void *arg3) { struct loginclass *lc; - mtx_lock(&loginclasses_lock); + rw_rlock(&loginclasses_lock); LIST_FOREACH(lc, &loginclasses, lc_next) (callback)(lc->lc_racct, arg2, arg3); - mtx_unlock(&loginclasses_lock); + rw_runlock(&loginclasses_lock); }