summaryrefslogtreecommitdiffstats
path: root/sys/kern/kern_prot.c
diff options
context:
space:
mode:
authorbrooks <brooks@FreeBSD.org>2009-06-19 17:10:35 +0000
committerbrooks <brooks@FreeBSD.org>2009-06-19 17:10:35 +0000
commitf53c1c309de799bd46cd12223b6f4966838f2e7a (patch)
tree851f3659dd95c07bf7aaf4a54cd53e83c804702d /sys/kern/kern_prot.c
parent020220234336a0527c3a4eb5fe739a256bd31981 (diff)
downloadFreeBSD-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.c170
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.
*/
OpenPOWER on IntegriCloud