gems-kernel/source/THIRDPARTY/xnu/bsd/kern/kern_prot.c
2024-06-03 11:29:39 -05:00

1752 lines
45 KiB
C

/*
* 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, 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.
* All or some portions of this file are derived from material licensed
* to the University of California by American Telephone and Telegraph
* Co. or Unix System Laboratories, Inc. and are reproduced herein with
* the permission of UNIX System Laboratories, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)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.
*
*/
/*
* System calls related to processes and protection
*/
#include <sys/param.h>
#include <sys/acct.h>
#include <sys/systm.h>
#include <sys/ucred.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 <security/audit/audit.h>
#if CONFIG_MACF
#include <security/mac_framework.h>
#endif
#include <sys/mount_internal.h>
#include <sys/sysproto.h>
#include <mach/message.h>
#include <kern/host.h>
#include <kern/task.h> /* for current_task() */
#include <kern/assert.h>
#if DEVELOPMENT || DEBUG
extern void task_importance_update_owner_info(task_t);
#endif
/* Used by pmap.c to copy kauth_cred_t structs */
void kauth_cred_copy(const uintptr_t kv, const uintptr_t new_data);
/*
* 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(proc_t p, struct setprivexec_args *uap, int32_t *retval)
{
AUDIT_ARG(value32, uap->flag);
*retval = p->p_debugger;
p->p_debugger = (uap->flag != 0);
return 0;
}
/*
* 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 = proc_getpid(p);
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;
}
/*
* 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;
}
/*
* 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(proc_t p, struct getpgid_args *uap, int32_t *retval)
{
proc_t pt;
int refheld = 0;
pt = p;
if (uap->pid == 0) {
goto found;
}
if ((pt = proc_find(uap->pid)) == 0) {
return ESRCH;
}
refheld = 1;
found:
*retval = pt->p_pgrpid;
if (refheld != 0) {
proc_rele(pt);
}
return 0;
}
/*
* 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(proc_t p, struct getsid_args *uap, int32_t *retval)
{
proc_t pt;
if (uap->pid == 0) {
*retval = proc_sessionid(p);
return 0;
}
if ((pt = proc_find(uap->pid)) != PROC_NULL) {
*retval = proc_sessionid(pt);
proc_rele(pt);
return 0;
}
return ESRCH;
}
/*
* 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;
}
/*
* 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;
}
/*
* 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)
{
thread_ro_t tro = current_thread_ro();
kauth_cred_t tro_cred = tro->tro_cred;
int error;
/*
* If this thread is not running with an override identity, we can't
* return one to the caller, so return an error instead.
*/
if (tro->tro_realcred == tro->tro_cred) {
return ESRCH;
}
if ((error = suword(uap->uidp, kauth_cred_getruid(tro_cred)))) {
return error;
}
if ((error = suword(uap->gidp, kauth_cred_getrgid(tro_cred)))) {
return error;
}
*retval = 0;
return 0;
}
/*
* 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 proc_t p, __unused struct getgid_args *uap, int32_t *retval)
{
*retval = kauth_getrgid();
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;
}
/*
* 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)
{
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 = 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 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;
}
/*
* setsid_internal
*
* Description: Core implementation of setsid().
*/
int
setsid_internal(proc_t p)
{
struct pgrp * pg = PGRP_NULL;
if (p->p_pgrpid == proc_getpid(p) ||
(pg = pgrp_find(proc_getpid(p)))) {
pgrp_rele(pg);
return EPERM;
}
/* enter pgrp works with its own pgrp refcount */
(void)enterpgrp(p, proc_getpid(p), 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, 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 = proc_getpid(p);
}
return rc;
}
/*
* setpgid
*
* Description: set process group ID for job control
*
* Parameters: uap->pid Process to change
* uap->pgid Process group to join or create
*
* 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
*/
int
setpgid(proc_t curp, struct setpgid_args *uap, __unused int32_t *retval)
{
proc_t targp = PROC_NULL; /* target process */
struct pgrp *curp_pg = PGRP_NULL;
struct pgrp *targp_pg = PGRP_NULL;
int error = 0;
int refheld = 0;
int samesess = 0;
curp_pg = proc_pgrp(curp, NULL);
if (uap->pid != 0 && uap->pid != proc_getpid(curp)) {
if ((targp = proc_find(uap->pid)) == 0 || !inferior(targp)) {
if (targp != PROC_NULL) {
refheld = 1;
}
error = ESRCH;
goto out;
}
refheld = 1;
targp_pg = proc_pgrp(targp, NULL);
if (targp_pg->pg_session != curp_pg->pg_session) {
error = EPERM;
goto out;
}
if (targp->p_flag & P_EXEC) {
error = EACCES;
goto out;
}
} else {
targp = curp;
targp_pg = proc_pgrp(targp, NULL);
}
if (SESS_LEADER(targp, targp_pg->pg_session)) {
error = EPERM;
goto out;
}
if (uap->pgid < 0) {
error = EINVAL;
goto out;
}
if (uap->pgid == 0) {
uap->pgid = proc_getpid(targp);
} else if (uap->pgid != proc_getpid(targp)) {
struct pgrp *pg = PGRP_NULL;
if ((pg = pgrp_find(uap->pgid)) == PGRP_NULL) {
error = EPERM;
goto out;
}
samesess = (pg->pg_session != curp_pg->pg_session);
pgrp_rele(pg);
if (samesess != 0) {
error = EPERM;
goto out;
}
}
error = enterpgrp(targp, uap->pgid, 0);
out:
pgrp_rele(curp_pg);
pgrp_rele(targp_pg);
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;
}
int
issetugid(proc_t p, __unused struct issetugid_args *uap, int32_t *retval)
{
/*
* Note: OpenBSD sets a P_SUGIDEXEC flag set at execve() time,
* we use P_SUGID because we consider changing the owners as
* "tainting" as well.
* This is significant for procs that start as root and "become"
* a user without an exec - programs cannot know *everything*
* that libc *might* have put in their data segment.
*/
*retval = proc_issetugid(p);
return 0;
}
/*
* 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)
{
__block int error = 0;
__block uid_t old_ruid;
__block uid_t ruid;
uid_t want_uid;
bool changed;
want_uid = uap->uid;
AUDIT_ARG(uid, want_uid);
changed = kauth_cred_proc_update(p, PROC_SETTOKEN_SETUGID,
^bool (kauth_cred_t parent, kauth_cred_t model) {
posix_cred_t cur_pcred = posix_cred_get(parent);
uid_t svuid = KAUTH_UID_NONE;
uid_t gmuid = KAUTH_UID_NONE;
ruid = KAUTH_UID_NONE;
old_ruid = cur_pcred->cr_ruid;
#if CONFIG_MACF
if ((error = mac_proc_check_setuid(p, parent, want_uid)) != 0) {
return false;
}
#endif
if (want_uid != cur_pcred->cr_ruid && /* allow setuid(getuid()) */
want_uid != cur_pcred->cr_svuid && /* allow setuid(saved uid) */
(error = suser(parent, &p->p_acflag))) {
return false;
}
/*
* If we are privileged, then set the saved and real UID too;
* otherwise, just set the effective UID
*/
if (suser(parent, &p->p_acflag) == 0) {
svuid = want_uid;
ruid = want_uid;
}
/*
* 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 (!(cur_pcred->cr_flags & CRF_NOMEMBERD)) {
gmuid = want_uid;
}
return kauth_cred_model_setresuid(model,
ruid, want_uid, svuid, gmuid);
});
if (changed && ruid != KAUTH_UID_NONE && old_ruid != ruid &&
!proc_has_persona(p)) {
(void)chgproccnt(ruid, 1);
(void)chgproccnt(old_ruid, -1);
}
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)
{
__block int error = 0;
uid_t want_euid;
want_euid = uap->euid;
AUDIT_ARG(euid, want_euid);
kauth_cred_proc_update(p, PROC_SETTOKEN_SETUGID,
^bool (kauth_cred_t parent, kauth_cred_t model) {
posix_cred_t cur_pcred = posix_cred_get(parent);
#if CONFIG_MACF
if ((error = mac_proc_check_seteuid(p, parent, want_euid)) != 0) {
return false;
}
#endif
if (want_euid != cur_pcred->cr_ruid && want_euid != cur_pcred->cr_svuid &&
(error = suser(parent, &p->p_acflag))) {
return false;
}
return kauth_cred_model_setresuid(model,
KAUTH_UID_NONE, want_euid,
KAUTH_UID_NONE, cur_pcred->cr_gmuid);
});
return error;
}
/*
* 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)
{
__block int error = 0;
__block uid_t old_ruid;
uid_t want_ruid, want_euid;
bool changed;
want_ruid = uap->ruid;
want_euid = uap->euid;
if (want_ruid == (uid_t)-1) {
want_ruid = KAUTH_UID_NONE;
}
if (want_euid == (uid_t)-1) {
want_euid = KAUTH_UID_NONE;
}
AUDIT_ARG(euid, want_euid);
AUDIT_ARG(ruid, want_ruid);
changed = kauth_cred_proc_update(p, PROC_SETTOKEN_SETUGID,
^bool (kauth_cred_t parent, kauth_cred_t model) {
posix_cred_t cur_pcred = posix_cred_get(parent);
uid_t svuid = KAUTH_UID_NONE;
#if CONFIG_MACF
if ((error = mac_proc_check_setreuid(p, parent, want_ruid, want_euid)) != 0) {
return false;
}
#endif
if (((want_ruid != KAUTH_UID_NONE && /* allow no change of ruid */
want_ruid != cur_pcred->cr_ruid && /* allow ruid = ruid */
want_ruid != cur_pcred->cr_uid && /* allow ruid = euid */
want_ruid != cur_pcred->cr_svuid) || /* allow ruid = svuid */
(want_euid != KAUTH_UID_NONE && /* allow no change of euid */
want_euid != cur_pcred->cr_uid && /* allow euid = euid */
want_euid != cur_pcred->cr_ruid && /* allow euid = ruid */
want_euid != cur_pcred->cr_svuid)) && /* allow euid = svuid */
(error = suser(parent, &p->p_acflag))) { /* allow root user any */
return false;
}
uid_t new_euid = cur_pcred->cr_uid;
if (want_euid != KAUTH_UID_NONE && cur_pcred->cr_uid != want_euid) {
new_euid = want_euid;
}
old_ruid = cur_pcred->cr_ruid;
/*
* 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 (cur_pcred->cr_svuid != uap->ruid &&
cur_pcred->cr_svuid != uap->euid) {
svuid = new_euid;
}
return kauth_cred_model_setresuid(model, want_ruid, want_euid,
svuid, cur_pcred->cr_gmuid);
});
if (changed && want_ruid != KAUTH_UID_NONE && want_ruid != old_ruid &&
!proc_has_persona(p)) {
(void)chgproccnt(want_ruid, 1);
(void)chgproccnt(old_ruid, -1);
}
return error;
}
/*
* 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)
{
__block int error = 0;
gid_t want_gid;
want_gid = uap->gid;
AUDIT_ARG(gid, want_gid);
kauth_cred_proc_update(p, PROC_SETTOKEN_SETUGID,
^bool (kauth_cred_t parent, kauth_cred_t model) {
posix_cred_t cur_pcred = posix_cred_get(parent);
gid_t rgid = KAUTH_GID_NONE;
gid_t svgid = KAUTH_GID_NONE;
#if CONFIG_MACF
if ((error = mac_proc_check_setgid(p, parent, want_gid)) != 0) {
return false;
}
#endif
if (want_gid != cur_pcred->cr_rgid && /* allow setgid(getgid()) */
want_gid != cur_pcred->cr_svgid && /* allow setgid(saved gid) */
(error = suser(parent, &p->p_acflag))) {
return false;
}
/*
* If we are privileged, then set the saved and real GID too;
* otherwise, just set the effective GID
*/
if (suser(parent, &p->p_acflag) == 0) {
svgid = want_gid;
rgid = want_gid;
}
return kauth_cred_model_setresgid(model, rgid, want_gid, svgid);
});
return error;
}
/*
* 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)
{
__block int error = 0;
gid_t want_egid;
want_egid = uap->egid;
AUDIT_ARG(egid, want_egid);
kauth_cred_proc_update(p, PROC_SETTOKEN_SETUGID,
^bool (kauth_cred_t parent, kauth_cred_t model) {
posix_cred_t cur_pcred = posix_cred_get(parent);
#if CONFIG_MACF
if ((error = mac_proc_check_setegid(p, parent, want_egid)) != 0) {
return false;
}
#endif
if (want_egid != cur_pcred->cr_rgid &&
want_egid != cur_pcred->cr_svgid &&
(error = suser(parent, &p->p_acflag))) {
return false;
}
return kauth_cred_model_setresgid(model, KAUTH_GID_NONE,
want_egid, KAUTH_GID_NONE);
});
return 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.
*/
int
setregid(proc_t p, struct setregid_args *uap, __unused int32_t *retval)
{
__block int error = 0;
gid_t want_rgid;
gid_t want_egid;
want_rgid = uap->rgid;
want_egid = uap->egid;
if (want_rgid == (gid_t)-1) {
want_rgid = KAUTH_GID_NONE;
}
if (want_egid == (gid_t)-1) {
want_egid = KAUTH_GID_NONE;
}
AUDIT_ARG(egid, want_egid);
AUDIT_ARG(rgid, want_rgid);
kauth_cred_proc_update(p, PROC_SETTOKEN_SETUGID,
^bool (kauth_cred_t parent, kauth_cred_t model) {
posix_cred_t cur_pcred = posix_cred_get(parent);
uid_t svgid = KAUTH_UID_NONE;
#if CONFIG_MACF
if ((error = mac_proc_check_setregid(p, parent, want_rgid,
want_egid)) != 0) {
return false;
}
#endif
if (((want_rgid != KAUTH_UID_NONE && /* allow no change of rgid */
want_rgid != cur_pcred->cr_rgid && /* allow rgid = rgid */
want_rgid != cur_pcred->cr_gid && /* allow rgid = egid */
want_rgid != cur_pcred->cr_svgid) || /* allow rgid = svgid */
(want_egid != KAUTH_UID_NONE && /* allow no change of egid */
want_egid != cur_pcred->cr_groups[0] && /* allow no change of egid */
want_egid != cur_pcred->cr_gid && /* allow egid = egid */
want_egid != cur_pcred->cr_rgid && /* allow egid = rgid */
want_egid != cur_pcred->cr_svgid)) && /* allow egid = svgid */
(error = suser(parent, &p->p_acflag))) { /* allow root user any */
return false;
}
uid_t new_egid = cur_pcred->cr_gid;
if (want_egid != KAUTH_UID_NONE && cur_pcred->cr_gid != want_egid) {
/* changing the effective GID */
new_egid = want_egid;
}
/*
* 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 (cur_pcred->cr_svgid != want_rgid &&
cur_pcred->cr_svgid != want_egid) {
svgid = new_egid;
}
return kauth_cred_model_setresgid(model, want_rgid, want_egid, svgid);
});
return error;
}
static void
kern_settid_assume_cred(thread_ro_t tro, kauth_cred_t tmp)
{
kauth_cred_t cred = NOCRED;
kauth_cred_set(&cred, tmp);
zalloc_ro_update_field(ZONE_ID_THREAD_RO, tro, tro_cred, &cred);
}
/*
* 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.
*/
static int
kern_settid(proc_t p, uid_t uid, gid_t gid)
{
kauth_cred_t cred;
struct thread_ro *tro = current_thread_ro();
#if CONFIG_MACF
int error;
if ((error = mac_proc_check_settid(p, uid, gid)) != 0) {
return error;
}
#endif
if (proc_suser(p) != 0) {
return EPERM;
}
if (uid == KAUTH_UID_NONE) {
/* must already be assuming another identity in order to revert back */
if (tro->tro_realcred == tro->tro_cred) {
return EPERM;
}
/* revert to delayed binding of process credential */
kern_settid_assume_cred(tro, tro->tro_realcred);
} else {
/* cannot already be assuming another identity */
if (tro->tro_realcred != tro->tro_cred) {
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.
*/
cred = kauth_cred_derive(tro->tro_cred,
^bool (kauth_cred_t parent __unused, kauth_cred_t model) {
return kauth_cred_model_setuidgid(model, uid, gid);
});
kern_settid_assume_cred(tro, cred);
kauth_cred_unref(&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;
}
int
sys_settid(proc_t p, struct settid_args *uap, __unused int32_t *retval)
{
AUDIT_ARG(uid, uap->uid);
AUDIT_ARG(gid, uap->gid);
return kern_settid(p, uap->uid, uap->gid);
}
/*
* 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
sys_settid_with_pid(proc_t p, struct settid_with_pid_args *uap, __unused int32_t *retval)
{
uid_t uid;
gid_t gid;
AUDIT_ARG(pid, uap->pid);
AUDIT_ARG(value32, uap->assume);
/*
* 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) {
kauth_cred_t cred;
if (uap->pid == 0) {
return ESRCH;
}
cred = kauth_cred_proc_ref_for_pid(uap->pid);
if (cred == NOCRED) {
return ESRCH;
}
uid = kauth_cred_getuid(cred);
gid = kauth_cred_getgid(cred);
kauth_cred_unref(&cred);
} else {
/*
* Otherwise, we are reverting back to normal mode of operation
* where delayed binding of the process credential sets the
* credential in the thread_ro (tro_cred)
*/
uid = KAUTH_UID_NONE;
gid = KAUTH_GID_NONE;
}
return kern_settid(p, uid, gid);
}
/*
* 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(proc_t p, u_int ngrp, user_addr_t gidset, uid_t gmuid, __unused int32_t *retval)
{
gid_t newgroups[NGROUPS] = { 0 };
int error;
if (ngrp > NGROUPS) {
return EINVAL;
}
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)
{
thread_ro_t tro = current_thread_ro();
kauth_cred_t cred;
int error;
error = proc_suser(p);
if (error) {
return error;
}
if (ngrp < 1) {
ngrp = 1;
newgroups[0] = 0;
}
kauth_cred_derive_t fn = ^bool (kauth_cred_t parent __unused, kauth_cred_t model) {
return kauth_cred_model_setgroups(model, newgroups, ngrp, gmuid);
};
if (tro->tro_realcred != tro->tro_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.
*/
cred = kauth_cred_derive(tro->tro_cred, fn);
kern_settid_assume_cred(tro, cred);
kauth_cred_unref(&cred);
} else {
kauth_cred_proc_update(p, PROC_SETTOKEN_SETUGID, fn);
AUDIT_ARG(groupset, &newgroups[0], ngrp);
}
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(proc_t p, struct initgroups_args *uap, __unused int32_t *retval)
{
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(proc_t p, struct setgroups_args *uap, __unused int32_t *retval)
{
return setgroups1(p, uap->gidsetsize, uap->gidset, KAUTH_UID_NONE, retval);
}
/*
* Set the per-thread/per-process supplementary groups list.
*
* XXX implement setsgroups
*
*/
int
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
*
*/
int
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; use kauth_cred_ismember_gid() directly
* XXX instead.
*/
int
groupmember(gid_t gid, kauth_cred_t cred)
{
int is_member;
if (kauth_cred_ismember_gid(cred, gid, &is_member) == 0 && is_member) {
return 1;
}
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 (kauth_cred_getuid(cred) == 0) {
if (acflag) {
*acflag |= ASU;
}
return 0;
}
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
getlogin(proc_t p, struct getlogin_args *uap, __unused int32_t *retval)
{
char buffer[MAXLOGNAME];
struct session *sessp;
struct pgrp *pg;
if (uap->namelen > MAXLOGNAME) {
uap->namelen = MAXLOGNAME;
}
if ((pg = proc_pgrp(p, &sessp)) != PGRP_NULL) {
session_lock(sessp);
bcopy(sessp->s_login, buffer, uap->namelen);
session_unlock(sessp);
pgrp_rele(pg);
} else {
bzero(buffer, uap->namelen);
}
return copyout((caddr_t)buffer, uap->namebuf, uap->namelen);
}
void
setlogin_internal(proc_t p, const char login[static MAXLOGNAME])
{
struct session *sessp;
struct pgrp *pg;
if ((pg = proc_pgrp(p, &sessp)) != PGRP_NULL) {
session_lock(sessp);
bcopy(login, sessp->s_login, MAXLOGNAME);
session_unlock(sessp);
pgrp_rele(pg);
}
}
/*
* 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
*/
int
setlogin(proc_t p, struct setlogin_args *uap, __unused int32_t *retval)
{
int error;
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;
}
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
* adds these fields. But nobody should rely on such
* a literal representation. Instead, the BSM library
* provides a function to convert an audit token into
* a BSM subject. Use of that mechanism will isolate
* the user of the trailer from future representation
* changes.
*/
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] = proc_getpid(p);
audit_token->val[6] = my_cred->cr_audit.as_aia_p->ai_asid;
audit_token->val[7] = proc_pidversion(p);
}
/* Set the secrity token of the task with current euid and eguid */
int
set_security_token(proc_t p, struct ucred *my_cred)
{
security_token_t sec_token;
audit_token_t audit_token;
host_priv_t host_priv;
task_t task = proc_task(p);
proc_calc_audit_token(p, my_cred, &audit_token);
sec_token.val[0] = kauth_cred_getuid(my_cred);
sec_token.val[1] = kauth_cred_getgid(my_cred);
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
#if DEVELOPMENT || DEBUG
/*
* Update the pid an proc name for importance base if any
*/
task_importance_update_owner_info(task);
#endif
return task_set_security_tokens(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 kauth_cred_t.
*/
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 = pcr->cr_ngroups;
bcopy(pcr->cr_groups, xcr->cr_groups, sizeof(xcr->cr_groups));
}
/*
* Copy kauth_cred into a virtual address by assignment.
* Needed because elements of kauth_cred are PACed
* so memcpy doesn't work.
*/
void
kauth_cred_copy(const uintptr_t kv, const uintptr_t new_data)
{
*(kauth_cred_t)kv = *(kauth_cred_t)new_data;
}