+/*
+ * Copyright (c) 2015 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@
+ */
+#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);
+
+ /*
+ * setup the default credentials that a persona temporarily
+ * inherits (to work around kauth APIs)
+ */
+ 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) {
+ /* too many 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;
+
+ /*
+ * Setup initial (temporary) kauth_cred structure
+ * We need to do this here because all kauth calls require
+ * an existing cred structure.
+ */
+ 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) {
+ /*
+ * someone else manually claimed this ID, and we're
+ * trying to allocate an ID for the caller: try again
+ */
+ 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) {
+ /*
+ * Disallow use of identical login names and re-use
+ * of previously allocated persona IDs
+ */
+ err = EEXIST;
+ break;
+ }
+ }
+ if (err)
+ goto out_unlock;
+
+ /* ensure the cred has proper UID/GID defaults */
+ 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;
+ }
+
+ /* it should be a member of exactly 1 group (equal to its UID) */
+ new_group = (gid_t)persona->pna_id;
+
+ kauth_cred_ref(persona->pna_cred);
+ /* opt _out_ of memberd as a default */
+ 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;
+
+ /* insert the, now valid, persona into the global list! */
+ persona->pna_valid = PERSONA_MAGIC;
+ LIST_INSERT_HEAD(&all_personas, persona, pna_list);
+
+ /* if the kernel supplied the persona ID, increment for next time */
+ 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));
+
+ /* release our credential reference */
+ if (persona->pna_cred)
+ kauth_cred_unref(&persona->pna_cred);
+
+ /* remove it from the global list and decrement the count */
+ 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;
+
+ /*
+ * simple, linear lookup for now: there shouldn't be too many
+ * of these in memory at any given time.
+ */
+ 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;
+}
+
+/**
+ * inherit a persona from parent to child
+ */
+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;
+ }
+
+ /* no persona to inherit */
+ 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);
+
+ /* put the reference from the lookup() */
+ persona_put(persona);
+
+ return ret;
+}
+
+
+typedef enum e_persona_reset_op {
+ PROC_REMOVE_PERSONA = 1,
+ PROC_RESET_OLD_PERSONA = 2,
+} persona_reset_op_t;
+
+/*
+ * internal cleanup routine for proc_set_cred_internal
+ *
+ */
+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;
+ /* fall through */
+ case PROC_RESET_OLD_PERSONA:
+ break;
+ default:
+ /* invalid arguments */
+ return NULL;
+ }
+
+ /* unlock the new persona (locked on entry) */
+ persona_unlock(new_persona);
+ /* lock the old persona and the process */
+ 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);
+
+ /* re-lock the new persona */
+ persona_lock(new_persona);
+ return old_persona;
+}
+
+/*
+ * Assumes persona is locked.
+ * On success, takes a reference to 'persona' and returns the
+ * previous persona the process had adopted. The caller is
+ * responsible to release the reference.
+ */
+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;
+
+ /*
+ * This operation must be done under the proc trans lock
+ * by the thread which took the trans lock!
+ */
+ assert(((p->p_lflag & P_LINTRANSIT) == P_LINTRANSIT) &&
+ p->p_transholder == current_thread());
+ assert(persona != NULL);
+
+ /* no work to do if we "re-adopt" the same persona */
+ if (p->p_persona == persona)
+ return NULL;
+
+ /*
+ * If p is in a persona, then we need to remove 'p' from the list of
+ * processes in that persona. To do this, we need to drop the lock
+ * held on the incoming (new) persona and lock the old one.
+ */
+ 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;
+
+ /*
+ * Check to see if we will hit a proc rlimit by moving the process
+ * into the persona. If so, we'll bail early before actually moving
+ * the process or changing its credentials.
+ */
+ 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 the new credentials on the proc
+ */
+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);
+ /*
+ * We need to protect against a race where another thread
+ * also changed the credential after we took our
+ * reference. If p_ucred has changed then we should
+ * restart this again with the new cred.
+ */
+ if (p->p_ucred != my_cred) {
+ proc_ucred_unlock(p);
+ kauth_cred_unref(&my_cred);
+ /* try again */
+ goto set_proc_cred;
+ }
+
+ /* update the credential and take a ref for the proc */
+ kauth_cred_ref(my_new_cred);
+ p->p_ucred = my_new_cred;
+
+ /* update cred on proc (and current thread) */
+ mach_kauth_cred_uthread_update();
+ PROC_UPDATE_CREDS_ONPROC(p);
+
+ /* drop the proc's old ref on the credential */
+ kauth_cred_unref(&old_cred);
+ proc_ucred_unlock(p);
+ }
+
+ /* drop this function's reference to the old cred */
+ kauth_cred_unref(&my_cred);
+
+ /*
+ * Update the proc count.
+ * If the UIDs are the same, then there is no work to do.
+ */
+ 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);
+
+ /*
+ * Increment the proc count on the UID associated with
+ * the new persona. Enforce the resource limit just
+ * as in fork1()
+ */
+ 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;
+ }
+
+ /* the persona credentials can no longer be adjusted */
+ persona->pna_cred_locked = 1;
+
+ /*
+ * assume the persona: this may drop and re-acquire the persona lock!
+ */
+ error = 0;
+ old_persona = proc_set_cred_internal(p, persona, auth_override, &error);
+
+ /* join the process group associated with the persona */
+ 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);
+ }
+
+ /* set the login name of the session */
+ 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);
+
+ /*
+ * Drop the reference to the old persona.
+ */
+ 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));
+
+ /*
+ * There are really no other credentials for us to assume,
+ * so we'll just continue running with the credentials
+ * we got from the persona.
+ */
+
+ /*
+ * the locks must be taken in reverse order here, so
+ * we have to be careful not to cause deadlock
+ */
+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); /* back-off time */
+ 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 the proc had a persona, then it is still locked here
+ * (preserving proper lock ordering)
+ */
+
+ 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;
+ }
+
+ /* create a new cred from the passed-in cred */
+ my_cred = kauth_cred_create(cred);
+
+ /* ensure that the UID matches the persona ID */
+ my_cred = kauth_cred_setresuid(my_cred, persona->pna_id,
+ persona->pna_id, persona->pna_id,
+ KAUTH_UID_NONE);
+
+ /* TODO: clear the saved GID?! */
+
+ /* replace the persona's cred with the new one */
+ 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);
+
+ /* TODO: clear the saved UID/GID! */
+
+ /* create a new cred from the proc's cred */
+ my_cred = kauth_cred_create(parent_cred);
+
+ /* ensure that the UID matches the persona ID */
+ my_cred = kauth_cred_setresuid(my_cred, persona->pna_id,
+ persona->pna_id, persona->pna_id,
+ KAUTH_UID_NONE);
+
+ /* replace the persona's cred with the new one */
+ 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 /* !CONFIG_PERSONAS */
+
+/*
+ * symbol exports for kext compatibility
+ */
+
+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