gems-kernel/source/THIRDPARTY/xnu/bsd/kern/kern_persona.c

963 lines
22 KiB
C
Raw Normal View History

2024-06-03 11:29:39 -05:00
/*
* Copyright (c) 2015-2020 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/commpage.h>
#include <sys/kernel_types.h>
#include <sys/persona.h>
#include <pexpert/pexpert.h>
#include <machine/cpu_capabilities.h>
#if CONFIG_PERSONAS
#include <machine/atomic.h>
#include <kern/assert.h>
#include <kern/simple_lock.h>
#include <kern/task.h>
#include <kern/zalloc.h>
#include <mach/thread_act.h>
#include <kern/thread.h>
#include <sys/param.h>
#include <sys/proc_internal.h>
#include <sys/kauth.h>
#include <sys/proc_info.h>
#include <sys/resourcevar.h>
#include <security/audit/audit.h>
#include <os/log.h>
#define pna_err(fmt, ...) \
os_log_error(OS_LOG_DEFAULT, "ERROR: " fmt, ## __VA_ARGS__)
#define MAX_PERSONAS 512
#define TEMP_PERSONA_ID 499
#define FIRST_PERSONA_ID 501
#define PERSONA_ID_STEP 10
#define PERSONA_ALLOC_TOKEN (0x7a0000ae)
#define PERSONA_INIT_TOKEN (0x7500005e)
#define PERSONA_MAGIC (0x0aa55aa0)
#define persona_initialized(p) ((p)->pna_valid == PERSONA_MAGIC || (p)->pna_valid == PERSONA_INIT_TOKEN)
#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 = LIST_HEAD_INITIALIZER(all_personas);
static uint32_t g_total_personas;
const uint32_t g_max_personas = MAX_PERSONAS;
static uid_t g_next_persona_id = FIRST_PERSONA_ID;
LCK_GRP_DECLARE(persona_lck_grp, "personas");
LCK_MTX_DECLARE(all_personas_lock, &persona_lck_grp);
os_refgrp_decl(static, persona_refgrp, "persona", NULL);
static KALLOC_TYPE_DEFINE(persona_zone, struct persona, KT_DEFAULT);
#define lock_personas() lck_mtx_lock(&all_personas_lock)
#define unlock_personas() lck_mtx_unlock(&all_personas_lock)
extern kern_return_t bank_get_bank_ledger_thread_group_and_persona(void *voucher,
void *bankledger, void **banktg, uint32_t *persona_id);
void
ipc_voucher_release(void *voucher);
struct persona *
persona_alloc(uid_t id, const char *login, persona_type_t type, char *path, uid_t uid, int *error)
{
struct persona *persona;
int err = 0;
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 = zalloc_flags(persona_zone, Z_WAITOK | Z_ZERO | Z_NOFAIL);
if (os_atomic_inc(&g_total_personas, relaxed) > 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);
persona_dbg("Starting persona allocation for: '%s'", persona->pna_login);
LIST_INIT(&persona->pna_members);
lck_mtx_init(&persona->pna_lock, &persona_lck_grp, LCK_ATTR_NULL);
os_ref_init(&persona->pna_refcount, &persona_refgrp);
persona->pna_type = type;
persona->pna_id = id;
persona->pna_valid = PERSONA_ALLOC_TOKEN;
persona->pna_path = path;
persona->pna_uid = uid;
/*
* NOTE: this persona has not been fully initialized. A subsequent
* call to persona_init_begin() followed by persona_init_end() will make
* the persona visible to the rest of the system.
*/
if (error) {
*error = 0;
}
return persona;
out_error:
os_atomic_dec(&g_total_personas, relaxed);
zfree(persona_zone, persona);
if (error) {
*error = err;
}
return NULL;
}
/**
* persona_init_begin
*
* This function begins initialization of a persona. It first acquires the
* global persona list lock via lock_personas(), then selects an appropriate
* persona ID and sets up the persona's credentials. This function *must* be
* followed by a call to persona_init_end() which will mark the persona
* structure as valid
*
* Conditions:
* persona has been allocated via persona_alloc()
* nothing locked
*
* Returns:
* global persona list is locked (even on error)
*/
int
persona_init_begin(struct persona *persona)
{
struct persona *tmp;
int err = 0;
uid_t id;
if (!persona || (persona->pna_valid != PERSONA_ALLOC_TOKEN)) {
return EINVAL;
}
id = persona->pna_id;
lock_personas();
try_again:
if (id == PERSONA_ID_NONE) {
persona->pna_id = g_next_persona_id;
}
persona_dbg("Beginning Initialization of %d:%d (%s)...", id, persona->pna_id, persona->pna_login);
err = 0;
LIST_FOREACH(tmp, &all_personas, pna_list) {
persona_lock(tmp);
if (id == PERSONA_ID_NONE && tmp->pna_id == persona->pna_id) {
persona_unlock(tmp);
/*
* 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, persona->pna_login, sizeof(tmp->pna_login)) == 0 ||
tmp->pna_id == persona->pna_id) {
persona_unlock(tmp);
/*
* Disallow use of identical login names and re-use
* of previously allocated persona IDs
*/
err = EEXIST;
break;
}
persona_unlock(tmp);
}
if (err) {
goto out;
}
/* if the kernel supplied the persona ID, increment for next time */
if (id == PERSONA_ID_NONE) {
g_next_persona_id += PERSONA_ID_STEP;
}
persona->pna_valid = PERSONA_INIT_TOKEN;
out:
if (err != 0) {
persona_dbg("ERROR:%d while initializing %d:%d (%s)...", err, id, persona->pna_id, persona->pna_login);
/*
* mark the persona with an error so that persona_init_end()
* will *not* add it to the global list.
*/
persona->pna_id = PERSONA_ID_NONE;
}
/*
* leave the global persona list locked: it will be
* unlocked in a call to persona_init_end()
*/
return err;
}
/**
* persona_init_end
*
* This function finalizes the persona initialization by marking it valid and
* adding it to the global list of personas. After unlocking the global list,
* the persona will be visible to the reset of the system. The function will
* only mark the persona valid if the input parameter 'error' is 0.
*
* Conditions:
* persona is initialized via persona_init_begin()
* global persona list is locked via lock_personas()
*
* Returns:
* global persona list is unlocked
*/
void
persona_init_end(struct persona *persona, int error)
{
if (persona == NULL) {
return;
}
/*
* If the pna_valid member is set to the INIT_TOKEN value, then it has
* successfully gone through persona_init_begin(), and we can mark it
* valid and make it visible to the rest of the system. However, if
* there was an error either during initialization or otherwise, we
* need to decrement the global count of personas because this one
* will be disposed-of by the callers invocation of persona_put().
*/
if (error != 0 || persona->pna_valid == PERSONA_ALLOC_TOKEN) {
persona_dbg("ERROR:%d after initialization of %d (%s)", error, persona->pna_id, persona->pna_login);
/* remove this persona from the global count */
os_atomic_dec(&g_total_personas, relaxed);
} else if (error == 0 &&
persona->pna_valid == PERSONA_INIT_TOKEN) {
persona->pna_valid = PERSONA_MAGIC;
LIST_INSERT_HEAD(&all_personas, persona, pna_list);
persona_dbg("Initialization of %d (%s) Complete.", persona->pna_id, persona->pna_login);
}
unlock_personas();
}
static struct persona *
persona_get_locked(struct persona *persona)
{
os_ref_retain_locked(&persona->pna_refcount);
return persona;
}
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;
}
struct persona *
proc_persona_get(proc_t p)
{
proc_lock(p);
struct persona *persona = persona_get(p->p_persona);
proc_unlock(p);
return persona;
}
static void
persona_put_and_unlock(struct persona *persona)
{
int destroy = 0;
if (os_ref_release_locked(&persona->pna_refcount) == 0) {
destroy = 1;
}
persona_unlock(persona);
if (!destroy) {
return;
}
persona_dbg("Destroying persona %s", persona_desc(persona, 0));
/* remove it from the global list and decrement the count */
lock_personas();
persona_lock(persona);
if (persona_valid(persona)) {
LIST_REMOVE(persona, pna_list);
if (os_atomic_dec_orig(&g_total_personas, relaxed) == 0) {
panic("persona count underflow!");
}
persona_mkinvalid(persona);
}
if (persona->pna_path != NULL) {
zfree(ZV_NAMEI, persona->pna_path);
}
persona_unlock(persona);
unlock_personas();
assert(LIST_EMPTY(&persona->pna_members));
memset(persona, 0, sizeof(*persona));
zfree(persona_zone, persona);
}
void
persona_put(struct persona *persona)
{
if (persona) {
persona_lock(persona);
persona_put_and_unlock(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;
}
struct persona *
persona_lookup_and_invalidate(uid_t id)
{
struct persona *persona, *entry, *tmp;
persona = NULL;
lock_personas();
LIST_FOREACH_SAFE(entry, &all_personas, pna_list, tmp) {
persona_lock(entry);
if (entry->pna_id == id) {
if (persona_valid(entry)) {
persona = persona_get_locked(entry);
assert(persona != NULL);
LIST_REMOVE(persona, pna_list);
if (os_atomic_dec_orig(&g_total_personas, relaxed) == 0) {
panic("persona ref count underflow!");
}
persona_mkinvalid(persona);
}
persona_unlock(entry);
break;
}
persona_unlock(entry);
}
unlock_personas();
return persona;
}
int
persona_find_by_type(persona_type_t persona_type, struct persona **persona, size_t *plen)
{
return persona_find_all(NULL, PERSONA_ID_NONE, persona_type, persona, plen);
}
int
persona_find(const char *login, uid_t uid,
struct persona **persona, size_t *plen)
{
return persona_find_all(login, uid, PERSONA_INVALID, persona, plen);
}
int
persona_find_all(const char *login, uid_t uid, persona_type_t persona_type,
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 ((persona_type > PERSONA_INVALID) && (persona_type <= PERSONA_TYPE_MAX)) {
match++;
} else if (persona_type != PERSONA_INVALID) {
return EINVAL;
}
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 (persona_type != PERSONA_INVALID && persona_type == tmp->pna_type) {
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)
{
proc_t p = proc_find(pid);
if (!p) {
return NULL;
}
struct persona *persona = proc_persona_get(p);
proc_rele(p);
return persona;
}
uid_t
current_persona_get_id(void)
{
uid_t current_persona_id = PERSONA_ID_NONE;
ipc_voucher_t voucher;
thread_get_mach_voucher(current_thread(), 0, &voucher);
/* returns a voucher ref */
if (voucher != IPC_VOUCHER_NULL) {
/*
* If the voucher doesn't contain a bank attribute, it uses
* the default bank task value to determine the persona id
* which is the same as the proc's persona id
*/
bank_get_bank_ledger_thread_group_and_persona(voucher, NULL,
NULL, &current_persona_id);
ipc_voucher_release(voucher);
} else {
/* Fallback - get the proc's persona */
current_persona_id = proc_persona_id(current_proc());
}
return current_persona_id;
}
struct persona *
current_persona_get(void)
{
struct persona *persona = NULL;
uid_t current_persona_id = PERSONA_ID_NONE;
current_persona_id = current_persona_get_id();
persona = persona_lookup(current_persona_id);
return persona;
}
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_persona_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;
OS_FALLTHROUGH;
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 */
assert(old_persona != NULL);
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_persona_internal(
proc_t p,
struct persona *persona,
kauth_cred_derive_t derive_fn,
int *rlim_error)
{
struct persona *old_persona = NULL;
uid_t old_uid, new_uid;
size_t count;
rlim_t nproc = proc_limitgetcur(p, RLIMIT_NPROC);
/*
* 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);
}
/*
* 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.
*/
new_uid = persona->pna_id;
if (new_uid != 0 &&
(rlim_t)chgproccnt(new_uid, 0) > nproc) {
pna_err("PID:%d hit proc rlimit in new persona(%d): %s",
proc_getpid(p), new_uid, persona_desc(persona, 1));
*rlim_error = EACCES;
if (old_persona) {
(void)proc_reset_persona_internal(p, PROC_RESET_OLD_PERSONA,
old_persona, persona);
}
return NULL;
}
*rlim_error = 0;
if (old_persona) {
old_uid = old_persona->pna_id;
} else {
/* proc_ucred_unsafe() is OK because p is a fork/exec/... child */
old_uid = kauth_cred_getruid(proc_ucred_unsafe(p));
}
if (derive_fn) {
kauth_cred_proc_update(p, PROC_SETTOKEN_SETUGID, derive_fn);
}
if (new_uid != old_uid) {
count = chgproccnt(old_uid, -1);
persona_dbg("Decrement %s:%d proc_count to: %lu",
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 proc_count to: %lu",
new_uid, 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);
return old_persona;
}
/* only called during fork or exec: child's ucred is stable */
int
persona_proc_adopt(
proc_t p,
struct persona *persona, /* consumed */
kauth_cred_derive_t derive_fn)
{
int error;
struct persona *old_persona;
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_valid(persona)) {
persona_dbg("Invalid persona (%s)!", persona_desc(persona, 1));
persona_put_and_unlock(persona);
return EINVAL;
}
/*
* assume the persona: this may drop and re-acquire the persona lock!
*/
error = 0;
old_persona = proc_set_persona_internal(p, persona, derive_fn, &error);
/* Only Multiuser Mode needs to update the session login name to the persona name */
#if XNU_TARGET_OS_IOS
uint32_t multiuser_flags = COMM_PAGE_READ(uint32_t, MULTIUSER_CONFIG);
/* set the login name of the session */
if (multiuser_flags & kIsMultiUserDevice) {
struct pgrp *pg;
struct session *sessp;
if ((pg = proc_pgrp(p, &sessp)) != PGRP_NULL) {
session_lock(sessp);
bcopy(persona->pna_login, sessp->s_login, MAXLOGNAME);
session_unlock(sessp);
pgrp_rele(pg);
}
}
#endif
persona_put_and_unlock(persona);
/*
* 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>", proc_getpid(p), 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;
smr_proc_task_enter();
ruid = kauth_cred_getruid(proc_ucred_smr(p));
smr_proc_task_leave();
puid = persona->pna_id;
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;
}
uid_t
persona_get_uid(struct persona *persona)
{
uid_t uid = KAUTH_UID_NONE;
if (!persona) {
return KAUTH_UID_NONE;
}
persona_lock(persona);
if (persona_valid(persona)) {
uid = persona->pna_uid;
}
persona_unlock(persona);
return uid;
}
boolean_t
persona_is_adoption_allowed(struct persona *persona)
{
if (!persona) {
return FALSE;
}
int type = persona->pna_type;
return type == PERSONA_SYSTEM || type == PERSONA_SYSTEM_PROXY;
}
#else /* !CONFIG_PERSONAS */
/*
* symbol exports for kext compatibility
*/
uid_t
persona_get_id(__unused struct persona *persona)
{
return PERSONA_ID_NONE;
}
uid_t
persona_get_uid(__unused struct persona *persona)
{
return KAUTH_UID_NONE;
}
int
persona_get_type(__unused struct persona *persona)
{
return PERSONA_INVALID;
}
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;
}
int
persona_find_by_type(__unused int persona_type,
__unused struct persona **persona,
__unused size_t *plen)
{
return ENOTSUP;
}
struct persona *
persona_proc_get(__unused pid_t pid)
{
return NULL;
}
uid_t
current_persona_get_id(void)
{
return PERSONA_ID_NONE;
}
struct persona *
current_persona_get(void)
{
return NULL;
}
struct persona *
persona_get(struct persona *persona)
{
return persona;
}
struct persona *
proc_persona_get(__unused proc_t p)
{
return NULL;
}
void
persona_put(__unused struct persona *persona)
{
return;
}
boolean_t
persona_is_adoption_allowed(__unused struct persona *persona)
{
return FALSE;
}
#endif