diff options
author | brooks <brooks@FreeBSD.org> | 2009-06-19 17:10:35 +0000 |
---|---|---|
committer | brooks <brooks@FreeBSD.org> | 2009-06-19 17:10:35 +0000 |
commit | f53c1c309de799bd46cd12223b6f4966838f2e7a (patch) | |
tree | 851f3659dd95c07bf7aaf4a54cd53e83c804702d /sys/kern/kern_prot.c | |
parent | 020220234336a0527c3a4eb5fe739a256bd31981 (diff) | |
download | FreeBSD-src-f53c1c309de799bd46cd12223b6f4966838f2e7a.zip FreeBSD-src-f53c1c309de799bd46cd12223b6f4966838f2e7a.tar.gz |
Rework the credential code to support larger values of NGROUPS and
NGROUPS_MAX, eliminate ABI dependencies on them, and raise the to 1024
and 1023 respectively. (Previously they were equal, but under a close
reading of POSIX, NGROUPS_MAX was defined to be too large by 1 since it
is the number of supplemental groups, not total number of groups.)
The bulk of the change consists of converting the struct ucred member
cr_groups from a static array to a pointer. Do the equivalent in
kinfo_proc.
Introduce new interfaces crcopysafe() and crsetgroups() for duplicating
a process credential before modifying it and for setting group lists
respectively. Both interfaces take care for the details of allocating
groups array. crsetgroups() takes care of truncating the group list
to the current maximum (NGROUPS) if necessary. In the future,
crsetgroups() may be responsible for insuring invariants such as sorting
the supplemental groups to allow groupmember() to be implemented as a
binary search.
Because we can not change struct xucred without breaking application
ABIs, we leave it alone and introduce a new XU_NGROUPS value which is
always 16 and is to be used or NGRPS as appropriate for things such as
NFS which need to use no more than 16 groups. When feasible, truncate
the group list rather than generating an error.
Minor changes:
- Reduce the number of hand rolled versions of groupmember().
- Do not assign to both cr_gid and cr_groups[0].
- Modify ipfw to cache ucreds instead of part of their contents since
they are immutable once referenced by more than one entity.
Submitted by: Isilon Systems (initial implementation)
X-MFC after: never
PR: bin/113398 kern/133867
Diffstat (limited to 'sys/kern/kern_prot.c')
-rw-r--r-- | sys/kern/kern_prot.c | 170 |
1 files changed, 135 insertions, 35 deletions
diff --git a/sys/kern/kern_prot.c b/sys/kern/kern_prot.c index 5deff69..17cba9c 100644 --- a/sys/kern/kern_prot.c +++ b/sys/kern/kern_prot.c @@ -82,6 +82,11 @@ static MALLOC_DEFINE(M_CRED, "cred", "credentials"); SYSCTL_NODE(_security, OID_AUTO, bsd, CTLFLAG_RW, 0, "BSD security policy"); +static void crextend(struct ucred *cr, int n); +static void crsetgroups_locked(struct ucred *cr, int ngrp, + gid_t *groups); + + #ifndef _SYS_SYSPROTO_H_ struct getpid_args { int dummy; @@ -276,18 +281,21 @@ struct getgroups_args { int getgroups(struct thread *td, register struct getgroups_args *uap) { - gid_t groups[NGROUPS]; + gid_t *groups; u_int ngrp; int error; ngrp = MIN(uap->gidsetsize, NGROUPS); + groups = malloc(ngrp * sizeof(*groups), M_TEMP, M_WAITOK); error = kern_getgroups(td, &ngrp, groups); if (error) - return (error); + goto out; if (uap->gidsetsize > 0) error = copyout(groups, uap->gidset, ngrp * sizeof(gid_t)); if (error == 0) td->td_retval[0] = ngrp; +out: + free(groups, M_TEMP); return (error); } @@ -486,7 +494,10 @@ setuid(struct thread *td, struct setuid_args *uap) newcred = crget(); uip = uifind(uid); PROC_LOCK(p); - oldcred = p->p_ucred; + /* + * Copy credentials so other references do not see our changes. + */ + oldcred = crcopysafe(p, newcred); #ifdef MAC error = mac_cred_check_setuid(oldcred, uid); @@ -521,10 +532,6 @@ setuid(struct thread *td, struct setuid_args *uap) (error = priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)) != 0) goto fail; - /* - * Copy credentials so other references do not see our changes. - */ - crcopy(newcred, oldcred); #ifdef _POSIX_SAVED_IDS /* * Do we have "appropriate privileges" (are we root or uid == euid) @@ -598,7 +605,10 @@ seteuid(struct thread *td, struct seteuid_args *uap) newcred = crget(); euip = uifind(euid); PROC_LOCK(p); - oldcred = p->p_ucred; + /* + * Copy credentials so other references do not see our changes. + */ + oldcred = crcopysafe(p, newcred); #ifdef MAC error = mac_cred_check_seteuid(oldcred, euid); @@ -612,8 +622,7 @@ seteuid(struct thread *td, struct seteuid_args *uap) goto fail; /* - * Everything's okay, do it. Copy credentials so other references do - * not see our changes. + * Everything's okay, do it. */ crcopy(newcred, oldcred); if (oldcred->cr_uid != euid) { @@ -651,7 +660,7 @@ setgid(struct thread *td, struct setgid_args *uap) AUDIT_ARG(gid, gid); newcred = crget(); PROC_LOCK(p); - oldcred = p->p_ucred; + oldcred = crcopysafe(p, newcred); #ifdef MAC error = mac_cred_check_setgid(oldcred, gid); @@ -680,7 +689,6 @@ setgid(struct thread *td, struct setgid_args *uap) (error = priv_check_cred(oldcred, PRIV_CRED_SETGID, 0)) != 0) goto fail; - crcopy(newcred, oldcred); #ifdef _POSIX_SAVED_IDS /* * Do we have "appropriate privileges" (are we root or gid == egid) @@ -750,7 +758,7 @@ setegid(struct thread *td, struct setegid_args *uap) AUDIT_ARG(egid, egid); newcred = crget(); PROC_LOCK(p); - oldcred = p->p_ucred; + oldcred = crcopysafe(p, newcred); #ifdef MAC error = mac_cred_check_setegid(oldcred, egid); @@ -763,7 +771,6 @@ setegid(struct thread *td, struct setegid_args *uap) (error = priv_check_cred(oldcred, PRIV_CRED_SETEGID, 0)) != 0) goto fail; - crcopy(newcred, oldcred); if (oldcred->cr_groups[0] != egid) { change_egid(newcred, egid); setsugid(p); @@ -789,15 +796,19 @@ struct setgroups_args { int setgroups(struct thread *td, struct setgroups_args *uap) { - gid_t groups[NGROUPS]; + gid_t *groups = NULL; int error; if (uap->gidsetsize > NGROUPS) return (EINVAL); + groups = malloc(uap->gidsetsize * sizeof(gid_t), M_TEMP, M_WAITOK); error = copyin(uap->gidset, groups, uap->gidsetsize * sizeof(gid_t)); if (error) - return (error); - return (kern_setgroups(td, uap->gidsetsize, groups)); + goto out; + error = kern_setgroups(td, uap->gidsetsize, groups); +out: + free(groups, M_TEMP); + return (error); } int @@ -811,8 +822,9 @@ kern_setgroups(struct thread *td, u_int ngrp, gid_t *groups) return (EINVAL); AUDIT_ARG(groupset, groups, ngrp); newcred = crget(); + crextend(newcred, ngrp); PROC_LOCK(p); - oldcred = p->p_ucred; + oldcred = crcopysafe(p, newcred); #ifdef MAC error = mac_cred_check_setgroups(oldcred, ngrp, groups); @@ -824,11 +836,6 @@ kern_setgroups(struct thread *td, u_int ngrp, gid_t *groups) if (error) goto fail; - /* - * XXX A little bit lazy here. We could test if anything has - * changed before crcopy() and setting P_SUGID. - */ - crcopy(newcred, oldcred); if (ngrp < 1) { /* * setgroups(0, NULL) is a legitimate way of clearing the @@ -838,8 +845,7 @@ kern_setgroups(struct thread *td, u_int ngrp, gid_t *groups) */ newcred->cr_ngroups = 1; } else { - bcopy(groups, newcred->cr_groups, ngrp * sizeof(gid_t)); - newcred->cr_ngroups = ngrp; + crsetgroups_locked(newcred, ngrp, groups); } setsugid(p); p->p_ucred = newcred; @@ -877,7 +883,7 @@ setreuid(register struct thread *td, struct setreuid_args *uap) euip = uifind(euid); ruip = uifind(ruid); PROC_LOCK(p); - oldcred = p->p_ucred; + oldcred = crcopysafe(p, newcred); #ifdef MAC error = mac_cred_check_setreuid(oldcred, ruid, euid); @@ -892,7 +898,6 @@ setreuid(register struct thread *td, struct setreuid_args *uap) (error = priv_check_cred(oldcred, PRIV_CRED_SETREUID, 0)) != 0) goto fail; - crcopy(newcred, oldcred); if (euid != (uid_t)-1 && oldcred->cr_uid != euid) { change_euid(newcred, euip); setsugid(p); @@ -942,7 +947,7 @@ setregid(register struct thread *td, struct setregid_args *uap) AUDIT_ARG(rgid, rgid); newcred = crget(); PROC_LOCK(p); - oldcred = p->p_ucred; + oldcred = crcopysafe(p, newcred); #ifdef MAC error = mac_cred_check_setregid(oldcred, rgid, egid); @@ -957,7 +962,6 @@ setregid(register struct thread *td, struct setregid_args *uap) (error = priv_check_cred(oldcred, PRIV_CRED_SETREGID, 0)) != 0) goto fail; - crcopy(newcred, oldcred); if (egid != (gid_t)-1 && oldcred->cr_groups[0] != egid) { change_egid(newcred, egid); setsugid(p); @@ -1013,7 +1017,7 @@ setresuid(register struct thread *td, struct setresuid_args *uap) euip = uifind(euid); ruip = uifind(ruid); PROC_LOCK(p); - oldcred = p->p_ucred; + oldcred = crcopysafe(p, newcred); #ifdef MAC error = mac_cred_check_setresuid(oldcred, ruid, euid, suid); @@ -1033,7 +1037,6 @@ setresuid(register struct thread *td, struct setresuid_args *uap) (error = priv_check_cred(oldcred, PRIV_CRED_SETRESUID, 0)) != 0) goto fail; - crcopy(newcred, oldcred); if (euid != (uid_t)-1 && oldcred->cr_uid != euid) { change_euid(newcred, euip); setsugid(p); @@ -1090,7 +1093,7 @@ setresgid(register struct thread *td, struct setresgid_args *uap) AUDIT_ARG(sgid, sgid); newcred = crget(); PROC_LOCK(p); - oldcred = p->p_ucred; + oldcred = crcopysafe(p, newcred); #ifdef MAC error = mac_cred_check_setresgid(oldcred, rgid, egid, sgid); @@ -1110,7 +1113,6 @@ setresgid(register struct thread *td, struct setresgid_args *uap) (error = priv_check_cred(oldcred, PRIV_CRED_SETRESGID, 0)) != 0) goto fail; - crcopy(newcred, oldcred); if (egid != (gid_t)-1 && oldcred->cr_groups[0] != egid) { change_egid(newcred, egid); setsugid(p); @@ -1780,6 +1782,7 @@ crget(void) #ifdef MAC mac_cred_init(cr); #endif + crextend(cr, XU_NGROUPS); return (cr); } @@ -1829,6 +1832,7 @@ crfree(struct ucred *cr) #ifdef MAC mac_cred_destroy(cr); #endif + free(cr->cr_groups, M_CRED); free(cr, M_CRED); } } @@ -1854,6 +1858,7 @@ crcopy(struct ucred *dest, struct ucred *src) bcopy(&src->cr_startcopy, &dest->cr_startcopy, (unsigned)((caddr_t)&src->cr_endcopy - (caddr_t)&src->cr_startcopy)); + crsetgroups(dest, src->cr_ngroups, src->cr_groups); uihold(dest->cr_uidinfo); uihold(dest->cr_ruidinfo); prison_hold(dest->cr_prison); @@ -1888,12 +1893,16 @@ crdup(struct ucred *cr) void cru2x(struct ucred *cr, struct xucred *xcr) { + int ngroups; bzero(xcr, sizeof(*xcr)); xcr->cr_version = XUCRED_VERSION; xcr->cr_uid = cr->cr_uid; - xcr->cr_ngroups = cr->cr_ngroups; - bcopy(cr->cr_groups, xcr->cr_groups, sizeof(cr->cr_groups)); + + ngroups = MIN(cr->cr_ngroups, XU_NGROUPS); + xcr->cr_ngroups = ngroups; + bcopy(cr->cr_groups, xcr->cr_groups, + ngroups * sizeof(*cr->cr_groups)); } /* @@ -1915,6 +1924,97 @@ cred_update_thread(struct thread *td) crfree(cred); } +struct ucred * +crcopysafe(struct proc *p, struct ucred *cr) +{ + struct ucred *oldcred; + int groups; + + PROC_LOCK_ASSERT(p, MA_OWNED); + + oldcred = p->p_ucred; + while (cr->cr_agroups < oldcred->cr_agroups) { + groups = oldcred->cr_agroups; + PROC_UNLOCK(p); + crextend(cr, groups); + PROC_LOCK(p); + oldcred = p->p_ucred; + } + crcopy(cr, oldcred); + + return (oldcred); +} + +/* + * Extend the passed in credential to hold n items. + */ +static void +crextend(struct ucred *cr, int n) +{ + int cnt; + + /* Truncate? */ + if (n <= cr->cr_agroups) + return; + + /* + * We extend by 2 each time since we're using a power of two + * allocator until we need enough groups to fill a page. + * Once we're allocating multiple pages, only allocate as many + * as we actually need. The case of processes needing a + * non-power of two number of pages seems more likely than + * a real world process that adds thousands of groups one at a + * time. + */ + if ( n < PAGE_SIZE / sizeof(gid_t) ) { + if (cr->cr_agroups == 0) + cnt = MINALLOCSIZE / sizeof(gid_t); + else + cnt = cr->cr_agroups * 2; + + while (cnt < n) + cnt *= 2; + } else + cnt = roundup2(n, PAGE_SIZE / sizeof(gid_t)); + + /* Free the old array. */ + if (cr->cr_groups) + free(cr->cr_groups, M_CRED); + + cr->cr_groups = malloc(cnt * sizeof(gid_t), M_CRED, M_WAITOK | M_ZERO); + cr->cr_agroups = cnt; +} + +/* + * Copy groups in to a credential, preserving any necessicary invariants + * (i.e. sorting in the future). crextend() must have been called + * before hand to ensure sufficient space is available. If + */ +static void +crsetgroups_locked(struct ucred *cr, int ngrp, gid_t *groups) +{ + + KASSERT(cr->cr_agroups >= ngrp, ("cr_ngroups is too small")); + + bcopy(groups, cr->cr_groups, ngrp * sizeof(gid_t)); + cr->cr_ngroups = ngrp; +} + +/* + * Copy groups in to a credential after expanding it if required. + * Truncate the list to NGROUPS if it is too large. + */ +void +crsetgroups(struct ucred *cr, int ngrp, gid_t *groups) +{ + + if (ngrp > NGROUPS) + ngrp = NGROUPS; + + crextend(cr, ngrp); + crsetgroups_locked(cr, ngrp, groups); +} + /* * Get login name, if available. */ |