X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/e5568f75972dfc723778653c11cb6b4dc825716a..a991bd8d3e7fe02dbca0644054bab73c5b75324a:/bsd/kern/kern_prot.c diff --git a/bsd/kern/kern_prot.c b/bsd/kern/kern_prot.c index db25475d6..d689b70d5 100644 --- a/bsd/kern/kern_prot.c +++ b/bsd/kern/kern_prot.c @@ -1,26 +1,34 @@ /* - * Copyright (c) 2000-2004 Apple Computer, Inc. All rights reserved. - * - * @APPLE_LICENSE_HEADER_START@ - * - * The contents of this file constitute Original Code as defined in and - * are subject to the Apple Public Source License Version 1.1 (the - * "License"). You may not use this file except in compliance with the - * License. Please obtain a copy of the License at - * http://www.apple.com/publicsource and read it before using this file. - * - * This Original Code and all software distributed under the License are - * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * Copyright (c) 2000-2008 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License. - * - * @APPLE_LICENSE_HEADER_END@ - */ -/* Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved */ -/* + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + * + * + * 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. @@ -58,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. + * */ /* @@ -68,279 +89,614 @@ #include <sys/acct.h> #include <sys/systm.h> #include <sys/ucred.h> -#include <sys/proc.h> +#include <sys/proc_internal.h> +#include <sys/user.h> +#include <sys/kauth.h> #include <sys/timeb.h> #include <sys/times.h> #include <sys/malloc.h> +#include <sys/persona.h> -#include <bsm/audit_kernel.h> +#include <security/audit/audit.h> -#include <sys/mount.h> +#if CONFIG_MACF +#include <security/mac_framework.h> +#endif + +#include <sys/mount_internal.h> +#include <sys/sysproto.h> #include <mach/message.h> #include <mach/host_security.h> #include <kern/host.h> +#include <kern/task.h> /* for current_task() */ +#include <kern/assert.h> + + +/* + * 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 */ + +#if DEVELOPMENT || DEBUG +extern void task_importance_update_owner_info(task_t); +#endif + /* - * 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 */ -struct setprivexec_args { - int flag; -}; int -setprivexec(p, uap, retval) - struct proc *p; - register 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); + return 0; } -/* ARGSUSED */ -getpid(p, uap, retval) - struct proc *p; - void *uap; - register_t *retval; -{ +/* + * getpid + * + * Description: get the process ID + * + * Parameters: (void) + * + * Returns: pid_t Current process ID + * + * XXX: Belongs in kern_proc.c + */ +int +getpid(proc_t p, __unused struct getpid_args *uap, int32_t *retval) +{ *retval = p->p_pid; -#if COMPAT_43 - retval[1] = p->p_pptr->p_pid; -#endif - return (0); + return 0; } -/* ARGSUSED */ -getppid(p, uap, retval) - struct proc *p; - void *uap; - register_t *retval; -{ - *retval = p->p_pptr->p_pid; - return (0); +/* + * getppid + * + * Description: get the parent process ID + * + * Parameters: (void) + * + * Returns: pid_t Parent process ID + * + * XXX: Belongs in kern_proc.c + */ +int +getppid(proc_t p, __unused struct getppid_args *uap, int32_t *retval) +{ + *retval = p->p_ppid; + return 0; } -/* Get process group ID; note that POSIX getpgrp takes no parameter */ -getpgrp(p, uap, retval) - struct proc *p; - void *uap; - register_t *retval; -{ - *retval = p->p_pgrp->pg_id; - return (0); +/* + * 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(proc_t p, __unused struct getpgrp_args *uap, int32_t *retval) +{ + *retval = p->p_pgrpid; + return 0; } -/* Get an arbitary pid's process group id */ -struct getpgid_args { - pid_t pid; -}; +/* + * 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(p, uap, retval) - 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) + if (uap->pid == 0) { goto found; + } - if ((pt = pfind(uap->pid)) == 0) - return (ESRCH); + if ((pt = proc_find(uap->pid)) == 0) { + return ESRCH; + } + refheld = 1; found: - *retval = pt->p_pgrp->pg_id; - return (0); + *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 */ -struct getsid_args { - pid_t pid; -}; - int -getsid(p, uap, retval) - 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) + if (uap->pid == 0) { goto found; + } - if ((pt = pfind(uap->pid)) == 0) - return (ESRCH); + if ((pt = proc_find(uap->pid)) == 0) { + return ESRCH; + } + refheld = 1; found: - *retval = pt->p_session->s_sid; - return (0); + sessp = proc_session(pt); + *retval = sessp->s_sid; + session_rele(sessp); + + if (refheld != 0) { + proc_rele(pt); + } + return 0; } -/* ARGSUSED */ -getuid(p, uap, retval) - struct proc *p; - void *uap; - register_t *retval; -{ - *retval = p->p_cred->p_ruid; -#if COMPAT_43 - retval[1] = p->p_ucred->cr_uid; -#endif - return (0); +/* + * getuid + * + * Description: get real user ID for caller + * + * Parameters: (void) + * + * Returns: uid_t The real uid of the caller + */ +int +getuid(__unused proc_t p, __unused struct getuid_args *uap, int32_t *retval) +{ + *retval = kauth_getruid(); + return 0; } -/* ARGSUSED */ -geteuid(p, uap, retval) - struct proc *p; - void *uap; - register_t *retval; -{ - *retval = p->p_ucred->cr_uid; - return (0); +/* + * geteuid + * + * Description: get effective user ID for caller + * + * Parameters: (void) + * + * Returns: uid_t The effective uid of the caller + */ +int +geteuid(__unused proc_t p, __unused struct geteuid_args *uap, int32_t *retval) +{ + *retval = kauth_getuid(); + return 0; } -/* ARGSUSED */ -getgid(p, uap, retval) - struct proc *p; - void *uap; - register_t *retval; + +/* + * 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 proc_t p, struct gettid_args *uap, int32_t *retval) { + struct uthread *uthread = get_bsdthread_info(current_thread()); + int error; - *retval = p->p_cred->p_rgid; -#if COMPAT_43 - retval[1] = p->p_ucred->cr_groups[0]; -#endif - return (0); + /* + * If this thread is not running with an override identity, we can't + * return one to the caller, so return an error instead. + */ + if (!(uthread->uu_flag & UT_SETUID)) { + return ESRCH; + } + + if ((error = suword(uap->uidp, kauth_cred_getruid(uthread->uu_ucred)))) { + return error; + } + if ((error = suword(uap->gidp, kauth_cred_getrgid(uthread->uu_ucred)))) { + return error; + } + + *retval = 0; + 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. + * getgid + * + * Description: get the real group ID for the calling process + * + * Parameters: (void) + * + * Returns: gid_t The real gid of the caller */ -/* ARGSUSED */ -getegid(p, uap, retval) - struct proc *p; - void *uap; - register_t *retval; +int +getgid(__unused proc_t p, __unused struct getgid_args *uap, int32_t *retval) { + *retval = kauth_getrgid(); + return 0; +} + - *retval = p->p_ucred->cr_groups[0]; - return (0); +/* + * 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. + */ +int +getegid(__unused proc_t p, __unused struct getegid_args *uap, int32_t *retval) +{ + *retval = kauth_getgid(); + return 0; } -struct getgroups_args { - u_int gidsetsize; - gid_t *gidset; -}; -getgroups(p, uap, retval) - struct proc *p; - register struct getgroups_args *uap; - register_t *retval; + +/* + * 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 proc_t p, struct getgroups_args *uap, int32_t *retval) { - register struct pcred *pc = p->p_cred; - register u_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 = pc->pc_ucred->cr_ngroups; - return (0); - } - if (ngrp < pc->pc_ucred->cr_ngroups) - return (EINVAL); - pcred_readlock(p); - ngrp = pc->pc_ucred->cr_ngroups; - if (error = copyout((caddr_t)pc->pc_ucred->cr_groups, - (caddr_t)uap->gidset, ngrp * sizeof(gid_t))) { - pcred_unlock(p); - return (error); - } - pcred_unlock(p); + *retval = pcred->cr_ngroups; + kauth_cred_unref(&cred); + return 0; + } + if (ngrp < pcred->cr_ngroups) { + kauth_cred_unref(&cred); + return EINVAL; + } + ngrp = pcred->cr_ngroups; + if ((error = copyout((caddr_t)pcred->cr_groups, + uap->gidset, + ngrp * sizeof(gid_t)))) { + kauth_cred_unref(&cred); + return error; + } + kauth_cred_unref(&cred); *retval = ngrp; - return (0); + return 0; +} + + +/* + * Return the per-thread/per-process supplementary groups list. + * + * XXX implement getsgroups + * + */ + +int +getsgroups(__unused proc_t p, __unused struct getsgroups_args *uap, __unused int32_t *retval) +{ + return ENOTSUP; +} + +/* + * Return the per-thread/per-process whiteout groups list. + * + * XXX implement getwgroups + * + */ + +int +getwgroups(__unused proc_t p, __unused struct getwgroups_args *uap, __unused int32_t *retval) +{ + return ENOTSUP; } -/* ARGSUSED */ -setsid(p, uap, retval) - register struct proc *p; - void *uap; - register_t *retval; +/* + * setsid_internal + * + * Description: Core implementation of setsid(). + */ +int +setsid_internal(proc_t p) { + struct pgrp * pg = PGRP_NULL; - if (p->p_pgid == p->p_pid || pgfind(p->p_pid) || p->p_flag & P_INVFORK) { - return (EPERM); + 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); + return 0; + } +} + +/* + * 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(proc_t p, __unused struct setsid_args *uap, int32_t *retval) +{ + int rc = setsid_internal(p); + if (rc == 0) { *retval = p->p_pid; - return (0); } + return rc; } + /* - * set process group (setpgid/old setpgrp) + * setpgid + * + * Description: set process group ID for job control + * + * Parameters: uap->pid Process to change + * uap->pgid Process group to join or create * - * caller does setpgid(targpid, targpgid) + * 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 * - * 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) - * if pgid != pid - * there must exist some pid in same session having pgid (EPERM) - * pid must not be session leader (EPERM) + * 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 */ -struct setpgid_args { - int pid; - int pgid; -}; -/* ARGSUSED */ -setpgid(curp, uap, retval) - struct proc *curp; - register struct setpgid_args *uap; - register_t *retval; +int +setpgid(proc_t curp, 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) + 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 +proc_issetugid(proc_t p) +{ + return (p->p_flag & P_SUGID) ? 1 : 0; } -struct issetugid_args { - int dummy; -}; -issetugid(p, uap, retval) - struct proc *p; - struct issetugid_args *uap; - register_t *retval; +int +issetugid(proc_t p, __unused struct issetugid_args *uap, int32_t *retval) { /* * Note: OpenBSD sets a P_SUGIDEXEC flag set at execve() time, @@ -351,472 +707,1336 @@ issetugid(p, uap, retval) * that libc *might* have put in their data segment. */ - *retval = (p->p_flag & P_SUGID) ? 1 : 0; - return (0); + *retval = proc_issetugid(p); + return 0; } -struct setuid_args { - uid_t uid; -}; -/* ARGSUSED */ -setuid(p, uap, retval) - struct proc *p; - struct setuid_args *uap; - register_t *retval; + +/* + * 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(proc_t p, struct setuid_args *uap, __unused int32_t *retval) { - register struct pcred *pc = p->p_cred; - 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 != pc->p_ruid && - (error = suser(pc->pc_ucred, &p->p_acflag))) - 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); - pcred_writelock(p); - (void)chgproccnt(pc->p_ruid, -1); - (void)chgproccnt(uid, 1); - pc->pc_ucred = crcopy(pc->pc_ucred); - pc->pc_ucred->cr_uid = uid; - pc->p_ruid = uid; - pc->p_svuid = uid; - pcred_unlock(p); - set_security_token(p); - p->p_flag |= P_SUGID; - return (0); -} -struct seteuid_args { - uid_t euid; -}; -/* ARGSUSED */ -seteuid(p, uap, retval) - struct proc *p; - struct seteuid_args *uap; - register_t *retval; -{ - register struct pcred *pc = p->p_cred; - register uid_t euid; - int error; + /* get current credential and take a reference while we muck with it */ + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); - euid = uap->euid; - AUDIT_ARG(uid, 0, euid, 0, 0); - if (euid != pc->p_ruid && euid != pc->p_svuid && - (error = suser(pc->pc_ucred, &p->p_acflag))) - return (error); - /* - * Everything's okay, do it. Copy credentials so other references do - * not see our changes. - */ - pcred_writelock(p); - pc->pc_ucred = crcopy(pc->pc_ucred); - pc->pc_ucred->cr_uid = euid; - pcred_unlock(p); - set_security_token(p); - p->p_flag |= P_SUGID; - return (0); -} + 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); -struct setgid_args { - gid_t gid; -}; -/* ARGSUSED */ -setgid(p, uap, retval) - struct proc *p; - struct setgid_args *uap; - register_t *retval; -{ - register struct pcred *pc = p->p_cred; - register gid_t gid; - int error; + for (;;) { + 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; + } - gid = uap->gid; - AUDIT_ARG(gid, gid, 0, 0, 0); - if (gid != pc->p_rgid && (error = suser(pc->pc_ucred, &p->p_acflag))) - return (error); - pcred_writelock(p); - pc->pc_ucred = crcopy(pc->pc_ucred); - pc->pc_ucred->cr_groups[0] = gid; - pc->p_rgid = gid; - pc->p_svgid = gid; /* ??? */ - pcred_unlock(p); - set_security_token(p); - p->p_flag |= P_SUGID; - return (0); -} + /* + * If we are privileged, 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; + } else { + svuid = KAUTH_UID_NONE; + ruid = KAUTH_UID_NONE; + } + /* + * 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). + */ + if (!(my_pcred->cr_flags & CRF_NOMEMBERD)) { + gmuid = uid; + } -struct setegid_args { - gid_t egid; -}; -/* ARGSUSED */ -setegid(p, uap, retval) - struct proc *p; - struct setegid_args *uap; - register_t *retval; -{ - register struct pcred *pc = p->p_cred; - register gid_t egid; - int error; + /* + * 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); + + /* + * If we're changing the ruid from A to B, we might race with another thread that's setting ruid from B to A. + * The current locking mechanisms don't allow us to make the entire credential switch operation atomic, + * thus we may be able to change the process credentials from ruid A to B, but get preempted before incrementing the proc + * count of B. If a second thread sees the new process credentials and switches back to ruid A, that other thread + * may be able to decrement the proc count of B before we can increment it. This results in a panic. + * Incrementing the proc count of the target ruid, B, before setting the process credentials prevents this race. + */ + if (ruid != KAUTH_UID_NONE && !proc_has_persona(p)) { + (void)chgproccnt(ruid, 1); + } + + proc_ucred_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. + * + * Note: the kauth_cred_setresuid has consumed a reference to my_cred, it p_ucred != my_cred, then my_cred must not be dereferenced! + */ + if (p->p_ucred != my_cred) { + proc_ucred_unlock(p); + /* + * We didn't successfully switch to the new ruid, so decrement + * the procs/uid count that we incremented above. + */ + if (ruid != KAUTH_UID_NONE && !proc_has_persona(p)) { + (void)chgproccnt(ruid, -1); + } + kauth_cred_unref(&my_new_cred); + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + /* 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_ucred_unlock(p); + /* + * If we've updated the ruid, decrement the count of procs running + * under the previous ruid + */ + if (ruid != KAUTH_UID_NONE && !proc_has_persona(p)) { + (void)chgproccnt(my_pcred->cr_ruid, -1); + } + } + break; + } + /* Drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); - egid = uap->egid; - AUDIT_ARG(gid, 0, egid, 0, 0); - if (egid != pc->p_rgid && egid != pc->p_svgid && - (error = suser(pc->pc_ucred, &p->p_acflag))) - return (error); - pcred_writelock(p); - pc->pc_ucred = crcopy(pc->pc_ucred); - pc->pc_ucred->cr_groups[0] = egid; - pcred_unlock(p); set_security_token(p); - p->p_flag |= P_SUGID; - return (0); + return 0; } -struct setgroups_args{ - u_int gidsetsize; - gid_t *gidset; -}; - -/* ARGSUSED */ -setgroups(p, uap, retval) - struct proc *p; - struct setgroups_args *uap; - register_t *retval; -{ - register struct pcred *pc = p->p_cred; - struct ucred *new, *old; - register u_int ngrp; - int error; - if (error = suser(pc->pc_ucred, &p->p_acflag)) - return (error); - ngrp = uap->gidsetsize; - if (ngrp > NGROUPS) - return (EINVAL); - new = crget(); - - if ( ngrp < 1 ) { - ngrp = 1; - } - else { - error = copyin((caddr_t)uap->gidset, - (caddr_t)new->cr_groups, ngrp * sizeof(gid_t)); - if (error) { - crfree(new); - return (error); +/* + * 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); + + for (;;) { + 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; + } + + /* + * 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_ucred_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_ucred_unlock(p); + kauth_cred_unref(&my_new_cred); + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + /* 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_ucred_unlock(p); } + break; } - new->cr_ngroups = ngrp; - AUDIT_ARG(groupset, new->cr_groups, ngrp); - pcred_writelock(p); - old = pc->pc_ucred; - new->cr_uid = old->cr_uid; - pc->pc_ucred = new; - pcred_unlock(p); + /* drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); + set_security_token(p); - p->p_flag |= P_SUGID; - if (old != NOCRED) - crfree(old); - return (0); -} - -#if COMPAT_43 -struct osetreuid_args{ - int ruid; - int euid; -}; -/* ARGSUSED */ -osetreuid(p, uap, retval) - register struct proc *p; - struct osetreuid_args *uap; - register_t *retval; -{ - struct seteuid_args seuidargs; - struct setuid_args suidargs; + return 0; +} - /* - * There are five cases, and we attempt to emulate them in - * the following fashion: - * -1, -1: return 0. This is correct emulation. - * -1, N: call seteuid(N). This is correct emulation. - * N, -1: if we called setuid(N), our euid would be changed - * to N as well. the theory is that we don't want to - * revoke root access yet, so we call seteuid(N) - * instead. This is incorrect emulation, but often - * suffices enough for binary compatibility. - * N, N: call setuid(N). This is correct emulation. - * N, M: call setuid(N). This is close to correct emulation. - */ - if (uap->ruid == (uid_t)-1) { - if (uap->euid == (uid_t)-1) - return (0); /* -1, -1 */ - seuidargs.euid = uap->euid; /* -1, N */ - return (seteuid(p, &seuidargs, retval)); + +/* + * 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 +setreuid(proc_t p, struct setreuid_args *uap, __unused int32_t *retval) +{ + 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; + if (ruid == (uid_t)-1) { + ruid = KAUTH_UID_NONE; + } + if (euid == (uid_t)-1) { + euid = KAUTH_UID_NONE; } - if (uap->euid == (uid_t)-1) { - seuidargs.euid = uap->ruid; /* N, -1 */ - return (seteuid(p, &seuidargs, retval)); + AUDIT_ARG(euid, euid); + AUDIT_ARG(ruid, ruid); + + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + + for (;;) { + 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 = svuid */ + (error = suser(my_cred, &p->p_acflag))) { /* allow root user any */ + kauth_cred_unref(&my_cred); + return error; + } + + uid_t new_euid; + uid_t svuid = KAUTH_UID_NONE; + + new_euid = my_pcred->cr_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. + */ + if (euid != KAUTH_UID_NONE && my_pcred->cr_uid != euid) { + /* changing the effective UID */ + new_euid = euid; + 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. + */ + 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); + + /* + * If we're changing the ruid from A to B, we might race with another thread that's setting ruid from B to A. + * The current locking mechanisms don't allow us to make the entire credential switch operation atomic, + * thus we may be able to change the process credentials from ruid A to B, but get preempted before incrementing the proc + * count of B. If a second thread sees the new process credentials and switches back to ruid A, that other thread + * may be able to decrement the proc count of B before we can increment it. This results in a panic. + * Incrementing the proc count of the target ruid, B, before setting the process credentials prevents this race. + */ + if (ruid != KAUTH_UID_NONE && !proc_has_persona(p)) { + (void)chgproccnt(ruid, 1); + } + + proc_ucred_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. + * + * Note: the kauth_cred_setresuid has consumed a reference to my_cred, it p_ucred != my_cred, then my_cred must not be dereferenced! + */ + if (p->p_ucred != my_cred) { + proc_ucred_unlock(p); + if (ruid != KAUTH_UID_NONE && !proc_has_persona(p)) { + /* + * We didn't successfully switch to the new ruid, so decrement + * the procs/uid count that we incremented above. + */ + (void)chgproccnt(ruid, -1); + } + kauth_cred_unref(&my_new_cred); + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + /* 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_ucred_unlock(p); + + if (ruid != KAUTH_UID_NONE && !proc_has_persona(p)) { + /* + * We switched to a new ruid, so decrement the count of procs running + * under the previous ruid + */ + (void)chgproccnt(my_pcred->cr_ruid, -1); + } + } + break; } - suidargs.uid = uap->ruid; /* N, N and N, M */ - return (setuid(p, &suidargs, retval)); + /* drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); + + set_security_token(p); + return 0; } -struct osetregid_args { - int rgid; - int egid; -}; -/* ARGSUSED */ -osetregid(p, uap, retval) - register struct proc *p; - struct osetregid_args *uap; - register_t *retval; + +/* + * 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(proc_t p, struct setgid_args *uap, __unused int32_t *retval) { - struct setegid_args segidargs; - struct setgid_args sgidargs; + 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; - /* - * There are five cases, described above in osetreuid() - */ - if (uap->rgid == (gid_t)-1) { - if (uap->egid == (gid_t)-1) - return (0); /* -1, -1 */ - segidargs.egid = uap->egid; /* -1, N */ - return (setegid(p, &segidargs, retval)); - } - if (uap->egid == (gid_t)-1) { - segidargs.egid = uap->rgid; /* N, -1 */ - return (setegid(p, &segidargs, retval)); + 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); + + /* get current credential and take a reference while we muck with it */ + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + + for (;;) { + 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 privileged, 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; + } else { + svgid = KAUTH_GID_NONE; + rgid = KAUTH_GID_NONE; + } + + /* + * 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_ucred_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_ucred_unlock(p); + kauth_cred_unref(&my_new_cred); + /* try again */ + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + continue; + } + p->p_ucred = my_new_cred; + /* update cred on proc */ + PROC_UPDATE_CREDS_ONPROC(p); + OSBitOrAtomic(P_SUGID, &p->p_flag); + proc_ucred_unlock(p); + } + break; } - sgidargs.gid = uap->rgid; /* N, N and N, M */ - return (setgid(p, &sgidargs, retval)); + /* Drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); + + set_security_token(p); + return 0; } -#endif /* COMPAT_43 */ + /* - * Check if gid is a member of the group set. + * 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. */ -groupmember(gid, cred) - gid_t gid; - register struct ucred *cred; +int +setegid(proc_t p, struct setegid_args *uap, __unused int32_t *retval) { - register gid_t *gp; - gid_t *egp; + 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); - egp = &(cred->cr_groups[cred->cr_ngroups]); - for (gp = cred->cr_groups; gp < egp; gp++) - if (*gp == gid) - return (1); - return (0); + /* get current credential and take a reference while we muck with it */ + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + + + for (;;) { + 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; + } + /* + * 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_ucred_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_ucred_unlock(p); + kauth_cred_unref(&my_new_cred); + /* try again */ + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + continue; + } + p->p_ucred = my_new_cred; + /* update cred on proc */ + PROC_UPDATE_CREDS_ONPROC(p); + OSBitOrAtomic(P_SUGID, &p->p_flag); + proc_ucred_unlock(p); + } + break; + } + + /* Drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); + + set_security_token(p); + 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. + * 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. */ -suser(cred, acflag) - struct ucred *cred; - u_short *acflag; +int +setregid(proc_t p, struct setregid_args *uap, __unused int32_t *retval) { -#if DIAGNOSTIC - if (cred == NOCRED || cred == FSCRED) - panic("suser"); -#endif - if (cred->cr_uid == 0) { - if (acflag) - *acflag |= ASU; - return (0); + 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; + + 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); + + /* get current credential and take a reference while we muck with it */ + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + + for (;;) { + 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; + } + + 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; 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. + */ + 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_ucred_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. + */ + if (p->p_ucred != my_cred) { + proc_ucred_unlock(p); + kauth_cred_unref(&my_new_cred); + /* try again */ + my_cred = kauth_cred_proc_ref(p); + my_pcred = posix_cred_get(my_cred); + continue; + } + p->p_ucred = my_new_cred; + /* update cred on proc */ + PROC_UPDATE_CREDS_ONPROC(p); + OSBitOrAtomic(P_SUGID, &p->p_flag); /* XXX redundant? */ + proc_ucred_unlock(p); + } + break; } - return (EPERM); + /* 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 privileged, it + * can be any UID. If it is KAUTH_UID_NONE, then as a special case, this + * means "revert to the per process credential"; otherwise, if permitted, + * it changes the effective, real, and saved UIDs and GIDs for the current + * thread to the requested UID and single GID, and clears all other GIDs. + */ int -is_suser(void) +settid(proc_t p, struct settid_args *uap, __unused int32_t *retval) { - struct proc *p = current_proc(); + kauth_cred_t uc; + struct uthread *uthread = get_bsdthread_info(current_thread()); + uid_t uid; + gid_t gid; + + uid = uap->uid; + gid = uap->gid; + AUDIT_ARG(uid, uid); + AUDIT_ARG(gid, gid); + + if (proc_suser(p) != 0) { + return EPERM; + } - if (!p) - return (0); + if (uid == KAUTH_UID_NONE) { + /* must already be assuming another identity in order to revert back */ + if ((uthread->uu_flag & UT_SETUID) == 0) { + return EPERM; + } + + /* revert to delayed binding of process credential */ + uc = kauth_cred_proc_ref(p); + kauth_cred_unref(&uthread->uu_ucred); + uthread->uu_ucred = uc; + uthread->uu_flag &= ~UT_SETUID; + } else { + kauth_cred_t my_cred, my_new_cred; - return (suser(p->p_ucred, &p->p_acflag) == 0); + /* cannot already be assuming another identity */ + if ((uthread->uu_flag & UT_SETUID) != 0) { + return EPERM; + } + + /* + * 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; + my_new_cred = kauth_cred_setuidgid(my_cred, uid, gid); + if (my_cred != my_new_cred) { + uthread->uu_ucred = my_new_cred; + } + uthread->uu_flag |= UT_SETUID; + + /* Drop old uthread reference or our extra reference */ + kauth_cred_unref(&my_cred); + } + /* + * XXX should potentially set per thread security token (there is + * XXX none). + * XXX it is unclear whether P_SUGID should be st at this point; + * XXX in theory, it is being deprecated. + */ + 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 + * of the current process. + * + * 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 -is_suser1(void) +settid_with_pid(proc_t p, struct settid_with_pid_args *uap, __unused int32_t *retval) { - struct proc *p = current_proc(); + 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; - if (!p) - return (0); + AUDIT_ARG(pid, uap->pid); + AUDIT_ARG(value32, uap->assume); + + if (proc_suser(p) != 0) { + return EPERM; + } - return (suser(p->p_ucred, &p->p_acflag) == 0 || - p->p_cred->p_ruid == 0 || p->p_cred->p_svuid == 0); + /* + * XXX should potentially set per thread security token (there is + * XXX none). + * XXX it is unclear whether P_SUGID should be st at this point; + * XXX in theory, it is being deprecated. + */ + + /* + * assume argument tells us to assume the identity of the process with the + * id passed in the pid argument. + */ + if (uap->assume != 0) { + /* can't do this if we have already assumed an identity */ + if ((uthread->uu_flag & UT_SETUID) != 0) { + return EPERM; + } + + 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. 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_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 old uthread reference or our extra reference */ + proc_rele(target_proc); + kauth_cred_unref(&my_cred); + kauth_cred_unref(&my_target_cred); + + return 0; + } + + /* + * 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_unref(&uthread->uu_ucred); + uthread->uu_ucred = my_new_cred; + uthread->uu_flag &= ~UT_SETUID; + + return 0; } + /* - * Allocate a zeroed cred structure. + * 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. */ -struct ucred * -crget() +static int +setgroups1(proc_t p, u_int ngrp, user_addr_t gidset, uid_t gmuid, __unused int32_t *retval) { - register struct ucred *cr; + gid_t newgroups[NGROUPS] = { 0 }; + int error; + + DEBUG_CRED_ENTER("setgroups1 (%d/%d): %d 0x%016x %d\n", p->p_pid, + (p->p_pptr ? p->p_pptr->p_pid : 0), ngrp, gidset, gmuid); + + if (ngrp > NGROUPS) { + return EINVAL; + } - MALLOC_ZONE(cr, struct ucred *, sizeof(*cr), M_CRED, M_WAITOK); - bzero((caddr_t)cr, sizeof(*cr)); - cr->cr_ref = 1; - return (cr); + if (ngrp >= 1) { + error = copyin(gidset, + (caddr_t)newgroups, ngrp * sizeof(gid_t)); + if (error) { + return error; + } + } + return setgroups_internal(p, ngrp, newgroups, gmuid); } +int +setgroups_internal(proc_t p, u_int ngrp, gid_t *newgroups, uid_t gmuid) +{ + struct uthread *uthread = get_bsdthread_info(current_thread()); + kauth_cred_t my_cred, my_new_cred; + int error; + + my_cred = kauth_cred_proc_ref(p); + if ((error = suser(my_cred, &p->p_acflag))) { + kauth_cred_unref(&my_cred); + return error; + } + + if (ngrp < 1) { + ngrp = 1; + newgroups[0] = 0; + } + + 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 + * of the process one. If we were the only reference holder, + * the credential is updated in place, otherwise, our reference + * 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. + */ + my_cred = uthread->uu_ucred; + 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 { + /* + * 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_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_ucred_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_ucred_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_ucred_unlock(p); + } + 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); + + + set_security_token(p); + } + + return 0; +} + + /* - * Free a cred structure. - * Throws away space when ref count gets to 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() */ -void -crfree(cr) - struct ucred *cr; +int +initgroups(proc_t p, struct initgroups_args *uap, __unused int32_t *retval) { -#if DIAGNOSTIC - if (cr == NOCRED || cr == FSCRED) - panic("crfree"); -#endif - if (--cr->cr_ref == 0) - FREE_ZONE((caddr_t)cr, sizeof *cr, M_CRED); + DEBUG_CRED_ENTER("initgroups\n"); + + return setgroups1(p, uap->gidsetsize, uap->gidset, uap->gmuid, retval); } + /* - * Copy cred structure to a new one and free the old one. + * 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() */ -struct ucred * -crcopy(cr) - struct ucred *cr; +int +setgroups(proc_t p, struct setgroups_args *uap, __unused int32_t *retval) { - struct ucred *newcr; + DEBUG_CRED_ENTER("setgroups\n"); -#if DIAGNOSTIC - if (cr == NOCRED || cr == FSCRED) - panic("crcopy"); -#endif - if (cr->cr_ref == 1) - return (cr); - newcr = crget(); - *newcr = *cr; - crfree(cr); - newcr->cr_ref = 1; - return (newcr); + return setgroups1(p, uap->gidsetsize, uap->gidset, KAUTH_UID_NONE, retval); } + /* - * Dup cred struct to a new held one. + * Set the per-thread/per-process supplementary groups list. + * + * XXX implement setsgroups + * */ -struct ucred * -crdup(cr) - struct ucred *cr; + +int +setsgroups(__unused proc_t p, __unused struct setsgroups_args *uap, __unused int32_t *retval) { - struct ucred *newcr; + return ENOTSUP; +} -#if DIAGNOSTIC - if (cr == NOCRED || cr == FSCRED) - panic("crdup"); -#endif - newcr = crget(); - *newcr = *cr; - newcr->cr_ref = 1; - return (newcr); +/* + * Set the per-thread/per-process whiteout groups list. + * + * XXX implement setwgroups + * + */ + +int +setwgroups(__unused proc_t p, __unused struct setwgroups_args *uap, __unused int32_t *retval) +{ + return ENOTSUP; } + /* - * compare two cred structs + * Check if gid is a member of the group set. + * + * XXX This interface is going away; use kauth_cred_ismember_gid() directly + * XXX instead. */ int -crcmp(cr1, cr2) - struct ucred *cr1; - struct ucred *cr2; +groupmember(gid_t gid, kauth_cred_t cred) { - int i; + int is_member; - if (cr1 == cr2) - return 0; - if (cr1 == NOCRED || cr1 == FSCRED || - cr2 == NOCRED || cr2 == FSCRED) - return 1; - if (cr1->cr_uid != cr2->cr_uid) - return 1; - if (cr1->cr_ngroups != cr2->cr_ngroups) + if (kauth_cred_ismember_gid(cred, gid, &is_member) == 0 && is_member) { return 1; - // XXX assumes groups will always be listed in some order - for (i=0; i < cr1->cr_ngroups; i++) - if (cr1->cr_groups[i] != cr2->cr_groups[i]) - return 1; - return (0); + } + 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; 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 (!IS_VALID_CRED(cred)) { + panic("suser"); + } +#endif + if (kauth_cred_getuid(cred) == 0) { + if (acflag) { + *acflag |= ASU; + } + return 0; + } + return EPERM; } + /* - * Get login name, if available. + * 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 */ -struct getlogin_args { - char *namebuf; - u_int namelen; -}; -/* ARGSUSED */ -getlogin(p, uap, retval) - struct proc *p; - struct getlogin_args *uap; - register_t *retval; +int +getlogin(proc_t p, struct getlogin_args *uap, __unused int32_t *retval) { + char buffer[MAXLOGNAME + 1]; + struct session * sessp; + + bzero(buffer, MAXLOGNAME + 1); + + sessp = proc_session(p); - 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, - (caddr_t)uap->namebuf, uap->namelen)); + if (uap->namelen > MAXLOGNAME) { + uap->namelen = MAXLOGNAME; + } + + if (sessp != SESSION_NULL) { + session_lock(sessp); + bcopy( sessp->s_login, buffer, uap->namelen); + session_unlock(sessp); + } + session_rele(sessp); + + return copyout((caddr_t)buffer, uap->namebuf, uap->namelen); +} + +void +setlogin_internal(proc_t p, const char login[static MAXLOGNAME]) +{ + struct session *sessp = proc_session(p); + + if (sessp != SESSION_NULL) { + session_lock(sessp); + bcopy(login, sessp->s_login, MAXLOGNAME); + session_unlock(sessp); + session_rele(sessp); + } } /* - * 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 */ -struct setlogin_args { - char *namebuf; -}; -/* ARGSUSED */ -setlogin(p, uap, retval) - struct proc *p; - struct setlogin_args *uap; - register_t *retval; +int +setlogin(proc_t p, struct setlogin_args *uap, __unused int32_t *retval) { int error; - int dummy=0; - - if (error = suser(p->p_ucred, &p->p_acflag)) - return (error); - - error = copyinstr((caddr_t) 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) + size_t dummy = 0; + char buffer[MAXLOGNAME + 1]; + + if ((error = proc_suser(p))) { + return error; + } + + bzero(&buffer[0], MAXLOGNAME + 1); + + + error = copyinstr(uap->namebuf, + (caddr_t) &buffer[0], + MAXLOGNAME - 1, (size_t *)&dummy); + + setlogin_internal(p, buffer); + + if (!error) { + AUDIT_ARG(text, buffer); + } else if (error == ENAMETOOLONG) { error = EINVAL; - return (error); + } + return error; } /* Set the secrity token of the task with current euid and eguid */ -kern_return_t -set_security_token(struct proc * p) +/* + * XXX This needs to change to give the task a reference and/or an opaque + * XXX identifier. + */ +int +set_security_token(proc_t p) { - security_token_t sec_token; - audit_token_t audit_token; + return set_security_token_task_internal(p, p->task); +} - sec_token.val[0] = p->p_ucred->cr_uid; - sec_token.val[1] = p->p_ucred->cr_gid; +static void +proc_calc_audit_token(proc_t p, kauth_cred_t my_cred, audit_token_t *audit_token) +{ + posix_cred_t my_pcred = posix_cred_get(my_cred); /* * The current layout of the Mach audit token explicitly @@ -827,36 +2047,122 @@ set_security_token(struct proc * p) * the user of the trailer from future representation * changes. */ - audit_token.val[0] = p->p_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_cred->p_ruid; - audit_token.val[4] = p->p_cred->p_rgid; - audit_token.val[5] = p->p_pid; - audit_token.val[6] = p->p_au->ai_asid; - audit_token.val[7] = p->p_au->ai_termid.port; + 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] = my_cred->cr_audit.as_aia_p->ai_asid; + audit_token->val[7] = p->p_idversion; +} + +/* + * Set the secrity token of the task with current euid and eguid + * The function takes a proc and a task, where proc->task might point to a + * different task if called from exec. + */ + +int +set_security_token_task_internal(proc_t p, void *t) +{ + kauth_cred_t my_cred; + security_token_t sec_token; + audit_token_t audit_token; + host_priv_t host_priv; + task_t task = t; + + /* + * Don't allow a vfork child to override the parent's token settings + * (since they share a task). Instead, the child will just have to + * suffer along using the parent's token until the exec(). It's all + * undefined behavior anyway, right? + */ + if (task == current_task()) { + uthread_t uthread; + uthread = (uthread_t)get_bsdthread_info(current_thread()); + if (uthread->uu_flag & UT_VFORK) { + return 1; + } + } + + my_cred = kauth_cred_proc_ref(p); + + proc_calc_audit_token(p, my_cred, &audit_token); + + /* XXX mach_init doesn't have a p_ucred when it calls this function */ + 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; + } + + 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); + +#if DEVELOPMENT || DEBUG + /* + * Update the pid an proc name for importance base if any + */ + task_importance_update_owner_info(task); +#endif 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()); + task, + sec_token, + audit_token, + host_priv) != KERN_SUCCESS; +} + +void +proc_parent_audit_token(proc_t p, audit_token_t *token_out) +{ + proc_t parent; + kauth_cred_t my_cred; + + proc_list_lock(); + + parent = p->p_pptr; + my_cred = kauth_cred_proc_ref(parent); + proc_calc_audit_token(parent, my_cred, token_out); + kauth_cred_unref(&my_cred); + + proc_list_unlock(); +} + + +int get_audit_token_pid(audit_token_t *audit_token); + +int +get_audit_token_pid(audit_token_t *audit_token) +{ + /* keep in-sync with set_security_token (above) */ + if (audit_token) { + return (int)audit_token->val[5]; + } + return -1; } /* - * Fill in a struct xucred based on a struct ucred. + * Fill in a struct xucred based on a kauth_cred_t. */ __private_extern__ void -cru2x(struct ucred *cr, struct xucred *xcr) +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 = cr->cr_uid; - xcr->cr_ngroups = cr->cr_ngroups; - bcopy(cr->cr_groups, xcr->cr_groups, sizeof(xcr->cr_groups)); + xcr->cr_uid = kauth_cred_getuid(cr); + xcr->cr_ngroups = pcr->cr_ngroups; + bcopy(pcr->cr_groups, xcr->cr_groups, sizeof(xcr->cr_groups)); }