#include <sys/cdefs.h>
#include "opt_inet.h"
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/abi_compat.h>
#include <sys/acct.h>
#include <sys/kdb.h>
#include <sys/kernel.h>
#include <sys/libkern.h>
#include <sys/lock.h>
#include <sys/loginclass.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/ptrace.h>
#include <sys/refcount.h>
#include <sys/sx.h>
#include <sys/priv.h>
#include <sys/proc.h>
#ifdef COMPAT_43
#include <sys/sysent.h>
#endif
#include <sys/sysproto.h>
#include <sys/jail.h>
#include <sys/racct.h>
#include <sys/rctl.h>
#include <sys/resourcevar.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/syscallsubr.h>
#include <sys/sysctl.h>
#ifdef MAC
#include <security/mac/mac_syscalls.h>
#endif
#include <vm/uma.h>
#ifdef REGRESSION
FEATURE(regression,
"Kernel support for interfaces necessary for regression testing (SECURITY RISK!)");
#endif
#include <security/audit/audit.h>
#include <security/mac/mac_framework.h>
static MALLOC_DEFINE(M_CRED, "cred", "credentials");
SYSCTL_NODE(_security, OID_AUTO, bsd, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
"BSD security policy");
static void crfree_final(struct ucred *cr);
static inline void
groups_check_positive_len(int ngrp)
{
MPASS2(ngrp >= 0, "negative number of groups");
}
static inline void
groups_check_max_len(int ngrp)
{
MPASS2(ngrp <= ngroups_max, "too many supplementary groups");
}
static void groups_normalize(int *ngrp, gid_t *groups);
static void crsetgroups_internal(struct ucred *cr, int ngrp,
const gid_t *groups);
static int cr_canseeotheruids(struct ucred *u1, struct ucred *u2);
static int cr_canseeothergids(struct ucred *u1, struct ucred *u2);
static int cr_canseejailproc(struct ucred *u1, struct ucred *u2);
#ifndef _SYS_SYSPROTO_H_
struct getpid_args {
int dummy;
};
#endif
int
sys_getpid(struct thread *td, struct getpid_args *uap)
{
struct proc *p = td->td_proc;
td->td_retval[0] = p->p_pid;
#if defined(COMPAT_43)
if (SV_PROC_FLAG(p, SV_AOUT))
td->td_retval[1] = kern_getppid(td);
#endif
return (0);
}
#ifndef _SYS_SYSPROTO_H_
struct getppid_args {
int dummy;
};
#endif
int
sys_getppid(struct thread *td, struct getppid_args *uap)
{
td->td_retval[0] = kern_getppid(td);
return (0);
}
int
kern_getppid(struct thread *td)
{
struct proc *p = td->td_proc;
return (p->p_oppid);
}
#ifndef _SYS_SYSPROTO_H_
struct getpgrp_args {
int dummy;
};
#endif
int
sys_getpgrp(struct thread *td, struct getpgrp_args *uap)
{
struct proc *p = td->td_proc;
PROC_LOCK(p);
td->td_retval[0] = p->p_pgrp->pg_id;
PROC_UNLOCK(p);
return (0);
}
#ifndef _SYS_SYSPROTO_H_
struct getpgid_args {
pid_t pid;
};
#endif
int
sys_getpgid(struct thread *td, struct getpgid_args *uap)
{
struct proc *p;
int error;
if (uap->pid == 0) {
p = td->td_proc;
PROC_LOCK(p);
} else {
p = pfind(uap->pid);
if (p == NULL)
return (ESRCH);
error = p_cansee(td, p);
if (error) {
PROC_UNLOCK(p);
return (error);
}
}
td->td_retval[0] = p->p_pgrp->pg_id;
PROC_UNLOCK(p);
return (0);
}
#ifndef _SYS_SYSPROTO_H_
struct getsid_args {
pid_t pid;
};
#endif
int
sys_getsid(struct thread *td, struct getsid_args *uap)
{
return (kern_getsid(td, uap->pid));
}
int
kern_getsid(struct thread *td, pid_t pid)
{
struct proc *p;
int error;
if (pid == 0) {
p = td->td_proc;
PROC_LOCK(p);
} else {
p = pfind(pid);
if (p == NULL)
return (ESRCH);
error = p_cansee(td, p);
if (error) {
PROC_UNLOCK(p);
return (error);
}
}
td->td_retval[0] = p->p_session->s_sid;
PROC_UNLOCK(p);
return (0);
}
#ifndef _SYS_SYSPROTO_H_
struct getuid_args {
int dummy;
};
#endif
int
sys_getuid(struct thread *td, struct getuid_args *uap)
{
td->td_retval[0] = td->td_ucred->cr_ruid;
#if defined(COMPAT_43)
td->td_retval[1] = td->td_ucred->cr_uid;
#endif
return (0);
}
#ifndef _SYS_SYSPROTO_H_
struct geteuid_args {
int dummy;
};
#endif
int
sys_geteuid(struct thread *td, struct geteuid_args *uap)
{
td->td_retval[0] = td->td_ucred->cr_uid;
return (0);
}
#ifndef _SYS_SYSPROTO_H_
struct getgid_args {
int dummy;
};
#endif
int
sys_getgid(struct thread *td, struct getgid_args *uap)
{
td->td_retval[0] = td->td_ucred->cr_rgid;
#if defined(COMPAT_43)
td->td_retval[1] = td->td_ucred->cr_gid;
#endif
return (0);
}
#ifndef _SYS_SYSPROTO_H_
struct getegid_args {
int dummy;
};
#endif
int
sys_getegid(struct thread *td, struct getegid_args *uap)
{
td->td_retval[0] = td->td_ucred->cr_gid;
return (0);
}
#ifdef COMPAT_FREEBSD14
int
freebsd14_getgroups(struct thread *td, struct freebsd14_getgroups_args *uap)
{
struct ucred *cred;
int ngrp, error;
cred = td->td_ucred;
ngrp = cred->cr_ngroups + 1;
if (uap->gidsetsize == 0) {
error = 0;
goto out;
} else if (uap->gidsetsize < ngrp) {
return (EINVAL);
}
error = copyout(&cred->cr_gid, uap->gidset, sizeof(gid_t));
if (error == 0)
error = copyout(cred->cr_groups, uap->gidset + 1,
(ngrp - 1) * sizeof(gid_t));
out:
td->td_retval[0] = ngrp;
return (error);
}
#endif
#ifndef _SYS_SYSPROTO_H_
struct getgroups_args {
int gidsetsize;
gid_t *gidset;
};
#endif
int
sys_getgroups(struct thread *td, struct getgroups_args *uap)
{
struct ucred *cred;
int ngrp, error;
cred = td->td_ucred;
ngrp = cred->cr_ngroups;
if (uap->gidsetsize == 0) {
error = 0;
goto out;
}
if (uap->gidsetsize < ngrp)
return (EINVAL);
error = copyout(cred->cr_groups, uap->gidset, ngrp * sizeof(gid_t));
out:
td->td_retval[0] = ngrp;
return (error);
}
#ifndef _SYS_SYSPROTO_H_
struct setsid_args {
int dummy;
};
#endif
int
sys_setsid(struct thread *td, struct setsid_args *uap)
{
struct pgrp *pgrp;
int error;
struct proc *p = td->td_proc;
struct pgrp *newpgrp;
struct session *newsess;
pgrp = NULL;
newpgrp = uma_zalloc(pgrp_zone, M_WAITOK);
newsess = malloc(sizeof(struct session), M_SESSION, M_WAITOK | M_ZERO);
again:
error = 0;
sx_xlock(&proctree_lock);
if (p->p_pgid == p->p_pid || (pgrp = pgfind(p->p_pid)) != NULL) {
if (pgrp != NULL)
PGRP_UNLOCK(pgrp);
error = EPERM;
} else {
error = enterpgrp(p, p->p_pid, newpgrp, newsess);
if (error == ERESTART)
goto again;
MPASS(error == 0);
td->td_retval[0] = p->p_pid;
newpgrp = NULL;
newsess = NULL;
}
sx_xunlock(&proctree_lock);
uma_zfree(pgrp_zone, newpgrp);
free(newsess, M_SESSION);
return (error);
}
#ifndef _SYS_SYSPROTO_H_
struct setpgid_args {
int pid;
int pgid;
};
#endif
int
sys_setpgid(struct thread *td, struct setpgid_args *uap)
{
struct proc *curp = td->td_proc;
struct proc *targp;
struct pgrp *pgrp;
int error;
struct pgrp *newpgrp;
if (uap->pgid < 0)
return (EINVAL);
newpgrp = uma_zalloc(pgrp_zone, M_WAITOK);
again:
error = 0;
sx_xlock(&proctree_lock);
if (uap->pid != 0 && uap->pid != curp->p_pid) {
if ((targp = pfind(uap->pid)) == NULL) {
error = ESRCH;
goto done;
}
if (!inferior(targp)) {
PROC_UNLOCK(targp);
error = ESRCH;
goto done;
}
if ((error = p_cansee(td, targp))) {
PROC_UNLOCK(targp);
goto done;
}
if (targp->p_pgrp == NULL ||
targp->p_session != curp->p_session) {
PROC_UNLOCK(targp);
error = EPERM;
goto done;
}
if (targp->p_flag & P_EXEC) {
PROC_UNLOCK(targp);
error = EACCES;
goto done;
}
PROC_UNLOCK(targp);
} else
targp = curp;
if (SESS_LEADER(targp)) {
error = EPERM;
goto done;
}
if (uap->pgid == 0)
uap->pgid = targp->p_pid;
if ((pgrp = pgfind(uap->pgid)) == NULL) {
if (uap->pgid == targp->p_pid) {
error = enterpgrp(targp, uap->pgid, newpgrp,
NULL);
if (error == 0)
newpgrp = NULL;
} else
error = EPERM;
} else {
if (pgrp == targp->p_pgrp) {
PGRP_UNLOCK(pgrp);
goto done;
}
if (pgrp->pg_id != targp->p_pid &&
pgrp->pg_session != curp->p_session) {
PGRP_UNLOCK(pgrp);
error = EPERM;
goto done;
}
PGRP_UNLOCK(pgrp);
error = enterthispgrp(targp, pgrp);
}
done:
KASSERT(error == 0 || newpgrp != NULL,
("setpgid failed and newpgrp is NULL"));
if (error == ERESTART)
goto again;
sx_xunlock(&proctree_lock);
uma_zfree(pgrp_zone, newpgrp);
return (error);
}
static int
gidp_cmp(const void *p1, const void *p2)
{
const gid_t g1 = *(const gid_t *)p1;
const gid_t g2 = *(const gid_t *)p2;
return ((g1 > g2) - (g1 < g2));
}
static int
user_setcred_copyin_supp_groups(struct setcred *const wcred,
const u_int flags, gid_t *const smallgroups)
{
gid_t *groups;
int error;
if ((flags & SETCREDF_SUPP_GROUPS) == 0) {
error = 0;
goto reset_groups_exit;
}
if (wcred->sc_supp_groups_nb > ngroups_max) {
error = EINVAL;
goto reset_groups_exit;
}
groups = wcred->sc_supp_groups_nb <= CRED_SMALLGROUPS_NB ?
smallgroups : malloc(wcred->sc_supp_groups_nb * sizeof(gid_t),
M_TEMP, M_WAITOK);
error = copyin(wcred->sc_supp_groups, groups,
wcred->sc_supp_groups_nb * sizeof(gid_t));
wcred->sc_supp_groups = groups;
if (error != 0) {
wcred->sc_supp_groups_nb = 0;
return (error);
}
return (0);
reset_groups_exit:
wcred->sc_supp_groups_nb = 0;
wcred->sc_supp_groups = NULL;
return (error);
}
int
user_setcred(struct thread *td, const u_int flags, struct setcred *const wcred)
{
#ifdef MAC
struct mac mac;
void *umac;
#endif
gid_t smallgroups[CRED_SMALLGROUPS_NB];
int error;
if ((flags & ~SETCREDF_MASK) != 0)
return (EINVAL);
#ifdef MAC
umac = wcred->sc_label;
#endif
wcred->sc_label = NULL;
error = user_setcred_copyin_supp_groups(wcred, flags, smallgroups);
if (error != 0)
goto free_groups;
#ifdef MAC
if ((flags & SETCREDF_MAC_LABEL) != 0) {
error = mac_label_copyin(umac, &mac, NULL);
if (error != 0)
goto free_groups;
wcred->sc_label = &mac;
}
#endif
error = kern_setcred(td, flags, wcred);
#ifdef MAC
if (wcred->sc_label != NULL)
free_copied_label(wcred->sc_label);
#endif
free_groups:
if (wcred->sc_supp_groups != smallgroups)
free(wcred->sc_supp_groups, M_TEMP);
return (error);
}
#ifndef _SYS_SYSPROTO_H_
struct setcred_args {
u_int flags;
const struct setcred *wcred;
size_t size;
};
#endif
int
sys_setcred(struct thread *td, struct setcred_args *uap)
{
struct setcred wcred;
int error;
if (uap->size != sizeof(wcred))
return (EINVAL);
error = copyin(uap->wcred, &wcred, sizeof(wcred));
if (error != 0)
return (error);
return (user_setcred(td, uap->flags, &wcred));
}
int
kern_setcred(struct thread *const td, const u_int flags,
struct setcred *const wcred)
{
struct proc *const p = td->td_proc;
struct ucred *new_cred, *old_cred, *to_free_cred = NULL;
struct uidinfo *uip = NULL, *ruip = NULL;
#ifdef MAC
void *mac_set_proc_data = NULL;
bool proc_label_set = false;
#endif
int error;
bool cred_set = false;
if (flags & ~SETCREDF_MASK)
return (EINVAL);
if ((flags & SETCREDF_SUPP_GROUPS) != 0 &&
wcred->sc_supp_groups_nb > ngroups_max)
return (EINVAL);
if (flags & SETCREDF_MAC_LABEL) {
#ifdef MAC
error = mac_set_proc_prepare(td, wcred->sc_label,
&mac_set_proc_data);
if (error != 0)
return (error);
#else
return (ENOTSUP);
#endif
}
if (flags & SETCREDF_UID) {
AUDIT_ARG_EUID(wcred->sc_uid);
uip = uifind(wcred->sc_uid);
}
if (flags & SETCREDF_RUID) {
AUDIT_ARG_RUID(wcred->sc_ruid);
ruip = uifind(wcred->sc_ruid);
}
if (flags & SETCREDF_SVUID)
AUDIT_ARG_SUID(wcred->sc_svuid);
if (flags & SETCREDF_GID)
AUDIT_ARG_EGID(wcred->sc_gid);
if (flags & SETCREDF_RGID)
AUDIT_ARG_RGID(wcred->sc_rgid);
if (flags & SETCREDF_SVGID)
AUDIT_ARG_SGID(wcred->sc_svgid);
if (flags & SETCREDF_SUPP_GROUPS) {
AUDIT_ARG_GROUPSET(wcred->sc_supp_groups,
wcred->sc_supp_groups_nb);
groups_normalize(&wcred->sc_supp_groups_nb,
wcred->sc_supp_groups);
}
new_cred = crget();
to_free_cred = new_cred;
if (flags & SETCREDF_SUPP_GROUPS)
crextend(new_cred, wcred->sc_supp_groups_nb);
#ifdef MAC
mac_cred_setcred_enter();
#endif
PROC_LOCK(p);
old_cred = crcopysafe(p, new_cred);
if (flags & SETCREDF_UID)
change_euid(new_cred, uip);
if (flags & SETCREDF_RUID)
change_ruid(new_cred, ruip);
if (flags & SETCREDF_SVUID)
change_svuid(new_cred, wcred->sc_svuid);
if (flags & SETCREDF_SUPP_GROUPS)
crsetgroups_internal(new_cred, wcred->sc_supp_groups_nb,
wcred->sc_supp_groups);
if (flags & SETCREDF_GID)
change_egid(new_cred, wcred->sc_gid);
if (flags & SETCREDF_RGID)
change_rgid(new_cred, wcred->sc_rgid);
if (flags & SETCREDF_SVGID)
change_svgid(new_cred, wcred->sc_svgid);
#ifdef MAC
if (flags & SETCREDF_MAC_LABEL) {
error = mac_set_proc_core(td, new_cred, mac_set_proc_data);
if (error != 0)
goto unlock_finish;
proc_label_set = true;
}
error = mac_cred_check_setcred(flags, old_cred, new_cred);
if (error != 0)
goto unlock_finish;
#endif
error = priv_check_cred(old_cred, PRIV_CRED_SETCRED);
if (error != 0)
goto unlock_finish;
#ifdef RACCT
crhold(new_cred);
#endif
cred_set = proc_set_cred_enforce_proc_lim(p, new_cred);
if (cred_set) {
setsugid(p);
#ifdef RACCT
racct_proc_ucred_changed(p, old_cred, new_cred);
#endif
to_free_cred = old_cred;
MPASS(error == 0);
} else {
#ifdef RACCT
crfree(new_cred);
#endif
error = EAGAIN;
}
unlock_finish:
PROC_UNLOCK(p);
#ifdef RACCT
if (cred_set) {
#ifdef RCTL
rctl_proc_ucred_changed(p, new_cred);
#endif
crfree(new_cred);
}
#endif
#ifdef MAC
if (mac_set_proc_data != NULL)
mac_set_proc_finish(td, proc_label_set, mac_set_proc_data);
mac_cred_setcred_exit();
#endif
crfree(to_free_cred);
if (uip != NULL)
uifree(uip);
if (ruip != NULL)
uifree(ruip);
return (error);
}
#define POSIX_APPENDIX_B_4_2_2
#ifndef _SYS_SYSPROTO_H_
struct setuid_args {
uid_t uid;
};
#endif
int
sys_setuid(struct thread *td, struct setuid_args *uap)
{
struct proc *p = td->td_proc;
struct ucred *newcred, *oldcred;
uid_t uid;
struct uidinfo *uip;
int error;
uid = uap->uid;
AUDIT_ARG_UID(uid);
newcred = crget();
uip = uifind(uid);
PROC_LOCK(p);
oldcred = crcopysafe(p, newcred);
#ifdef MAC
error = mac_cred_check_setuid(oldcred, uid);
if (error)
goto fail;
#endif
if (uid != oldcred->cr_ruid &&
#ifdef _POSIX_SAVED_IDS
uid != oldcred->cr_svuid &&
#endif
#ifdef POSIX_APPENDIX_B_4_2_2
uid != oldcred->cr_uid &&
#endif
(error = priv_check_cred(oldcred, PRIV_CRED_SETUID)) != 0)
goto fail;
#ifdef _POSIX_SAVED_IDS
if (
#ifdef POSIX_APPENDIX_B_4_2_2
uid == oldcred->cr_uid ||
#endif
priv_check_cred(oldcred, PRIV_CRED_SETUID) == 0)
#endif
{
if (uid != oldcred->cr_ruid) {
change_ruid(newcred, uip);
setsugid(p);
}
if (uid != oldcred->cr_svuid) {
change_svuid(newcred, uid);
setsugid(p);
}
}
if (uid != oldcred->cr_uid) {
change_euid(newcred, uip);
setsugid(p);
}
#ifdef RACCT
racct_proc_ucred_changed(p, oldcred, newcred);
#endif
#ifdef RCTL
crhold(newcred);
#endif
proc_set_cred(p, newcred);
PROC_UNLOCK(p);
#ifdef RCTL
rctl_proc_ucred_changed(p, newcred);
crfree(newcred);
#endif
uifree(uip);
crfree(oldcred);
return (0);
fail:
PROC_UNLOCK(p);
uifree(uip);
crfree(newcred);
return (error);
}
#ifndef _SYS_SYSPROTO_H_
struct seteuid_args {
uid_t euid;
};
#endif
int
sys_seteuid(struct thread *td, struct seteuid_args *uap)
{
struct proc *p = td->td_proc;
struct ucred *newcred, *oldcred;
uid_t euid;
struct uidinfo *euip;
int error;
euid = uap->euid;
AUDIT_ARG_EUID(euid);
newcred = crget();
euip = uifind(euid);
PROC_LOCK(p);
oldcred = crcopysafe(p, newcred);
#ifdef MAC
error = mac_cred_check_seteuid(oldcred, euid);
if (error)
goto fail;
#endif
if (euid != oldcred->cr_ruid &&
euid != oldcred->cr_svuid &&
(error = priv_check_cred(oldcred, PRIV_CRED_SETEUID)) != 0)
goto fail;
if (oldcred->cr_uid != euid) {
change_euid(newcred, euip);
setsugid(p);
}
proc_set_cred(p, newcred);
PROC_UNLOCK(p);
uifree(euip);
crfree(oldcred);
return (0);
fail:
PROC_UNLOCK(p);
uifree(euip);
crfree(newcred);
return (error);
}
#ifndef _SYS_SYSPROTO_H_
struct setgid_args {
gid_t gid;
};
#endif
int
sys_setgid(struct thread *td, struct setgid_args *uap)
{
struct proc *p = td->td_proc;
struct ucred *newcred, *oldcred;
gid_t gid;
int error;
gid = uap->gid;
AUDIT_ARG_GID(gid);
newcred = crget();
PROC_LOCK(p);
oldcred = crcopysafe(p, newcred);
#ifdef MAC
error = mac_cred_check_setgid(oldcred, gid);
if (error)
goto fail;
#endif
if (gid != oldcred->cr_rgid &&
#ifdef _POSIX_SAVED_IDS
gid != oldcred->cr_svgid &&
#endif
#ifdef POSIX_APPENDIX_B_4_2_2
gid != oldcred->cr_gid &&
#endif
(error = priv_check_cred(oldcred, PRIV_CRED_SETGID)) != 0)
goto fail;
#ifdef _POSIX_SAVED_IDS
if (
#ifdef POSIX_APPENDIX_B_4_2_2
gid == oldcred->cr_gid ||
#endif
priv_check_cred(oldcred, PRIV_CRED_SETGID) == 0)
#endif
{
if (oldcred->cr_rgid != gid) {
change_rgid(newcred, gid);
setsugid(p);
}
if (oldcred->cr_svgid != gid) {
change_svgid(newcred, gid);
setsugid(p);
}
}
if (oldcred->cr_gid != gid) {
change_egid(newcred, gid);
setsugid(p);
}
proc_set_cred(p, newcred);
PROC_UNLOCK(p);
crfree(oldcred);
return (0);
fail:
PROC_UNLOCK(p);
crfree(newcred);
return (error);
}
#ifndef _SYS_SYSPROTO_H_
struct setegid_args {
gid_t egid;
};
#endif
int
sys_setegid(struct thread *td, struct setegid_args *uap)
{
struct proc *p = td->td_proc;
struct ucred *newcred, *oldcred;
gid_t egid;
int error;
egid = uap->egid;
AUDIT_ARG_EGID(egid);
newcred = crget();
PROC_LOCK(p);
oldcred = crcopysafe(p, newcred);
#ifdef MAC
error = mac_cred_check_setegid(oldcred, egid);
if (error)
goto fail;
#endif
if (egid != oldcred->cr_rgid &&
egid != oldcred->cr_svgid &&
(error = priv_check_cred(oldcred, PRIV_CRED_SETEGID)) != 0)
goto fail;
if (oldcred->cr_gid != egid) {
change_egid(newcred, egid);
setsugid(p);
}
proc_set_cred(p, newcred);
PROC_UNLOCK(p);
crfree(oldcred);
return (0);
fail:
PROC_UNLOCK(p);
crfree(newcred);
return (error);
}
#ifdef COMPAT_FREEBSD14
int
freebsd14_setgroups(struct thread *td, struct freebsd14_setgroups_args *uap)
{
gid_t smallgroups[CRED_SMALLGROUPS_NB];
gid_t *groups;
int gidsetsize, error;
gidsetsize = uap->gidsetsize;
if (gidsetsize > ngroups_max + 1 || gidsetsize < 0)
return (EINVAL);
if (gidsetsize > CRED_SMALLGROUPS_NB)
groups = malloc(gidsetsize * sizeof(gid_t), M_TEMP, M_WAITOK);
else
groups = smallgroups;
error = copyin(uap->gidset, groups, gidsetsize * sizeof(gid_t));
if (error == 0) {
int ngroups = gidsetsize > 0 ? gidsetsize - 1 : 0;
error = kern_setgroups(td, &ngroups, groups + 1);
if (error == 0 && gidsetsize > 0)
td->td_proc->p_ucred->cr_gid = groups[0];
}
if (groups != smallgroups)
free(groups, M_TEMP);
return (error);
}
#endif
#ifndef _SYS_SYSPROTO_H_
struct setgroups_args {
int gidsetsize;
gid_t *gidset;
};
#endif
int
sys_setgroups(struct thread *td, struct setgroups_args *uap)
{
gid_t smallgroups[CRED_SMALLGROUPS_NB];
gid_t *groups;
int gidsetsize, error;
gidsetsize = uap->gidsetsize;
if (gidsetsize > ngroups_max || gidsetsize < 0)
return (EINVAL);
if (gidsetsize > CRED_SMALLGROUPS_NB)
groups = malloc(gidsetsize * sizeof(gid_t), M_TEMP, M_WAITOK);
else
groups = smallgroups;
error = copyin(uap->gidset, groups, gidsetsize * sizeof(gid_t));
if (error == 0)
error = kern_setgroups(td, &gidsetsize, groups);
if (groups != smallgroups)
free(groups, M_TEMP);
return (error);
}
int
kern_setgroups(struct thread *td, int *ngrpp, gid_t *groups)
{
struct proc *p = td->td_proc;
struct ucred *newcred, *oldcred;
int ngrp, error;
ngrp = *ngrpp;
if (ngrp < 0 || ngrp > ngroups_max)
return (EINVAL);
AUDIT_ARG_GROUPSET(groups, ngrp);
groups_normalize(&ngrp, groups);
*ngrpp = ngrp;
newcred = crget();
crextend(newcred, ngrp);
PROC_LOCK(p);
oldcred = crcopysafe(p, newcred);
#ifdef MAC
error = mac_cred_check_setgroups(oldcred, ngrp,
ngrp == 0 ? NULL : groups);
if (error)
goto fail;
#endif
error = priv_check_cred(oldcred, PRIV_CRED_SETGROUPS);
if (error)
goto fail;
crsetgroups_internal(newcred, ngrp, groups);
setsugid(p);
proc_set_cred(p, newcred);
PROC_UNLOCK(p);
crfree(oldcred);
return (0);
fail:
PROC_UNLOCK(p);
crfree(newcred);
return (error);
}
#ifndef _SYS_SYSPROTO_H_
struct setreuid_args {
uid_t ruid;
uid_t euid;
};
#endif
int
sys_setreuid(struct thread *td, struct setreuid_args *uap)
{
struct proc *p = td->td_proc;
struct ucred *newcred, *oldcred;
uid_t euid, ruid;
struct uidinfo *euip, *ruip;
int error;
euid = uap->euid;
ruid = uap->ruid;
AUDIT_ARG_EUID(euid);
AUDIT_ARG_RUID(ruid);
newcred = crget();
euip = uifind(euid);
ruip = uifind(ruid);
PROC_LOCK(p);
oldcred = crcopysafe(p, newcred);
#ifdef MAC
error = mac_cred_check_setreuid(oldcred, ruid, euid);
if (error)
goto fail;
#endif
if (((ruid != (uid_t)-1 && ruid != oldcred->cr_ruid &&
ruid != oldcred->cr_svuid) ||
(euid != (uid_t)-1 && euid != oldcred->cr_uid &&
euid != oldcred->cr_ruid && euid != oldcred->cr_svuid)) &&
(error = priv_check_cred(oldcred, PRIV_CRED_SETREUID)) != 0)
goto fail;
if (euid != (uid_t)-1 && oldcred->cr_uid != euid) {
change_euid(newcred, euip);
setsugid(p);
}
if (ruid != (uid_t)-1 && oldcred->cr_ruid != ruid) {
change_ruid(newcred, ruip);
setsugid(p);
}
if ((ruid != (uid_t)-1 || newcred->cr_uid != newcred->cr_ruid) &&
newcred->cr_svuid != newcred->cr_uid) {
change_svuid(newcred, newcred->cr_uid);
setsugid(p);
}
#ifdef RACCT
racct_proc_ucred_changed(p, oldcred, newcred);
#endif
#ifdef RCTL
crhold(newcred);
#endif
proc_set_cred(p, newcred);
PROC_UNLOCK(p);
#ifdef RCTL
rctl_proc_ucred_changed(p, newcred);
crfree(newcred);
#endif
uifree(ruip);
uifree(euip);
crfree(oldcred);
return (0);
fail:
PROC_UNLOCK(p);
uifree(ruip);
uifree(euip);
crfree(newcred);
return (error);
}
#ifndef _SYS_SYSPROTO_H_
struct setregid_args {
gid_t rgid;
gid_t egid;
};
#endif
int
sys_setregid(struct thread *td, struct setregid_args *uap)
{
struct proc *p = td->td_proc;
struct ucred *newcred, *oldcred;
gid_t egid, rgid;
int error;
egid = uap->egid;
rgid = uap->rgid;
AUDIT_ARG_EGID(egid);
AUDIT_ARG_RGID(rgid);
newcred = crget();
PROC_LOCK(p);
oldcred = crcopysafe(p, newcred);
#ifdef MAC
error = mac_cred_check_setregid(oldcred, rgid, egid);
if (error)
goto fail;
#endif
if (((rgid != (gid_t)-1 && rgid != oldcred->cr_rgid &&
rgid != oldcred->cr_svgid) ||
(egid != (gid_t)-1 && egid != oldcred->cr_gid &&
egid != oldcred->cr_rgid && egid != oldcred->cr_svgid)) &&
(error = priv_check_cred(oldcred, PRIV_CRED_SETREGID)) != 0)
goto fail;
if (egid != (gid_t)-1 && oldcred->cr_gid != egid) {
change_egid(newcred, egid);
setsugid(p);
}
if (rgid != (gid_t)-1 && oldcred->cr_rgid != rgid) {
change_rgid(newcred, rgid);
setsugid(p);
}
if ((rgid != (gid_t)-1 || newcred->cr_gid != newcred->cr_rgid) &&
newcred->cr_svgid != newcred->cr_gid) {
change_svgid(newcred, newcred->cr_gid);
setsugid(p);
}
proc_set_cred(p, newcred);
PROC_UNLOCK(p);
crfree(oldcred);
return (0);
fail:
PROC_UNLOCK(p);
crfree(newcred);
return (error);
}
#ifndef _SYS_SYSPROTO_H_
struct setresuid_args {
uid_t ruid;
uid_t euid;
uid_t suid;
};
#endif
int
sys_setresuid(struct thread *td, struct setresuid_args *uap)
{
struct proc *p = td->td_proc;
struct ucred *newcred, *oldcred;
uid_t euid, ruid, suid;
struct uidinfo *euip, *ruip;
int error;
euid = uap->euid;
ruid = uap->ruid;
suid = uap->suid;
AUDIT_ARG_EUID(euid);
AUDIT_ARG_RUID(ruid);
AUDIT_ARG_SUID(suid);
newcred = crget();
euip = uifind(euid);
ruip = uifind(ruid);
PROC_LOCK(p);
oldcred = crcopysafe(p, newcred);
#ifdef MAC
error = mac_cred_check_setresuid(oldcred, ruid, euid, suid);
if (error)
goto fail;
#endif
if (((ruid != (uid_t)-1 && ruid != oldcred->cr_ruid &&
ruid != oldcred->cr_svuid &&
ruid != oldcred->cr_uid) ||
(euid != (uid_t)-1 && euid != oldcred->cr_ruid &&
euid != oldcred->cr_svuid &&
euid != oldcred->cr_uid) ||
(suid != (uid_t)-1 && suid != oldcred->cr_ruid &&
suid != oldcred->cr_svuid &&
suid != oldcred->cr_uid)) &&
(error = priv_check_cred(oldcred, PRIV_CRED_SETRESUID)) != 0)
goto fail;
if (euid != (uid_t)-1 && oldcred->cr_uid != euid) {
change_euid(newcred, euip);
setsugid(p);
}
if (ruid != (uid_t)-1 && oldcred->cr_ruid != ruid) {
change_ruid(newcred, ruip);
setsugid(p);
}
if (suid != (uid_t)-1 && oldcred->cr_svuid != suid) {
change_svuid(newcred, suid);
setsugid(p);
}
#ifdef RACCT
racct_proc_ucred_changed(p, oldcred, newcred);
#endif
#ifdef RCTL
crhold(newcred);
#endif
proc_set_cred(p, newcred);
PROC_UNLOCK(p);
#ifdef RCTL
rctl_proc_ucred_changed(p, newcred);
crfree(newcred);
#endif
uifree(ruip);
uifree(euip);
crfree(oldcred);
return (0);
fail:
PROC_UNLOCK(p);
uifree(ruip);
uifree(euip);
crfree(newcred);
return (error);
}
#ifndef _SYS_SYSPROTO_H_
struct setresgid_args {
gid_t rgid;
gid_t egid;
gid_t sgid;
};
#endif
int
sys_setresgid(struct thread *td, struct setresgid_args *uap)
{
struct proc *p = td->td_proc;
struct ucred *newcred, *oldcred;
gid_t egid, rgid, sgid;
int error;
egid = uap->egid;
rgid = uap->rgid;
sgid = uap->sgid;
AUDIT_ARG_EGID(egid);
AUDIT_ARG_RGID(rgid);
AUDIT_ARG_SGID(sgid);
newcred = crget();
PROC_LOCK(p);
oldcred = crcopysafe(p, newcred);
#ifdef MAC
error = mac_cred_check_setresgid(oldcred, rgid, egid, sgid);
if (error)
goto fail;
#endif
if (((rgid != (gid_t)-1 && rgid != oldcred->cr_rgid &&
rgid != oldcred->cr_svgid &&
rgid != oldcred->cr_gid) ||
(egid != (gid_t)-1 && egid != oldcred->cr_rgid &&
egid != oldcred->cr_svgid &&
egid != oldcred->cr_gid) ||
(sgid != (gid_t)-1 && sgid != oldcred->cr_rgid &&
sgid != oldcred->cr_svgid &&
sgid != oldcred->cr_gid)) &&
(error = priv_check_cred(oldcred, PRIV_CRED_SETRESGID)) != 0)
goto fail;
if (egid != (gid_t)-1 && oldcred->cr_gid != egid) {
change_egid(newcred, egid);
setsugid(p);
}
if (rgid != (gid_t)-1 && oldcred->cr_rgid != rgid) {
change_rgid(newcred, rgid);
setsugid(p);
}
if (sgid != (gid_t)-1 && oldcred->cr_svgid != sgid) {
change_svgid(newcred, sgid);
setsugid(p);
}
proc_set_cred(p, newcred);
PROC_UNLOCK(p);
crfree(oldcred);
return (0);
fail:
PROC_UNLOCK(p);
crfree(newcred);
return (error);
}
#ifndef _SYS_SYSPROTO_H_
struct getresuid_args {
uid_t *ruid;
uid_t *euid;
uid_t *suid;
};
#endif
int
sys_getresuid(struct thread *td, struct getresuid_args *uap)
{
struct ucred *cred;
int error1 = 0, error2 = 0, error3 = 0;
cred = td->td_ucred;
if (uap->ruid)
error1 = copyout(&cred->cr_ruid,
uap->ruid, sizeof(cred->cr_ruid));
if (uap->euid)
error2 = copyout(&cred->cr_uid,
uap->euid, sizeof(cred->cr_uid));
if (uap->suid)
error3 = copyout(&cred->cr_svuid,
uap->suid, sizeof(cred->cr_svuid));
return (error1 ? error1 : error2 ? error2 : error3);
}
#ifndef _SYS_SYSPROTO_H_
struct getresgid_args {
gid_t *rgid;
gid_t *egid;
gid_t *sgid;
};
#endif
int
sys_getresgid(struct thread *td, struct getresgid_args *uap)
{
struct ucred *cred;
int error1 = 0, error2 = 0, error3 = 0;
cred = td->td_ucred;
if (uap->rgid)
error1 = copyout(&cred->cr_rgid,
uap->rgid, sizeof(cred->cr_rgid));
if (uap->egid)
error2 = copyout(&cred->cr_gid,
uap->egid, sizeof(cred->cr_gid));
if (uap->sgid)
error3 = copyout(&cred->cr_svgid,
uap->sgid, sizeof(cred->cr_svgid));
return (error1 ? error1 : error2 ? error2 : error3);
}
#ifndef _SYS_SYSPROTO_H_
struct issetugid_args {
int dummy;
};
#endif
int
sys_issetugid(struct thread *td, struct issetugid_args *uap)
{
struct proc *p = td->td_proc;
td->td_retval[0] = (p->p_flag & P_SUGID) ? 1 : 0;
return (0);
}
int
sys___setugid(struct thread *td, struct __setugid_args *uap)
{
#ifdef REGRESSION
struct proc *p;
p = td->td_proc;
switch (uap->flag) {
case 0:
PROC_LOCK(p);
p->p_flag &= ~P_SUGID;
PROC_UNLOCK(p);
return (0);
case 1:
PROC_LOCK(p);
p->p_flag |= P_SUGID;
PROC_UNLOCK(p);
return (0);
default:
return (EINVAL);
}
#else
return (ENOSYS);
#endif
}
#ifdef INVARIANTS
static void
groups_check_normalized(int ngrp, const gid_t *groups)
{
gid_t prev_g;
groups_check_positive_len(ngrp);
groups_check_max_len(ngrp);
if (ngrp <= 1)
return;
prev_g = groups[0];
for (int i = 1; i < ngrp; ++i) {
const gid_t g = groups[i];
if (prev_g >= g)
panic("%s: groups[%d] (%u) >= groups[%d] (%u)",
__func__, i - 1, prev_g, i, g);
prev_g = g;
}
}
#else
#define groups_check_normalized(...)
#endif
bool
group_is_supplementary(const gid_t gid, const struct ucred *const cred)
{
groups_check_normalized(cred->cr_ngroups, cred->cr_groups);
return (bsearch(&gid, cred->cr_groups, cred->cr_ngroups,
sizeof(gid), gidp_cmp) != NULL);
}
bool
groupmember(gid_t gid, const struct ucred *cred)
{
groups_check_positive_len(cred->cr_ngroups);
if (gid == cred->cr_gid)
return (true);
return (group_is_supplementary(gid, cred));
}
bool
realgroupmember(gid_t gid, const struct ucred *cred)
{
groups_check_positive_len(cred->cr_ngroups);
if (gid == cred->cr_rgid)
return (true);
return (group_is_supplementary(gid, cred));
}
int
securelevel_gt(struct ucred *cr, int level)
{
return (cr->cr_prison->pr_securelevel > level ? EPERM : 0);
}
int
securelevel_ge(struct ucred *cr, int level)
{
return (cr->cr_prison->pr_securelevel >= level ? EPERM : 0);
}
static int see_other_uids = 1;
SYSCTL_INT(_security_bsd, OID_AUTO, see_other_uids, CTLFLAG_RW,
&see_other_uids, 0,
"Unprivileged processes may see subjects/objects with different real uid");
static int
cr_canseeotheruids(struct ucred *u1, struct ucred *u2)
{
if (!see_other_uids && u1->cr_ruid != u2->cr_ruid) {
if (priv_check_cred(u1, PRIV_SEEOTHERUIDS) != 0)
return (ESRCH);
}
return (0);
}
static int see_other_gids = 1;
SYSCTL_INT(_security_bsd, OID_AUTO, see_other_gids, CTLFLAG_RW,
&see_other_gids, 0,
"Unprivileged processes may see subjects/objects with different real gid");
static int
cr_canseeothergids(struct ucred *u1, struct ucred *u2)
{
if (see_other_gids)
return (0);
if (realgroupmember(u1->cr_rgid, u2))
return (0);
for (int i = 0; i < u1->cr_ngroups; i++)
if (realgroupmember(u1->cr_groups[i], u2))
return (0);
if (priv_check_cred(u1, PRIV_SEEOTHERGIDS) == 0)
return (0);
return (ESRCH);
}
static int see_jail_proc = 1;
SYSCTL_INT(_security_bsd, OID_AUTO, see_jail_proc, CTLFLAG_RW,
&see_jail_proc, 0,
"Unprivileged processes may see subjects/objects with different jail ids");
static int
cr_canseejailproc(struct ucred *u1, struct ucred *u2)
{
if (see_jail_proc ||
u1->cr_prison == u2->cr_prison ||
priv_check_cred(u1, PRIV_SEEJAILPROC) == 0)
return (0);
return (ESRCH);
}
static int
cr_can_tamper_with_subjail(struct ucred *u1, struct ucred *u2, int priv)
{
MPASS(prison_check(u1, u2) == 0);
if (u1->cr_prison == u2->cr_prison)
return (0);
if (priv_check_cred(u1, priv) == 0)
return (0);
if (prison_allow(u2, PR_ALLOW_UNPRIV_PARENT_TAMPER))
return (0);
return (EPERM);
}
int
cr_bsd_visible(struct ucred *u1, struct ucred *u2)
{
int error;
error = cr_canseeotheruids(u1, u2);
if (error != 0)
return (error);
error = cr_canseeothergids(u1, u2);
if (error != 0)
return (error);
error = cr_canseejailproc(u1, u2);
if (error != 0)
return (error);
return (0);
}
int
cr_cansee(struct ucred *u1, struct ucred *u2)
{
int error;
if ((error = prison_check(u1, u2)))
return (error);
#ifdef MAC
if ((error = mac_cred_check_visible(u1, u2)))
return (error);
#endif
if ((error = cr_bsd_visible(u1, u2)))
return (error);
return (0);
}
int
p_cansee(struct thread *td, struct proc *p)
{
KASSERT(td == curthread, ("%s: td not curthread", __func__));
PROC_LOCK_ASSERT(p, MA_OWNED);
if (td->td_proc == p)
return (0);
return (cr_cansee(td->td_ucred, p->p_ucred));
}
static int conservative_signals = 1;
SYSCTL_INT(_security_bsd, OID_AUTO, conservative_signals, CTLFLAG_RW,
&conservative_signals, 0, "Unprivileged processes prevented from "
"sending certain signals to processes whose credentials have changed");
int
cr_cansignal(struct ucred *cred, struct proc *proc, int signum)
{
int error;
PROC_LOCK_ASSERT(proc, MA_OWNED);
error = prison_check(cred, proc->p_ucred);
if (error)
return (error);
#ifdef MAC
if ((error = mac_proc_check_signal(cred, proc, signum)))
return (error);
#endif
if ((error = cr_bsd_visible(cred, proc->p_ucred)))
return (error);
if (conservative_signals && (proc->p_flag & P_SUGID)) {
switch (signum) {
case 0:
case SIGKILL:
case SIGINT:
case SIGTERM:
case SIGALRM:
case SIGSTOP:
case SIGTTIN:
case SIGTTOU:
case SIGTSTP:
case SIGHUP:
case SIGUSR1:
case SIGUSR2:
break;
default:
error = priv_check_cred(cred, PRIV_SIGNAL_SUGID);
if (error)
return (error);
}
}
if (cred->cr_ruid != proc->p_ucred->cr_ruid &&
cred->cr_ruid != proc->p_ucred->cr_svuid &&
cred->cr_uid != proc->p_ucred->cr_ruid &&
cred->cr_uid != proc->p_ucred->cr_svuid) {
error = priv_check_cred(cred, PRIV_SIGNAL_DIFFCRED);
if (error)
return (error);
}
error = cr_can_tamper_with_subjail(cred, proc->p_ucred,
PRIV_SIGNAL_DIFFJAIL);
if (error)
return (error);
return (0);
}
int
p_cansignal(struct thread *td, struct proc *p, int signum)
{
KASSERT(td == curthread, ("%s: td not curthread", __func__));
PROC_LOCK_ASSERT(p, MA_OWNED);
if (td->td_proc == p)
return (0);
if (signum == SIGCONT && td->td_proc->p_session == p->p_session)
return (0);
if (td->td_proc->p_leader != NULL && signum >= SIGTHR &&
signum < SIGTHR + 4 && td->td_proc->p_leader == p->p_leader)
return (0);
return (cr_cansignal(td->td_ucred, p, signum));
}
int
p_cansched(struct thread *td, struct proc *p)
{
int error;
KASSERT(td == curthread, ("%s: td not curthread", __func__));
PROC_LOCK_ASSERT(p, MA_OWNED);
if (td->td_proc == p)
return (0);
if ((error = prison_check(td->td_ucred, p->p_ucred)))
return (error);
#ifdef MAC
if ((error = mac_proc_check_sched(td->td_ucred, p)))
return (error);
#endif
if ((error = cr_bsd_visible(td->td_ucred, p->p_ucred)))
return (error);
if (td->td_ucred->cr_ruid != p->p_ucred->cr_ruid &&
td->td_ucred->cr_uid != p->p_ucred->cr_ruid) {
error = priv_check(td, PRIV_SCHED_DIFFCRED);
if (error)
return (error);
}
error = cr_can_tamper_with_subjail(td->td_ucred, p->p_ucred,
PRIV_SCHED_DIFFJAIL);
if (error)
return (error);
return (0);
}
static int
sysctl_unprivileged_proc_debug(SYSCTL_HANDLER_ARGS)
{
int error, val;
val = prison_allow(req->td->td_ucred, PR_ALLOW_UNPRIV_DEBUG);
error = sysctl_handle_int(oidp, &val, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
if (val != 0 && val != 1)
return (EINVAL);
prison_set_allow(req->td->td_ucred, PR_ALLOW_UNPRIV_DEBUG, val);
return (0);
}
SYSCTL_PROC(_security_bsd, OID_AUTO, unprivileged_proc_debug,
CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_PRISON | CTLFLAG_SECURE |
CTLFLAG_MPSAFE, 0, 0, sysctl_unprivileged_proc_debug, "I",
"Unprivileged processes may use process debugging facilities");
bool
cr_xids_subset(struct ucred *active_cred, struct ucred *obj_cred)
{
int i;
bool grpsubset, uidsubset;
grpsubset = true;
for (i = 0; i < obj_cred->cr_ngroups; i++) {
if (!groupmember(obj_cred->cr_groups[i], active_cred)) {
grpsubset = false;
break;
}
}
grpsubset = grpsubset &&
groupmember(obj_cred->cr_gid, active_cred) &&
groupmember(obj_cred->cr_rgid, active_cred) &&
groupmember(obj_cred->cr_svgid, active_cred);
uidsubset = (active_cred->cr_uid == obj_cred->cr_uid &&
active_cred->cr_uid == obj_cred->cr_svuid &&
active_cred->cr_uid == obj_cred->cr_ruid);
return (uidsubset && grpsubset);
}
int
p_candebug(struct thread *td, struct proc *p)
{
int error;
KASSERT(td == curthread, ("%s: td not curthread", __func__));
PROC_LOCK_ASSERT(p, MA_OWNED);
if (td->td_proc == p)
return (0);
if ((error = priv_check(td, PRIV_DEBUG_UNPRIV)))
return (error);
if ((error = prison_check(td->td_ucred, p->p_ucred)))
return (error);
#ifdef MAC
if ((error = mac_proc_check_debug(td->td_ucred, p)))
return (error);
#endif
if ((error = cr_bsd_visible(td->td_ucred, p->p_ucred)))
return (error);
if (!cr_xids_subset(td->td_ucred, p->p_ucred)) {
error = priv_check(td, PRIV_DEBUG_DIFFCRED);
if (error)
return (error);
}
if ((p->p_flag & P_SUGID) != 0) {
error = priv_check(td, PRIV_DEBUG_SUGID);
if (error)
return (error);
}
error = cr_can_tamper_with_subjail(td->td_ucred, p->p_ucred,
PRIV_DEBUG_DIFFJAIL);
if (error)
return (error);
if (p == initproc) {
error = securelevel_gt(td->td_ucred, 0);
if (error)
return (error);
}
if ((p->p_flag & P_INEXEC) != 0)
return (EBUSY);
if ((p->p_flag2 & P2_NOTRACE) != 0) {
error = priv_check(td, PRIV_DEBUG_DENIED);
if (error != 0)
return (error);
}
return (0);
}
int
cr_canseesocket(struct ucred *cred, struct socket *so)
{
int error;
error = prison_check(cred, so->so_cred);
if (error)
return (ENOENT);
#ifdef MAC
error = mac_socket_check_visible(cred, so);
if (error)
return (error);
#endif
if (cr_bsd_visible(cred, so->so_cred))
return (ENOENT);
return (0);
}
int
p_canwait(struct thread *td, struct proc *p)
{
int error;
KASSERT(td == curthread, ("%s: td not curthread", __func__));
PROC_LOCK_ASSERT(p, MA_OWNED);
if ((error = prison_check(td->td_ucred, p->p_ucred)))
return (error);
#ifdef MAC
if ((error = mac_proc_check_wait(td->td_ucred, p)))
return (error);
#endif
#if 0
if ((error = cr_bsd_visible(td->td_ucred, p->p_ucred)))
return (error);
#endif
return (0);
}
struct ucred *
crcowget(struct ucred *cr)
{
mtx_lock(&cr->cr_mtx);
KASSERT(cr->cr_users > 0, ("%s: users %d not > 0 on cred %p",
__func__, cr->cr_users, cr));
cr->cr_users++;
cr->cr_ref++;
mtx_unlock(&cr->cr_mtx);
return (cr);
}
static struct ucred *
crunuse(struct thread *td)
{
struct ucred *cr, *crold;
MPASS(td->td_realucred == td->td_ucred);
cr = td->td_realucred;
mtx_lock(&cr->cr_mtx);
cr->cr_ref += td->td_ucredref;
td->td_ucredref = 0;
KASSERT(cr->cr_users > 0, ("%s: users %d not > 0 on cred %p",
__func__, cr->cr_users, cr));
cr->cr_users--;
if (cr->cr_users == 0) {
KASSERT(cr->cr_ref > 0, ("%s: ref %ld not > 0 on cred %p",
__func__, cr->cr_ref, cr));
crold = cr;
} else {
cr->cr_ref--;
crold = NULL;
}
mtx_unlock(&cr->cr_mtx);
td->td_realucred = NULL;
return (crold);
}
static void
crunusebatch(struct ucred *cr, u_int users, long ref)
{
KASSERT(users > 0, ("%s: passed users %d not > 0 ; cred %p",
__func__, users, cr));
mtx_lock(&cr->cr_mtx);
KASSERT(cr->cr_users >= users, ("%s: users %d not > %d on cred %p",
__func__, cr->cr_users, users, cr));
cr->cr_users -= users;
cr->cr_ref += ref;
cr->cr_ref -= users;
if (cr->cr_users > 0) {
mtx_unlock(&cr->cr_mtx);
return;
}
KASSERT(cr->cr_ref >= 0, ("%s: ref %ld not >= 0 on cred %p",
__func__, cr->cr_ref, cr));
if (cr->cr_ref > 0) {
mtx_unlock(&cr->cr_mtx);
return;
}
crfree_final(cr);
}
void
crcowfree(struct thread *td)
{
struct ucred *cr;
cr = crunuse(td);
if (cr != NULL)
crfree(cr);
}
struct ucred *
crcowsync(void)
{
struct thread *td;
struct proc *p;
struct ucred *crnew, *crold;
td = curthread;
p = td->td_proc;
PROC_LOCK_ASSERT(p, MA_OWNED);
MPASS(td->td_realucred == td->td_ucred);
if (td->td_realucred == p->p_ucred)
return (NULL);
crnew = crcowget(p->p_ucred);
crold = crunuse(td);
td->td_realucred = crnew;
td->td_ucred = td->td_realucred;
return (crold);
}
void
credbatch_add(struct credbatch *crb, struct thread *td)
{
struct ucred *cr;
MPASS(td->td_realucred != NULL);
MPASS(td->td_realucred == td->td_ucred);
MPASS(TD_GET_STATE(td) == TDS_INACTIVE);
cr = td->td_realucred;
KASSERT(cr->cr_users > 0, ("%s: users %d not > 0 on cred %p",
__func__, cr->cr_users, cr));
if (crb->cred != cr) {
if (crb->users > 0) {
MPASS(crb->cred != NULL);
crunusebatch(crb->cred, crb->users, crb->ref);
crb->users = 0;
crb->ref = 0;
}
}
crb->cred = cr;
crb->users++;
crb->ref += td->td_ucredref;
td->td_ucredref = 0;
td->td_realucred = NULL;
}
void
credbatch_final(struct credbatch *crb)
{
MPASS(crb->cred != NULL);
MPASS(crb->users > 0);
crunusebatch(crb->cred, crb->users, crb->ref);
}
struct ucred *
crget(void)
{
struct ucred *cr;
cr = malloc(sizeof(*cr), M_CRED, M_WAITOK | M_ZERO);
mtx_init(&cr->cr_mtx, "cred", NULL, MTX_DEF);
cr->cr_ref = 1;
#ifdef AUDIT
audit_cred_init(cr);
#endif
#ifdef MAC
mac_cred_init(cr);
#endif
cr->cr_groups = cr->cr_smallgroups;
cr->cr_agroups = nitems(cr->cr_smallgroups);
return (cr);
}
struct ucred *
crhold(struct ucred *cr)
{
struct thread *td;
td = curthread;
if (__predict_true(td->td_realucred == cr)) {
KASSERT(cr->cr_users > 0, ("%s: users %d not > 0 on cred %p",
__func__, cr->cr_users, cr));
td->td_ucredref++;
return (cr);
}
mtx_lock(&cr->cr_mtx);
cr->cr_ref++;
mtx_unlock(&cr->cr_mtx);
return (cr);
}
void
crfree(struct ucred *cr)
{
struct thread *td;
td = curthread;
if (__predict_true(td->td_realucred == cr)) {
KASSERT(cr->cr_users > 0, ("%s: users %d not > 0 on cred %p",
__func__, cr->cr_users, cr));
td->td_ucredref--;
return;
}
mtx_lock(&cr->cr_mtx);
KASSERT(cr->cr_users >= 0, ("%s: users %d not >= 0 on cred %p",
__func__, cr->cr_users, cr));
cr->cr_ref--;
if (cr->cr_users > 0) {
mtx_unlock(&cr->cr_mtx);
return;
}
KASSERT(cr->cr_ref >= 0, ("%s: ref %ld not >= 0 on cred %p",
__func__, cr->cr_ref, cr));
if (cr->cr_ref > 0) {
mtx_unlock(&cr->cr_mtx);
return;
}
crfree_final(cr);
}
static void
crfree_final(struct ucred *cr)
{
KASSERT(cr->cr_users == 0, ("%s: users %d not == 0 on cred %p",
__func__, cr->cr_users, cr));
KASSERT(cr->cr_ref == 0, ("%s: ref %ld not == 0 on cred %p",
__func__, cr->cr_ref, cr));
if (cr->cr_uidinfo != NULL)
uifree(cr->cr_uidinfo);
if (cr->cr_ruidinfo != NULL)
uifree(cr->cr_ruidinfo);
if (cr->cr_prison != NULL)
prison_free(cr->cr_prison);
if (cr->cr_loginclass != NULL)
loginclass_free(cr->cr_loginclass);
#ifdef AUDIT
audit_cred_destroy(cr);
#endif
#ifdef MAC
mac_cred_destroy(cr);
#endif
mtx_destroy(&cr->cr_mtx);
if (cr->cr_groups != cr->cr_smallgroups)
free(cr->cr_groups, M_CRED);
free(cr, M_CRED);
}
void
crcopy(struct ucred *dest, struct ucred *src)
{
bcopy(&src->cr_startcopy, &dest->cr_startcopy,
(unsigned)((caddr_t)&src->cr_endcopy -
(caddr_t)&src->cr_startcopy));
dest->cr_flags = src->cr_flags;
crsetgroups(dest, src->cr_ngroups, src->cr_groups);
uihold(dest->cr_uidinfo);
uihold(dest->cr_ruidinfo);
prison_hold(dest->cr_prison);
loginclass_hold(dest->cr_loginclass);
#ifdef AUDIT
audit_cred_copy(src, dest);
#endif
#ifdef MAC
mac_cred_copy(src, dest);
#endif
}
struct ucred *
crdup(struct ucred *cr)
{
struct ucred *newcr;
newcr = crget();
crcopy(newcr, cr);
return (newcr);
}
void
cru2x(struct ucred *cr, struct xucred *xcr)
{
int ngroups;
bzero(xcr, sizeof(*xcr));
xcr->cr_version = XUCRED_VERSION;
xcr->cr_uid = cr->cr_uid;
xcr->cr_gid = cr->cr_gid;
ngroups = MIN(cr->cr_ngroups + 1, nitems(xcr->cr_groups));
xcr->cr_ngroups = ngroups;
bcopy(cr->cr_groups, xcr->cr_sgroups,
(ngroups - 1) * sizeof(*cr->cr_groups));
}
void
cru2xt(struct thread *td, struct xucred *xcr)
{
cru2x(td->td_ucred, xcr);
xcr->cr_pid = td->td_proc->p_pid;
}
static bool
_proc_set_cred(struct proc *p, struct ucred *newcred, bool enforce_proc_lim)
{
struct ucred *const oldcred = p->p_ucred;
MPASS(oldcred != NULL);
PROC_LOCK_ASSERT(p, MA_OWNED);
if (newcred->cr_ruidinfo != oldcred->cr_ruidinfo) {
const int proccnt_changed = chgproccnt(newcred->cr_ruidinfo, 1,
enforce_proc_lim ? lim_cur_proc(p, RLIMIT_NPROC) : 0);
if (!proccnt_changed) {
if (priv_check_cred(oldcred, PRIV_PROC_LIMIT) != 0)
return (false);
(void)chgproccnt(newcred->cr_ruidinfo, 1, 0);
}
}
mtx_lock(&oldcred->cr_mtx);
KASSERT(oldcred->cr_users > 0, ("%s: users %d not > 0 on cred %p",
__func__, oldcred->cr_users, oldcred));
oldcred->cr_users--;
mtx_unlock(&oldcred->cr_mtx);
mtx_lock(&newcred->cr_mtx);
newcred->cr_users++;
mtx_unlock(&newcred->cr_mtx);
p->p_ucred = newcred;
PROC_UPDATE_COW(p);
if (newcred->cr_ruidinfo != oldcred->cr_ruidinfo)
(void)chgproccnt(oldcred->cr_ruidinfo, -1, 0);
return (true);
}
void
proc_set_cred(struct proc *p, struct ucred *newcred)
{
bool success __diagused = _proc_set_cred(p, newcred, false);
MPASS(success);
}
bool
proc_set_cred_enforce_proc_lim(struct proc *p, struct ucred *newcred)
{
return (_proc_set_cred(p, newcred, true));
}
void
proc_unset_cred(struct proc *p, bool decrement_proc_count)
{
struct ucred *cr;
MPASS(p->p_state == PRS_ZOMBIE || p->p_state == PRS_NEW);
cr = p->p_ucred;
p->p_ucred = NULL;
KASSERT(cr->cr_users > 0, ("%s: users %d not > 0 on cred %p",
__func__, cr->cr_users, cr));
mtx_lock(&cr->cr_mtx);
cr->cr_users--;
if (cr->cr_users == 0)
KASSERT(cr->cr_ref > 0, ("%s: ref %ld not > 0 on cred %p",
__func__, cr->cr_ref, cr));
mtx_unlock(&cr->cr_mtx);
if (decrement_proc_count)
(void)chgproccnt(cr->cr_ruidinfo, -1, 0);
crfree(cr);
}
struct ucred *
crcopysafe(struct proc *p, struct ucred *cr)
{
struct ucred *oldcred;
int groups;
PROC_LOCK_ASSERT(p, MA_OWNED);
oldcred = p->p_ucred;
while (cr->cr_agroups < oldcred->cr_ngroups) {
groups = oldcred->cr_ngroups;
PROC_UNLOCK(p);
crextend(cr, groups);
PROC_LOCK(p);
oldcred = p->p_ucred;
}
crcopy(cr, oldcred);
return (oldcred);
}
void
crextend(struct ucred *cr, int n)
{
size_t nbytes;
MPASS2(cr->cr_ref == 1, "'cr_ref' must be 1 (referenced, unshared)");
MPASS2((cr->cr_flags & CRED_FLAG_GROUPSET) == 0,
"groups on 'cr' already set!");
groups_check_positive_len(n);
groups_check_max_len(n);
if (n <= cr->cr_agroups)
return;
nbytes = n * sizeof(gid_t);
if (nbytes < n)
panic("Too many groups (memory size overflow)! "
"Computation of 'kern.ngroups' should have prevented this, "
"please fix it. In the meantime, reduce 'kern.ngroups'.");
if (nbytes < PAGE_SIZE) {
if (!powerof2(nbytes))
nbytes = 1 << flsl(nbytes);
} else
nbytes = roundup2(nbytes, PAGE_SIZE);
if (cr->cr_groups != cr->cr_smallgroups)
free(cr->cr_groups, M_CRED);
cr->cr_groups = malloc(nbytes, M_CRED, M_WAITOK | M_ZERO);
cr->cr_agroups = nbytes / sizeof(gid_t);
}
static void
groups_normalize(int *ngrp, gid_t *groups)
{
gid_t prev_g;
int ins_idx;
groups_check_positive_len(*ngrp);
groups_check_max_len(*ngrp);
if (*ngrp <= 1)
return;
qsort(groups, *ngrp, sizeof(*groups), gidp_cmp);
prev_g = groups[0];
ins_idx = 1;
for (int i = ins_idx; i < *ngrp; ++i) {
const gid_t g = groups[i];
if (g != prev_g) {
if (i != ins_idx)
groups[ins_idx] = g;
++ins_idx;
prev_g = g;
}
}
*ngrp = ins_idx;
groups_check_normalized(*ngrp, groups);
}
static void
crsetgroups_internal(struct ucred *cr, int ngrp, const gid_t *groups)
{
MPASS2(cr->cr_ref == 1, "'cr_ref' must be 1 (referenced, unshared)");
MPASS2(cr->cr_agroups >= ngrp, "'cr_agroups' too small");
groups_check_positive_len(ngrp);
bcopy(groups, cr->cr_groups, ngrp * sizeof(gid_t));
cr->cr_ngroups = ngrp;
cr->cr_flags |= CRED_FLAG_GROUPSET;
}
void
crsetgroups(struct ucred *cr, int ngrp, const gid_t *groups)
{
if (ngrp > ngroups_max)
ngrp = ngroups_max;
cr->cr_ngroups = 0;
if (ngrp == 0) {
cr->cr_flags |= CRED_FLAG_GROUPSET;
return;
}
cr->cr_flags &= ~CRED_FLAG_GROUPSET;
crextend(cr, ngrp);
crsetgroups_internal(cr, ngrp, groups);
groups_normalize(&cr->cr_ngroups, cr->cr_groups);
}
void
crsetgroups_and_egid(struct ucred *cr, int ngrp, const gid_t *groups,
const gid_t default_egid)
{
if (ngrp == 0) {
cr->cr_gid = default_egid;
cr->cr_ngroups = 0;
cr->cr_flags |= CRED_FLAG_GROUPSET;
return;
}
crsetgroups(cr, ngrp - 1, groups + 1);
cr->cr_gid = groups[0];
}
#ifndef _SYS_SYSPROTO_H_
struct getlogin_args {
char *namebuf;
u_int namelen;
};
#endif
int
sys_getlogin(struct thread *td, struct getlogin_args *uap)
{
char login[MAXLOGNAME];
struct proc *p = td->td_proc;
size_t len;
if (uap->namelen > MAXLOGNAME)
uap->namelen = MAXLOGNAME;
PROC_LOCK(p);
SESS_LOCK(p->p_session);
len = strlcpy(login, p->p_session->s_login, uap->namelen) + 1;
SESS_UNLOCK(p->p_session);
PROC_UNLOCK(p);
if (len > uap->namelen)
return (ERANGE);
return (copyout(login, uap->namebuf, len));
}
#ifndef _SYS_SYSPROTO_H_
struct setlogin_args {
char *namebuf;
};
#endif
int
sys_setlogin(struct thread *td, struct setlogin_args *uap)
{
struct proc *p = td->td_proc;
int error;
char logintmp[MAXLOGNAME];
CTASSERT(sizeof(p->p_session->s_login) >= sizeof(logintmp));
error = priv_check(td, PRIV_PROC_SETLOGIN);
if (error)
return (error);
error = copyinstr(uap->namebuf, logintmp, sizeof(logintmp), NULL);
if (error != 0) {
if (error == ENAMETOOLONG)
error = EINVAL;
return (error);
}
AUDIT_ARG_LOGIN(logintmp);
PROC_LOCK(p);
SESS_LOCK(p->p_session);
strcpy(p->p_session->s_login, logintmp);
SESS_UNLOCK(p->p_session);
PROC_UNLOCK(p);
return (0);
}
void
setsugid(struct proc *p)
{
PROC_LOCK_ASSERT(p, MA_OWNED);
p->p_flag |= P_SUGID;
}
void
change_euid(struct ucred *newcred, struct uidinfo *euip)
{
newcred->cr_uid = euip->ui_uid;
uihold(euip);
uifree(newcred->cr_uidinfo);
newcred->cr_uidinfo = euip;
}
void
change_egid(struct ucred *newcred, gid_t egid)
{
newcred->cr_gid = egid;
}
void
change_ruid(struct ucred *newcred, struct uidinfo *ruip)
{
newcred->cr_ruid = ruip->ui_uid;
uihold(ruip);
uifree(newcred->cr_ruidinfo);
newcred->cr_ruidinfo = ruip;
}
void
change_rgid(struct ucred *newcred, gid_t rgid)
{
newcred->cr_rgid = rgid;
}
void
change_svuid(struct ucred *newcred, uid_t svuid)
{
newcred->cr_svuid = svuid;
}
void
change_svgid(struct ucred *newcred, gid_t svgid)
{
newcred->cr_svgid = svgid;
}
bool allow_ptrace = true;
SYSCTL_BOOL(_security_bsd, OID_AUTO, allow_ptrace, CTLFLAG_RWTUN,
&allow_ptrace, 0,
"Deny ptrace(2) use by returning ENOSYS");