#include <sys/kernel.h>
#include <sys/kernel_types.h>
#include <sys/persona.h>
#if CONFIG_PERSONAS
#include <kern/assert.h>
#include <kern/simple_lock.h>
#include <kern/task.h>
#include <kern/zalloc.h>
#include <sys/param.h>
#include <sys/proc_internal.h>
#include <sys/kauth.h>
#include <sys/proc_info.h>
#include <sys/resourcevar.h>
#define pna_info(fmt, ...) \
printf("%s: " fmt "\n", __func__, ## __VA_ARGS__)
#define pna_err(fmt, ...) \
printf("ERROR[%s]: " fmt "\n", __func__, ## __VA_ARGS__)
#define MAX_PERSONAS 512
#define TEMP_PERSONA_ID 499
#define FIRST_PERSONA_ID 501
#define PERSONA_ID_STEP 10
#define PERSONA_SYSTEM_UID ((uid_t)99)
#define PERSONA_SYSTEM_LOGIN "system"
#define PERSONA_MAGIC (0x0aa55aa0)
#define persona_valid(p) ((p)->pna_valid == PERSONA_MAGIC)
#define persona_mkinvalid(p) ((p)->pna_valid = ~(PERSONA_MAGIC))
static LIST_HEAD(personalist, persona) all_personas;
static uint32_t g_total_personas;
uint32_t g_max_personas = MAX_PERSONAS;
struct persona *g_system_persona = NULL;
static uid_t g_next_persona_id;
lck_mtx_t all_personas_lock;
lck_attr_t *persona_lck_attr;
lck_grp_t *persona_lck_grp;
lck_grp_attr_t *persona_lck_grp_attr;
static zone_t persona_zone;
kauth_cred_t g_default_persona_cred;
#define lock_personas() lck_mtx_lock(&all_personas_lock)
#define unlock_personas() lck_mtx_unlock(&all_personas_lock)
extern void mach_kauth_cred_uthread_update(void);
void personas_bootstrap(void)
{
struct posix_cred pcred;
persona_dbg("Initializing persona subsystem");
LIST_INIT(&all_personas);
g_total_personas = 0;
g_next_persona_id = FIRST_PERSONA_ID;
persona_lck_grp_attr = lck_grp_attr_alloc_init();
lck_grp_attr_setstat(persona_lck_grp_attr);
persona_lck_grp = lck_grp_alloc_init("personas", persona_lck_grp_attr);
persona_lck_attr = lck_attr_alloc_init();
lck_mtx_init(&all_personas_lock, persona_lck_grp, persona_lck_attr);
persona_zone = zinit(sizeof(struct persona),
MAX_PERSONAS * sizeof(struct persona),
MAX_PERSONAS, "personas");
assert(persona_zone != NULL);
bzero(&pcred, sizeof(pcred));
pcred.cr_uid = pcred.cr_ruid = pcred.cr_svuid = TEMP_PERSONA_ID;
pcred.cr_rgid = pcred.cr_svgid = TEMP_PERSONA_ID;
pcred.cr_groups[0] = TEMP_PERSONA_ID;
pcred.cr_ngroups = 1;
pcred.cr_flags = CRF_NOMEMBERD;
pcred.cr_gmuid = KAUTH_UID_NONE;
g_default_persona_cred = posix_cred_create(&pcred);
if (!g_default_persona_cred)
panic("couldn't create default persona credentials!");
g_system_persona = persona_alloc(PERSONA_SYSTEM_UID,
PERSONA_SYSTEM_LOGIN,
PERSONA_SYSTEM, NULL);
assert(g_system_persona != NULL);
}
struct persona *persona_alloc(uid_t id, const char *login, int type, int *error)
{
struct persona *persona, *tmp;
int err = 0;
kauth_cred_t tmp_cred;
gid_t new_group;
if (!login) {
pna_err("Must provide a login name for a new persona!");
if (error)
*error = EINVAL;
return NULL;
}
if (type <= PERSONA_INVALID || type > PERSONA_TYPE_MAX) {
pna_err("Invalid type: %d", type);
if (error)
*error = EINVAL;
return NULL;
}
persona = (struct persona *)zalloc(persona_zone);
if (!persona) {
if (error)
*error = ENOMEM;
return NULL;
}
bzero(persona, sizeof(*persona));
if (hw_atomic_add(&g_total_personas, 1) > MAX_PERSONAS) {
pna_err("too many active personas!");
err = EBUSY;
goto out_error;
}
strncpy(persona->pna_login, login, sizeof(persona->pna_login)-1);
LIST_INIT(&persona->pna_members);
lck_mtx_init(&persona->pna_lock, persona_lck_grp, persona_lck_attr);
persona->pna_refcount = 1;
persona->pna_cred = kauth_cred_create(g_default_persona_cred);
if (!persona->pna_cred) {
pna_err("could not copy initial credentials!");
err = EIO;
goto out_error;
}
lock_personas();
try_again:
if (id != PERSONA_ID_NONE)
persona->pna_id = id;
else
persona->pna_id = g_next_persona_id;
persona_dbg("Adding %d (%s) to global list...", persona->pna_id, persona->pna_login);
err = 0;
LIST_FOREACH(tmp, &all_personas, pna_list) {
if (id == PERSONA_ID_NONE && tmp->pna_id == id) {
g_next_persona_id += PERSONA_ID_STEP;
goto try_again;
}
if (strncmp(tmp->pna_login, login, sizeof(tmp->pna_login)) == 0
|| tmp->pna_id == id) {
err = EEXIST;
break;
}
}
if (err)
goto out_unlock;
kauth_cred_ref(persona->pna_cred);
tmp_cred = kauth_cred_setuidgid(persona->pna_cred,
persona->pna_id,
persona->pna_id);
kauth_cred_unref(&persona->pna_cred);
if (tmp_cred != persona->pna_cred)
persona->pna_cred = tmp_cred;
if (!persona->pna_cred) {
err = EACCES;
goto out_unlock;
}
new_group = (gid_t)persona->pna_id;
kauth_cred_ref(persona->pna_cred);
tmp_cred = kauth_cred_setgroups(persona->pna_cred,
&new_group, 1, KAUTH_UID_NONE);
kauth_cred_unref(&persona->pna_cred);
if (tmp_cred != persona->pna_cred)
persona->pna_cred = tmp_cred;
if (!persona->pna_cred) {
err = EACCES;
goto out_unlock;
}
persona->pna_type = type;
persona->pna_valid = PERSONA_MAGIC;
LIST_INSERT_HEAD(&all_personas, persona, pna_list);
if (id == PERSONA_ID_NONE)
g_next_persona_id += PERSONA_ID_STEP;
out_unlock:
unlock_personas();
if (err) {
switch (err) {
case EEXIST:
persona_dbg("Login '%s' (%d) already exists",
login, persona->pna_id);
break;
case EACCES:
persona_dbg("kauth_error for persona:%d", persona->pna_id);
break;
default:
persona_dbg("Unknown error:%d", err);
}
goto out_error;
}
return persona;
out_error:
(void)hw_atomic_add(&g_total_personas, -1);
zfree(persona_zone, persona);
if (error)
*error = err;
return NULL;
}
int persona_invalidate(struct persona *persona)
{
int error = 0;
if (!persona)
return EINVAL;
lock_personas();
persona_lock(persona);
if (!persona_valid(persona))
panic("Double-invalidation of persona %p", persona);
LIST_REMOVE(persona, pna_list);
if (hw_atomic_add(&g_total_personas, -1) == UINT_MAX)
panic("persona ref count underflow!\n");
persona_mkinvalid(persona);
persona_unlock(persona);
unlock_personas();
return error;
}
static struct persona *persona_get_locked(struct persona *persona)
{
if (persona->pna_refcount) {
persona->pna_refcount++;
return persona;
}
return NULL;
}
struct persona *persona_get(struct persona *persona)
{
struct persona *ret;
if (!persona)
return NULL;
persona_lock(persona);
ret = persona_get_locked(persona);
persona_unlock(persona);
return ret;
}
void persona_put(struct persona *persona)
{
int destroy = 0;
if (!persona)
return;
persona_lock(persona);
if (persona->pna_refcount >= 0) {
if (--(persona->pna_refcount) == 0)
destroy = 1;
}
persona_unlock(persona);
if (!destroy)
return;
persona_dbg("Destroying persona %s", persona_desc(persona, 0));
if (persona->pna_cred)
kauth_cred_unref(&persona->pna_cred);
lock_personas();
if (persona_valid(persona)) {
LIST_REMOVE(persona, pna_list);
if (hw_atomic_add(&g_total_personas, -1) == UINT_MAX)
panic("persona count underflow!\n");
persona_mkinvalid(persona);
}
unlock_personas();
assert(LIST_EMPTY(&persona->pna_members));
memset(persona, 0, sizeof(*persona));
zfree(persona_zone, persona);
}
uid_t persona_get_id(struct persona *persona)
{
if (persona)
return persona->pna_id;
return PERSONA_ID_NONE;
}
struct persona *persona_lookup(uid_t id)
{
struct persona *persona, *tmp;
persona = NULL;
lock_personas();
LIST_FOREACH(tmp, &all_personas, pna_list) {
persona_lock(tmp);
if (tmp->pna_id == id && persona_valid(tmp)) {
persona = persona_get_locked(tmp);
persona_unlock(tmp);
break;
}
persona_unlock(tmp);
}
unlock_personas();
return persona;
}
int persona_find(const char *login, uid_t uid,
struct persona **persona, size_t *plen)
{
struct persona *tmp;
int match = 0;
size_t found = 0;
if (login)
match++;
if (uid != PERSONA_ID_NONE)
match++;
if (match == 0)
return EINVAL;
persona_dbg("Searching with %d parameters (l:\"%s\", u:%d)",
match, login, uid);
lock_personas();
LIST_FOREACH(tmp, &all_personas, pna_list) {
int m = 0;
persona_lock(tmp);
if (login && strncmp(tmp->pna_login, login, sizeof(tmp->pna_login)) == 0)
m++;
if (uid != PERSONA_ID_NONE && uid == tmp->pna_id)
m++;
if (m == match) {
if (persona && *plen > found)
persona[found] = persona_get_locked(tmp);
found++;
}
#ifdef PERSONA_DEBUG
if (m > 0)
persona_dbg("ID:%d Matched %d/%d, found:%d, *plen:%d",
tmp->pna_id, m, match, (int)found, (int)*plen);
#endif
persona_unlock(tmp);
}
unlock_personas();
*plen = found;
if (!found)
return ESRCH;
return 0;
}
struct persona *persona_proc_get(pid_t pid)
{
struct persona *persona;
proc_t p = proc_find(pid);
if (!p)
return NULL;
proc_lock(p);
persona = persona_get(p->p_persona);
proc_unlock(p);
proc_rele(p);
return persona;
}
struct persona *current_persona_get(void)
{
proc_t p = current_proc();
struct persona *persona;
proc_lock(p);
persona = persona_get(p->p_persona);
proc_unlock(p);
return persona;
}
int persona_proc_inherit(proc_t child, proc_t parent)
{
if (child->p_persona != NULL) {
persona_dbg("proc_inherit: child already in persona: %s",
persona_desc(child->p_persona, 0));
return -1;
}
if (parent->p_persona == NULL)
return 0;
return persona_proc_adopt(child, parent->p_persona, parent->p_ucred);
}
int persona_proc_adopt_id(proc_t p, uid_t id, kauth_cred_t auth_override)
{
int ret;
struct persona *persona;
persona = persona_lookup(id);
if (!persona)
return ESRCH;
ret = persona_proc_adopt(p, persona, auth_override);
persona_put(persona);
return ret;
}
typedef enum e_persona_reset_op {
PROC_REMOVE_PERSONA = 1,
PROC_RESET_OLD_PERSONA = 2,
} persona_reset_op_t;
static struct persona *proc_reset_persona_internal(proc_t p, persona_reset_op_t op,
struct persona *old_persona,
struct persona *new_persona)
{
#if (DEVELOPMENT || DEBUG)
persona_lock_assert_held(new_persona);
#endif
switch (op) {
case PROC_REMOVE_PERSONA:
old_persona = p->p_persona;
case PROC_RESET_OLD_PERSONA:
break;
default:
return NULL;
}
persona_unlock(new_persona);
persona_lock(old_persona);
proc_lock(p);
switch (op) {
case PROC_REMOVE_PERSONA:
LIST_REMOVE(p, p_persona_list);
p->p_persona = NULL;
break;
case PROC_RESET_OLD_PERSONA:
p->p_persona = old_persona;
LIST_INSERT_HEAD(&old_persona->pna_members, p, p_persona_list);
break;
}
proc_unlock(p);
persona_unlock(old_persona);
persona_lock(new_persona);
return old_persona;
}
static struct persona *proc_set_cred_internal(proc_t p, struct persona *persona,
kauth_cred_t auth_override, int *rlim_error)
{
struct persona *old_persona = NULL;
kauth_cred_t my_cred, my_new_cred;
uid_t old_uid, new_uid;
int count;
assert(((p->p_lflag & P_LINTRANSIT) == P_LINTRANSIT) &&
p->p_transholder == current_thread());
assert(persona != NULL);
if (p->p_persona == persona)
return NULL;
if (p->p_persona) {
old_persona = proc_reset_persona_internal(p, PROC_REMOVE_PERSONA,
NULL, persona);
}
if (auth_override)
my_new_cred = auth_override;
else
my_new_cred = persona->pna_cred;
if (!my_new_cred)
panic("NULL credentials (persona:%p)", persona);
*rlim_error = 0;
kauth_cred_ref(my_new_cred);
new_uid = persona->pna_id;
if (new_uid != 0 &&
(rlim_t)chgproccnt(new_uid, 0) > p->p_rlimit[RLIMIT_NPROC].rlim_cur) {
pna_err("PID:%d hit proc rlimit in new persona(%d): %s",
p->p_pid, new_uid, persona_desc(persona, 1));
*rlim_error = EACCES;
(void)proc_reset_persona_internal(p, PROC_RESET_OLD_PERSONA,
old_persona, persona);
kauth_cred_unref(&my_new_cred);
return NULL;
}
set_proc_cred:
my_cred = kauth_cred_proc_ref(p);
persona_dbg("proc_adopt PID:%d, %s -> %s",
p->p_pid,
persona_desc(old_persona, 1),
persona_desc(persona, 1));
old_uid = kauth_cred_getruid(my_cred);
if (my_cred != my_new_cred) {
kauth_cred_t old_cred = my_cred;
proc_ucred_lock(p);
if (p->p_ucred != my_cred) {
proc_ucred_unlock(p);
kauth_cred_unref(&my_cred);
goto set_proc_cred;
}
kauth_cred_ref(my_new_cred);
p->p_ucred = my_new_cred;
mach_kauth_cred_uthread_update();
PROC_UPDATE_CREDS_ONPROC(p);
kauth_cred_unref(&old_cred);
proc_ucred_unlock(p);
}
kauth_cred_unref(&my_cred);
if (old_persona)
old_uid = old_persona->pna_id;
if (new_uid != old_uid) {
count = chgproccnt(old_uid, -1);
persona_dbg("Decrement %s:%d proc_count to: %d",
old_persona ? "Persona" : "UID", old_uid, count);
count = chgproccnt(new_uid, 1);
persona_dbg("Increment Persona:%d (UID:%d) proc_count to: %d",
new_uid, kauth_cred_getuid(my_new_cred), count);
}
OSBitOrAtomic(P_ADOPTPERSONA, &p->p_flag);
proc_lock(p);
p->p_persona = persona_get_locked(persona);
LIST_INSERT_HEAD(&persona->pna_members, p, p_persona_list);
proc_unlock(p);
kauth_cred_unref(&my_new_cred);
return old_persona;
}
int persona_proc_adopt(proc_t p, struct persona *persona, kauth_cred_t auth_override)
{
int error;
struct persona *old_persona;
struct session * sessp;
if (!persona)
return EINVAL;
persona_dbg("%d adopting Persona %d (%s)", proc_pid(p),
persona->pna_id, persona_desc(persona, 0));
persona_lock(persona);
if (!persona->pna_cred || !persona_valid(persona)) {
persona_dbg("Invalid persona (%s): NULL credentials!", persona_desc(persona, 1));
persona_unlock(persona);
return EINVAL;
}
persona->pna_cred_locked = 1;
error = 0;
old_persona = proc_set_cred_internal(p, persona, auth_override, &error);
if (persona->pna_pgid) {
uid_t uid = kauth_cred_getuid(persona->pna_cred);
persona_dbg(" PID:%d, pgid:%d%s",
p->p_pid, persona->pna_pgid,
persona->pna_pgid == uid ? ", new_session" : ".");
enterpgrp(p, persona->pna_pgid, persona->pna_pgid == uid);
}
sessp = proc_session(p);
if (sessp != SESSION_NULL) {
session_lock(sessp);
bcopy(persona->pna_login, sessp->s_login, MAXLOGNAME);
session_unlock(sessp);
session_rele(sessp);
}
persona_unlock(persona);
set_security_token(p);
if (old_persona)
persona_put(old_persona);
persona_dbg("%s", error == 0 ? "SUCCESS" : "FAILED");
return error;
}
int persona_proc_drop(proc_t p)
{
struct persona *persona = NULL;
persona_dbg("PID:%d, %s -> <none>", p->p_pid, persona_desc(p->p_persona, 0));
try_again:
proc_lock(p);
if (p->p_persona) {
uid_t puid, ruid;
if (!persona_try_lock(p->p_persona)) {
proc_unlock(p);
mutex_pause(0);
goto try_again;
}
persona = p->p_persona;
LIST_REMOVE(p, p_persona_list);
p->p_persona = NULL;
ruid = kauth_cred_getruid(p->p_ucred);
puid = kauth_cred_getuid(persona->pna_cred);
proc_unlock(p);
(void)chgproccnt(ruid, 1);
(void)chgproccnt(puid, -1);
} else {
proc_unlock(p);
}
if (persona) {
persona_unlock(persona);
persona_put(persona);
}
return 0;
}
int persona_get_type(struct persona *persona)
{
int type;
if (!persona)
return PERSONA_INVALID;
persona_lock(persona);
if (!persona_valid(persona)) {
persona_unlock(persona);
return PERSONA_INVALID;
}
type = persona->pna_type;
persona_unlock(persona);
return type;
}
int persona_set_cred(struct persona *persona, kauth_cred_t cred)
{
int ret = 0;
kauth_cred_t my_cred;
if (!persona || !cred)
return EINVAL;
persona_lock(persona);
if (!persona_valid(persona)) {
ret = EINVAL;
goto out_unlock;
}
if (persona->pna_cred_locked) {
ret = EPERM;
goto out_unlock;
}
my_cred = kauth_cred_create(cred);
my_cred = kauth_cred_setresuid(my_cred, persona->pna_id,
persona->pna_id, persona->pna_id,
KAUTH_UID_NONE);
if (persona->pna_cred)
kauth_cred_unref(&persona->pna_cred);
persona->pna_cred = my_cred;
out_unlock:
persona_unlock(persona);
return ret;
}
int persona_set_cred_from_proc(struct persona *persona, proc_t proc)
{
int ret = 0;
kauth_cred_t parent_cred, my_cred;
if (!persona || !proc)
return EINVAL;
persona_lock(persona);
if (!persona_valid(persona)) {
ret = EINVAL;
goto out_unlock;
}
if (persona->pna_cred_locked) {
ret = EPERM;
goto out_unlock;
}
parent_cred = kauth_cred_proc_ref(proc);
my_cred = kauth_cred_create(parent_cred);
my_cred = kauth_cred_setresuid(my_cred, persona->pna_id,
persona->pna_id, persona->pna_id,
KAUTH_UID_NONE);
if (persona->pna_cred)
kauth_cred_unref(&persona->pna_cred);
persona->pna_cred = my_cred;
kauth_cred_unref(&parent_cred);
out_unlock:
persona_unlock(persona);
return ret;
}
kauth_cred_t persona_get_cred(struct persona *persona)
{
kauth_cred_t cred = NULL;
if (!persona)
return NULL;
persona_lock(persona);
if (!persona_valid(persona))
goto out_unlock;
if (persona->pna_cred) {
kauth_cred_ref(persona->pna_cred);
cred = persona->pna_cred;
}
out_unlock:
persona_unlock(persona);
return cred;
}
uid_t persona_get_uid(struct persona *persona)
{
uid_t uid = UID_MAX;
if (!persona || !persona->pna_cred)
return UID_MAX;
persona_lock(persona);
if (persona_valid(persona)) {
uid = kauth_cred_getuid(persona->pna_cred);
assert(uid == persona->pna_id);
}
persona_unlock(persona);
return uid;
}
int persona_set_gid(struct persona *persona, gid_t gid)
{
int ret = 0;
kauth_cred_t my_cred, new_cred;
if (!persona || !persona->pna_cred)
return EINVAL;
persona_lock(persona);
if (!persona_valid(persona)) {
ret = EINVAL;
goto out_unlock;
}
if (persona->pna_cred_locked) {
ret = EPERM;
goto out_unlock;
}
my_cred = persona->pna_cred;
kauth_cred_ref(my_cred);
new_cred = kauth_cred_setresgid(my_cred, gid, gid, gid);
if (new_cred != my_cred)
persona->pna_cred = new_cred;
kauth_cred_unref(&my_cred);
out_unlock:
persona_unlock(persona);
return ret;
}
gid_t persona_get_gid(struct persona *persona)
{
gid_t gid = GID_MAX;
if (!persona || !persona->pna_cred)
return GID_MAX;
persona_lock(persona);
if (persona_valid(persona))
gid = kauth_cred_getgid(persona->pna_cred);
persona_unlock(persona);
return gid;
}
int persona_set_groups(struct persona *persona, gid_t *groups, int ngroups, uid_t gmuid)
{
int ret = 0;
kauth_cred_t my_cred, new_cred;
if (!persona || !persona->pna_cred)
return EINVAL;
if (ngroups > NGROUPS_MAX)
return EINVAL;
persona_lock(persona);
if (!persona_valid(persona)) {
ret = EINVAL;
goto out_unlock;
}
if (persona->pna_cred_locked) {
ret = EPERM;
goto out_unlock;
}
my_cred = persona->pna_cred;
kauth_cred_ref(my_cred);
new_cred = kauth_cred_setgroups(my_cred, groups, ngroups, gmuid);
if (new_cred != my_cred)
persona->pna_cred = new_cred;
kauth_cred_unref(&my_cred);
out_unlock:
persona_unlock(persona);
return ret;
}
int persona_get_groups(struct persona *persona, int *ngroups, gid_t *groups, int groups_sz)
{
int ret = EINVAL;
if (!persona || !persona->pna_cred || !groups || !ngroups)
return EINVAL;
*ngroups = groups_sz;
persona_lock(persona);
if (persona_valid(persona)) {
kauth_cred_getgroups(persona->pna_cred, groups, ngroups);
ret = 0;
}
persona_unlock(persona);
return ret;
}
uid_t persona_get_gmuid(struct persona *persona)
{
uid_t gmuid = KAUTH_UID_NONE;
if (!persona || !persona->pna_cred)
return gmuid;
persona_lock(persona);
if (!persona_valid(persona))
goto out_unlock;
posix_cred_t pcred = posix_cred_get(persona->pna_cred);
gmuid = pcred->cr_gmuid;
out_unlock:
persona_unlock(persona);
return gmuid;
}
int persona_get_login(struct persona *persona, char login[MAXLOGNAME+1])
{
int ret = EINVAL;
if (!persona || !persona->pna_cred)
return EINVAL;
persona_lock(persona);
if (!persona_valid(persona))
goto out_unlock;
strlcpy(login, persona->pna_login, MAXLOGNAME);
ret = 0;
out_unlock:
persona_unlock(persona);
login[MAXLOGNAME] = 0;
return ret;
}
#else
uid_t persona_get_id(__unused struct persona *persona)
{
return PERSONA_ID_NONE;
}
int persona_get_type(__unused struct persona *persona)
{
return PERSONA_INVALID;
}
kauth_cred_t persona_get_cred(__unused struct persona *persona)
{
return NULL;
}
struct persona *persona_lookup(__unused uid_t id)
{
return NULL;
}
int persona_find(__unused const char *login,
__unused uid_t uid,
__unused struct persona **persona,
__unused size_t *plen)
{
return ENOTSUP;
}
struct persona *current_persona_get(void)
{
return NULL;
}
struct persona *persona_get(struct persona *persona)
{
return persona;
}
void persona_put(__unused struct persona *persona)
{
return;
}
#endif