X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/8f6c56a50524aa785f7e596d52dddfb331e18961..15129b1c8dbb3650c63b70adb1cad9af601c6c17:/bsd/kern/kern_prot.c diff --git a/bsd/kern/kern_prot.c b/bsd/kern/kern_prot.c index 2e2a52dff..73806d055 100644 --- a/bsd/kern/kern_prot.c +++ b/bsd/kern/kern_prot.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000-2003 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2000-2008 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -24,9 +24,11 @@ * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ - */ -/* Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved */ -/* + * + * + * Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved + * + * * Copyright (c) 1982, 1986, 1989, 1990, 1991, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. @@ -64,6 +66,19 @@ * SUCH DAMAGE. * * @(#)kern_prot.c 8.9 (Berkeley) 2/14/95 + * + * + * NOTICE: This file was modified by McAfee Research in 2004 to introduce + * support for mandatory and extensible security protections. This notice + * is included in support of clause 2.2 (b) of the Apple Public License, + * Version 2.0. + * + * + * NOTICE: This file was modified by SPARTA, Inc. in 2005 to introduce + * support for mandatory and extensible security protections. This notice + * is included in support of clause 2.2 (b) of the Apple Public License, + * Version 2.0. + * */ /* @@ -81,7 +96,18 @@ #include #include -#include +#include + +#if CONFIG_LCTX +#include +#endif + +#if CONFIG_MACF +#include +#if CONFIG_MACF_MACH +#include +#endif +#endif #include #include @@ -89,113 +115,240 @@ #include #include +#include /* for current_task() */ +#include + + +/* + * Credential debugging; we can track entry into a function that might + * change a credential, and we can track actual credential changes that + * result. + * + * Note: Does *NOT* currently include per-thread credential changes + * + * We don't use kauth_cred_print() in current debugging, but it + * can be used if needed when debugging is active. + */ +#if DEBUG_CRED +#define DEBUG_CRED_ENTER printf +#define DEBUG_CRED_CHANGE printf +extern void kauth_cred_print(kauth_cred_t cred); +#else /* !DEBUG_CRED */ +#define DEBUG_CRED_ENTER(fmt, ...) do {} while (0) +#define DEBUG_CRED_CHANGE(fmt, ...) do {} while (0) +#endif /* !DEBUG_CRED */ -int groupmember(gid_t gid, kauth_cred_t cred); -int is_suser(void); -int is_suser1(void); -extern int prepare_profile_database(int user); /* - * setprivexec: (dis)allow this process to hold - * task, thread, or execption ports of processes about to exec. + * setprivexec + * + * Description: (dis)allow this process to hold task, thread, or execption + * ports of processes about to exec. + * + * Parameters: uap->flag New value for flag + * + * Returns: int Previous value of flag + * + * XXX: Belongs in kern_proc.c */ int -setprivexec(struct proc *p, struct setprivexec_args *uap, register_t *retval) +setprivexec(proc_t p, struct setprivexec_args *uap, int32_t *retval) { - AUDIT_ARG(value, uap->flag); + AUDIT_ARG(value32, uap->flag); *retval = p->p_debugger; p->p_debugger = (uap->flag != 0); return(0); } -/* ARGSUSED */ + +/* + * getpid + * + * Description: get the process ID + * + * Parameters: (void) + * + * Returns: pid_t Current process ID + * + * XXX: Belongs in kern_proc.c + */ int -getpid(struct proc *p, __unused struct getpid_args *uap, register_t *retval) +getpid(proc_t p, __unused struct getpid_args *uap, int32_t *retval) { *retval = p->p_pid; return (0); } -/* ARGSUSED */ + +/* + * getppid + * + * Description: get the parent process ID + * + * Parameters: (void) + * + * Returns: pid_t Parent process ID + * + * XXX: Belongs in kern_proc.c + */ int -getppid(struct proc *p, __unused struct getppid_args *uap, register_t *retval) +getppid(proc_t p, __unused struct getppid_args *uap, int32_t *retval) { - *retval = p->p_pptr->p_pid; + *retval = p->p_ppid; return (0); } -/* Get process group ID; note that POSIX getpgrp takes no parameter */ + +/* + * getpgrp + * + * Description: get the process group ID of the calling process + * + * Parameters: (void) + * + * Returns: pid_t Process group ID + * + * XXX: Belongs in kern_proc.c + */ int -getpgrp(struct proc *p, __unused struct getpgrp_args *uap, register_t *retval) +getpgrp(proc_t p, __unused struct getpgrp_args *uap, int32_t *retval) { - *retval = p->p_pgrp->pg_id; + *retval = p->p_pgrpid; return (0); } -/* Get an arbitary pid's process group id */ + +/* + * getpgid + * + * Description: Get an arbitary pid's process group id + * + * Parameters: uap->pid The target pid + * + * Returns: 0 Success + * ESRCH No such process + * + * Notes: We are permitted to return EPERM in the case that the target + * process is not in the same session as the calling process, + * which could be a security consideration + * + * XXX: Belongs in kern_proc.c + */ int -getpgid(struct proc *p, struct getpgid_args *uap, register_t *retval) +getpgid(proc_t p, struct getpgid_args *uap, int32_t *retval) { - struct proc *pt; + proc_t pt; + int refheld = 0; pt = p; if (uap->pid == 0) goto found; - if ((pt = pfind(uap->pid)) == 0) + if ((pt = proc_find(uap->pid)) == 0) return (ESRCH); + refheld = 1; found: - *retval = pt->p_pgrp->pg_id; + *retval = pt->p_pgrpid; + if (refheld != 0) + proc_rele(pt); return (0); } + /* - * Get an arbitary pid's session id. + * getsid + * + * Description: Get an arbitary pid's session leaders process group ID + * + * Parameters: uap->pid The target pid + * + * Returns: 0 Success + * ESRCH No such process + * + * Notes: We are permitted to return EPERM in the case that the target + * process is not in the same session as the calling process, + * which could be a security consideration + * + * XXX: Belongs in kern_proc.c */ - int -getsid(struct proc *p, struct getsid_args *uap, register_t *retval) +getsid(proc_t p, struct getsid_args *uap, int32_t *retval) { - struct proc *pt; + proc_t pt; + int refheld = 0; + struct session * sessp; pt = p; if (uap->pid == 0) goto found; - if ((pt = pfind(uap->pid)) == 0) + if ((pt = proc_find(uap->pid)) == 0) return (ESRCH); + refheld = 1; found: - *retval = pt->p_session->s_sid; + sessp = proc_session(pt); + *retval = sessp->s_sid; + session_rele(sessp); + + if (refheld != 0) + proc_rele(pt); return (0); } -/* ARGSUSED */ + +/* + * getuid + * + * Description: get real user ID for caller + * + * Parameters: (void) + * + * Returns: uid_t The real uid of the caller + */ int -getuid(__unused struct proc *p, __unused struct getuid_args *uap, register_t *retval) +getuid(__unused proc_t p, __unused struct getuid_args *uap, int32_t *retval) { *retval = kauth_getruid(); return (0); } -/* ARGSUSED */ + +/* + * geteuid + * + * Description: get effective user ID for caller + * + * Parameters: (void) + * + * Returns: uid_t The effective uid of the caller + */ int -geteuid(__unused struct proc *p, __unused struct geteuid_args *uap, register_t *retval) +geteuid(__unused proc_t p, __unused struct geteuid_args *uap, int32_t *retval) { *retval = kauth_getuid(); return (0); } + /* - * Return the per-thread override identity. + * gettid + * + * Description: Return the per-thread override identity. + * + * Parameters: uap->uidp Address of uid_t to get uid + * uap->gidp Address of gid_t to get gid + * + * Returns: 0 Success + * ESRCH No per thread identity active */ int -gettid(__unused struct proc *p, struct gettid_args *uap, register_t *retval) +gettid(__unused proc_t p, struct gettid_args *uap, int32_t *retval) { struct uthread *uthread = get_bsdthread_info(current_thread()); int error; @@ -207,150 +360,320 @@ gettid(__unused struct proc *p, struct gettid_args *uap, register_t *retval) if (!(uthread->uu_flag & UT_SETUID)) return (ESRCH); - if ((error = suword(uap->uidp, uthread->uu_ucred->cr_ruid))) + if ((error = suword(uap->uidp, kauth_cred_getruid(uthread->uu_ucred)))) return (error); - if ((error = suword(uap->gidp, uthread->uu_ucred->cr_rgid))) + if ((error = suword(uap->gidp, kauth_cred_getrgid(uthread->uu_ucred)))) return (error); *retval = 0; return (0); } -/* ARGSUSED */ + +/* + * getgid + * + * Description: get the real group ID for the calling process + * + * Parameters: (void) + * + * Returns: gid_t The real gid of the caller + */ int -getgid(__unused struct proc *p, __unused struct getgid_args *uap, register_t *retval) +getgid(__unused proc_t p, __unused struct getgid_args *uap, int32_t *retval) { *retval = kauth_getrgid(); return (0); } + /* - * Get effective group ID. The "egid" is groups[0], and could be obtained - * via getgroups. This syscall exists because it is somewhat painful to do - * correctly in a library function. + * getegid + * + * Description: get the effective group ID for the calling process + * + * Parameters: (void) + * + * Returns: gid_t The effective gid of the caller + * + * Notes: As an implementation detail, the effective gid is stored as + * the first element of the supplementary group list. + * + * This could be implemented in Libc instead because of the above + * detail. */ -/* ARGSUSED */ int -getegid(struct proc *p, __unused struct getegid_args *uap, register_t *retval) +getegid(__unused proc_t p, __unused struct getegid_args *uap, int32_t *retval) { *retval = kauth_getgid(); return (0); } + +/* + * getgroups + * + * Description: get the list of supplementary groups for the calling process + * + * Parameters: uap->gidsetsize # of gid_t's in user buffer + * uap->gidset Pointer to user buffer + * + * Returns: 0 Success + * EINVAL User buffer too small + * copyout:EFAULT User buffer invalid + * + * Retval: -1 Error + * !0 # of groups + * + * Notes: The caller may specify a 0 value for gidsetsize, and we will + * then return how large a buffer is required (in gid_t's) to + * contain the answer at the time of the call. Otherwise, we + * return the number of gid_t's catually copied to user space. + * + * When called with a 0 gidsetsize from a multithreaded program, + * there is no guarantee that another thread may not change the + * number of supplementary groups, and therefore a subsequent + * call could still fail, unless the maximum possible buffer + * size is supplied by the user. + * + * As an implementation detail, the effective gid is stored as + * the first element of the supplementary group list, and will + * be returned by this call. + */ int -getgroups(__unused struct proc *p, struct getgroups_args *uap, register_t *retval) +getgroups(__unused proc_t p, struct getgroups_args *uap, int32_t *retval) { - register int ngrp; + int ngrp; int error; kauth_cred_t cred; + posix_cred_t pcred; /* grab reference while we muck around with the credential */ cred = kauth_cred_get_with_ref(); + pcred = posix_cred_get(cred); if ((ngrp = uap->gidsetsize) == 0) { - *retval = cred->cr_ngroups; - kauth_cred_rele(cred); + *retval = pcred->cr_ngroups; + kauth_cred_unref(&cred); return (0); } - if (ngrp < cred->cr_ngroups) { - kauth_cred_rele(cred); + if (ngrp < pcred->cr_ngroups) { + kauth_cred_unref(&cred); return (EINVAL); } - ngrp = cred->cr_ngroups; - if ((error = copyout((caddr_t)cred->cr_groups, + ngrp = pcred->cr_ngroups; + if ((error = copyout((caddr_t)pcred->cr_groups, uap->gidset, ngrp * sizeof(gid_t)))) { - kauth_cred_rele(cred); + kauth_cred_unref(&cred); return (error); } - kauth_cred_rele(cred); + kauth_cred_unref(&cred); *retval = ngrp; return (0); } + /* * Return the per-thread/per-process supplementary groups list. + * + * XXX implement getsgroups + * */ -#warning XXX implement + int -getsgroups(__unused struct proc *p, __unused struct getsgroups_args *uap, __unused register_t *retval) +getsgroups(__unused proc_t p, __unused struct getsgroups_args *uap, __unused int32_t *retval) { - /* XXX implement */ return(ENOTSUP); } /* * Return the per-thread/per-process whiteout groups list. + * + * XXX implement getwgroups + * */ -#warning XXX implement + int -getwgroups(__unused struct proc *p, __unused struct getwgroups_args *uap, __unused register_t *retval) +getwgroups(__unused proc_t p, __unused struct getwgroups_args *uap, __unused int32_t *retval) { - /* XXX implement */ return(ENOTSUP); } -/* ARGSUSED */ + +/* + * setsid + * + * Description: Create a new session and set the process group ID to the + * session ID + * + * Parameters: (void) + * + * Returns: 0 Success + * EPERM Permission denied + * + * Notes: If the calling process is not the process group leader; there + * is no existing process group with its ID, and we are not + * currently in vfork, then this function will create a new + * session, a new process group, and put the caller in the + * process group (as the sole member) and make it the session + * leader (as the sole process in the session). + * + * The existing controlling tty (if any) will be dissociated + * from the process, and the next non-O_NOCTTY open of a tty + * will establish a new controlling tty. + * + * XXX: Belongs in kern_proc.c + */ int -setsid(struct proc *p, __unused struct setsid_args *uap, register_t *retval) +setsid(proc_t p, __unused struct setsid_args *uap, int32_t *retval) { + struct pgrp * pg = PGRP_NULL; - if (p->p_pgid == p->p_pid || pgfind(p->p_pid) || p->p_flag & P_INVFORK) { + if (p->p_pgrpid == p->p_pid || (pg = pgfind(p->p_pid)) || p->p_lflag & P_LINVFORK) { + if (pg != PGRP_NULL) + pg_rele(pg); return (EPERM); } else { + /* enter pgrp works with its own pgrp refcount */ (void)enterpgrp(p, p->p_pid, 1); *retval = p->p_pid; return (0); } } + /* - * set process group (setpgid/old setpgrp) + * setpgid + * + * Description: set process group ID for job control * - * caller does setpgid(targpid, targpgid) + * Parameters: uap->pid Process to change + * uap->pgid Process group to join or create * - * pid must be caller or child of caller (ESRCH) - * if a child - * pid must be in same session (EPERM) - * pid can't have done an exec (EACCES) - * ig pgid is -ve return EINVAL (as per SUV spec) - * if pgid != pid - * there must exist some pid in same session having pgid (EPERM) - * pid must not be session leader (EPERM) + * Returns: 0 Success + * ESRCH pid is not the caller or a child of + * the caller + * enterpgrp:ESRCH No such process + * EACCES Permission denied due to exec + * EINVAL Invalid argument + * EPERM The target process is not in the same + * session as the calling process + * EPERM The target process is a session leader + * EPERM pid and pgid are not the same, and + * there is no process in the calling + * process whose process group ID matches + * pgid + * + * Notes: This function will cause the target process to either join + * an existing process process group, or create a new process + * group in the session of the calling process. It cannot be + * used to change the process group ID of a process which is + * already a session leader. + * + * If the target pid is 0, the pid of the calling process is + * substituted as the new target; if pgid is 0, the target pid + * is used as the target process group ID. + * + * Legacy: This system call entry point is also used to implement the + * legacy library routine setpgrp(), which under POSIX + * + * XXX: Belongs in kern_proc.c */ -/* ARGSUSED */ int -setpgid(struct proc *curp, register struct setpgid_args *uap, __unused register_t *retval) +setpgid(proc_t curp, register struct setpgid_args *uap, __unused int32_t *retval) { - register struct proc *targp; /* target process */ - register struct pgrp *pgrp; /* target pgrp */ + proc_t targp = PROC_NULL; /* target process */ + struct pgrp *pg = PGRP_NULL; /* target pgrp */ + int error = 0; + int refheld = 0; + int samesess = 0; + struct session * curp_sessp = SESSION_NULL; + struct session * targp_sessp = SESSION_NULL; + + curp_sessp = proc_session(curp); if (uap->pid != 0 && uap->pid != curp->p_pid) { - if ((targp = pfind(uap->pid)) == 0 || !inferior(targp)) - return (ESRCH); - if (targp->p_session != curp->p_session) - return (EPERM); - if (targp->p_flag & P_EXEC) - return (EACCES); - } else + if ((targp = proc_find(uap->pid)) == 0 || !inferior(targp)) { + if (targp != PROC_NULL) + refheld = 1; + error = ESRCH; + goto out; + } + refheld = 1; + targp_sessp = proc_session(targp); + if (targp_sessp != curp_sessp) { + error = EPERM; + goto out; + } + if (targp->p_flag & P_EXEC) { + error = EACCES; + goto out; + } + } else { targp = curp; - if (SESS_LEADER(targp)) - return (EPERM); - if (uap->pgid < 0) - return(EINVAL); + targp_sessp = proc_session(targp); + } + + if (SESS_LEADER(targp, targp_sessp)) { + error = EPERM; + goto out; + } + if (targp_sessp != SESSION_NULL) { + session_rele(targp_sessp); + targp_sessp = SESSION_NULL; + } + + if (uap->pgid < 0) { + error = EINVAL; + goto out; + } if (uap->pgid == 0) uap->pgid = targp->p_pid; - else if (uap->pgid != targp->p_pid) - if ((pgrp = pgfind(uap->pgid)) == 0 || - pgrp->pg_session != curp->p_session) - return (EPERM); - return (enterpgrp(targp, uap->pgid, 0)); + else if (uap->pgid != targp->p_pid) { + if ((pg = pgfind(uap->pgid)) == 0){ + error = EPERM; + goto out; + } + samesess = (pg->pg_session != curp_sessp); + pg_rele(pg); + if (samesess != 0) { + error = EPERM; + goto out; + } + } + error = enterpgrp(targp, uap->pgid, 0); +out: + if (targp_sessp != SESSION_NULL) + session_rele(targp_sessp); + if (curp_sessp != SESSION_NULL) + session_rele(curp_sessp); + if (refheld != 0) + proc_rele(targp); + return(error); } + +/* + * issetugid + * + * Description: Is current process tainted by uid or gid changes system call + * + * Parameters: (void) + * + * Returns: 0 Not tainted + * 1 Tainted + * + * Notes: A process is considered tainted if it was created as a retult + * of an execve call from an imnage that had either the SUID or + * SGID bit set on the executable, or if it has changed any of its + * real, effective, or saved user or group IDs since beginning + * execution. + */ int -issetugid(struct proc *p, __unused struct issetugid_args *uap, register_t *retval) +issetugid(proc_t p, __unused struct issetugid_args *uap, int32_t *retval) { /* * Note: OpenBSD sets a P_SUGIDEXEC flag set at execve() time, @@ -365,215 +688,703 @@ issetugid(struct proc *p, __unused struct issetugid_args *uap, register_t *retva return (0); } -/* ARGSUSED */ + +/* + * setuid + * + * Description: Set user ID system call + * + * Parameters: uap->uid uid to set + * + * Returns: 0 Success + * suser:EPERM Permission denied + * + * Notes: If called by a privileged process, this function will set the + * real, effective, and saved uid to the requested value. + * + * If called from an unprivileged process, but uid is equal to the + * real or saved uid, then the effective uid will be set to the + * requested value, but the real and saved uid will not change. + * + * If the credential is changed as a result of this call, then we + * flag the process as having set privilege since the last exec. + */ int -setuid(struct proc *p, struct setuid_args *uap, __unused register_t *retval) +setuid(proc_t p, struct setuid_args *uap, __unused int32_t *retval) { - register uid_t uid; + uid_t uid; + uid_t svuid = KAUTH_UID_NONE; + uid_t ruid = KAUTH_UID_NONE; + uid_t gmuid = KAUTH_UID_NONE; int error; kauth_cred_t my_cred, my_new_cred; + posix_cred_t my_pcred; + uid = uap->uid; - AUDIT_ARG(uid, uid, 0, 0, 0); - if (uid != p->p_ucred->cr_ruid && - (error = suser(p->p_ucred, &p->p_acflag))) + + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + + DEBUG_CRED_ENTER("setuid (%d/%d): %p %d\n", p->p_pid, (p->p_pptr ? p->p_pptr->p_pid : 0), my_cred, uap->uid); + AUDIT_ARG(uid, uid); + + if (uid != my_pcred->cr_ruid && /* allow setuid(getuid()) */ + uid != my_pcred->cr_svuid && /* allow setuid(saved uid) */ + (error = suser(my_cred, &p->p_acflag))) { + kauth_cred_unref(&my_cred); return (error); + } /* * Everything's okay, do it. - * Transfer proc count to new user. - * Copy credentials so other references do not see our changes. */ - /* prepare app access profile files */ - prepare_profile_database(uap->uid); - (void)chgproccnt(kauth_getruid(), -1); - (void)chgproccnt(uid, 1); + /* + * If we are priviledged, then set the saved and real UID too; + * otherwise, just set the effective UID + */ + if (suser(my_cred, &p->p_acflag) == 0) { + svuid = uid; + ruid = uid; + /* + * Transfer proc count to new user. + * chgproccnt uses list lock for protection + */ + (void)chgproccnt(uid, 1); + (void)chgproccnt(my_pcred->cr_ruid, -1); + } /* get current credential and take a reference while we muck with it */ for (;;) { - my_cred = kauth_cred_proc_ref(p); - - /* - * set the credential with new info. If there is no change we get back - * the same credential we passed in. + /* + * Only set the gmuid if the current cred has not opt'ed out; + * this normally only happens when calling setgroups() instead + * of initgroups() to set an explicit group list, or one of the + * other group manipulation functions is invoked and results in + * a dislocation (i.e. the credential group membership changes + * to something other than the default list for the user, as + * in entering a group or leaving an exclusion group). */ - my_new_cred = kauth_cred_setuid(my_cred, uid); + if (!(my_pcred->cr_flags & CRF_NOMEMBERD)) + gmuid = uid; + + /* + * Set the credential with new info. If there is no change, + * we get back the same credential we passed in; if there is + * a change, we drop the reference on the credential we + * passed in. The subsequent compare is safe, because it is + * a pointer compare rather than a contents compare. + */ + my_new_cred = kauth_cred_setresuid(my_cred, ruid, uid, svuid, gmuid); if (my_cred != my_new_cred) { + + DEBUG_CRED_CHANGE("setuid CH(%d): %p/0x%08x -> %p/0x%08x\n", p->p_pid, my_cred, my_pcred->cr_flags, my_new_cred, posix_cred_get(my_new_cred)->cr_flags); + proc_lock(p); - /* need to protect for a race where another thread also changed - * the credential after we took our reference. If p_ucred has - * changed then we should restart this again with the new cred. + /* + * We need to protect for a race where another thread + * also changed the credential after we took our + * reference. If p_ucred has changed then we should + * restart this again with the new cred. */ if (p->p_ucred != my_cred) { proc_unlock(p); - kauth_cred_rele(my_cred); - kauth_cred_rele(my_new_cred); + kauth_cred_unref(&my_new_cred); + my_cred = kauth_cred_proc_ref(p); /* try again */ continue; } p->p_ucred = my_new_cred; - p->p_flag |= P_SUGID; + /* update cred on proc */ + PROC_UPDATE_CREDS_ONPROC(p); + + OSBitOrAtomic(P_SUGID, &p->p_flag); proc_unlock(p); } - /* drop our extra reference */ - kauth_cred_rele(my_cred); break; } + /* Drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); + + set_security_token(p); + return (0); +} + + +/* + * seteuid + * + * Description: Set effective user ID system call + * + * Parameters: uap->euid effective uid to set + * + * Returns: 0 Success + * suser:EPERM Permission denied + * + * Notes: If called by a privileged process, or called from an + * unprivileged process but euid is equal to the real or saved + * uid, then the effective uid will be set to the requested + * value, but the real and saved uid will not change. + * + * If the credential is changed as a result of this call, then we + * flag the process as having set privilege since the last exec. + */ +int +seteuid(proc_t p, struct seteuid_args *uap, __unused int32_t *retval) +{ + uid_t euid; + int error; + kauth_cred_t my_cred, my_new_cred; + posix_cred_t my_pcred; + + DEBUG_CRED_ENTER("seteuid: %d\n", uap->euid); + + euid = uap->euid; + AUDIT_ARG(euid, euid); + + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + + if (euid != my_pcred->cr_ruid && euid != my_pcred->cr_svuid && + (error = suser(my_cred, &p->p_acflag))) { + kauth_cred_unref(&my_cred); + return (error); + } + + /* + * Everything's okay, do it. Copy credentials so other references do + * not see our changes. get current credential and take a reference + * while we muck with it + */ + for (;;) { + /* + * Set the credential with new info. If there is no change, + * we get back the same credential we passed in; if there is + * a change, we drop the reference on the credential we + * passed in. The subsequent compare is safe, because it is + * a pointer compare rather than a contents compare. + */ + my_new_cred = kauth_cred_setresuid(my_cred, KAUTH_UID_NONE, euid, KAUTH_UID_NONE, my_pcred->cr_gmuid); + if (my_cred != my_new_cred) { + + DEBUG_CRED_CHANGE("seteuid CH(%d): %p/0x%08x -> %p/0x%08x\n", p->p_pid, my_cred, my_pcred->cr_flags, my_new_cred, posix_cred_get(my_new_cred)->cr_flags); + + proc_lock(p); + /* + * We need to protect for a race where another thread + * also changed the credential after we took our + * reference. If p_ucred has changed then we + * should restart this again with the new cred. + */ + if (p->p_ucred != my_cred) { + proc_unlock(p); + kauth_cred_unref(&my_new_cred); + my_cred = kauth_cred_proc_ref(p); + /* try again */ + continue; + } + p->p_ucred = my_new_cred; + /* update cred on proc */ + PROC_UPDATE_CREDS_ONPROC(p); + OSBitOrAtomic(P_SUGID, &p->p_flag); + proc_unlock(p); + } + break; + } + /* drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); + set_security_token(p); return (0); } -/* ARGSUSED */ + +/* + * setreuid + * + * Description: Set real and effective user ID system call + * + * Parameters: uap->ruid real uid to set + * uap->euid effective uid to set + * + * Returns: 0 Success + * suser:EPERM Permission denied + * + * Notes: A value of -1 is a special case indicating that the uid for + * which that value is specified not be changed. If both values + * are specified as -1, no action is taken. + * + * If called by a privileged process, the real and effective uid + * will be set to the new value(s) specified. + * + * If called from an unprivileged process, the real uid may be + * set to the current value of the real uid, or to the current + * value of the saved uid. The effective uid may be set to the + * current value of any of the effective, real, or saved uid. + * + * If the newly requested real uid or effective uid does not + * match the saved uid, then set the saved uid to the new + * effective uid (potentially unrecoverably dropping saved + * privilege). + * + * If the credential is changed as a result of this call, then we + * flag the process as having set privilege since the last exec. + */ int -seteuid(struct proc *p, struct seteuid_args *uap, __unused register_t *retval) +setreuid(proc_t p, struct setreuid_args *uap, __unused int32_t *retval) { - register uid_t euid; + uid_t ruid, euid; int error; kauth_cred_t my_cred, my_new_cred; + posix_cred_t my_pcred; + DEBUG_CRED_ENTER("setreuid %d %d\n", uap->ruid, uap->euid); + + ruid = uap->ruid; euid = uap->euid; - AUDIT_ARG(uid, 0, euid, 0, 0); - if (euid != p->p_ucred->cr_ruid && euid != p->p_ucred->cr_svuid && - (error = suser(p->p_ucred, &p->p_acflag))) + if (ruid == (uid_t)-1) + ruid = KAUTH_UID_NONE; + if (euid == (uid_t)-1) + euid = KAUTH_UID_NONE; + AUDIT_ARG(euid, euid); + AUDIT_ARG(ruid, ruid); + + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + + if (((ruid != KAUTH_UID_NONE && /* allow no change of ruid */ + ruid != my_pcred->cr_ruid && /* allow ruid = ruid */ + ruid != my_pcred->cr_uid && /* allow ruid = euid */ + ruid != my_pcred->cr_svuid) || /* allow ruid = svuid */ + (euid != KAUTH_UID_NONE && /* allow no change of euid */ + euid != my_pcred->cr_uid && /* allow euid = euid */ + euid != my_pcred->cr_ruid && /* allow euid = ruid */ + euid != my_pcred->cr_svuid)) && /* allow euid = svui */ + (error = suser(my_cred, &p->p_acflag))) { /* allow root user any */ + kauth_cred_unref(&my_cred); return (error); + } + /* * Everything's okay, do it. Copy credentials so other references do * not see our changes. get current credential and take a reference * while we muck with it */ for (;;) { - my_cred = kauth_cred_proc_ref(p); + uid_t new_euid; + uid_t new_ruid; + uid_t svuid = KAUTH_UID_NONE; + + new_euid = my_pcred->cr_uid; + new_ruid = my_pcred->cr_ruid; - /* - * set the credential with new info. If there is no change we get back - * the same credential we passed in. + /* + * Set the credential with new info. If there is no change, + * we get back the same credential we passed in; if there is + * a change, we drop the reference on the credential we + * passed in. The subsequent compare is safe, because it is + * a pointer compare rather than a contents compare. + */ + if (euid == KAUTH_UID_NONE && my_pcred->cr_uid != euid) { + /* changing the effective UID */ + new_euid = euid; + OSBitOrAtomic(P_SUGID, &p->p_flag); + } + if (ruid != KAUTH_UID_NONE && my_pcred->cr_ruid != ruid) { + /* changing the real UID; must do user accounting */ + /* chgproccnt uses list lock for protection */ + (void)chgproccnt(ruid, 1); + (void)chgproccnt(my_pcred->cr_ruid, -1); + new_ruid = ruid; + OSBitOrAtomic(P_SUGID, &p->p_flag); + } + /* + * If the newly requested real uid or effective uid does + * not match the saved uid, then set the saved uid to the + * new effective uid. We are protected from escalation + * by the prechecking. */ - my_new_cred = kauth_cred_seteuid(p->p_ucred, euid); + if (my_pcred->cr_svuid != uap->ruid && + my_pcred->cr_svuid != uap->euid) { + svuid = new_euid; + OSBitOrAtomic(P_SUGID, &p->p_flag); + } + + my_new_cred = kauth_cred_setresuid(my_cred, ruid, euid, svuid, my_pcred->cr_gmuid); if (my_cred != my_new_cred) { + + DEBUG_CRED_CHANGE("setreuid CH(%d): %p/0x%08x -> %p/0x%08x\n", p->p_pid, my_cred, my_pcred->cr_flags, my_new_cred, posix_cred_get(my_new_cred)->cr_flags); + proc_lock(p); - /* need to protect for a race where another thread also changed - * the credential after we took our reference. If p_ucred has - * changed then we should restart this again with the new cred. + /* + * We need to protect for a race where another thread + * also changed the credential after we took our + * reference. If p_ucred has changed then we should + * restart this again with the new cred. */ if (p->p_ucred != my_cred) { proc_unlock(p); - kauth_cred_rele(my_cred); - kauth_cred_rele(my_new_cred); + kauth_cred_unref(&my_new_cred); + my_cred = kauth_cred_proc_ref(p); /* try again */ continue; } p->p_ucred = my_new_cred; - p->p_flag |= P_SUGID; + /* update cred on proc */ + PROC_UPDATE_CREDS_ONPROC(p); + OSBitOrAtomic(P_SUGID, &p->p_flag); /* XXX redundant? */ proc_unlock(p); } - /* drop our extra reference */ - kauth_cred_rele(my_cred); break; } + /* drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); set_security_token(p); return (0); } -/* ARGSUSED */ + +/* + * setgid + * + * Description: Set group ID system call + * + * Parameters: uap->gid gid to set + * + * Returns: 0 Success + * suser:EPERM Permission denied + * + * Notes: If called by a privileged process, this function will set the + * real, effective, and saved gid to the requested value. + * + * If called from an unprivileged process, but gid is equal to the + * real or saved gid, then the effective gid will be set to the + * requested value, but the real and saved gid will not change. + * + * If the credential is changed as a result of this call, then we + * flag the process as having set privilege since the last exec. + * + * As an implementation detail, the effective gid is stored as + * the first element of the supplementary group list, and + * therefore the effective group list may be reordered to keep + * the supplementary group list unchanged. + */ int -setgid(struct proc *p, struct setgid_args *uap, __unused register_t *retval) +setgid(proc_t p, struct setgid_args *uap, __unused int32_t *retval) { - register gid_t gid; + gid_t gid; + gid_t rgid = KAUTH_GID_NONE; + gid_t svgid = KAUTH_GID_NONE; int error; kauth_cred_t my_cred, my_new_cred; + posix_cred_t my_pcred; + + DEBUG_CRED_ENTER("setgid(%d/%d): %d\n", p->p_pid, (p->p_pptr ? p->p_pptr->p_pid : 0), uap->gid); gid = uap->gid; - AUDIT_ARG(gid, gid, 0, 0, 0); - if (gid != p->p_ucred->cr_rgid && (error = suser(p->p_ucred, &p->p_acflag))) + AUDIT_ARG(gid, gid); + + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + + if (gid != my_pcred->cr_rgid && /* allow setgid(getgid()) */ + gid != my_pcred->cr_svgid && /* allow setgid(saved gid) */ + (error = suser(my_cred, &p->p_acflag))) { + kauth_cred_unref(&my_cred); return (error); + } + + /* + * If we are priviledged, then set the saved and real GID too; + * otherwise, just set the effective GID + */ + if (suser(my_cred, &p->p_acflag) == 0) { + svgid = gid; + rgid = gid; + } /* get current credential and take a reference while we muck with it */ for (;;) { - my_cred = kauth_cred_proc_ref(p); - /* - * set the credential with new info. If there is no change we get back - * the same credential we passed in. - */ - my_new_cred = kauth_cred_setgid(p->p_ucred, gid); + /* + * Set the credential with new info. If there is no change, + * we get back the same credential we passed in; if there is + * a change, we drop the reference on the credential we + * passed in. The subsequent compare is safe, because it is + * a pointer compare rather than a contents compare. + */ + my_new_cred = kauth_cred_setresgid(my_cred, rgid, gid, svgid); if (my_cred != my_new_cred) { + + DEBUG_CRED_CHANGE("setgid(CH)%d: %p/0x%08x->%p/0x%08x\n", p->p_pid, my_cred, my_cred->cr_flags, my_new_cred, my_new_cred->cr_flags); + proc_lock(p); - /* need to protect for a race where another thread also changed - * the credential after we took our reference. If p_ucred has - * changed then we should restart this again with the new cred. + /* + * We need to protect for a race where another thread + * also changed the credential after we took our + * reference. If p_ucred has changed then we + * should restart this again with the new cred. */ if (p->p_ucred != my_cred) { proc_unlock(p); - kauth_cred_rele(my_cred); - kauth_cred_rele(my_new_cred); + kauth_cred_unref(&my_new_cred); /* try again */ + my_cred = kauth_cred_proc_ref(p); continue; } p->p_ucred = my_new_cred; - p->p_flag |= P_SUGID; + /* update cred on proc */ + PROC_UPDATE_CREDS_ONPROC(p); + OSBitOrAtomic(P_SUGID, &p->p_flag); proc_unlock(p); } - /* drop our extra reference */ - kauth_cred_rele(my_cred); break; } + /* Drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); set_security_token(p); return (0); } -/* ARGSUSED */ + +/* + * setegid + * + * Description: Set effective group ID system call + * + * Parameters: uap->egid effective gid to set + * + * Returns: 0 Success + * suser:EPERM + * + * Notes: If called by a privileged process, or called from an + * unprivileged process but egid is equal to the real or saved + * gid, then the effective gid will be set to the requested + * value, but the real and saved gid will not change. + * + * If the credential is changed as a result of this call, then we + * flag the process as having set privilege since the last exec. + * + * As an implementation detail, the effective gid is stored as + * the first element of the supplementary group list, and + * therefore the effective group list may be reordered to keep + * the supplementary group list unchanged. + */ +int +setegid(proc_t p, struct setegid_args *uap, __unused int32_t *retval) +{ + gid_t egid; + int error; + kauth_cred_t my_cred, my_new_cred; + posix_cred_t my_pcred; + + DEBUG_CRED_ENTER("setegid %d\n", uap->egid); + + egid = uap->egid; + AUDIT_ARG(egid, egid); + + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + + if (egid != my_pcred->cr_rgid && + egid != my_pcred->cr_svgid && + (error = suser(my_cred, &p->p_acflag))) { + kauth_cred_unref(&my_cred); + return (error); + } + + /* get current credential and take a reference while we muck with it */ + for (;;) { + /* + * Set the credential with new info. If there is no change, + * we get back the same credential we passed in; if there is + * a change, we drop the reference on the credential we + * passed in. The subsequent compare is safe, because it is + * a pointer compare rather than a contents compare. + */ + my_new_cred = kauth_cred_setresgid(my_cred, KAUTH_GID_NONE, egid, KAUTH_GID_NONE); + if (my_cred != my_new_cred) { + + DEBUG_CRED_CHANGE("setegid(CH)%d: %p/0x%08x->%p/0x%08x\n", p->p_pid, my_cred, my_pcred->cr_flags, my_new_cred, posix_cred_get(my_new_cred)->cr_flags); + + proc_lock(p); + /* + * We need to protect for a race where another thread + * also changed the credential after we took our + * reference. If p_ucred has changed then we + * should restart this again with the new cred. + */ + if (p->p_ucred != my_cred) { + proc_unlock(p); + kauth_cred_unref(&my_new_cred); + /* try again */ + my_cred = kauth_cred_proc_ref(p); + continue; + } + p->p_ucred = my_new_cred; + /* update cred on proc */ + PROC_UPDATE_CREDS_ONPROC(p); + OSBitOrAtomic(P_SUGID, &p->p_flag); + proc_unlock(p); + } + break; + } + + /* Drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); + + set_security_token(p); + return (0); +} + +/* + * setregid + * + * Description: Set real and effective group ID system call + * + * Parameters: uap->rgid real gid to set + * uap->egid effective gid to set + * + * Returns: 0 Success + * suser:EPERM Permission denied + * + * Notes: A value of -1 is a special case indicating that the gid for + * which that value is specified not be changed. If both values + * are specified as -1, no action is taken. + * + * If called by a privileged process, the real and effective gid + * will be set to the new value(s) specified. + * + * If called from an unprivileged process, the real gid may be + * set to the current value of the real gid, or to the current + * value of the saved gid. The effective gid may be set to the + * current value of any of the effective, real, or saved gid. + * + * If the new real and effective gid will not be equal, or the + * new real or effective gid is not the same as the saved gid, + * then the saved gid will be updated to reflect the new + * effective gid (potentially unrecoverably dropping saved + * privilege). + * + * If the credential is changed as a result of this call, then we + * flag the process as having set privilege since the last exec. + * + * As an implementation detail, the effective gid is stored as + * the first element of the supplementary group list, and + * therefore the effective group list may be reordered to keep + * the supplementary group list unchanged. + */ int -setegid(struct proc *p, struct setegid_args *uap, __unused register_t *retval) +setregid(proc_t p, struct setregid_args *uap, __unused int32_t *retval) { - register gid_t egid; + gid_t rgid, egid; int error; kauth_cred_t my_cred, my_new_cred; + posix_cred_t my_pcred; + + DEBUG_CRED_ENTER("setregid %d %d\n", uap->rgid, uap->egid); + rgid = uap->rgid; egid = uap->egid; - AUDIT_ARG(gid, 0, egid, 0, 0); - if (egid != p->p_ucred->cr_rgid && egid != p->p_ucred->cr_svgid && - (error = suser(p->p_ucred, &p->p_acflag))) + + if (rgid == (uid_t)-1) + rgid = KAUTH_GID_NONE; + if (egid == (uid_t)-1) + egid = KAUTH_GID_NONE; + AUDIT_ARG(egid, egid); + AUDIT_ARG(rgid, rgid); + + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + + if (((rgid != KAUTH_UID_NONE && /* allow no change of rgid */ + rgid != my_pcred->cr_rgid && /* allow rgid = rgid */ + rgid != my_pcred->cr_gid && /* allow rgid = egid */ + rgid != my_pcred->cr_svgid) || /* allow rgid = svgid */ + (egid != KAUTH_UID_NONE && /* allow no change of egid */ + egid != my_pcred->cr_groups[0] && /* allow no change of egid */ + egid != my_pcred->cr_gid && /* allow egid = egid */ + egid != my_pcred->cr_rgid && /* allow egid = rgid */ + egid != my_pcred->cr_svgid)) && /* allow egid = svgid */ + (error = suser(my_cred, &p->p_acflag))) { /* allow root user any */ + kauth_cred_unref(&my_cred); return (error); + } /* get current credential and take a reference while we muck with it */ for (;;) { - my_cred = kauth_cred_proc_ref(p); + uid_t new_egid = my_pcred->cr_gid; + uid_t new_rgid = my_pcred->cr_rgid; + uid_t svgid = KAUTH_UID_NONE; + - /* - * set the credential with new info. If there is no change we get back - * the same credential we passed in. + /* + * Set the credential with new info. If there is no change, + * we get back the same credential we passed in; if there is + * a change, we drop the reference on the credential we + * passed in. The subsequent compare is safe, because it is + * a pointer compare rather than a contents compare. + */ + if (egid == KAUTH_UID_NONE && my_pcred->cr_gid != egid) { + /* changing the effective GID */ + new_egid = egid; + OSBitOrAtomic(P_SUGID, &p->p_flag); + } + if (rgid != KAUTH_UID_NONE && my_pcred->cr_rgid != rgid) { + /* changing the real GID */ + new_rgid = rgid; + OSBitOrAtomic(P_SUGID, &p->p_flag); + } + /* + * If the newly requested real gid or effective gid does + * not match the saved gid, then set the saved gid to the + * new effective gid. We are protected from escalation + * by the prechecking. */ - my_new_cred = kauth_cred_setegid(p->p_ucred, egid); + if (my_pcred->cr_svgid != uap->rgid && + my_pcred->cr_svgid != uap->egid) { + svgid = new_egid; + OSBitOrAtomic(P_SUGID, &p->p_flag); + } + + my_new_cred = kauth_cred_setresgid(my_cred, rgid, egid, svgid); if (my_cred != my_new_cred) { + + DEBUG_CRED_CHANGE("setregid(CH)%d: %p/0x%08x->%p/0x%08x\n", p->p_pid, my_cred, my_pcred->cr_flags, my_new_cred, posix_cred_get(my_new_cred)->cr_flags); + proc_lock(p); - /* need to protect for a race where another thread also changed - * the credential after we took our reference. If p_ucred has - * changed then we should restart this again with the new cred. + /* need to protect for a race where another thread + * also changed the credential after we took our + * reference. If p_ucred has changed then we + * should restart this again with the new cred. */ if (p->p_ucred != my_cred) { proc_unlock(p); - kauth_cred_rele(my_cred); - kauth_cred_rele(my_new_cred); + kauth_cred_unref(&my_new_cred); /* try again */ + my_cred = kauth_cred_proc_ref(p); continue; } p->p_ucred = my_new_cred; - p->p_flag |= P_SUGID; + /* update cred on proc */ + PROC_UPDATE_CREDS_ONPROC(p); + OSBitOrAtomic(P_SUGID, &p->p_flag); /* XXX redundant? */ proc_unlock(p); } - /* drop our extra reference */ - kauth_cred_rele(my_cred); break; } + /* Drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); set_security_token(p); return (0); } + /* * Set the per-thread override identity. The first parameter can be the * current real UID, KAUTH_UID_NONE, or, if the caller is priviledged, it @@ -583,20 +1394,20 @@ setegid(struct proc *p, struct setegid_args *uap, __unused register_t *retval) * thread to the requested UID and single GID, and clears all other GIDs. */ int -settid(struct proc *p, struct settid_args *uap, __unused register_t *retval) +settid(proc_t p, struct settid_args *uap, __unused int32_t *retval) { kauth_cred_t uc; struct uthread *uthread = get_bsdthread_info(current_thread()); - register uid_t uid; - register gid_t gid; + uid_t uid; + gid_t gid; uid = uap->uid; gid = uap->gid; - AUDIT_ARG(uid, uid, gid, gid, 0); + AUDIT_ARG(uid, uid); + AUDIT_ARG(gid, gid); - if (suser(p->p_ucred, &p->p_acflag) != 0) { + if (proc_suser(p) != 0) return (EPERM); - } if (uid == KAUTH_UID_NONE) { @@ -606,7 +1417,7 @@ settid(struct proc *p, struct settid_args *uap, __unused register_t *retval) /* revert to delayed binding of process credential */ uc = kauth_cred_proc_ref(p); - kauth_cred_rele(uthread->uu_ucred); + kauth_cred_unref(&uthread->uu_ucred); uthread->uu_ucred = uc; uthread->uu_flag &= ~UT_SETUID; } else { @@ -618,9 +1429,11 @@ settid(struct proc *p, struct settid_args *uap, __unused register_t *retval) } /* - * get a new credential instance from the old if this one changes else - * kauth_cred_setuidgid returns the same credential. we take an extra - * reference on the current credential while we muck wit it here. + * Get a new credential instance from the old if this one + * changes; otherwise kauth_cred_setuidgid() returns the + * same credential. We take an extra reference on the + * current credential while we muck with it, so we can do + * the post-compare for changes by pointer. */ kauth_cred_ref(uthread->uu_ucred); my_cred = uthread->uu_ucred; @@ -629,8 +1442,8 @@ settid(struct proc *p, struct settid_args *uap, __unused register_t *retval) uthread->uu_ucred = my_new_cred; uthread->uu_flag |= UT_SETUID; - /* drop our extra reference */ - kauth_cred_rele(my_cred); + /* Drop old uthread reference or our extra reference */ + kauth_cred_unref(&my_cred); } /* * XXX should potentially set per thread security token (there is @@ -641,25 +1454,29 @@ settid(struct proc *p, struct settid_args *uap, __unused register_t *retval) return (0); } + /* * Set the per-thread override identity. Use this system call for a thread to - * assume the identity of another process or to revert back to normal identity + * assume the identity of another process or to revert back to normal identity * of the current process. - * When the "assume" argument is non zero the current thread will assume the + * + * When the "assume" argument is non zero the current thread will assume the * identity of the process represented by the pid argument. + * * When the assume argument is zero we revert back to our normal identity. */ int -settid_with_pid(struct proc *p, struct settid_with_pid_args *uap, __unused register_t *retval) +settid_with_pid(proc_t p, struct settid_with_pid_args *uap, __unused int32_t *retval) { proc_t target_proc; struct uthread *uthread = get_bsdthread_info(current_thread()); kauth_cred_t my_cred, my_target_cred, my_new_cred; + posix_cred_t my_target_pcred; AUDIT_ARG(pid, uap->pid); - AUDIT_ARG(value, uap->assume); + AUDIT_ARG(value32, uap->assume); - if (suser(p->p_ucred, &p->p_acflag) != 0) { + if (proc_suser(p) != 0) { return (EPERM); } @@ -679,67 +1496,115 @@ settid_with_pid(struct proc *p, struct settid_with_pid_args *uap, __unused regis if ((uthread->uu_flag & UT_SETUID) != 0) return (EPERM); - target_proc = pfind(uap->pid); + target_proc = proc_find(uap->pid); /* can't assume the identity of the kernel process */ if (target_proc == NULL || target_proc == kernproc) { + if (target_proc!= NULL) + proc_rele(target_proc); return (ESRCH); } /* - * take a reference on the credential used in our target process then use - * it as the identity for our current thread. + * Take a reference on the credential used in our target + * process then use it as the identity for our current + * thread. We take an extra reference on the current + * credential while we muck with it, so we can do the + * post-compare for changes by pointer. + * + * The post-compare is needed for the case that our process + * credential has been changed to be identical to our thread + * credential following our assumption of a per-thread one, + * since the credential cache will maintain a unique instance. */ kauth_cred_ref(uthread->uu_ucred); my_cred = uthread->uu_ucred; my_target_cred = kauth_cred_proc_ref(target_proc); - my_new_cred = kauth_cred_setuidgid(my_cred, my_target_cred->cr_uid, my_target_cred->cr_gid); + my_target_pcred = posix_cred_get(my_target_cred); + my_new_cred = kauth_cred_setuidgid(my_cred, my_target_pcred->cr_uid, my_target_pcred->cr_gid); if (my_cred != my_new_cred) uthread->uu_ucred = my_new_cred; uthread->uu_flag |= UT_SETUID; - /* drop our extra references */ - kauth_cred_rele(my_cred); - kauth_cred_rele(my_target_cred); + /* Drop old uthread reference or our extra reference */ + proc_rele(target_proc); + kauth_cred_unref(&my_cred); + kauth_cred_unref(&my_target_cred); return (0); } - /* we are reverting back to normal mode of operation where delayed binding - * of the process credential sets the credential in the thread (uu_ucred) + /* + * Otherwise, we are reverting back to normal mode of operation where + * delayed binding of the process credential sets the credential in + * the thread (uu_ucred) */ if ((uthread->uu_flag & UT_SETUID) == 0) return (EPERM); /* revert to delayed binding of process credential */ my_new_cred = kauth_cred_proc_ref(p); - kauth_cred_rele(uthread->uu_ucred); + kauth_cred_unref(&uthread->uu_ucred); uthread->uu_ucred = my_new_cred; uthread->uu_flag &= ~UT_SETUID; return (0); } -/* ARGSUSED */ + +/* + * setgroups1 + * + * Description: Internal implementation for both the setgroups and initgroups + * system calls + * + * Parameters: gidsetsize Number of groups in set + * gidset Pointer to group list + * gmuid Base gid (initgroups only!) + * + * Returns: 0 Success + * suser:EPERM Permision denied + * EINVAL Invalid gidsetsize value + * copyin:EFAULT Bad gidset or gidsetsize is + * too large + * + * Notes: When called from a thread running under an assumed per-thread + * identity, this function will operate against the per-thread + * credential, rather than against the process credential. In + * this specific case, the process credential is verified to + * still be privileged at the time of the call, rather than the + * per-thread credential for this operation to be permitted. + * + * This effectively means that setgroups/initigroups calls in + * a thread running a per-thread credential should occur *after* + * the settid call that created it, not before (unlike setuid, + * which must be called after, since it will result in privilege + * being dropped). + * + * When called normally (i.e. no per-thread assumed identity), + * the per process credential is updated per POSIX. + * + * If the credential is changed as a result of this call, then we + * flag the process as having set privilege since the last exec. + */ static int -setgroups1(struct proc *p, u_int gidsetsize, user_addr_t gidset, uid_t gmuid, __unused register_t *retval) +setgroups1(proc_t p, u_int gidsetsize, user_addr_t gidset, uid_t gmuid, __unused int32_t *retval) { - register u_int ngrp; + u_int ngrp; gid_t newgroups[NGROUPS] = { 0 }; int error; kauth_cred_t my_cred, my_new_cred; struct uthread *uthread = get_bsdthread_info(current_thread()); - if ((error = suser(p->p_ucred, &p->p_acflag))) - return (error); + DEBUG_CRED_ENTER("setgroups1 (%d/%d): %d 0x%016x %d\n", p->p_pid, (p->p_pptr ? p->p_pptr->p_pid : 0), gidsetsize, gidset, gmuid); + ngrp = gidsetsize; if (ngrp > NGROUPS) return (EINVAL); if ( ngrp < 1 ) { ngrp = 1; - } - else { + } else { error = copyin(gidset, (caddr_t)newgroups, ngrp * sizeof(gid_t)); if (error) { @@ -747,7 +1612,18 @@ setgroups1(struct proc *p, u_int gidsetsize, user_addr_t gidset, uid_t gmuid, __ } } + my_cred = kauth_cred_proc_ref(p); + if ((error = suser(my_cred, &p->p_acflag))) { + kauth_cred_unref(&my_cred); + return (error); + } + if ((uthread->uu_flag & UT_SETUID) != 0) { +#if DEBUG_CRED + int my_cred_flags = uthread->uu_ucred->cr_flags; +#endif /* DEBUG_CRED */ + kauth_cred_unref(&my_cred); + /* * If this thread is under an assumed identity, set the * supplementary grouplist on the thread credential instead @@ -756,13 +1632,14 @@ setgroups1(struct proc *p, u_int gidsetsize, user_addr_t gidset, uid_t gmuid, __ * is dropped and we get back a different cred with a reference * already held on it. Because this is per-thread, we don't * need the referencing/locking/retry required for per-process. - * - * Hack: this opts into memberd to avoid needing to use a per - * thread credential initgroups() instead of setgroups() in - * AFP server to address */ my_cred = uthread->uu_ucred; - uthread->uu_ucred = kauth_cred_setgroups(my_cred, &newgroups[0], ngrp, my_cred->cr_gmuid); + uthread->uu_ucred = kauth_cred_setgroups(my_cred, &newgroups[0], ngrp, gmuid); +#if DEBUG_CRED + if (my_cred != uthread->uu_ucred) { + DEBUG_CRED_CHANGE("setgroups1(CH)%d: %p/0x%08x->%p/0x%08x\n", p->p_pid, my_cred, my_cred_flags, uthread->uu_ucred , uthread->uu_ucred ->cr_flags); + } +#endif /* DEBUG_CRED */ } else { /* @@ -770,17 +1647,22 @@ setgroups1(struct proc *p, u_int gidsetsize, user_addr_t gidset, uid_t gmuid, __ * with it */ for (;;) { - my_cred = kauth_cred_proc_ref(p); - /* - * set the credential with new info. If there is no - * change we get back the same credential we passed in. + * Set the credential with new info. If there is no + * change, we get back the same credential we passed + * in; if there is a change, we drop the reference on + * the credential we passed in. The subsequent + * compare is safe, because it is a pointer compare + * rather than a contents compare. */ my_new_cred = kauth_cred_setgroups(my_cred, &newgroups[0], ngrp, gmuid); if (my_cred != my_new_cred) { + + DEBUG_CRED_CHANGE("setgroups1(CH)%d: %p/0x%08x->%p/0x%08x\n", p->p_pid, my_cred, my_cred->cr_flags, my_new_cred, my_new_cred->cr_flags); + proc_lock(p); /* - * need to protect for a race where another + * We need to protect for a race where another * thread also changed the credential after we * took our reference. If p_ucred has * changed then we should restart this again @@ -788,63 +1670,135 @@ setgroups1(struct proc *p, u_int gidsetsize, user_addr_t gidset, uid_t gmuid, __ */ if (p->p_ucred != my_cred) { proc_unlock(p); - kauth_cred_rele(my_cred); - kauth_cred_rele(my_new_cred); + kauth_cred_unref(&my_new_cred); + my_cred = kauth_cred_proc_ref(p); /* try again */ continue; } p->p_ucred = my_new_cred; - p->p_flag |= P_SUGID; + /* update cred on proc */ + PROC_UPDATE_CREDS_ONPROC(p); + OSBitOrAtomic(P_SUGID, &p->p_flag); proc_unlock(p); } - /* drop our extra reference */ - kauth_cred_rele(my_cred); break; } + /* Drop old proc reference or our extra reference */ + AUDIT_ARG(groupset, posix_cred_get(my_cred)->cr_groups, ngrp); + kauth_cred_unref(&my_cred); + - AUDIT_ARG(groupset, p->p_ucred->cr_groups, ngrp); set_security_token(p); } return (0); } + +/* + * initgroups + * + * Description: Initialize the default supplementary groups list and set the + * gmuid for use by the external group resolver (if any) + * + * Parameters: uap->gidsetsize Number of groups in set + * uap->gidset Pointer to group list + * uap->gmuid Base gid + * + * Returns: 0 Success + * setgroups1:EPERM Permision denied + * setgroups1:EINVAL Invalid gidsetsize value + * setgroups1:EFAULT Bad gidset or gidsetsize is + * + * Notes: This function opts *IN* to memberd participation + * + * The normal purpose of this function is for a privileged + * process to indicate supplementary groups and identity for + * participation in extended group membership resolution prior + * to dropping privilege by assuming a specific user identity. + * + * It is the first half of the primary mechanism whereby user + * identity is established to the system by programs such as + * /usr/bin/login. The second half is the drop of uid privilege + * for a specific uid corresponding to the user. + * + * See also: setgroups1() + */ int -initgroups(struct proc *p, struct initgroups_args *uap, __unused register_t *retval) +initgroups(proc_t p, struct initgroups_args *uap, __unused int32_t *retval) { + DEBUG_CRED_ENTER("initgroups\n"); + return(setgroups1(p, uap->gidsetsize, uap->gidset, uap->gmuid, retval)); } + +/* + * setgroups + * + * Description: Initialize the default supplementary groups list + * + * Parameters: gidsetsize Number of groups in set + * gidset Pointer to group list + * + * Returns: 0 Success + * setgroups1:EPERM Permision denied + * setgroups1:EINVAL Invalid gidsetsize value + * setgroups1:EFAULT Bad gidset or gidsetsize is + * + * Notes: This functions opts *OUT* of memberd participation. + * + * This function exists for compatibility with POSIX. Most user + * programs should use initgroups() instead to ensure correct + * participation in group membership resolution when utilizing + * a directory service for authentication. + * + * It is identical to an initgroups() call with a gmuid argument + * of KAUTH_UID_NONE. + * + * See also: setgroups1() + */ int -setgroups(struct proc *p, struct setgroups_args *uap, __unused register_t *retval) +setgroups(proc_t p, struct setgroups_args *uap, __unused int32_t *retval) { + DEBUG_CRED_ENTER("setgroups\n"); + return(setgroups1(p, uap->gidsetsize, uap->gidset, KAUTH_UID_NONE, retval)); } + /* * Set the per-thread/per-process supplementary groups list. + * + * XXX implement setsgroups + * */ -#warning XXX implement + int -setsgroups(__unused struct proc *p, __unused struct setsgroups_args *uap, __unused register_t *retval) +setsgroups(__unused proc_t p, __unused struct setsgroups_args *uap, __unused int32_t *retval) { return(ENOTSUP); } /* * Set the per-thread/per-process whiteout groups list. + * + * XXX implement setwgroups + * */ -#warning XXX implement + int -setwgroups(__unused struct proc *p, __unused struct setwgroups_args *uap, __unused register_t *retval) +setwgroups(__unused proc_t p, __unused struct setwgroups_args *uap, __unused int32_t *retval) { return(ENOTSUP); } + /* * Check if gid is a member of the group set. * - * XXX This interface is going away + * XXX This interface is going away; use kauth_cred_ismember_gid() directly + * XXX instead. */ int groupmember(gid_t gid, kauth_cred_t cred) @@ -856,19 +1810,26 @@ groupmember(gid_t gid, kauth_cred_t cred) return (0); } + /* * Test whether the specified credentials imply "super-user" * privilege; if so, and we have accounting info, set the flag * indicating use of super-powers. * Returns 0 or error. * - * XXX This interface is going away + * XXX This interface is going away; use kauth_cred_issuser() directly + * XXX instead. + * + * Note: This interface exists to implement the "has used privilege" + * bit (ASU) in the p_acflags field of the process, which is + * only externalized via private sysctl and in process accounting + * records. The flag is technically not required in either case. */ int suser(kauth_cred_t cred, u_short *acflag) { #if DIAGNOSTIC - if (cred == NOCRED || cred == FSCRED) + if (!IS_VALID_CRED(cred)) panic("suser"); #endif if (kauth_cred_getuid(cred) == 0) { @@ -879,62 +1840,101 @@ suser(kauth_cred_t cred, u_short *acflag) return (EPERM); } + +/* + * getlogin + * + * Description: Get login name, if available. + * + * Parameters: uap->namebuf User buffer for return + * uap->namelen User buffer length + * + * Returns: 0 Success + * copyout:EFAULT + * + * Notes: Intended to obtain a string containing the user name of the + * user associated with the controlling terminal for the calling + * process. + * + * Not very useful on modern systems, due to inherent length + * limitations for the static array in the session structure + * which is used to store the login name. + * + * Permitted to return NULL + * + * XXX: Belongs in kern_proc.c + */ int -is_suser(void) +getlogin(proc_t p, struct getlogin_args *uap, __unused int32_t *retval) { - struct proc *p = current_proc(); + char buffer[MAXLOGNAME+1]; + struct session * sessp; - if (!p) - return (0); + bzero(buffer, MAXLOGNAME+1); - return (suser(p->p_ucred, &p->p_acflag) == 0); -} + sessp = proc_session(p); -int -is_suser1(void) -{ - struct proc *p = current_proc(); + if (uap->namelen > MAXLOGNAME) + uap->namelen = MAXLOGNAME; - if (!p) - return (0); + if(sessp != SESSION_NULL) { + session_lock(sessp); + bcopy( sessp->s_login, buffer, uap->namelen); + session_unlock(sessp); + } + session_rele(sessp); - return (suser(p->p_ucred, &p->p_acflag) == 0 || - p->p_ucred->cr_ruid == 0 || p->p_ucred->cr_svuid == 0); + return (copyout((caddr_t)buffer, uap->namebuf, uap->namelen)); } -/* - * Get login name, if available. - */ -/* ARGSUSED */ -int -getlogin(struct proc *p, struct getlogin_args *uap, __unused register_t *retval) -{ - - if (uap->namelen > sizeof (p->p_pgrp->pg_session->s_login)) - uap->namelen = sizeof (p->p_pgrp->pg_session->s_login); - return (copyout((caddr_t) p->p_pgrp->pg_session->s_login, - uap->namebuf, uap->namelen)); -} /* - * Set login name. + * setlogin + * + * Description: Set login name. + * + * Parameters: uap->namebuf User buffer containing name + * + * Returns: 0 Success + * suser:EPERM Permission denied + * copyinstr:EFAULT User buffer invalid + * copyinstr:EINVAL Supplied name was too long + * + * Notes: This is a utility system call to support getlogin(). + * + * XXX: Belongs in kern_proc.c */ -/* ARGSUSED */ int -setlogin(struct proc *p, struct setlogin_args *uap, __unused register_t *retval) +setlogin(proc_t p, struct setlogin_args *uap, __unused int32_t *retval) { int error; - int dummy=0; + size_t dummy=0; + char buffer[MAXLOGNAME+1]; + struct session * sessp; - if ((error = suser(p->p_ucred, &p->p_acflag))) + if ((error = proc_suser(p))) return (error); - + + bzero(&buffer[0], MAXLOGNAME+1); + + error = copyinstr(uap->namebuf, - (caddr_t) p->p_pgrp->pg_session->s_login, - sizeof (p->p_pgrp->pg_session->s_login) - 1, (size_t *)&dummy); - if (!error) - AUDIT_ARG(text, p->p_pgrp->pg_session->s_login); - else if (error == ENAMETOOLONG) + (caddr_t) &buffer[0], + MAXLOGNAME - 1, (size_t *)&dummy); + + sessp = proc_session(p); + + if (sessp != SESSION_NULL) { + session_lock(sessp); + bcopy(buffer, sessp->s_login, MAXLOGNAME); + session_unlock(sessp); + session_rele(sessp); + } + + + if (!error) { + AUDIT_ARG(text, buffer); + } else if (error == ENAMETOOLONG) error = EINVAL; return (error); } @@ -946,10 +1946,13 @@ setlogin(struct proc *p, struct setlogin_args *uap, __unused register_t *retval) * XXX identifier. */ int -set_security_token(struct proc * p) +set_security_token(proc_t p) { security_token_t sec_token; audit_token_t audit_token; + kauth_cred_t my_cred; + posix_cred_t my_pcred; + host_priv_t host_priv; /* * Don't allow a vfork child to override the parent's token settings @@ -964,10 +1967,13 @@ set_security_token(struct proc * p) return (1); } + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + /* XXX mach_init doesn't have a p_ucred when it calls this function */ - if (p->p_ucred != NOCRED && p->p_ucred != FSCRED) { - sec_token.val[0] = kauth_cred_getuid(p->p_ucred); - sec_token.val[1] = p->p_ucred->cr_gid; + if (IS_VALID_CRED(my_cred)) { + sec_token.val[0] = kauth_cred_getuid(my_cred); + sec_token.val[1] = kauth_cred_getgid(my_cred); } else { sec_token.val[0] = 0; sec_token.val[1] = 0; @@ -982,22 +1988,31 @@ set_security_token(struct proc * p) * the user of the trailer from future representation * changes. */ - audit_token.val[0] = p->p_ucred->cr_au.ai_auid; - audit_token.val[1] = p->p_ucred->cr_uid; - audit_token.val[2] = p->p_ucred->cr_gid; - audit_token.val[3] = p->p_ucred->cr_ruid; - audit_token.val[4] = p->p_ucred->cr_rgid; + audit_token.val[0] = my_cred->cr_audit.as_aia_p->ai_auid; + audit_token.val[1] = my_pcred->cr_uid; + audit_token.val[2] = my_pcred->cr_gid; + audit_token.val[3] = my_pcred->cr_ruid; + audit_token.val[4] = my_pcred->cr_rgid; audit_token.val[5] = p->p_pid; - audit_token.val[6] = p->p_ucred->cr_au.ai_asid; - audit_token.val[7] = p->p_ucred->cr_au.ai_termid.port; + audit_token.val[6] = my_cred->cr_audit.as_aia_p->ai_asid; + audit_token.val[7] = p->p_idversion; + +#if CONFIG_MACF_MACH + mac_task_label_update_cred(my_cred, p->task); +#endif + + host_priv = (sec_token.val[0]) ? HOST_PRIV_NULL : host_priv_self(); +#if CONFIG_MACF + if (host_priv != HOST_PRIV_NULL && mac_system_check_host_priv(my_cred)) + host_priv = HOST_PRIV_NULL; +#endif + kauth_cred_unref(&my_cred); return (host_security_set_task_token(host_security_self(), p->task, sec_token, audit_token, - (sec_token.val[0]) ? - HOST_PRIV_NULL : - host_priv_self()) != KERN_SUCCESS); + host_priv) != KERN_SUCCESS); } @@ -1008,10 +2023,178 @@ __private_extern__ void cru2x(kauth_cred_t cr, struct xucred *xcr) { + posix_cred_t pcr = posix_cred_get(cr); bzero(xcr, sizeof(*xcr)); xcr->cr_version = XUCRED_VERSION; xcr->cr_uid = kauth_cred_getuid(cr); - xcr->cr_ngroups = cr->cr_ngroups; - bcopy(cr->cr_groups, xcr->cr_groups, sizeof(xcr->cr_groups)); + xcr->cr_ngroups = pcr->cr_ngroups; + bcopy(pcr->cr_groups, xcr->cr_groups, sizeof(xcr->cr_groups)); +} + +#if CONFIG_LCTX + +/* + * Set Login Context ID + */ +/* + * MPSAFE - assignment of (visible) process to context protected by ALLLCTX_LOCK, + * LCTX by its own locks. + */ +int +setlcid(proc_t p0, struct setlcid_args *uap, __unused int32_t *retval) +{ + proc_t p; + struct lctx *l; + int error = 0; + int refheld = 0; + + AUDIT_ARG(pid, uap->pid); + AUDIT_ARG(value32, uap->lcid); + if (uap->pid == LCID_PROC_SELF) { /* Create/Join/Leave */ + p = p0; + } else { /* Adopt/Orphan */ + p = proc_find(uap->pid); + if (p == NULL) + return (ESRCH); + refheld = 1; + } + +#if CONFIG_MACF + error = mac_proc_check_setlcid(p0, p, uap->pid, uap->lcid); + if (error) + goto out; +#endif + + switch (uap->lcid) { + /* Leave/Orphan */ + case LCID_REMOVE: + + /* Only root may Leave/Orphan. */ + if (!kauth_cred_issuser(kauth_cred_get())) { + error = EPERM; + goto out; + } + + /* Process not in login context. */ + if (p->p_lctx == NULL) { + error = ENOATTR; + goto out; + } + + l = NULL; + + break; + + /* Create */ + case LCID_CREATE: + + /* Create only valid for self! */ + if (uap->pid != LCID_PROC_SELF) { + error = EPERM; + goto out; + } + + /* Already in a login context. */ + if (p->p_lctx != NULL) { + error = EPERM; + goto out; + } + + l = lccreate(); + if (l == NULL) { + error = ENOMEM; + goto out; + } + + LCTX_LOCK(l); + + break; + + /* Join/Adopt */ + default: + + /* Only root may Join/Adopt. */ + if (!kauth_cred_issuser(kauth_cred_get())) { + error = EPERM; + goto out; + } + + l = lcfind(uap->lcid); + if (l == NULL) { + error = ENOATTR; + goto out; + } + + break; + } + + ALLLCTX_LOCK; + leavelctx(p); + enterlctx(p, l, (uap->lcid == LCID_CREATE) ? 1 : 0); + ALLLCTX_UNLOCK; + +out: + if (refheld != 0) + proc_rele(p); + return (error); +} + +/* + * Get Login Context ID + */ +/* + * MPSAFE - membership of (visible) process in a login context + * protected by the all-context lock. + */ +int +getlcid(proc_t p0, struct getlcid_args *uap, int32_t *retval) +{ + proc_t p; + int error = 0; + int refheld = 0; + + AUDIT_ARG(pid, uap->pid); + if (uap->pid == LCID_PROC_SELF) { + p = p0; + } else { + p = proc_find(uap->pid); + if (p == NULL) + return (ESRCH); + refheld = 1; + } + +#if CONFIG_MACF + error = mac_proc_check_getlcid(p0, p, uap->pid); + if (error) + goto out; +#endif + ALLLCTX_LOCK; + if (p->p_lctx == NULL) { + error = ENOATTR; + ALLLCTX_UNLOCK; + goto out; + } + *retval = p->p_lctx->lc_id; + ALLLCTX_UNLOCK; + out: + if (refheld != 0) + proc_rele(p); + + return (error); +} +#else /* LCTX */ +int +setlcid(proc_t p0, struct setlcid_args *uap, int32_t *retval) +{ + + return (ENOSYS); +} + +int +getlcid(proc_t p0, struct getlcid_args *uap, int32_t *retval) +{ + + return (ENOSYS); } +#endif /* !LCTX */