X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/378393581903b274cb7a4d18e0d978071a6b592d..cf7d32b81c573a0536dc4da4157f9c26f8d0bed3:/bsd/kern/kern_credential.c?ds=inline diff --git a/bsd/kern/kern_credential.c b/bsd/kern/kern_credential.c index 0a917310f..89d2e8dbf 100644 --- a/bsd/kern/kern_credential.c +++ b/bsd/kern/kern_credential.c @@ -1,27 +1,40 @@ /* - * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2004-2007 Apple Inc. All rights reserved. * - * @APPLE_LICENSE_HEADER_START@ + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * - * The contents of this file constitute Original Code as defined in and - * are subject to the Apple Public Source License Version 1.1 (the - * "License"). You may not use this file except in compliance with the - * License. Please obtain a copy of the License at - * http://www.apple.com/publicsource and read it before using this file. + * 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. * - * This Original Code and all software distributed under the License are - * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * 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 OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License. + * 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_LICENSE_HEADER_END@ + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * NOTICE: This file was modified by SPARTA, Inc. in 2005 to introduce + * support for mandatory and extensible security protections. This notice + * is included in support of clause 2.2 (b) of the Apple Public License, + * Version 2.0. */ /* - * Kernel Authorization framework: Management of process/thread credentials and identity information. + * Kernel Authorization framework: Management of process/thread credentials + * and identity information. */ @@ -54,16 +67,68 @@ #define MACH_ASSERT 1 /* XXX so bogus */ #include -#define CRED_DIAGNOSTIC 1 +#if CONFIG_MACF +#include +#include +#endif + +#define CRED_DIAGNOSTIC 0 + +# define NULLCRED_CHECK(_c) do {if (!IS_VALID_CRED(_c)) panic("%s: bad credential %p", __FUNCTION__,_c);} while(0) + +/* + * Credential debugging; we can track entry into a function that might + * change a credential, and we can track actual credential changes that + * result. + * + * Note: Does *NOT* currently include per-thread credential changes + */ + +#if DEBUG_CRED +#define DEBUG_CRED_ENTER printf +#define DEBUG_CRED_CHANGE printf +extern void kauth_cred_print(kauth_cred_t cred); + +#include /* needed for get_backtrace( ) */ + +int is_target_cred( kauth_cred_t the_cred ); +void get_backtrace( void ); + +static int sysctl_dump_creds( __unused struct sysctl_oid *oidp, __unused void *arg1, + __unused int arg2, struct sysctl_req *req ); +static int +sysctl_dump_cred_backtraces( __unused struct sysctl_oid *oidp, __unused void *arg1, + __unused int arg2, struct sysctl_req *req ); + +#define MAX_STACK_DEPTH 8 +struct cred_backtrace { + int depth; + void * stack[ MAX_STACK_DEPTH ]; +}; +typedef struct cred_backtrace cred_backtrace; + +#define MAX_CRED_BUFFER_SLOTS 200 +struct cred_debug_buffer { + int next_slot; + cred_backtrace stack_buffer[ MAX_CRED_BUFFER_SLOTS ]; +}; +typedef struct cred_debug_buffer cred_debug_buffer; +cred_debug_buffer * cred_debug_buf_p = NULL; + +#else /* !DEBUG_CRED */ -# define NULLCRED_CHECK(_c) do {if (((_c) == NOCRED) || ((_c) == FSCRED)) panic("bad credential %p", _c);} while(0) +#define DEBUG_CRED_ENTER(fmt, ...) do {} while (0) +#define DEBUG_CRED_CHANGE(fmt, ...) do {} while (0) + +#endif /* !DEBUG_CRED */ /* * Interface to external identity resolver. * - * The architecture of the interface is simple; the external resolver calls in to - * get work, then calls back with completed work. It also calls us to let us know - * that it's (re)started, so that we can resubmit work if it times out. + * The architecture of the interface is simple; the external resolver calls + * in to get work, then calls back with completed work. It also calls us + * to let us know that it's (re)started, so that we can resubmit work if it + * times out. */ static lck_mtx_t *kauth_resolver_mtx; @@ -93,22 +158,23 @@ TAILQ_HEAD(kauth_resolver_done_head, kauth_resolver_work) kauth_resolver_done; static int kauth_resolver_submit(struct kauth_identity_extlookup *lkp); static int kauth_resolver_complete(user_addr_t message); static int kauth_resolver_getwork(user_addr_t message); +static int kauth_resolver_getwork2(user_addr_t message); -#define KAUTH_CRED_PRIMES_COUNT 7 -static const int kauth_cred_primes[KAUTH_CRED_PRIMES_COUNT] = {97, 241, 397, 743, 1499, 3989, 7499}; +static const int kauth_cred_primes[KAUTH_CRED_PRIMES_COUNT] = KAUTH_CRED_PRIMES; static int kauth_cred_primes_index = 0; static int kauth_cred_table_size = 0; TAILQ_HEAD(kauth_cred_entry_head, ucred); static struct kauth_cred_entry_head * kauth_cred_table_anchor = NULL; -#define KAUTH_CRED_HASH_DEBUG 0 +#define KAUTH_CRED_HASH_DEBUG 0 static int kauth_cred_add(kauth_cred_t new_cred); static void kauth_cred_remove(kauth_cred_t cred); static inline u_long kauth_cred_hash(const uint8_t *datap, int data_len, u_long start_key); static u_long kauth_cred_get_hashkey(kauth_cred_t cred); static kauth_cred_t kauth_cred_update(kauth_cred_t old_cred, kauth_cred_t new_cred, boolean_t retain_auditinfo); +static void kauth_cred_unref_hashlocked(kauth_cred_t *credp); #if KAUTH_CRED_HASH_DEBUG static int kauth_cred_count = 0; @@ -116,6 +182,34 @@ static void kauth_cred_hash_print(void); static void kauth_cred_print(kauth_cred_t cred); #endif + +/* + * kauth_resolver_init + * + * Description: Initialize the daemon side of the credential identity resolver + * + * Parameters: (void) + * + * Returns: (void) + * + * Notes: Intialize the credential identity resolver for use; the + * credential identity resolver is the KPI used by the user + * space credential identity resolver daemon to communicate + * with the kernel via the identitysvc() system call.. + * + * This is how membership in more than 16 groups (1 effective + * and 15 supplementary) is supported, and also how UID's, + * UUID's, and so on, are translated to/from POSIX credential + * values. + * + * The credential identity resolver operates by attempting to + * determine identity first from the credential, then from + * the kernel credential identity cache, and finally by + * enqueueing a request to a user space daemon. + * + * This function is called from kauth_init() in the file + * kern_authorization.c. + */ void kauth_resolver_init(void) { @@ -126,10 +220,27 @@ kauth_resolver_init(void) kauth_resolver_mtx = lck_mtx_alloc_init(kauth_lck_grp, 0/*LCK_ATTR_NULL*/); } + /* - * Allocate a work queue entry, submit the work and wait for completion. + * kauth_resolver_submit * - * XXX do we want an 'interruptible' flag vs. always being interruptible? + * Description: Submit an external credential identity resolution request to + * the user space daemon. + * + * Parameters: lkp A pointer to an external + * lookup request + * + * Returns: 0 Success + * EWOULDBLOCK No resolver registered + * EINTR Operation interrupted (e.g. by + * a signal) + * ENOMEM Could not allocate work item + * ??? An error from the user space + * daemon + * + * Notes: Allocate a work queue entry, submit the work and wait for + * the operation to either complete or time out. Outstanding + * operations may also be cancelled. */ static int kauth_resolver_submit(struct kauth_identity_extlookup *lkp) @@ -168,14 +279,17 @@ kauth_resolver_submit(struct kauth_identity_extlookup *lkp) workp->kr_result = 0; /* - * We insert the request onto the unsubmitted queue, the call in from the - * resolver will it to the submitted thread when appropriate. + * We insert the request onto the unsubmitted queue, the call in from + * the resolver will it to the submitted thread when appropriate. */ KAUTH_RESOLVER_LOCK(); workp->kr_seqno = workp->kr_work.el_seqno = kauth_resolver_sequence++; workp->kr_work.el_result = KAUTH_EXTLOOKUP_INPROG; - /* XXX as an optimisation, we could check the queue for identical items and coalesce */ + /* + * XXX As an optimisation, we could check the queue for identical + * XXX items and coalesce them + */ TAILQ_INSERT_TAIL(&kauth_resolver_unsubmitted, workp, kr_link); wakeup_one((caddr_t)&kauth_resolver_unsubmitted); @@ -201,9 +315,10 @@ kauth_resolver_submit(struct kauth_identity_extlookup *lkp) *lkp = workp->kr_work; /* - * If the request timed out and was never collected, the resolver is dead and - * probably not coming back anytime soon. In this case we revert to no-resolver - * behaviour, and punt all the other sleeping requests to clear the backlog. + * If the request timed out and was never collected, the resolver + * is dead and probably not coming back anytime soon. In this + * case we revert to no-resolver behaviour, and punt all the other + * sleeping requests to clear the backlog. */ if ((error == EWOULDBLOCK) && (workp->kr_flags & KAUTH_REQUEST_UNSUBMITTED)) { KAUTH_DEBUG("RESOLVER - request timed out without being collected for processing, resolver dead"); @@ -215,7 +330,10 @@ kauth_resolver_submit(struct kauth_identity_extlookup *lkp) wakeup(killp); } - /* drop our reference on the work item, and note whether we should free it or not */ + /* + * drop our reference on the work item, and note whether we should + * free it or not + */ if (--workp->kr_refs <= 0) { /* work out which list we have to remove it from */ if (workp->kr_flags & KAUTH_REQUEST_DONE) { @@ -246,8 +364,29 @@ kauth_resolver_submit(struct kauth_identity_extlookup *lkp) return(error); } + /* - * System call interface for the external identity resolver. + * identitysvc + * + * Description: System call interface for the external identity resolver. + * + * Parameters: uap->message Message from daemon to kernel + * + * Returns: 0 Successfully became resolver + * EPERM Not the resolver process + * kauth_authorize_generic:EPERM Not root user + * kauth_resolver_complete:EIO + * kauth_resolver_complete:EFAULT + * kauth_resolver_getwork:EINTR + * kauth_resolver_getwork:EFAULT + * + * Notes: This system call blocks until there is work enqueued, at + * which time the kernel wakes it up, and a message from the + * kernel is copied out to the identity resolution daemon, which + * proceed to attempt to resolve it. When the resolution has + * completed (successfully or not), the daemon called back into + * this system call to give the result to the kernel, and wait + * for the next request. */ int identitysvc(__unused struct proc *p, struct identitysvc_args *uap, __unused register_t *retval) @@ -314,40 +453,152 @@ identitysvc(__unused struct proc *p, struct identitysvc_args *uap, __unused regi return(0); } + /* - * Get work for a caller. + * kauth_resolver_getwork_continue + * + * Description: Continuation for kauth_resolver_getwork + * + * Parameters: result Error code or 0 for the sleep + * that got us to this function + * + * Returns: 0 Success + * EINTR Interrupted (e.g. by signal) + * kauth_resolver_getwork2:EFAULT + * + * Notes: See kauth_resolver_getwork(0 and kauth_resolver_getwork2() for + * more information. */ static int -kauth_resolver_getwork(user_addr_t message) +kauth_resolver_getwork_continue(int result) +{ + thread_t thread; + struct uthread *ut; + user_addr_t message; + + if (result) { + KAUTH_RESOLVER_UNLOCK(); + return(result); + } + + /* + * If we lost a race with another thread/memberd restarting, then we + * need to go back to sleep to look for more work. If it was memberd + * restarting, then the msleep0() will error out here, as our thread + * will already be "dead". + */ + if (TAILQ_FIRST(&kauth_resolver_unsubmitted) == NULL) { + int error; + + error = msleep0(&kauth_resolver_unsubmitted, kauth_resolver_mtx, PCATCH, "GRGetWork", 0, kauth_resolver_getwork_continue); + KAUTH_RESOLVER_UNLOCK(); + return(error); + } + + thread = current_thread(); + ut = get_bsdthread_info(thread); + message = ut->uu_kauth.message; + return(kauth_resolver_getwork2(message)); +} + + +/* + * kauth_resolver_getwork2 + * + * Decription: Common utility function to copy out a identity resolver work + * item from the kernel to user space as part of the user space + * identity resolver requesting work. + * + * Parameters: message message to user space + * + * Returns: 0 Success + * EFAULT Bad user space message address + * + * Notes: This common function exists to permit the use of continuations + * in the identity resoultion process. This frees up the stack + * while we are waiting for the user space resolver to complete + * a request. This is specifically used so that our per thread + * cost can be small, and we will therefore be willing to run a + * larger number of threads in the user space identity resolver. + */ +static int +kauth_resolver_getwork2(user_addr_t message) { struct kauth_resolver_work *workp; int error; - KAUTH_RESOLVER_LOCK(); - error = 0; - while ((workp = TAILQ_FIRST(&kauth_resolver_unsubmitted)) == NULL) { - error = msleep(&kauth_resolver_unsubmitted, kauth_resolver_mtx, PCATCH, "GRGetWork", 0); - if (error != 0) - break; - } - if (workp != NULL) { - if ((error = copyout(&workp->kr_work, message, sizeof(workp->kr_work))) != 0) { - KAUTH_DEBUG("RESOLVER - error submitting work to resolve"); - goto out; - } - TAILQ_REMOVE(&kauth_resolver_unsubmitted, workp, kr_link); - workp->kr_flags &= ~KAUTH_REQUEST_UNSUBMITTED; - workp->kr_flags |= KAUTH_REQUEST_SUBMITTED; - TAILQ_INSERT_TAIL(&kauth_resolver_submitted, workp, kr_link); + /* + * Note: We depend on the caller protecting us from a NULL work item + * queue, since we must have the kauth resolver lock on entry to this + * function. + */ + workp = TAILQ_FIRST(&kauth_resolver_unsubmitted); + + if ((error = copyout(&workp->kr_work, message, sizeof(workp->kr_work))) != 0) { + KAUTH_DEBUG("RESOLVER - error submitting work to resolve"); + goto out; } + TAILQ_REMOVE(&kauth_resolver_unsubmitted, workp, kr_link); + workp->kr_flags &= ~KAUTH_REQUEST_UNSUBMITTED; + workp->kr_flags |= KAUTH_REQUEST_SUBMITTED; + TAILQ_INSERT_TAIL(&kauth_resolver_submitted, workp, kr_link); out: KAUTH_RESOLVER_UNLOCK(); return(error); } + +/* + * kauth_resolver_getwork + * + * Description: Get a work item from the enqueued requests from the kernel and + * give it to the user space daemon. + * + * Parameters: message message to user space + * + * Returns: 0 Success + * EINTR Interrupted (e.g. by signal) + * kauth_resolver_getwork2:EFAULT + * + * Notes: This function blocks in a continuation if there are no work + * items available for processing at the time the user space + * identity resolution daemon makes a request for work. This + * permits a large number of threads to be used by the daemon, + * without using a lot of wired kernel memory when there are no + * acctual request outstanding. + */ +static int +kauth_resolver_getwork(user_addr_t message) +{ + struct kauth_resolver_work *workp; + int error; + + KAUTH_RESOLVER_LOCK(); + error = 0; + while ((workp = TAILQ_FIRST(&kauth_resolver_unsubmitted)) == NULL) { + thread_t thread = current_thread(); + struct uthread *ut = get_bsdthread_info(thread); + + ut->uu_kauth.message = message; + error = msleep0(&kauth_resolver_unsubmitted, kauth_resolver_mtx, PCATCH, "GRGetWork", 0, kauth_resolver_getwork_continue); + KAUTH_RESOLVER_UNLOCK(); + return(error); + } + return kauth_resolver_getwork2(message); +} + + /* - * Return a result from userspace. + * kauth_resolver_complete + * + * Description: Return a result from userspace. + * + * Parameters: message message from user space + * + * Returns: 0 Success + * EIO The resolver is dead + * copyin:EFAULT Bad message from user space */ static int kauth_resolver_complete(user_addr_t message) @@ -470,7 +721,7 @@ static lck_mtx_t *kauth_identity_mtx; static struct kauth_identity *kauth_identity_alloc(uid_t uid, gid_t gid, guid_t *guidp, time_t guid_expiry, ntsid_t *ntsidp, time_t ntsid_expiry); -static void kauth_identity_register(struct kauth_identity *kip); +static void kauth_identity_register_and_free(struct kauth_identity *kip); static void kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_identity *kip); static void kauth_identity_lru(struct kauth_identity *kip); static int kauth_identity_guid_expired(struct kauth_identity *kip); @@ -480,6 +731,23 @@ static int kauth_identity_find_gid(gid_t gid, struct kauth_identity *kir); static int kauth_identity_find_guid(guid_t *guidp, struct kauth_identity *kir); static int kauth_identity_find_ntsid(ntsid_t *ntsid, struct kauth_identity *kir); + +/* + * kauth_identity_init + * + * Description: Initialize the kernel side of the credential identity resolver + * + * Parameters: (void) + * + * Returns: (void) + * + * Notes: Intialize the credential identity resolver for use; the + * credential identity resolver is the KPI used to communicate + * with a user space credential identity resolver daemon. + * + * This function is called from kauth_init() in the file + * kern_authorization.c. + */ void kauth_identity_init(void) { @@ -487,12 +755,24 @@ kauth_identity_init(void) kauth_identity_mtx = lck_mtx_alloc_init(kauth_lck_grp, 0/*LCK_ATTR_NULL*/); } -static int -kauth_identity_resolve(__unused struct kauth_identity_extlookup *el) -{ - return(kauth_resolver_submit(el)); -} +/* + * kauth_identity_alloc + * + * Description: Allocate and fill out a kauth_identity structure for + * translation between {UID|GID}/GUID/NTSID + * + * Parameters: uid + * + * Returns: NULL Insufficient memory to satisfy + * the request + * !NULL A pointer to the applocated + * structure, filled in + * + * Notes: It is illegal to translate between UID and GID; any given UUID + * or NTSID can oly refer to an NTSIDE or UUID (respectively), + * and *either* a UID *or* a GID, but not both. + */ static struct kauth_identity * kauth_identity_alloc(uid_t uid, gid_t gid, guid_t *guidp, time_t guid_expiry, ntsid_t *ntsidp, time_t ntsid_expiry) { @@ -525,17 +805,29 @@ kauth_identity_alloc(uid_t uid, gid_t gid, guid_t *guidp, time_t guid_expiry, nt return(kip); } + /* - * Register an association between identity tokens. + * kauth_identity_register_and_free + * + * Description: Register an association between identity tokens. The passed + * 'kip' is freed by this function. + * + * Parameters: kip Pointer to kauth_identity + * structure to register + * + * Returns: (void) + * + * Notes: The memory pointer to by 'kip' is assumed to have been + * previously allocated via kauth_identity_alloc(). */ static void -kauth_identity_register(struct kauth_identity *kip) +kauth_identity_register_and_free(struct kauth_identity *kip) { struct kauth_identity *ip; /* - * We search the cache for the UID listed in the incoming association. If we - * already have an entry, the new information is merged. + * We search the cache for the UID listed in the incoming association. + * If we already have an entry, the new information is merged. */ ip = NULL; KAUTH_IDENTITY_LOCK(); @@ -583,9 +875,22 @@ kauth_identity_register(struct kauth_identity *kip) FREE(ip, M_KAUTH); } + /* - * Given a lookup result, add any associations that we don't - * currently have. + * kauth_identity_updatecache + * + * Description: Given a lookup result, add any associations that we don't + * currently have. + * + * Parameters: elp External lookup result from + * user space daemon to kernel + * rkip pointer to returned kauth + * identity, or NULL + * + * Returns: (void) + * + * Implicit returns: + * *rkip Modified (if non-NULL) */ static void kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_identity *rkip) @@ -630,7 +935,7 @@ kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_id if (rkip != NULL) *rkip = *kip; KAUTH_DEBUG("CACHE - learned %d is " K_UUID_FMT, kip->ki_uid, K_UUID_ARG(kip->ki_guid)); - kauth_identity_register(kip); + kauth_identity_register_and_free(kip); } } } @@ -670,20 +975,30 @@ kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_id if (rkip != NULL) *rkip = *kip; KAUTH_DEBUG("CACHE - learned %d is " K_UUID_FMT, kip->ki_uid, K_UUID_ARG(kip->ki_guid)); - kauth_identity_register(kip); + kauth_identity_register_and_free(kip); } } } } + /* - * Promote the entry to the head of the LRU, assumes the cache is locked. + * kauth_identity_lru + * + * Description: Promote the entry to the head of the LRU, assumes the cache + * is locked. * - * This is called even if the entry has expired; typically an expired entry - * that's been looked up is about to be revalidated, and having it closer to - * the head of the LRU means finding it quickly again when the revalidation - * comes through. + * Parameters: kip kauth identity to move to the + * head of the LRU list, if it's + * not already there + * + * Returns: (void) + * + * Notes: This is called even if the entry has expired; typically an + * expired entry that's been looked up is about to be revalidated, + * and having it closer to the head of the LRU means finding it + * quickly again when the revalidation comes through. */ static void kauth_identity_lru(struct kauth_identity *kip) @@ -694,8 +1009,17 @@ kauth_identity_lru(struct kauth_identity *kip) } } + /* - * Handly lazy expiration of translations. + * kauth_identity_guid_expired + * + * Description: Handle lazy expiration of GUID translations. + * + * Parameters: kip kauth identity to check for + * GUID expiration + * + * Returns: 1 Expired + * 0 Not expired */ static int kauth_identity_guid_expired(struct kauth_identity *kip) @@ -707,6 +1031,18 @@ kauth_identity_guid_expired(struct kauth_identity *kip) return((kip->ki_guid_expiry <= tv.tv_sec) ? 1 : 0); } + +/* + * kauth_identity_ntsid_expired + * + * Description: Handle lazy expiration of NTSID translations. + * + * Parameters: kip kauth identity to check for + * NTSID expiration + * + * Returns: 1 Expired + * 0 Not expired + */ static int kauth_identity_ntsid_expired(struct kauth_identity *kip) { @@ -717,9 +1053,20 @@ kauth_identity_ntsid_expired(struct kauth_identity *kip) return((kip->ki_ntsid_expiry <= tv.tv_sec) ? 1 : 0); } + /* - * Search for an entry by UID. Returns a copy of the entry, ENOENT if no valid - * association exists for the UID. + * kauth_identity_find_uid + * + * Description: Search for an entry by UID + * + * Parameters: uid UID to find + * kir Pointer to return area + * + * Returns: 0 Found + * ENOENT Not found + * + * Implicit returns: + * *klr Modified, if found */ static int kauth_identity_find_uid(uid_t uid, struct kauth_identity *kir) @@ -730,6 +1077,7 @@ kauth_identity_find_uid(uid_t uid, struct kauth_identity *kir) TAILQ_FOREACH(kip, &kauth_identities, ki_link) { if ((kip->ki_valid & KI_VALID_UID) && (uid == kip->ki_uid)) { kauth_identity_lru(kip); + /* Copy via structure assignment */ *kir = *kip; break; } @@ -740,8 +1088,18 @@ kauth_identity_find_uid(uid_t uid, struct kauth_identity *kir) /* - * Search for an entry by GID. Returns a copy of the entry, ENOENT if no valid - * association exists for the GID. + * kauth_identity_find_uid + * + * Description: Search for an entry by GID + * + * Parameters: gid GID to find + * kir Pointer to return area + * + * Returns: 0 Found + * ENOENT Not found + * + * Implicit returns: + * *klr Modified, if found */ static int kauth_identity_find_gid(uid_t gid, struct kauth_identity *kir) @@ -752,6 +1110,7 @@ kauth_identity_find_gid(uid_t gid, struct kauth_identity *kir) TAILQ_FOREACH(kip, &kauth_identities, ki_link) { if ((kip->ki_valid & KI_VALID_GID) && (gid == kip->ki_gid)) { kauth_identity_lru(kip); + /* Copy via structure assignment */ *kir = *kip; break; } @@ -762,9 +1121,21 @@ kauth_identity_find_gid(uid_t gid, struct kauth_identity *kir) /* - * Search for an entry by GUID. Returns a copy of the entry, ENOENT if no valid - * association exists for the GUID. Note that the association may be expired, - * in which case the caller may elect to call out to userland to revalidate. + * kauth_identity_find_guid + * + * Description: Search for an entry by GUID + * + * Parameters: guidp Pointer to GUID to find + * kir Pointer to return area + * + * Returns: 0 Found + * ENOENT Not found + * + * Implicit returns: + * *klr Modified, if found + * + * Note: The association may be expired, in which case the caller + * may elect to call out to userland to revalidate. */ static int kauth_identity_find_guid(guid_t *guidp, struct kauth_identity *kir) @@ -775,6 +1146,7 @@ kauth_identity_find_guid(guid_t *guidp, struct kauth_identity *kir) TAILQ_FOREACH(kip, &kauth_identities, ki_link) { if ((kip->ki_valid & KI_VALID_GUID) && (kauth_guid_equal(guidp, &kip->ki_guid))) { kauth_identity_lru(kip); + /* Copy via structure assignment */ *kir = *kip; break; } @@ -783,10 +1155,23 @@ kauth_identity_find_guid(guid_t *guidp, struct kauth_identity *kir) return((kip == NULL) ? ENOENT : 0); } + /* - * Search for an entry by NT Security ID. Returns a copy of the entry, ENOENT if no valid - * association exists for the SID. Note that the association may be expired, - * in which case the caller may elect to call out to userland to revalidate. + * kauth_identity_find_ntsid + * + * Description: Search for an entry by NTSID + * + * Parameters: ntsid Pointer to NTSID to find + * kir Pointer to return area + * + * Returns: 0 Found + * ENOENT Not found + * + * Implicit returns: + * *klr Modified, if found + * + * Note: The association may be expired, in which case the caller + * may elect to call out to userland to revalidate. */ static int kauth_identity_find_ntsid(ntsid_t *ntsid, struct kauth_identity *kir) @@ -797,6 +1182,7 @@ kauth_identity_find_ntsid(ntsid_t *ntsid, struct kauth_identity *kir) TAILQ_FOREACH(kip, &kauth_identities, ki_link) { if ((kip->ki_valid & KI_VALID_NTSID) && (kauth_ntsid_equal(ntsid, &kip->ki_ntsid))) { kauth_identity_lru(kip); + /* Copy via structure assignment */ *kir = *kip; break; } @@ -805,19 +1191,43 @@ kauth_identity_find_ntsid(ntsid_t *ntsid, struct kauth_identity *kir) return((kip == NULL) ? ENOENT : 0); } + /* * GUID handling. */ guid_t kauth_null_guid; + +/* + * kauth_guid_equal + * + * Description: Determine the equality of two GUIDs + * + * Parameters: guid1 Pointer to first GUID + * guid2 Pointer to second GUID + * + * Returns: 0 If GUIDs are inequal + * !0 If GUIDs are equal + */ int kauth_guid_equal(guid_t *guid1, guid_t *guid2) { - return(!bcmp(guid1, guid2, sizeof(*guid1))); + return(bcmp(guid1, guid2, sizeof(*guid1)) == 0); } + /* - * Look for well-known GUIDs. + * kauth_wellknown_guid + * + * Description: Determine if a GUID is a well-known GUID + * + * Parameters: guid Pointer to GUID to check + * + * Returns: KAUTH_WKG_NOT Not a wel known GUID + * KAUTH_WKG_EVERYBODY "Everybody" + * KAUTH_WKG_NOBODY "Nobody" + * KAUTH_WKG_OWNER "Other" + * KAUTH_WKG_GROUP "Group" */ int kauth_wellknown_guid(guid_t *guid) @@ -827,11 +1237,11 @@ kauth_wellknown_guid(guid_t *guid) /* * All WKGs begin with the same 12 bytes. */ - if (!bcmp((void *)guid, fingerprint, 12)) { + if (bcmp((void *)guid, fingerprint, 12) == 0) { /* - * The final 4 bytes are our code. + * The final 4 bytes are our code (in network byte order). */ - code = *(u_int32_t *)&guid->g_guid[12]; + code = OSSwapHostToBigInt32(*(u_int32_t *)&guid->g_guid[12]); switch(code) { case 0x0000000c: return(KAUTH_WKG_EVERYBODY); @@ -848,7 +1258,15 @@ kauth_wellknown_guid(guid_t *guid) /* - * NT Security Identifier handling. + * kauth_ntsid_equal + * + * Description: Determine the equality of two NTSIDs (NT Security Identifiers) + * + * Paramters: sid1 Pointer to first NTSID + * sid2 Pointer to second NTSID + * + * Returns: 0 If GUIDs are inequal + * !0 If GUIDs are equal */ int kauth_ntsid_equal(ntsid_t *sid1, ntsid_t *sid2) @@ -856,11 +1274,12 @@ kauth_ntsid_equal(ntsid_t *sid1, ntsid_t *sid2) /* check sizes for equality, also sanity-check size while we're at it */ if ((KAUTH_NTSID_SIZE(sid1) == KAUTH_NTSID_SIZE(sid2)) && (KAUTH_NTSID_SIZE(sid1) <= sizeof(*sid1)) && - !bcmp(sid1, sid2, KAUTH_NTSID_SIZE(sid1))) + bcmp(sid1, sid2, KAUTH_NTSID_SIZE(sid1)) == 0) return(1); return(0); } + /* * Identity KPI * @@ -876,8 +1295,96 @@ kauth_ntsid_equal(ntsid_t *sid1, ntsid_t *sid2) static int kauth_cred_cache_lookup(int from, int to, void *src, void *dst); + +/* + * kauth_cred_change_egid + * + * Description: Set EGID by changing the first element of cr_groups for the + * passed credential; if the new EGID exists in the list of + * groups already, then rotate the old EGID into its position, + * otherwise replace it + * + * Parameters: cred Pointer to the credential to modify + * new_egid The new EGID to set + * + * Returns: 0 The egid did not displace a member of + * the supplementary group list + * 1 The egid being set displaced a member + * of the supplementary groups list + * + * Note: Utility function; internal use only because of locking. + * + * This function operates on the credential passed; the caller + * must operate either on a newly allocated credential (one for + * which there is no hash cache reference and no externally + * visible pointer reference), or a template credential. + */ +static int +kauth_cred_change_egid(kauth_cred_t cred, gid_t new_egid) +{ + int i; + int displaced = 1; +#if radar_4600026 + int is_member; +#endif /* radar_4600026 */ + gid_t old_egid = cred->cr_groups[0]; + + /* Ignoring the first entry, scan for a match for the new egid */ + for (i = 1; i < cred->cr_ngroups; i++) { + /* + * If we find a match, swap them so we don't lose overall + * group information + */ + if (cred->cr_groups[i] == new_egid) { + cred->cr_groups[i] = old_egid; + DEBUG_CRED_CHANGE("kauth_cred_change_egid: unset displaced\n"); + displaced = 0; + break; + } + } + +#if radar_4600026 +#error Fix radar 4600026 first!!! + +/* +This is correct for memberd behaviour, but incorrect for POSIX; to address +this, we would need to automatically opt-out any SUID/SGID binary, and force +it to use initgroups to opt back in. We take the approach of considering it +opt'ed out in any group of 16 displacement instead, since it's a much more +conservative approach (i.e. less likely to cause things to break). +*/ + + /* + * If we displaced a member of the supplementary groups list of the + * credential, and we have not opted out of memberd, then if memberd + * says that the credential is a member of the group, then it has not + * actually been displaced. + * + * NB: This is typically a cold code path. + */ + if (displaced && !(cred->cr_flags & CRF_NOMEMBERD) && + kauth_cred_ismember_gid(cred, new_egid, &is_member) == 0 && + is_member) { + displaced = 0; + DEBUG_CRED_CHANGE("kauth_cred_change_egid: reset displaced\n"); + } +#endif /* radar_4600026 */ + + /* set the new EGID into the old spot */ + cred->cr_groups[0] = new_egid; + + return (displaced); +} + + /* - * Fetch UID from credential. + * kauth_cred_getuid + * + * Description: Fetch UID from credential + * + * Parameters: cred Credential to examine + * + * Returns: (uid_t) UID associated with credential */ uid_t kauth_cred_getuid(kauth_cred_t cred) @@ -886,8 +1393,15 @@ kauth_cred_getuid(kauth_cred_t cred) return(cred->cr_uid); } + /* - * Fetch GID from credential. + * kauth_cred_getgid + * + * Description: Fetch GID from credential + * + * Parameters: cred Credential to examine + * + * Returns: (gid_t) GID associated with credential */ uid_t kauth_cred_getgid(kauth_cred_t cred) @@ -896,8 +1410,20 @@ kauth_cred_getgid(kauth_cred_t cred) return(cred->cr_gid); } + /* - * Fetch UID from GUID. + * kauth_cred_guid2uid + * + * Description: Fetch UID from GUID + * + * Parameters: guidp Pointer to GUID to examine + * uidp Pointer to buffer for UID + * + * Returns: 0 Success + * kauth_cred_cache_lookup:EINVAL + * + * Implicit returns: + * *uidp Modified, if successful */ int kauth_cred_guid2uid(guid_t *guidp, uid_t *uidp) @@ -905,8 +1431,20 @@ kauth_cred_guid2uid(guid_t *guidp, uid_t *uidp) return(kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_UID, guidp, uidp)); } + /* - * Fetch GID from GUID. + * kauth_cred_guid2gid + * + * Description: Fetch GID from GUID + * + * Parameters: guidp Pointer to GUID to examine + * gidp Pointer to buffer for GID + * + * Returns: 0 Success + * kauth_cred_cache_lookup:EINVAL + * + * Implicit returns: + * *gidp Modified, if successful */ int kauth_cred_guid2gid(guid_t *guidp, gid_t *gidp) @@ -914,8 +1452,20 @@ kauth_cred_guid2gid(guid_t *guidp, gid_t *gidp) return(kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_GID, guidp, gidp)); } + /* - * Fetch UID from NT SID. + * kauth_cred_ntsid2uid + * + * Description: Fetch UID from NTSID + * + * Parameters: sidp Pointer to NTSID to examine + * uidp Pointer to buffer for UID + * + * Returns: 0 Success + * kauth_cred_cache_lookup:EINVAL + * + * Implicit returns: + * *uidp Modified, if successful */ int kauth_cred_ntsid2uid(ntsid_t *sidp, uid_t *uidp) @@ -923,8 +1473,20 @@ kauth_cred_ntsid2uid(ntsid_t *sidp, uid_t *uidp) return(kauth_cred_cache_lookup(KI_VALID_NTSID, KI_VALID_UID, sidp, uidp)); } + /* - * Fetch GID from NT SID. + * kauth_cred_ntsid2gid + * + * Description: Fetch GID from NTSID + * + * Parameters: sidp Pointer to NTSID to examine + * gidp Pointer to buffer for GID + * + * Returns: 0 Success + * kauth_cred_cache_lookup:EINVAL + * + * Implicit returns: + * *gidp Modified, if successful */ int kauth_cred_ntsid2gid(ntsid_t *sidp, gid_t *gidp) @@ -932,8 +1494,20 @@ kauth_cred_ntsid2gid(ntsid_t *sidp, gid_t *gidp) return(kauth_cred_cache_lookup(KI_VALID_NTSID, KI_VALID_GID, sidp, gidp)); } + /* - * Fetch GUID from NT SID. + * kauth_cred_ntsid2guid + * + * Description: Fetch GUID from NTSID + * + * Parameters: sidp Pointer to NTSID to examine + * guidp Pointer to buffer for GUID + * + * Returns: 0 Success + * kauth_cred_cache_lookup:EINVAL + * + * Implicit returns: + * *guidp Modified, if successful */ int kauth_cred_ntsid2guid(ntsid_t *sidp, guid_t *guidp) @@ -941,8 +1515,20 @@ kauth_cred_ntsid2guid(ntsid_t *sidp, guid_t *guidp) return(kauth_cred_cache_lookup(KI_VALID_NTSID, KI_VALID_GUID, sidp, guidp)); } + /* - * Fetch GUID from UID. + * kauth_cred_uid2guid + * + * Description: Fetch GUID from UID + * + * Parameters: uid UID to examine + * guidp Pointer to buffer for GUID + * + * Returns: 0 Success + * kauth_cred_cache_lookup:EINVAL + * + * Implicit returns: + * *guidp Modified, if successful */ int kauth_cred_uid2guid(uid_t uid, guid_t *guidp) @@ -950,8 +1536,20 @@ kauth_cred_uid2guid(uid_t uid, guid_t *guidp) return(kauth_cred_cache_lookup(KI_VALID_UID, KI_VALID_GUID, &uid, guidp)); } + /* - * Fetch user GUID from credential. + * kauth_cred_getguid + * + * Description: Fetch GUID from credential + * + * Parameters: cred Credential to examine + * guidp Pointer to buffer for GUID + * + * Returns: 0 Success + * kauth_cred_cache_lookup:EINVAL + * + * Implicit returns: + * *guidp Modified, if successful */ int kauth_cred_getguid(kauth_cred_t cred, guid_t *guidp) @@ -960,17 +1558,41 @@ kauth_cred_getguid(kauth_cred_t cred, guid_t *guidp) return(kauth_cred_uid2guid(kauth_cred_getuid(cred), guidp)); } + /* - * Fetch GUID from GID. - */ -int + * kauth_cred_getguid + * + * Description: Fetch GUID from GID + * + * Parameters: gid GID to examine + * guidp Pointer to buffer for GUID + * + * Returns: 0 Success + * kauth_cred_cache_lookup:EINVAL + * + * Implicit returns: + * *guidp Modified, if successful + */ +int kauth_cred_gid2guid(gid_t gid, guid_t *guidp) { return(kauth_cred_cache_lookup(KI_VALID_GID, KI_VALID_GUID, &gid, guidp)); } + /* - * Fetch NT SID from UID. + * kauth_cred_uid2ntsid + * + * Description: Fetch NTSID from UID + * + * Parameters: uid UID to examine + * sidp Pointer to buffer for NTSID + * + * Returns: 0 Success + * kauth_cred_cache_lookup:EINVAL + * + * Implicit returns: + * *sidp Modified, if successful */ int kauth_cred_uid2ntsid(uid_t uid, ntsid_t *sidp) @@ -978,8 +1600,20 @@ kauth_cred_uid2ntsid(uid_t uid, ntsid_t *sidp) return(kauth_cred_cache_lookup(KI_VALID_UID, KI_VALID_NTSID, &uid, sidp)); } + /* - * Fetch NT SID from credential. + * kauth_cred_getntsid + * + * Description: Fetch NTSID from credential + * + * Parameters: cred Credential to examine + * sidp Pointer to buffer for NTSID + * + * Returns: 0 Success + * kauth_cred_cache_lookup:EINVAL + * + * Implicit returns: + * *sidp Modified, if successful */ int kauth_cred_getntsid(kauth_cred_t cred, ntsid_t *sidp) @@ -988,8 +1622,20 @@ kauth_cred_getntsid(kauth_cred_t cred, ntsid_t *sidp) return(kauth_cred_uid2ntsid(kauth_cred_getuid(cred), sidp)); } + /* - * Fetch NT SID from GID. + * kauth_cred_gid2ntsid + * + * Description: Fetch NTSID from GID + * + * Parameters: gid GID to examine + * sidp Pointer to buffer for NTSID + * + * Returns: 0 Success + * kauth_cred_cache_lookup:EINVAL + * + * Implicit returns: + * *sidp Modified, if successful */ int kauth_cred_gid2ntsid(gid_t gid, ntsid_t *sidp) @@ -997,8 +1643,20 @@ kauth_cred_gid2ntsid(gid_t gid, ntsid_t *sidp) return(kauth_cred_cache_lookup(KI_VALID_GID, KI_VALID_NTSID, &gid, sidp)); } + /* - * Fetch NT SID from GUID. + * kauth_cred_guid2ntsid + * + * Description: Fetch NTSID from GUID + * + * Parameters: guidp Pointer to GUID to examine + * sidp Pointer to buffer for NTSID + * + * Returns: 0 Success + * kauth_cred_cache_lookup:EINVAL + * + * Implicit returns: + * *sidp Modified, if successful */ int kauth_cred_guid2ntsid(guid_t *guidp, ntsid_t *sidp) @@ -1007,9 +1665,22 @@ kauth_cred_guid2ntsid(guid_t *guidp, ntsid_t *sidp) } - /* - * Lookup a translation in the cache. + * kauth_cred_cache_lookup + * + * Description: Lookup a translation in the cache; if one is not found, and + * the attempt was not fatal, submit the request to the resolver + * instead, and wait for it to complete or be aborted. + * + * Parameters: from Identity information we have + * to Identity information we want + * src Pointer to buffer containing + * the source identity + * dst Pointer to buffer to receive + * the target identity + * + * Returns: 0 Success + * EINVAL Unknown source identity type */ static int kauth_cred_cache_lookup(int from, int to, void *src, void *dst) @@ -1046,6 +1717,7 @@ kauth_cred_cache_lookup(int from, int to, void *src, void *dst) if (error != 0) { /* any other error is fatal */ if (error != ENOENT) { + /* XXX bogus check - this is not possible */ KAUTH_DEBUG("CACHE - cache search error %d", error); return(error); } @@ -1086,16 +1758,19 @@ kauth_cred_cache_lookup(int from, int to, void *src, void *dst) goto found; } /* - * We leave ki_valid set here; it contains a translation but the TTL has - * expired. If we can't get a result from the resolver, we will - * use it as a better-than nothing alternative. + * We leave ki_valid set here; it contains a + * translation but the TTL has expired. If we can't + * get a result from the resolver, we will use it as + * a better-than nothing alternative. */ KAUTH_DEBUG("CACHE - expired entry found"); } } /* - * Call the resolver. We ask for as much data as we can get. + * We failed to find a cache entry; call the resolver. + * + * Note: We ask for as much data as we can get. */ switch(from) { case KI_VALID_UID: @@ -1132,13 +1807,13 @@ kauth_cred_cache_lookup(int from, int to, void *src, void *dst) KAUTH_EXTLOOKUP_WANT_UGUID | KAUTH_EXTLOOKUP_WANT_GGUID | KAUTH_EXTLOOKUP_WANT_USID | KAUTH_EXTLOOKUP_WANT_GSID; KAUTH_DEBUG("CACHE - calling resolver for %x", el.el_flags); - error = kauth_identity_resolve(&el); + error = kauth_resolver_submit(&el); KAUTH_DEBUG("CACHE - resolver returned %d", error); /* was the lookup successful? */ if (error == 0) { /* - * Save the results from the lookup - may have other information even if we didn't - * get a guid. + * Save the results from the lookup - may have other + * information even if we didn't get a guid. */ kauth_identity_updatecache(&el, &ki); } @@ -1198,6 +1873,22 @@ static int kauth_groups_expired(struct kauth_group_membership *gm); static void kauth_groups_lru(struct kauth_group_membership *gm); static void kauth_groups_updatecache(struct kauth_identity_extlookup *el); + +/* + * kauth_groups_init + * + * Description: Initialize the groups cache + * + * Parameters: (void) + * + * Returns: (void) + * + * Notes: Intialize the groups cache for use; the group cache is used + * to avoid unnecessary calls out to user space. + * + * This function is called from kauth_init() in the file + * kern_authorization.c. + */ void kauth_groups_init(void) { @@ -1205,6 +1896,18 @@ kauth_groups_init(void) kauth_groups_mtx = lck_mtx_alloc_init(kauth_lck_grp, 0/*LCK_ATTR_NULL*/); } + +/* + * kauth_groups_expired + * + * Description: Handle lazy expiration of group membership cache entries + * + * Parameters: gm group membership entry to + * check for expiration + * + * Returns: 1 Expired + * 0 Not expired + */ static int kauth_groups_expired(struct kauth_group_membership *gm) { @@ -1214,6 +1917,24 @@ kauth_groups_expired(struct kauth_group_membership *gm) return((gm->gm_expiry <= tv.tv_sec) ? 1 : 0); } + +/* + * kauth_groups_lru + * + * Description: Promote the entry to the head of the LRU, assumes the cache + * is locked. + * + * Parameters: kip group membership entry to move + * to the head of the LRU list, + * if it's not already there + * + * Returns: (void) + * + * Notes: This is called even if the entry has expired; typically an + * expired entry that's been looked up is about to be revalidated, + * and having it closer to the head of the LRU means finding it + * quickly again when the revalidation comes through. + */ static void kauth_groups_lru(struct kauth_group_membership *gm) { @@ -1223,6 +1944,20 @@ kauth_groups_lru(struct kauth_group_membership *gm) } } + +/* + * kauth_groups_updatecache + * + * Description: Given a lookup result, add any group cache associations that + * we don't currently have. + * + * Parameters: elp External lookup result from + * user space daemon to kernel + * rkip pointer to returned kauth + * identity, or NULL + * + * Returns: (void) + */ static void kauth_groups_updatecache(struct kauth_identity_extlookup *el) { @@ -1237,7 +1972,10 @@ kauth_groups_updatecache(struct kauth_identity_extlookup *el) microuptime(&tv); - /* search for an existing record for this association before inserting */ + /* + * Search for an existing record for this association before inserting + * a new one; if we find one, update it instead of creating a new one + */ KAUTH_GROUPS_LOCK(); TAILQ_FOREACH(gm, &kauth_groups, gm_link) { if ((el->el_uid == gm->gm_uid) && @@ -1272,10 +2010,10 @@ kauth_groups_updatecache(struct kauth_identity_extlookup *el) } /* - * Insert the new entry. Note that it's possible to race ourselves here - * and end up with duplicate entries in the list. Wasteful, but harmless - * since the first into the list will never be looked up, and thus will - * eventually just fall off the end. + * Insert the new entry. Note that it's possible to race ourselves + * here and end up with duplicate entries in the list. Wasteful, but + * harmless since the first into the list will never be looked up, + * and thus will eventually just fall off the end. */ KAUTH_GROUPS_LOCK(); TAILQ_INSERT_HEAD(&kauth_groups, gm, gm_link); @@ -1293,11 +2031,39 @@ kauth_groups_updatecache(struct kauth_identity_extlookup *el) FREE(gm, M_KAUTH); } + /* * Group membership KPI */ + /* - * This function guarantees not to modify resultp when returning an error. + * kauth_cred_ismember_gid + * + * Description: Given a credential and a GID, determine if the GID is a member + * of one of the supplementary groups associated with the given + * credential + * + * Parameters: cred Credential to check in + * gid GID to check for membership + * resultp Pointer to int to contain the + * result of the call + * + * Returns: 0 Success + * ENOENT Could not proform lookup + * kauth_resolver_submit:EWOULDBLOCK + * kauth_resolver_submit:EINTR + * kauth_resolver_submit:ENOMEM + * kauth_resolver_submit:??? Unlikely error from user space + * + * Implicit returns: + * *resultp (modified) 1 Is member + * 0 Is not member + * + * Notes: This function guarantees not to modify resultp when returning + * an error. + * + * This function effectively checkes the EGID as well, since the + * EGID is cr_groups[0] as an implementation detail. */ int kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) @@ -1330,8 +2096,8 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) /* - * If the resolver hasn't checked in yet, we are early in the boot phase and - * the local group list is complete and authoritative. + * If the resolver hasn't checked in yet, we are early in the boot + * phase and the local group list is complete and authoritative. */ if (!kauth_resolver_registered) { *resultp = 0; @@ -1367,7 +2133,8 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) el.el_flags = KAUTH_EXTLOOKUP_VALID_UID | KAUTH_EXTLOOKUP_VALID_GID | KAUTH_EXTLOOKUP_WANT_MEMBERSHIP; el.el_uid = cred->cr_gmuid; el.el_gid = gid; - error = kauth_identity_resolve(&el); + el.el_member_valid = 0; /* XXX set by resolver? */ + error = kauth_resolver_submit(&el); if (error != 0) return(error); /* save the results from the lookup */ @@ -1382,9 +2149,30 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) return(ENOENT); } + /* - * Determine whether the supplied credential is a member of the - * group nominated by GUID. + * kauth_cred_ismember_guid + * + * Description: Determine whether the supplied credential is a member of the + * group nominated by GUID. + * + * Parameters: cred Credential to check in + * guidp Pointer to GUID whose group + * we are testing for membership + * resultp Pointer to int to contain the + * result of the call + * + * Returns: 0 Success + * kauth_cred_guid2gid:EINVAL + * kauth_cred_ismember_gid:ENOENT + * kauth_cred_ismember_gid:EWOULDBLOCK + * kauth_cred_ismember_gid:EINTR + * kauth_cred_ismember_gid:ENOMEM + * kauth_cred_ismember_gid:??? Unlikely error from user space + * + * Implicit returns: + * *resultp (modified) 1 Is member + * 0 Is not member */ int kauth_cred_ismember_guid(kauth_cred_t cred, guid_t *guidp, int *resultp) @@ -1420,7 +2208,86 @@ kauth_cred_ismember_guid(kauth_cred_t cred, guid_t *guidp, int *resultp) } /* - * Fast replacement for issuser() + * kauth_cred_gid_subset + * + * Description: Given two credentials, determine if all GIDs associated with + * the first are also associated with the second + * + * Parameters: cred1 Credential to check for + * cred2 Credential to check in + * resultp Pointer to int to contain the + * result of the call + * + * Returns: 0 Success + * non-zero See kauth_cred_ismember_gid for + * error codes + * + * Implicit returns: + * *resultp (modified) 1 Is subset + * 0 Is not subset + * + * Notes: This function guarantees not to modify resultp when returning + * an error. + */ +int +kauth_cred_gid_subset(kauth_cred_t cred1, kauth_cred_t cred2, int *resultp) +{ + int i, err, res = 1; + gid_t gid; + + /* First, check the local list of groups */ + for (i = 0; i < cred1->cr_ngroups; i++) { + gid = cred1->cr_groups[i]; + if ((err = kauth_cred_ismember_gid(cred2, gid, &res)) != 0) { + return err; + } + + if (!res && gid != cred2->cr_rgid && gid != cred2->cr_svgid) { + *resultp = 0; + return 0; + } + } + + /* Check real gid */ + if ((err = kauth_cred_ismember_gid(cred2, cred1->cr_rgid, &res)) != 0) { + return err; + } + + if (!res && cred1->cr_rgid != cred2->cr_rgid && + cred1->cr_rgid != cred2->cr_svgid) { + *resultp = 0; + return 0; + } + + /* Finally, check saved gid */ + if ((err = kauth_cred_ismember_gid(cred2, cred1->cr_svgid, &res)) != 0){ + return err; + } + + if (!res && cred1->cr_svgid != cred2->cr_rgid && + cred1->cr_svgid != cred2->cr_svgid) { + *resultp = 0; + return 0; + } + + *resultp = 1; + return 0; +} + + +/* + * kauth_cred_issuser + * + * Description: Fast replacement for issuser() + * + * Parameters: cred Credential to check for super + * user privileges + * + * Returns: 0 Not super user + * !0 Is super user + * + * Notes: This function uses a magic number which is not a manifest + * constant; this is bad practice. */ int kauth_cred_issuser(kauth_cred_t cred) @@ -1428,6 +2295,7 @@ kauth_cred_issuser(kauth_cred_t cred) return(cred->cr_uid == 0); } + /* * Credential KPI */ @@ -1436,7 +2304,44 @@ kauth_cred_issuser(kauth_cred_t cred) static lck_mtx_t *kauth_cred_hash_mtx; #define KAUTH_CRED_HASH_LOCK() lck_mtx_lock(kauth_cred_hash_mtx); #define KAUTH_CRED_HASH_UNLOCK() lck_mtx_unlock(kauth_cred_hash_mtx); +#if KAUTH_CRED_HASH_DEBUG +#define KAUTH_CRED_HASH_LOCK_ASSERT() _mutex_assert(kauth_cred_hash_mtx, MA_OWNED) +#else /* !KAUTH_CRED_HASH_DEBUG */ +#define KAUTH_CRED_HASH_LOCK_ASSERT() +#endif /* !KAUTH_CRED_HASH_DEBUG */ + +/* + * kauth_cred_init + * + * Description: Initialize the credential hash cache + * + * Parameters: (void) + * + * Returns: (void) + * + * Notes: Intialize the credential hash cache for use; the credential + * hash cache is used convert duplicate credentials into a + * single reference counted credential in order to save wired + * kernel memory. In practice, this generally means a desktop + * system runs with a few tens of credentials, instead of one + * per process, one per thread, one per vnode cache entry, and + * so on. This generally results in savings of 200K or more + * (potentially much more on server systems). + * + * The hash cache internally has a reference on the credential + * for itself as a means of avoiding a reclaim race for a + * credential in the process of having it's last non-hash + * reference released. This would otherwise result in the + * possibility of a freed credential that was still in uses due + * a race. This use is protected by the KAUTH_CRED_HASH_LOCK. + * + * On final release, the hash reference is droped, and the + * credential is freed back to the system. + * + * This function is called from kauth_init() in the file + * kern_authorization.c. + */ void kauth_cred_init(void) { @@ -1449,13 +2354,23 @@ kauth_cred_init(void) MALLOC(kauth_cred_table_anchor, struct kauth_cred_entry_head *, (sizeof(struct kauth_cred_entry_head) * kauth_cred_table_size), M_KAUTH, M_WAITOK | M_ZERO); + if (kauth_cred_table_anchor == NULL) + panic("startup: kauth_cred_init"); for (i = 0; i < kauth_cred_table_size; i++) { TAILQ_INIT(&kauth_cred_table_anchor[i]); } } + /* - * Return the current thread's effective UID. + * kauth_getuid + * + * Description: Get the current thread's effective UID. + * + * Parameters: (void) + * + * Returns: (uid_t) The effective UID of the + * current thread */ uid_t kauth_getuid(void) @@ -1463,8 +2378,16 @@ kauth_getuid(void) return(kauth_cred_get()->cr_uid); } + /* - * Return the current thread's real UID. + * kauth_getruid + * + * Description: Get the current thread's real UID. + * + * Parameters: (void) + * + * Returns: (uid_t) The real UID of the current + * thread */ uid_t kauth_getruid(void) @@ -1472,8 +2395,16 @@ kauth_getruid(void) return(kauth_cred_get()->cr_ruid); } + /* - * Return the current thread's effective GID. + * kauth_getgid + * + * Description: Get the current thread's effective GID. + * + * Parameters: (void) + * + * Returns: (gid_t) The effective GID of the + * current thread */ gid_t kauth_getgid(void) @@ -1481,8 +2412,16 @@ kauth_getgid(void) return(kauth_cred_get()->cr_groups[0]); } + /* - * Return the current thread's real GID. + * kauth_getgid + * + * Description: Get the current thread's real GID. + * + * Parameters: (void) + * + * Returns: (gid_t) The real GID of the current + * thread */ gid_t kauth_getrgid(void) @@ -1490,10 +2429,28 @@ kauth_getrgid(void) return(kauth_cred_get()->cr_rgid); } + /* - * Returns a pointer to the current thread's credential, does not take a - * reference (so the caller must not do anything that would let the thread's - * credential change while using the returned value). + * kauth_cred_get + * + * Description: Returns a pointer to the current thread's credential + * + * Parameters: (void) + * + * Returns: (kauth_cred_t) Pointer to the current thread's + * credential + * + * Notes: This function does not take a reference; because of this, the + * caller MUST NOT do anything that would let the thread's + * credential change while using the returned value, without + * first explicitly taking their own reference. + * + * If a caller intends to take a reference on the resulting + * credential pointer from calling this function, it is strongly + * recommended that the caller use kauth_cred_get_with_ref() + * instead, to protect against any future changes to the cred + * locking protocols; such changes could otherwise potentially + * introduce race windows in the callers code. */ kauth_cred_t kauth_cred_get(void) @@ -1506,22 +2463,74 @@ kauth_cred_get(void) if (uthread == NULL) panic("thread wants credential but has no BSD thread info"); /* - * We can lazy-bind credentials to threads, as long as their processes have them. - * If we later inline this function, the code in this block should probably be - * called out in a function. + * We can lazy-bind credentials to threads, as long as their processes + * have them. + * + * XXX If we later inline this function, the code in this block + * XXX should probably be called out in a function. */ if (uthread->uu_ucred == NOCRED) { if ((p = (proc_t) get_bsdtask_info(get_threadtask(current_thread()))) == NULL) panic("thread wants credential but has no BSD process"); - proc_lock(p); - kauth_cred_ref(uthread->uu_ucred = p->p_ucred); - proc_unlock(p); + uthread->uu_ucred = kauth_cred_proc_ref(p); } return(uthread->uu_ucred); } + +/* + * kauth_cred_uthread_update + * + * Description: Given a uthread, a proc, and whether or not the proc is locked, + * late-bind the uthread cred to the proc cred. + * + * Parameters: uthread_t The uthread to update + * proc_t The process to update to + * + * Returns: (void) + * + * Notes: This code is common code called from system call or trap entry + * in the case that the process thread may have been changed + * since the last time the thread entered the kernel. It is + * generally only called with the current uthread and process as + * parameters. + */ +void +kauth_cred_uthread_update(uthread_t uthread, proc_t proc) +{ + if (uthread->uu_ucred != proc->p_ucred && + (uthread->uu_flag & UT_SETUID) == 0) { + kauth_cred_t old = uthread->uu_ucred; + uthread->uu_ucred = kauth_cred_proc_ref(proc); + if (IS_VALID_CRED(old)) + kauth_cred_unref(&old); + } +} + + /* - * Returns a pointer to the current thread's credential, takes a reference. + * kauth_cred_get_with_ref + * + * Description: Takes a reference on the current thread's credential, and then + * returns a pointer to it to the caller. + * + * Parameters: (void) + * + * Returns: (kauth_cred_t) Pointer to the current thread's + * newly referenced credential + * + * Notes: This function takes a reference on the credential before + * returning it to the caller. + * + * It is the responsibility of the calling code to release this + * reference when the credential is no longer in use. + * + * Since the returned reference may be a persistent reference + * (e.g. one cached in another data structure with a lifetime + * longer than the calling function), this release may be delayed + * until such time as the persistent reference is to be destroyed. + * An example of this would be the per vnode credential cache used + * to accelerate lookup operations. */ kauth_cred_t kauth_cred_get_with_ref(void) @@ -1537,23 +2546,50 @@ kauth_cred_get_with_ref(void) panic("%s - thread wants credential but has no BSD process", __FUNCTION__); /* - * We can lazy-bind credentials to threads, as long as their processes have them. - * If we later inline this function, the code in this block should probably be - * called out in a function. + * We can lazy-bind credentials to threads, as long as their processes + * have them. + * + * XXX If we later inline this function, the code in this block + * XXX should probably be called out in a function. */ - proc_lock(procp); if (uthread->uu_ucred == NOCRED) { /* take reference for new cred in thread */ - kauth_cred_ref(uthread->uu_ucred = proc_ucred(procp)); + uthread->uu_ucred = kauth_cred_proc_ref(procp); } /* take a reference for our caller */ kauth_cred_ref(uthread->uu_ucred); - proc_unlock(procp); return(uthread->uu_ucred); } + /* - * Returns a pointer to the given process's credential, takes a reference. + * kauth_cred_proc_ref + * + * Description: Takes a reference on the current process's credential, and + * then returns a pointer to it to the caller. + * + * Parameters: procp Process whose credential we + * intend to take a reference on + * + * Returns: (kauth_cred_t) Pointer to the process's + * newly referenced credential + * + * Locks: PROC_LOCK is held before taking the reference and released + * after the refeence is taken to protect the p_ucred field of + * the process referred to by procp. + * + * Notes: This function takes a reference on the credential before + * returning it to the caller. + * + * It is the responsibility of the calling code to release this + * reference when the credential is no longer in use. + * + * Since the returned reference may be a persistent reference + * (e.g. one cached in another data structure with a lifetime + * longer than the calling function), this release may be delayed + * until such time as the persistent reference is to be destroyed. + * An example of this would be the per vnode credential cache used + * to accelerate lookup operations. */ kauth_cred_t kauth_cred_proc_ref(proc_t procp) @@ -1567,16 +2603,55 @@ kauth_cred_proc_ref(proc_t procp) return(cred); } + /* - * Allocates a new credential. + * kauth_cred_alloc + * + * Description: Allocate a new credential + * + * Parameters: (void) + * + * Returns: !NULL Newly allocated credential + * NULL Insufficient memory + * + * Notes: The newly allocated credential is zero'ed as part of the + * allocation process, with the exception of the reference + * count, which is set to 1 to indicate a single reference + * held by the caller. + * + * Since newly allocated credentials have no external pointers + * referencing them, prior to making them visible in an externally + * visible pointer (e.g. by adding them to the credential hash + * cache) is the only legal time in which an existing credential + * can be safely iinitialized or modified directly. + * + * After initialization, the caller is expected to call the + * function kauth_cred_add() to add the credential to the hash + * cache, after which time it's frozen and becomes publically + * visible. + * + * The release protocol depends on kauth_hash_add() being called + * before kauth_cred_rele() (there is a diagnostic panic which + * will trigger if this protocol is not observed). + * + * XXX: This function really ought to be static, rather than being + * exported as KPI, since a failure of kauth_cred_add() can only + * be handled by an explicit free of the credential; such frees + * depend on knowlegdge of the allocation method used, which is + * permitted to change between kernel revisions. + * + * XXX: In the insufficient resource case, this code panic's rather + * than returning a NULL pointer; the code that calls this + * function needs to be audited before this can be changed. */ kauth_cred_t kauth_cred_alloc(void) { kauth_cred_t newcred; - MALLOC(newcred, kauth_cred_t, sizeof(*newcred), M_KAUTH, M_WAITOK | M_ZERO); + MALLOC_ZONE(newcred, kauth_cred_t, sizeof(*newcred), M_CRED, M_WAITOK); if (newcred != 0) { + bzero(newcred, sizeof(*newcred)); newcred->cr_ref = 1; /* must do this, or cred has same group membership as uid 0 */ newcred->cr_gmuid = KAUTH_UID_NONE; @@ -1590,37 +2665,74 @@ kauth_cred_alloc(void) kauth_cred_count++; #endif +#if CONFIG_MACF + mac_cred_label_init(newcred); +#endif + return(newcred); } + /* - * Looks to see if we already have a known credential and if found bumps the - * reference count and returns it. If there are no credentials that match - * the given credential then we allocate a new credential. + * kauth_cred_create + * + * Description: Look to see if we already have a known credential in the hash + * cache; if one is found, bump the reference count and return + * it. If there are no credentials that match the given + * credential, then allocate a new credential. * - * Note that the gmuid is hard-defaulted to the UID specified. Since we maintain - * this field, we can't expect callers to know how it needs to be set. Callers - * should be prepared for this field to be overwritten. + * Parameters: cred Template for credential to + * be created + * + * Returns: (kauth_cred_t) The credential that was found + * in the hash or created + * NULL kauth_cred_add() failed, or + * there was not an egid specified + * + * Notes: The gmuid is hard-defaulted to the UID specified. Since we + * maintain this field, we can't expect callers to know how it + * needs to be set. Callers should be prepared for this field + * to be overwritten. + * + * XXX: This code will tight-loop if memory for a new credential is + * persistently unavailable; this is perhaps not the wisest way + * to handle this condition, but current callers do not expect + * a failure. */ kauth_cred_t kauth_cred_create(kauth_cred_t cred) { kauth_cred_t found_cred, new_cred = NULL; - cred->cr_gmuid = cred->cr_uid; + KAUTH_CRED_HASH_LOCK_ASSERT(); + + if (cred->cr_flags & CRF_NOMEMBERD) + cred->cr_gmuid = KAUTH_UID_NONE; + else + cred->cr_gmuid = cred->cr_uid; + + /* Caller *must* specify at least the egid in cr_groups[0] */ + if (cred->cr_ngroups < 1) + return(NULL); for (;;) { KAUTH_CRED_HASH_LOCK(); found_cred = kauth_cred_find(cred); if (found_cred != NULL) { - /* found an existing credential so we'll bump reference count and return */ + /* + * Found an existing credential so we'll bump + * reference count and return + */ kauth_cred_ref(found_cred); KAUTH_CRED_HASH_UNLOCK(); return(found_cred); } KAUTH_CRED_HASH_UNLOCK(); - /* no existing credential found. create one and add it to our hash table */ + /* + * No existing credential found. Create one and add it to + * our hash table. + */ new_cred = kauth_cred_alloc(); if (new_cred != NULL) { int err; @@ -1632,14 +2744,19 @@ kauth_cred_create(kauth_cred_t cred) new_cred->cr_gmuid = cred->cr_gmuid; new_cred->cr_ngroups = cred->cr_ngroups; bcopy(&cred->cr_groups[0], &new_cred->cr_groups[0], sizeof(new_cred->cr_groups)); + new_cred->cr_flags = cred->cr_flags; + KAUTH_CRED_HASH_LOCK(); err = kauth_cred_add(new_cred); KAUTH_CRED_HASH_UNLOCK(); - /* retry if kauth_cred_add returns non zero value */ + /* Retry if kauth_cred_add returns non zero value */ if (err == 0) break; - FREE(new_cred, M_KAUTH); +#if CONFIG_MACF + mac_cred_label_destroy(new_cred); +#endif + FREE_ZONE(new_cred, sizeof(*new_cred), M_CRED); new_cred = NULL; } } @@ -1647,145 +2764,217 @@ kauth_cred_create(kauth_cred_t cred) return(new_cred); } + /* - * Update the given credential using the uid argument. The given uid is used - * set the effective user ID, real user ID, and saved user ID. We only - * allocate a new credential when the given uid actually results in changes to - * the existing credential. + * kauth_cred_setresuid + * + * Description: Update the given credential using the UID arguments. The given + * UIDs are used to set the effective UID, real UID, saved UID, + * and GMUID (used for group membership checking). + * + * Parameters: cred The original credential + * ruid The new real UID + * euid The new effective UID + * svuid The new saved UID + * gmuid KAUTH_UID_NONE -or- the new + * group membership UID + * + * Returns: (kauth_cred_t) The updated credential + * + * Note: gmuid is different in that a KAUTH_UID_NONE is a valid + * setting, so if you don't want it to change, pass it the + * previous value, explicitly. + * + * IMPORTANT: This function is implemented via kauth_cred_update(), which, + * if it returns a credential other than the one it is passed, + * will have dropped the reference on the passed credential. All + * callers should be aware of this, and treat this function as an + * unref + ref, potentially on different credentials. + * + * Because of this, the caller is expected to take its own + * reference on the credential passed as the first parameter, + * and be prepared to release the reference on the credential + * that is returned to them, if it is not intended to be a + * persistent reference. */ kauth_cred_t -kauth_cred_setuid(kauth_cred_t cred, uid_t uid) +kauth_cred_setresuid(kauth_cred_t cred, uid_t ruid, uid_t euid, uid_t svuid, uid_t gmuid) { struct ucred temp_cred; NULLCRED_CHECK(cred); - /* don't need to do anything if the effective, real and saved user IDs are - * already the same as the user ID passed in + /* + * We don't need to do anything if the UIDs we are changing are + * already the same as the UIDs passed in */ - if (cred->cr_uid == uid && cred->cr_ruid == uid && cred->cr_svuid == uid) { + if ((euid == KAUTH_UID_NONE || cred->cr_uid == euid) && + (ruid == KAUTH_UID_NONE || cred->cr_ruid == ruid) && + (svuid == KAUTH_UID_NONE || cred->cr_svuid == svuid) && + (cred->cr_gmuid == gmuid)) { /* no change needed */ return(cred); } - /* look up in cred hash table to see if we have a matching credential - * with new values. + /* + * Look up in cred hash table to see if we have a matching credential + * with the new values; this is done by calling kauth_cred_update(). */ bcopy(cred, &temp_cred, sizeof(temp_cred)); - temp_cred.cr_uid = uid; - temp_cred.cr_ruid = uid; - temp_cred.cr_svuid = uid; - temp_cred.cr_gmuid = uid; + if (euid != KAUTH_UID_NONE) { + temp_cred.cr_uid = euid; + } + if (ruid != KAUTH_UID_NONE) { + temp_cred.cr_ruid = ruid; + } + if (svuid != KAUTH_UID_NONE) { + temp_cred.cr_svuid = svuid; + } + + /* + * If we are setting the gmuid to KAUTH_UID_NONE, then we want to + * opt out of participation in external group resolution, unless we + * unless we explicitly opt back in later. + */ + if ((temp_cred.cr_gmuid = gmuid) == KAUTH_UID_NONE) { + temp_cred.cr_flags |= CRF_NOMEMBERD; + } return(kauth_cred_update(cred, &temp_cred, TRUE)); } + /* - * Update the given credential using the euid argument. The given uid is used - * set the effective user ID. We only allocate a new credential when the given - * uid actually results in changes to the existing credential. + * kauth_cred_setresgid + * + * Description: Update the given credential using the GID arguments. The given + * GIDs are used to set the effective GID, real GID, and saved + * GID. + * + * Parameters: cred The original credential + * rgid The new real GID + * egid The new effective GID + * svgid The new saved GID + * + * Returns: (kauth_cred_t) The updated credential + * + * IMPORTANT: This function is implemented via kauth_cred_update(), which, + * if it returns a credential other than the one it is passed, + * will have dropped the reference on the passed credential. All + * callers should be aware of this, and treat this function as an + * unref + ref, potentially on different credentials. + * + * Because of this, the caller is expected to take its own + * reference on the credential passed as the first parameter, + * and be prepared to release the reference on the credential + * that is returned to them, if it is not intended to be a + * persistent reference. */ kauth_cred_t -kauth_cred_seteuid(kauth_cred_t cred, uid_t euid) +kauth_cred_setresgid(kauth_cred_t cred, gid_t rgid, gid_t egid, gid_t svgid) { - struct ucred temp_cred; + struct ucred temp_cred; NULLCRED_CHECK(cred); + DEBUG_CRED_ENTER("kauth_cred_setresgid %p %d %d %d\n", cred, rgid, egid, svgid); - /* don't need to do anything if the given effective user ID is already the - * same as the effective user ID in the credential. + /* + * We don't need to do anything if the given GID are already the + * same as the GIDs in the credential. */ - if (cred->cr_uid == euid) { + if (cred->cr_groups[0] == egid && + cred->cr_rgid == rgid && + cred->cr_svgid == svgid) { /* no change needed */ return(cred); } - /* look up in cred hash table to see if we have a matching credential - * with new values. + /* + * Look up in cred hash table to see if we have a matching credential + * with the new values; this is done by calling kauth_cred_update(). */ bcopy(cred, &temp_cred, sizeof(temp_cred)); - temp_cred.cr_uid = euid; + if (egid != KAUTH_GID_NONE) { + /* displacing a supplementary group opts us out of memberd */ + if (kauth_cred_change_egid(&temp_cred, egid)) { + DEBUG_CRED_CHANGE("displaced!\n"); + temp_cred.cr_flags |= CRF_NOMEMBERD; + temp_cred.cr_gmuid = KAUTH_UID_NONE; + } else { + DEBUG_CRED_CHANGE("not displaced\n"); + } + } + if (rgid != KAUTH_GID_NONE) { + temp_cred.cr_rgid = rgid; + } + if (svgid != KAUTH_GID_NONE) { + temp_cred.cr_svgid = svgid; + } return(kauth_cred_update(cred, &temp_cred, TRUE)); } + +/* + * Update the given credential with the given groups. We only allocate a new + * credential when the given gid actually results in changes to the existing + * credential. + * The gmuid argument supplies a new uid (or KAUTH_UID_NONE to opt out) + * which will be used for group membership checking. + */ /* - * Update the given credential using the gid argument. The given gid is used - * set the effective group ID, real group ID, and saved group ID. We only - * allocate a new credential when the given gid actually results in changes to - * the existing credential. + * kauth_cred_setgroups + * + * Description: Update the given credential using the provide supplementary + * group list and group membership UID + * + * Parameters: cred The original credential + * groups Pointer to gid_t array which + * contains the new group list + * groupcount The cound of valid groups which + * are contained in 'groups' + * gmuid KAUTH_UID_NONE -or- the new + * group membership UID + * + * Returns: (kauth_cred_t) The updated credential + * + * Note: gmuid is different in that a KAUTH_UID_NONE is a valid + * setting, so if you don't want it to change, pass it the + * previous value, explicitly. + * + * IMPORTANT: This function is implemented via kauth_cred_update(), which, + * if it returns a credential other than the one it is passed, + * will have dropped the reference on the passed credential. All + * callers should be aware of this, and treat this function as an + * unref + ref, potentially on different credentials. + * + * Because of this, the caller is expected to take its own + * reference on the credential passed as the first parameter, + * and be prepared to release the reference on the credential + * that is returned to them, if it is not intended to be a + * persistent reference. + * + * XXX: Changes are determined in ordinal order - if the caller pasess + * in the same groups list that is already present in the + * credential, but the members are in a different order, even if + * the EGID is not modified (i.e. cr_groups[0] is the same), it + * is considered a modification to the credential, and a new + * credential is created. + * + * This should perhaps be better optimized, but it is considered + * to be the caller's problem. */ kauth_cred_t -kauth_cred_setgid(kauth_cred_t cred, gid_t gid) +kauth_cred_setgroups(kauth_cred_t cred, gid_t *groups, int groupcount, uid_t gmuid) { - struct ucred temp_cred; + int i; + struct ucred temp_cred; NULLCRED_CHECK(cred); - /* don't need to do anything if the given group ID is already the - * same as the group ID in the credential. - */ - if (cred->cr_groups[0] == gid && cred->cr_rgid == gid && cred->cr_svgid == gid) { - /* no change needed */ - return(cred); - } - - /* look up in cred hash table to see if we have a matching credential - * with new values. - */ - bcopy(cred, &temp_cred, sizeof(temp_cred)); - temp_cred.cr_groups[0] = gid; - temp_cred.cr_rgid = gid; - temp_cred.cr_svgid = gid; - - return(kauth_cred_update(cred, &temp_cred, TRUE)); -} - -/* - * Update the given credential using the egid argument. The given gid is used - * set the effective user ID. We only allocate a new credential when the given - * gid actually results in changes to the existing credential. - */ -kauth_cred_t -kauth_cred_setegid(kauth_cred_t cred, gid_t egid) -{ - struct ucred temp_cred; - - NULLCRED_CHECK(cred); - - /* don't need to do anything if the given group ID is already the - * same as the group Id in the credential. - */ - if (cred->cr_groups[0] == egid) { - /* no change needed */ - return(cred); - } - - /* look up in cred hash table to see if we have a matching credential - * with new values. - */ - bcopy(cred, &temp_cred, sizeof(temp_cred)); - temp_cred.cr_groups[0] = egid; - - return(kauth_cred_update(cred, &temp_cred, TRUE)); -} - -/* - * Update the given credential with the given groups. We only allocate a new - * credential when the given gid actually results in changes to the existing - * credential. - * The gmuid argument supplies a new uid (or KAUTH_UID_NONE to opt out) - * which will be used for group membership checking. - */ -kauth_cred_t -kauth_cred_setgroups(kauth_cred_t cred, gid_t *groups, int groupcount, uid_t gmuid) -{ - int i; - struct ucred temp_cred; - - NULLCRED_CHECK(cred); - - /* don't need to do anything if the given list of groups does not change. + /* + * We don't need to do anything if the given list of groups does not + * change. */ if ((cred->cr_gmuid == gmuid) && (cred->cr_ngroups == groupcount)) { for (i = 0; i < groupcount; i++) { @@ -1798,24 +2987,60 @@ kauth_cred_setgroups(kauth_cred_t cred, gid_t *groups, int groupcount, uid_t gmu } } - /* look up in cred hash table to see if we have a matching credential - * with new values. + /* + * Look up in cred hash table to see if we have a matching credential + * with new values. If we are setting or clearing the gmuid, then + * update the cr_flags, since clearing it is sticky. This permits an + * opt-out of memberd processing using setgroups(), and an opt-in + * using initgroups(). This is required for POSIX conformance. */ bcopy(cred, &temp_cred, sizeof(temp_cred)); temp_cred.cr_ngroups = groupcount; bcopy(groups, temp_cred.cr_groups, sizeof(temp_cred.cr_groups)); temp_cred.cr_gmuid = gmuid; + if (gmuid == KAUTH_UID_NONE) + temp_cred.cr_flags |= CRF_NOMEMBERD; + else + temp_cred.cr_flags &= ~CRF_NOMEMBERD; return(kauth_cred_update(cred, &temp_cred, TRUE)); } + /* - * Update the given credential using the uid and gid arguments. The given uid - * is used set the effective user ID, real user ID, and saved user ID. - * The given gid is used set the effective group ID, real group ID, and saved - * group ID. - * We only allocate a new credential when the given uid and gid actually results - * in changes to the existing credential. + * kauth_cred_setuidgid + * + * Description: Update the given credential using the UID and GID arguments. + * The given UID is used to set the effective UID, real UID, and + * saved UID. The given GID is used to set the effective GID, + * real GID, and saved GID. + * + * Parameters: cred The original credential + * uid The new UID to use + * gid The new GID to use + * + * Returns: (kauth_cred_t) The updated credential + * + * Notes: We set the gmuid to uid if the credential we are inheriting + * from has not opted out of memberd participation; otherwise + * we set it to KAUTH_UID_NONE + * + * This code is only ever called from the per-thread credential + * code path in the "set per thread credential" case; and in + * posix_spawn() in the case that the POSIX_SPAWN_RESETIDS + * flag is set. + * + * IMPORTANT: This function is implemented via kauth_cred_update(), which, + * if it returns a credential other than the one it is passed, + * will have dropped the reference on the passed credential. All + * callers should be aware of this, and treat this function as an + * unref + ref, potentially on different credentials. + * + * Because of this, the caller is expected to take its own + * reference on the credential passed as the first parameter, + * and be prepared to release the reference on the credential + * that is returned to them, if it is not intended to be a + * persistent reference. */ kauth_cred_t kauth_cred_setuidgid(kauth_cred_t cred, uid_t uid, gid_t gid) @@ -1824,8 +3049,9 @@ kauth_cred_setuidgid(kauth_cred_t cred, uid_t uid, gid_t gid) NULLCRED_CHECK(cred); - /* don't need to do anything if the effective, real and saved user IDs are - * already the same as the user ID passed in + /* + * We don't need to do anything if the effective, real and saved + * user IDs are already the same as the user ID passed into us. */ if (cred->cr_uid == uid && cred->cr_ruid == uid && cred->cr_svuid == uid && cred->cr_groups[0] == gid && cred->cr_rgid == gid && cred->cr_svgid == gid) { @@ -1833,28 +3059,61 @@ kauth_cred_setuidgid(kauth_cred_t cred, uid_t uid, gid_t gid) return(cred); } - /* look up in cred hash table to see if we have a matching credential - * with new values. + /* + * Look up in cred hash table to see if we have a matching credential + * with the new values. */ bzero(&temp_cred, sizeof(temp_cred)); temp_cred.cr_uid = uid; temp_cred.cr_ruid = uid; temp_cred.cr_svuid = uid; - temp_cred.cr_gmuid = uid; + /* inherit the opt-out of memberd */ + if (cred->cr_flags & CRF_NOMEMBERD) { + temp_cred.cr_gmuid = KAUTH_UID_NONE; + temp_cred.cr_flags |= CRF_NOMEMBERD; + } else { + temp_cred.cr_gmuid = uid; + temp_cred.cr_flags &= ~CRF_NOMEMBERD; + } temp_cred.cr_ngroups = 1; - temp_cred.cr_groups[0] = gid; + /* displacing a supplementary group opts us out of memberd */ + if (kauth_cred_change_egid(&temp_cred, gid)) { + temp_cred.cr_gmuid = KAUTH_UID_NONE; + temp_cred.cr_flags |= CRF_NOMEMBERD; + } temp_cred.cr_rgid = gid; temp_cred.cr_svgid = gid; +#if CONFIG_MACF + temp_cred.cr_label = cred->cr_label; +#endif return(kauth_cred_update(cred, &temp_cred, TRUE)); } + /* - * Update the given credential using the uid and gid arguments. The given uid - * is used to set the saved user ID. The given gid is used to set the - * saved group ID. - * We only allocate a new credential when the given uid and gid actually results - * in changes to the existing credential. + * kauth_cred_setsvuidgid + * + * Description: Function used by execve to set the saved uid and gid values + * for suid/sgid programs + * + * Parameters: cred The credential to update + * uid The saved uid to set + * gid The saved gid to set + * + * Returns: (kauth_cred_t) The updated credential + * + * IMPORTANT: This function is implemented via kauth_cred_update(), which, + * if it returns a credential other than the one it is passed, + * will have dropped the reference on the passed credential. All + * callers should be aware of this, and treat this function as an + * unref + ref, potentially on different credentials. + * + * Because of this, the caller is expected to take its own + * reference on the credential passed as the first parameter, + * and be prepared to release the reference on the credential + * that is returned to them, if it is not intended to be a + * persistent reference. */ kauth_cred_t kauth_cred_setsvuidgid(kauth_cred_t cred, uid_t uid, gid_t gid) @@ -1862,14 +3121,18 @@ kauth_cred_setsvuidgid(kauth_cred_t cred, uid_t uid, gid_t gid) struct ucred temp_cred; NULLCRED_CHECK(cred); + DEBUG_CRED_ENTER("kauth_cred_setsvuidgid: %p u%d->%d g%d->%d\n", cred, cred->cr_svuid, uid, cred->cr_svgid, gid); - /* don't need to do anything if the effective, real and saved user IDs are - * already the same as the user ID passed in + /* + * We don't need to do anything if the effective, real and saved + * uids are already the same as the uid provided. This check is + * likely insufficient. */ if (cred->cr_svuid == uid && cred->cr_svgid == gid) { /* no change needed */ return(cred); } + DEBUG_CRED_CHANGE("kauth_cred_setsvuidgid: cred change\n"); /* look up in cred hash table to see if we have a matching credential * with new values. @@ -1881,10 +3144,28 @@ kauth_cred_setsvuidgid(kauth_cred_t cred, uid_t uid, gid_t gid) return(kauth_cred_update(cred, &temp_cred, TRUE)); } + /* - * Update the given credential using the given auditinfo_t. - * We only allocate a new credential when the given auditinfo_t actually results - * in changes to the existing credential. + * kauth_cred_setauditinfo + * + * Description: Update the given credential using the given auditinfo_t. + * + * Parameters: cred The original credential + * auditinfo_p Pointer to ne audit information + * + * Returns: (kauth_cred_t) The updated credential + * + * IMPORTANT: This function is implemented via kauth_cred_update(), which, + * if it returns a credential other than the one it is passed, + * will have dropped the reference on the passed credential. All + * callers should be aware of this, and treat this function as an + * unref + ref, potentially on different credentials. + * + * Because of this, the caller is expected to take its own + * reference on the credential passed as the first parameter, + * and be prepared to release the reference on the credential + * that is returned to them, if it is not intended to be a + * persistent reference. */ kauth_cred_t kauth_cred_setauditinfo(kauth_cred_t cred, auditinfo_t *auditinfo_p) @@ -1893,25 +3174,314 @@ kauth_cred_setauditinfo(kauth_cred_t cred, auditinfo_t *auditinfo_p) NULLCRED_CHECK(cred); - /* don't need to do anything if the audit info is already the same as the - * audit info in the credential passed in + /* + * We don't need to do anything if the audit info is already the + * same as the audit info in the credential provided. */ if (bcmp(&cred->cr_au, auditinfo_p, sizeof(cred->cr_au)) == 0) { /* no change needed */ return(cred); } - /* look up in cred hash table to see if we have a matching credential - * with new values. - */ bcopy(cred, &temp_cred, sizeof(temp_cred)); bcopy(auditinfo_p, &temp_cred.cr_au, sizeof(temp_cred.cr_au)); return(kauth_cred_update(cred, &temp_cred, FALSE)); } +#if CONFIG_MACF /* - * Add a reference to the passed credential. + * kauth_cred_label_update + * + * Description: Update the MAC label associated with a credential + * + * Parameters: cred The original credential + * label The MAC label to set + * + * Returns: (kauth_cred_t) The updated credential + * + * IMPORTANT: This function is implemented via kauth_cred_update(), which, + * if it returns a credential other than the one it is passed, + * will have dropped the reference on the passed credential. All + * callers should be aware of this, and treat this function as an + * unref + ref, potentially on different credentials. + * + * Because of this, the caller is expected to take its own + * reference on the credential passed as the first parameter, + * and be prepared to release the reference on the credential + * that is returned to them, if it is not intended to be a + * persistent reference. + */ +kauth_cred_t +kauth_cred_label_update(kauth_cred_t cred, struct label *label) +{ + kauth_cred_t newcred; + struct ucred temp_cred; + + bcopy(cred, &temp_cred, sizeof(temp_cred)); + + mac_cred_label_init(&temp_cred); + mac_cred_label_associate(cred, &temp_cred); + mac_cred_label_update(&temp_cred, label); + + newcred = kauth_cred_update(cred, &temp_cred, TRUE); + mac_cred_label_destroy(&temp_cred); + return (newcred); +} + +/* + * kauth_cred_label_update_execve + * + * Description: Update the MAC label associated with a credential as + * part of exec + * + * Parameters: cred The original credential + * vp The exec vnode + * scriptl The script MAC label + * execl The executable MAC label + * + * Returns: (kauth_cred_t) The updated credential + * + * IMPORTANT: This function is implemented via kauth_cred_update(), which, + * if it returns a credential other than the one it is passed, + * will have dropped the reference on the passed credential. All + * callers should be aware of this, and treat this function as an + * unref + ref, potentially on different credentials. + * + * Because of this, the caller is expected to take its own + * reference on the credential passed as the first parameter, + * and be prepared to release the reference on the credential + * that is returned to them, if it is not intended to be a + * persistent reference. + */ +static +kauth_cred_t +kauth_cred_label_update_execve(kauth_cred_t cred, vfs_context_t ctx, + struct vnode *vp, struct label *scriptl, struct label *execl) +{ + kauth_cred_t newcred; + struct ucred temp_cred; + + bcopy(cred, &temp_cred, sizeof(temp_cred)); + + mac_cred_label_init(&temp_cred); + mac_cred_label_associate(cred, &temp_cred); + mac_cred_label_update_execve(ctx, &temp_cred, + vp, scriptl, execl); + + newcred = kauth_cred_update(cred, &temp_cred, TRUE); + mac_cred_label_destroy(&temp_cred); + return (newcred); +} + +/* + * kauth_proc_label_update + * + * Description: Update the label inside the credential associated with the process. + * + * Parameters: p The process to modify + * label The label to place in the process credential + * + * Notes: The credential associated with the process may change as a result + * of this call. The caller should not assume the process reference to + * the old credential still exists. + */ +int kauth_proc_label_update(struct proc *p, struct label *label) +{ + kauth_cred_t my_cred, my_new_cred; + + my_cred = kauth_cred_proc_ref(p); + + DEBUG_CRED_ENTER("kauth_proc_label_update: %p\n", my_cred); + + /* get current credential and take a reference while we muck with it */ + for (;;) { + + /* + * Set the credential with new info. If there is no change, + * we get back the same credential we passed in; if there is + * a change, we drop the reference on the credential we + * passed in. The subsequent compare is safe, because it is + * a pointer compare rather than a contents compare. + */ + my_new_cred = kauth_cred_label_update(my_cred, label); + if (my_cred != my_new_cred) { + + DEBUG_CRED_CHANGE("kauth_proc_setlabel_unlocked CH(%d): %p/0x%08x -> %p/0x%08x\n", p->p_pid, my_cred, my_cred->cr_flags, my_new_cred, my_new_cred->cr_flags); + + proc_lock(p); + /* + * We need to protect for 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_unlock(p); + kauth_cred_unref(&my_new_cred); + my_cred = kauth_cred_proc_ref(p); + /* try again */ + continue; + } + p->p_ucred = my_new_cred; + mac_proc_set_enforce(p, MAC_ALL_ENFORCE); + proc_unlock(p); + } + break; + } + /* Drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); + + return (0); +} + +/* + * kauth_proc_label_update_execve + * + * Description: Update the label inside the credential associated with the + * process as part of a transitioning execve. The label will + * be updated by the policies as part of this processing, not + * provided up front. + * + * Parameters: p The process to modify + * ctx The context of the exec + * vp The vnode being exec'ed + * scriptl The script MAC label + * execl The executable MAC label + * + * Notes: The credential associated with the process WILL change as a + * result of this call. The caller should not assume the process + * reference to the old credential still exists. + */ +int kauth_proc_label_update_execve(struct proc *p, vfs_context_t ctx, + struct vnode *vp, struct label *scriptl, struct label *execl) +{ + kauth_cred_t my_cred, my_new_cred; + + my_cred = kauth_cred_proc_ref(p); + + DEBUG_CRED_ENTER("kauth_proc_label_update_execve: %p\n", my_cred); + + /* get current credential and take a reference while we muck with it */ + for (;;) { + + /* + * Set the credential with new info. If there is no change, + * we get back the same credential we passed in; if there is + * a change, we drop the reference on the credential we + * passed in. The subsequent compare is safe, because it is + * a pointer compare rather than a contents compare. + */ + my_new_cred = kauth_cred_label_update_execve(my_cred, ctx, vp, scriptl, execl); + if (my_cred != my_new_cred) { + + DEBUG_CRED_CHANGE("kauth_proc_label_update_execve_unlocked CH(%d): %p/0x%08x -> %p/0x%08x\n", p->p_pid, my_cred, my_cred->cr_flags, my_new_cred, my_new_cred->cr_flags); + + proc_lock(p); + /* + * We need to protect for 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_unlock(p); + kauth_cred_unref(&my_new_cred); + my_cred = kauth_cred_proc_ref(p); + /* try again */ + continue; + } + p->p_ucred = my_new_cred; + mac_proc_set_enforce(p, MAC_ALL_ENFORCE); + proc_unlock(p); + } + break; + } + /* Drop old proc reference or our extra reference */ + kauth_cred_unref(&my_cred); + + return (0); +} + +#if 1 +/* + * for temporary binary compatibility + */ +kauth_cred_t kauth_cred_setlabel(kauth_cred_t cred, struct label *label); +kauth_cred_t +kauth_cred_setlabel(kauth_cred_t cred, struct label *label) +{ + return kauth_cred_label_update(cred, label); +} + +int kauth_proc_setlabel(struct proc *p, struct label *label); +int +kauth_proc_setlabel(struct proc *p, struct label *label) +{ + return kauth_proc_label_update(p, label); +} +#endif + +#else + +/* this is a temp hack to cover us when MACF is not built in a kernel configuration. + * Since we cannot build our export lists based on the kernel configuration we need + * to define a stub. + */ +kauth_cred_t +kauth_cred_label_update(__unused kauth_cred_t cred, __unused void *label) +{ + return(NULL); +} + +int +kauth_proc_label_update(__unused struct proc *p, __unused void *label) +{ + return (0); +} + +#if 1 +/* + * for temporary binary compatibility + */ +kauth_cred_t kauth_cred_setlabel(kauth_cred_t cred, void *label); +kauth_cred_t +kauth_cred_setlabel(__unused kauth_cred_t cred, __unused void *label) +{ + return NULL; +} + +int kauth_proc_setlabel(struct proc *p, void *label); +int +kauth_proc_setlabel(__unused struct proc *p, __unused void *label) +{ + return (0); +} +#endif +#endif + +/* + * kauth_cred_ref + * + * Description: Add a reference to the passed credential + * + * Parameters: cred The credential to reference + * + * Returns: (void) + * + * Notes: This function adds a reference to the provided credential; + * the existing reference on the credential is assumed to be + * held stable over this operation by taking the appropriate + * lock to protect the pointer from which it is being referenced, + * if necessary (e.g. the proc lock is held over the call if the + * credential being referenced is from p_ucred, the vnode lock + * if from the per vnode name cache cred cache, and so on). + * + * This is safe from the kauth_cred_unref() path, since an atomic + * add is used, and the unref path specifically checks to see that + * the value has not been changed to add a reference between the + * time the credential is unreferenced by another pointer and the + * time it is unreferenced from the cred hash cache. */ void kauth_cred_ref(kauth_cred_t cred) @@ -1920,47 +3490,181 @@ kauth_cred_ref(kauth_cred_t cred) NULLCRED_CHECK(cred); - old_value = OSAddAtomic(1, &cred->cr_ref); + // XXX SInt32 not safe for an LP64 kernel + old_value = OSAddAtomic(1, (SInt32 *)&cred->cr_ref); if (old_value < 1) panic("kauth_cred_ref: trying to take a reference on a cred with no references"); + +#if 0 // use this to watch a specific credential + if ( is_target_cred( cred ) != 0 ) { + get_backtrace( ); + } +#endif return; } + /* - * Drop a reference from the passed credential, potentially destroying it. + * kauth_cred_unref_hashlocked + * + * Description: release a credential reference; when the last reference is + * released, the credential will be freed. + * + * Parameters: credp Pointer to address containing + * credential to be freed + * + * Returns: (void) + * + * Implicit returns: + * *credp Set to NOCRED + * + * Notes: This function assumes the credential hash lock is held. + * + * This function is internal use only, since the hash lock is + * scoped to this compilation unit. + * + * This function destroys the contents of the pointer passed by + * the caller to prevent the caller accidently attempting to + * release a given reference twice in error. + * + * The last reference is considered to be released when a release + * of a credential of a reference count of 2 occurs; this is an + * intended effect, to take into accout the reference held by + * the credential hash, which is released at the same time. */ -void -kauth_cred_rele(kauth_cred_t cred) +static void +kauth_cred_unref_hashlocked(kauth_cred_t *credp) { int old_value; - NULLCRED_CHECK(cred); + KAUTH_CRED_HASH_LOCK_ASSERT(); + NULLCRED_CHECK(*credp); - KAUTH_CRED_HASH_LOCK(); - old_value = OSAddAtomic(-1, &cred->cr_ref); + // XXX SInt32 not safe for an LP64 kernel + old_value = OSAddAtomic(-1, (SInt32 *)&(*credp)->cr_ref); #if DIAGNOSTIC if (old_value == 0) - panic("kauth_cred_rele: dropping a reference on a cred with no references"); + panic("%s:0x%08x kauth_cred_unref_hashlocked: dropping a reference on a cred with no references", current_proc()->p_comm, *credp); + if (old_value == 1) + panic("%s:0x%08x kauth_cred_unref_hashlocked: dropping a reference on a cred with no hash entry", current_proc()->p_comm, *credp); #endif +#if 0 // use this to watch a specific credential + if ( is_target_cred( *credp ) != 0 ) { + get_backtrace( ); + } +#endif + + /* + * If the old_value is 2, then we have just released the last external + * reference to this credential + */ if (old_value < 3) { - /* the last reference is our credential hash table */ - kauth_cred_remove(cred); + /* The last absolute reference is our credential hash table */ + kauth_cred_remove(*credp); } + *credp = NOCRED; +} + + +/* + * kauth_cred_unref + * + * Description: Release a credential reference while holding the credential + * hash lock; when the last reference is released, the credential + * will be freed. + * + * Parameters: credp Pointer to address containing + * credential to be freed + * + * Returns: (void) + * + * Implicit returns: + * *credp Set to NOCRED + * + * Notes: See kauth_cred_unref_hashlocked() for more information. + * + */ +void +kauth_cred_unref(kauth_cred_t *credp) +{ + KAUTH_CRED_HASH_LOCK(); + kauth_cred_unref_hashlocked(credp); KAUTH_CRED_HASH_UNLOCK(); } + /* - * Duplicate a credential. - * NOTE - caller should call kauth_cred_add after any credential changes are made. + * kauth_cred_rele + * + * Description: release a credential reference; when the last reference is + * released, the credential will be freed + * + * Parameters: cred Credential to release + * + * Returns: (void) + * + * DEPRECATED: This interface is obsolete due to a failure to clear out the + * clear the pointer in the caller to avoid multiple releases of + * the same credential. The currently recommended interface is + * kauth_cred_unref(). + */ +void +kauth_cred_rele(kauth_cred_t cred) +{ + kauth_cred_unref(&cred); +} + + +/* + * kauth_cred_dup + * + * Description: Duplicate a credential via alloc and copy; the new credential + * has only it's own + * + * Parameters: cred The credential to duplicate + * + * Returns: (kauth_cred_t) The duplicate credential + * + * Notes: The typical value to calling this routine is if you are going + * to modify an existing credential, and expect to need a new one + * from the hash cache. + * + * This should probably not be used in the majority of cases; + * if you are using it instead of kauth_cred_create(), you are + * likely making a mistake. + * + * The newly allocated credential is copied as part of the + * allocation process, with the exception of the reference + * count, which is set to 1 to indicate a single reference + * held by the caller. + * + * Since newly allocated credentials have no external pointers + * referencing them, prior to making them visible in an externally + * visible pointer (e.g. by adding them to the credential hash + * cache) is the only legal time in which an existing credential + * can be safely iinitialized or modified directly. + * + * After initialization, the caller is expected to call the + * function kauth_cred_add() to add the credential to the hash + * cache, after which time it's frozen and becomes publically + * visible. + * + * The release protocol depends on kauth_hash_add() being called + * before kauth_cred_rele() (there is a diagnostic panic which + * will trigger if this protocol is not observed). + * */ kauth_cred_t kauth_cred_dup(kauth_cred_t cred) { kauth_cred_t newcred; +#if CONFIG_MACF + struct label *temp_label; +#endif #if CRED_DIAGNOSTIC if (cred == NOCRED || cred == FSCRED) @@ -1968,16 +3672,35 @@ kauth_cred_dup(kauth_cred_t cred) #endif newcred = kauth_cred_alloc(); if (newcred != NULL) { +#if CONFIG_MACF + temp_label = newcred->cr_label; +#endif bcopy(cred, newcred, sizeof(*newcred)); +#if CONFIG_MACF + newcred->cr_label = temp_label; + mac_cred_label_associate(cred, newcred); +#endif newcred->cr_ref = 1; } return(newcred); } /* - * Returns a credential based on the passed credential but which - * reflects the real rather than effective UID and GID. - * NOTE - we do NOT decrement cred reference count on passed in credential + * kauth_cred_copy_real + * + * Description: Returns a credential based on the passed credential but which + * reflects the real rather than effective UID and GID. + * + * Parameters: cred The credential from which to + * derive the new credential + * + * Returns: (kauth_cred_t) The copied credential + * + * IMPORTANT: This function DOES NOT utilize kauth_cred_update(); as a + * result, the caller is responsible for dropping BOTH the + * additional reference on the passed cred (if any), and the + * credential returned by this function. The drop should be + * via the satnadr kauth_cred_unref() KPI. */ kauth_cred_t kauth_cred_copy_real(kauth_cred_t cred) @@ -1992,13 +3715,21 @@ kauth_cred_copy_real(kauth_cred_t cred) return(cred); } - /* look up in cred hash table to see if we have a matching credential - * with new values. + /* + * Look up in cred hash table to see if we have a matching credential + * with the new values. */ bcopy(cred, &temp_cred, sizeof(temp_cred)); temp_cred.cr_uid = cred->cr_ruid; - temp_cred.cr_groups[0] = cred->cr_rgid; - /* if the cred is not opted out, make sure we are using the r/euid for group checks */ + /* displacing a supplementary group opts us out of memberd */ + if (kauth_cred_change_egid(&temp_cred, cred->cr_rgid)) { + temp_cred.cr_flags |= CRF_NOMEMBERD; + temp_cred.cr_gmuid = KAUTH_UID_NONE; + } + /* + * If the cred is not opted out, make sure we are using the r/euid + * for group checks + */ if (temp_cred.cr_gmuid != KAUTH_UID_NONE) temp_cred.cr_gmuid = cred->cr_ruid; @@ -2013,43 +3744,75 @@ kauth_cred_copy_real(kauth_cred_t cred) return(cred); } if (found_cred != NULL) { - /* found a match so we bump reference count on new one and decrement - * reference count on the old one. + /* + * Found a match so we bump reference count on new + * one. We leave the old one alone. */ kauth_cred_ref(found_cred); KAUTH_CRED_HASH_UNLOCK(); return(found_cred); } - /* must allocate a new credential, copy in old credential data and update - * with real user and group IDs. + /* + * Must allocate a new credential, copy in old credential + * data and update the real user and group IDs. */ newcred = kauth_cred_dup(&temp_cred); err = kauth_cred_add(newcred); KAUTH_CRED_HASH_UNLOCK(); - /* retry if kauth_cred_add returns non zero value */ + /* Retry if kauth_cred_add() fails */ if (err == 0) break; - FREE(newcred, M_KAUTH); +#if CONFIG_MACF + mac_cred_label_destroy(newcred); +#endif + FREE_ZONE(newcred, sizeof(*newcred), M_CRED); newcred = NULL; } return(newcred); } - + + /* - * common code to update a credential. model_cred is a temporary, non reference - * counted credential used only for comparison and modeling purposes. old_cred - * is a live reference counted credential that we intend to update using model_cred - * as our model. + * kauth_cred_update + * + * Description: Common code to update a credential + * + * Parameters: old_cred Reference counted credential + * to update + * model_cred Non-reference counted model + * credential to apply to the + * credential to be updated + * retain_auditinfo Flag as to whether or not the + * audit information should be + * copied from the old_cred into + * the model_cred + * + * Returns: (kauth_cred_t) The updated credential + * + * IMPORTANT: This function will potentially return a credential other than + * the one it is passed, and if so, it will have dropped the + * reference on the passed credential. All callers should be + * aware of this, and treat this function as an unref + ref, + * potentially on different credentials. + * + * Because of this, the caller is expected to take its own + * reference on the credential passed as the first parameter, + * and be prepared to release the reference on the credential + * that is returned to them, if it is not intended to be a + * persistent reference. */ -static kauth_cred_t kauth_cred_update(kauth_cred_t old_cred, kauth_cred_t model_cred, boolean_t retain_auditinfo) +static kauth_cred_t +kauth_cred_update(kauth_cred_t old_cred, kauth_cred_t model_cred, + boolean_t retain_auditinfo) { kauth_cred_t found_cred, new_cred = NULL; - /* make sure we carry the auditinfo forward to the new credential unless - * we are actually updating the auditinfo. + /* + * Make sure we carry the auditinfo forward to the new credential + * unless we are actually updating the auditinfo. */ if (retain_auditinfo) bcopy(&old_cred->cr_au, &model_cred->cr_au, sizeof(model_cred->cr_au)); @@ -2065,16 +3828,19 @@ static kauth_cred_t kauth_cred_update(kauth_cred_t old_cred, kauth_cred_t model_ return(old_cred); } if (found_cred != NULL) { - /* found a match so we bump reference count on new one and decrement - * reference count on the old one. + DEBUG_CRED_CHANGE("kauth_cred_update(cache hit): %p -> %p\n", old_cred, found_cred); + /* + * Found a match so we bump reference count on new + * one and decrement reference count on the old one. */ kauth_cred_ref(found_cred); + kauth_cred_unref_hashlocked(&old_cred); KAUTH_CRED_HASH_UNLOCK(); - kauth_cred_rele(old_cred); return(found_cred); } - /* must allocate a new credential using the model. also + /* + * Must allocate a new credential using the model. also * adds the new credential to the credential hash table. */ new_cred = kauth_cred_dup(model_cred); @@ -2084,23 +3850,44 @@ static kauth_cred_t kauth_cred_update(kauth_cred_t old_cred, kauth_cred_t model_ /* retry if kauth_cred_add returns non zero value */ if (err == 0) break; - FREE(new_cred, M_KAUTH); +#if CONFIG_MACF + mac_cred_label_destroy(new_cred); +#endif + FREE_ZONE(new_cred, sizeof(*new_cred), M_CRED); new_cred = NULL; } - kauth_cred_rele(old_cred); + DEBUG_CRED_CHANGE("kauth_cred_update(cache miss): %p -> %p\n", old_cred, new_cred); + kauth_cred_unref(&old_cred); return(new_cred); } -/* - * Add the given credential to our credential hash table and take an additional - * reference to account for our use of the credential in the hash table. - * NOTE - expects caller to hold KAUTH_CRED_HASH_LOCK! + +/* + * kauth_cred_add + * + * Description: Add the given credential to our credential hash table and + * take an additional reference to account for our use of the + * credential in the hash table + * + * Parameters: new_cred Credential to insert into cred + * hash cache + * + * Returns: 0 Success + * -1 Hash insertion failed: caller + * should retry + * + * Locks: Caller is expected to hold KAUTH_CRED_HASH_LOCK + * + * Notes: The 'new_cred' MUST NOT already be in the cred hash cache */ -static int kauth_cred_add(kauth_cred_t new_cred) +static int +kauth_cred_add(kauth_cred_t new_cred) { u_long hash_key; - + + KAUTH_CRED_HASH_LOCK_ASSERT(); + hash_key = kauth_cred_get_hashkey(new_cred); hash_key %= kauth_cred_table_size; @@ -2122,11 +3909,26 @@ static int kauth_cred_add(kauth_cred_t new_cred) return(0); } + /* - * Remove the given credential from our credential hash table. - * NOTE - expects caller to hold KAUTH_CRED_HASH_LOCK! + * kauth_cred_remove + * + * Description: Remove the given credential from our credential hash table + * + * Parameters: cred Credential to remove from cred + * hash cache + * + * Returns: (void) + * + * Locks: Caller is expected to hold KAUTH_CRED_HASH_LOCK + * + * Notes: The check for the reference increment after entry is generally + * agree to be safe, since we use atomic operations, and the + * following code occurs with the hash lock held; in theory, this + * protects us from the 2->1 reference that gets us here. */ -static void kauth_cred_remove(kauth_cred_t cred) +static void +kauth_cred_remove(kauth_cred_t cred) { u_long hash_key; kauth_cred_t found_cred; @@ -2134,18 +3936,22 @@ static void kauth_cred_remove(kauth_cred_t cred) hash_key = kauth_cred_get_hashkey(cred); hash_key %= kauth_cred_table_size; - /* avoid race */ + /* Avoid race */ if (cred->cr_ref < 1) panic("cred reference underflow"); if (cred->cr_ref > 1) return; /* someone else got a ref */ - /* find cred in the credential hash table */ + /* Find cred in the credential hash table */ TAILQ_FOREACH(found_cred, &kauth_cred_table_anchor[hash_key], cr_link) { if (found_cred == cred) { /* found a match, remove it from the hash table */ TAILQ_REMOVE(&kauth_cred_table_anchor[hash_key], found_cred, cr_link); - FREE(cred, M_KAUTH); +#if CONFIG_MACF + mac_cred_label_destroy(cred); +#endif + cred->cr_ref = 0; + FREE_ZONE(cred, sizeof(*cred), M_CRED); #if KAUTH_CRED_HASH_DEBUG kauth_cred_count--; #endif @@ -2153,21 +3959,35 @@ static void kauth_cred_remove(kauth_cred_t cred) } } - /* did not find a match. this should not happen! */ - printf("%s - %d - %s - did not find a match \n", __FILE__, __LINE__, __FUNCTION__); + /* Did not find a match... this should not happen! XXX Make panic? */ + printf("%s:%d - %s - %s - did not find a match for %p\n", __FILE__, __LINE__, __FUNCTION__, current_proc()->p_comm, cred); return; } + /* - * Using the given credential data, look for a match in our credential hash - * table. - * NOTE - expects caller to hold KAUTH_CRED_HASH_LOCK! + * kauth_cred_find + * + * Description: Using the given credential data, look for a match in our + * credential hash table + * + * Parameters: cred Credential to lookup in cred + * hash cache + * + * Returns: NULL Not found + * !NULL Matching cedential already in + * cred hash cache + * + * Locks: Caller is expected to hold KAUTH_CRED_HASH_LOCK */ -kauth_cred_t kauth_cred_find(kauth_cred_t cred) +kauth_cred_t +kauth_cred_find(kauth_cred_t cred) { u_long hash_key; kauth_cred_t found_cred; - + + KAUTH_CRED_HASH_LOCK_ASSERT(); + #if KAUTH_CRED_HASH_DEBUG static int test_count = 0; @@ -2180,34 +4000,52 @@ kauth_cred_t kauth_cred_find(kauth_cred_t cred) hash_key = kauth_cred_get_hashkey(cred); hash_key %= kauth_cred_table_size; - /* find cred in the credential hash table */ + /* Find cred in the credential hash table */ TAILQ_FOREACH(found_cred, &kauth_cred_table_anchor[hash_key], cr_link) { - if (bcmp(&found_cred->cr_uid, &cred->cr_uid, (sizeof(struct ucred) - offsetof(struct ucred, cr_uid))) == 0) { + boolean_t match; + + /* + * don't worry about the label unless the flags in + * either credential tell us to. + */ + if ((found_cred->cr_flags & CRF_MAC_ENFORCE) != 0 || + (cred->cr_flags & CRF_MAC_ENFORCE) != 0) { + /* include the label pointer in the compare */ + match = (bcmp(&found_cred->cr_uid, &cred->cr_uid, + (sizeof(struct ucred) - + offsetof(struct ucred, cr_uid))) == 0); + } else { + /* flags have to match, but skip the label in bcmp */ + match = (found_cred->cr_flags == cred->cr_flags && + bcmp(&found_cred->cr_uid, &cred->cr_uid, + (offsetof(struct ucred, cr_label) - + offsetof(struct ucred, cr_uid))) == 0); + } + if (match) { /* found a match */ return(found_cred); } } - /* no match found */ + /* No match found */ + return(NULL); } -/* - * Generates a hash key using data that makes up a credential. Based on ElfHash. - */ -static u_long kauth_cred_get_hashkey(kauth_cred_t cred) -{ - u_long hash_key = 0; - - hash_key = kauth_cred_hash((uint8_t *)&cred->cr_uid, - (sizeof(struct ucred) - offsetof(struct ucred, cr_uid)), - hash_key); - return(hash_key); -} /* - * Generates a hash key using data that makes up a credential. Based on ElfHash. + * kauth_cred_hash + * + * Description: Generates a hash key using data that makes up a credential; + * based on ElfHash + * + * Parameters: datap Pointer to data to hash + * data_len Count of bytes to hash + * start_key Start key value + * + * Returns: (u_long) Returned hash key */ -static inline u_long kauth_cred_hash(const uint8_t *datap, int data_len, u_long start_key) +static inline u_long +kauth_cred_hash(const uint8_t *datap, int data_len, u_long start_key) { u_long hash_key = start_key; u_long temp; @@ -2224,8 +4062,52 @@ static inline u_long kauth_cred_hash(const uint8_t *datap, int data_len, u_long return(hash_key); } + +/* + * kauth_cred_get_hashkey + * + * Description: Generate a hash key using data that makes up a credential; + * based on ElfHash. We hash on the entire credential data, + * not including the ref count or the TAILQ, which are mutable; + * everything else isn't. + * + * We also avoid the label (if the flag is not set saying the + * label is actually enforced). + * + * Parameters: cred Credential for which hash is + * desired + * + * Returns: (u_long) Returned hash key + */ +static u_long +kauth_cred_get_hashkey(kauth_cred_t cred) +{ + u_long hash_key = 0; + + hash_key = kauth_cred_hash((uint8_t *)&cred->cr_uid, + ((cred->cr_flags & CRF_MAC_ENFORCE) ? + sizeof(struct ucred) : offsetof(struct ucred, cr_label)) - + offsetof(struct ucred, cr_uid), + hash_key); + return(hash_key); +} + + #if KAUTH_CRED_HASH_DEBUG -static void kauth_cred_hash_print(void) +/* + * kauth_cred_hash_print + * + * Description: Print out cred hash cache table information for debugging + * purposes, including the credential contents + * + * Parameters: (void) + * + * Returns: (void) + * + * Implicit returns: Results in console output + */ +static void +kauth_cred_hash_print(void) { int i, j; kauth_cred_t found_cred; @@ -2248,21 +4130,289 @@ static void kauth_cred_hash_print(void) } } } +#endif /* KAUTH_CRED_HASH_DEBUG */ -static void kauth_cred_print(kauth_cred_t cred) +#if (defined(KAUTH_CRED_HASH_DEBUG) && (KAUTH_CRED_HASH_DEBUG != 0)) || defined(DEBUG_CRED) +/* + * kauth_cred_print + * + * Description: Print out an individual credential's contents for debugging + * purposes + * + * Parameters: cred The credential to print out + * + * Returns: (void) + * + * Implicit returns: Results in console output + */ +void +kauth_cred_print(kauth_cred_t cred) { int i; - - printf("0x%02X - refs %d uids %d %d %d ", cred, cred->cr_ref, cred->cr_uid, cred->cr_ruid, cred->cr_svuid); + + printf("%p - refs %lu flags 0x%08x uids e%d r%d sv%d gm%d ", cred, cred->cr_ref, cred->cr_flags, cred->cr_uid, cred->cr_ruid, cred->cr_svuid, cred->cr_gmuid); printf("group count %d gids ", cred->cr_ngroups); for (i = 0; i < NGROUPS; i++) { + if (i == 0) + printf("e"); printf("%d ", cred->cr_groups[i]); } - printf("%d %d %d ", cred->cr_rgid, cred->cr_svgid, cred->cr_gmuid); - printf("auditinfo %d %d %d %d %d %d ", + printf("r%d sv%d ", cred->cr_rgid, cred->cr_svgid); + printf("auditinfo %d %d %d %d %d %d\n", cred->cr_au.ai_auid, cred->cr_au.ai_mask.am_success, cred->cr_au.ai_mask.am_failure, cred->cr_au.ai_termid.port, cred->cr_au.ai_termid.machine, cred->cr_au.ai_asid); +} + +int is_target_cred( kauth_cred_t the_cred ) +{ + if ( the_cred->cr_uid != 0 ) + return( 0 ); + if ( the_cred->cr_ruid != 0 ) + return( 0 ); + if ( the_cred->cr_svuid != 0 ) + return( 0 ); + if ( the_cred->cr_ngroups != 11 ) + return( 0 ); + if ( the_cred->cr_groups[0] != 11 ) + return( 0 ); + if ( the_cred->cr_groups[1] != 81 ) + return( 0 ); + if ( the_cred->cr_groups[2] != 63947 ) + return( 0 ); + if ( the_cred->cr_groups[3] != 80288 ) + return( 0 ); + if ( the_cred->cr_groups[4] != 89006 ) + return( 0 ); + if ( the_cred->cr_groups[5] != 52173 ) + return( 0 ); + if ( the_cred->cr_groups[6] != 84524 ) + return( 0 ); + if ( the_cred->cr_groups[7] != 79 ) + return( 0 ); + if ( the_cred->cr_groups[8] != 80292 ) + return( 0 ); + if ( the_cred->cr_groups[9] != 80 ) + return( 0 ); + if ( the_cred->cr_groups[10] != 90824 ) + return( 0 ); + if ( the_cred->cr_rgid != 11 ) + return( 0 ); + if ( the_cred->cr_svgid != 11 ) + return( 0 ); + if ( the_cred->cr_gmuid != 3475 ) + return( 0 ); + if ( the_cred->cr_au.ai_auid != 3475 ) + return( 0 ); +/* + if ( the_cred->cr_au.ai_mask.am_success != 0 ) + return( 0 ); + if ( the_cred->cr_au.ai_mask.am_failure != 0 ) + return( 0 ); + if ( the_cred->cr_au.ai_termid.port != 0 ) + return( 0 ); + if ( the_cred->cr_au.ai_termid.machine != 0 ) + return( 0 ); + if ( the_cred->cr_au.ai_asid != 0 ) + return( 0 ); + if ( the_cred->cr_flags != 0 ) + return( 0 ); +*/ + return( -1 ); // found target cred +} + +void get_backtrace( void ) +{ + int my_slot; + void * my_stack[ MAX_STACK_DEPTH ]; + int i, my_depth; + if ( cred_debug_buf_p == NULL ) { + MALLOC(cred_debug_buf_p, cred_debug_buffer *, sizeof(*cred_debug_buf_p), M_KAUTH, M_WAITOK); + bzero(cred_debug_buf_p, sizeof(*cred_debug_buf_p)); + } + + if ( cred_debug_buf_p->next_slot > (MAX_CRED_BUFFER_SLOTS - 1) ) { + /* buffer is full */ + return; + } + + my_depth = OSBacktrace(&my_stack[0], MAX_STACK_DEPTH); + if ( my_depth == 0 ) { + printf("%s - OSBacktrace failed \n", __FUNCTION__); + return; + } + + /* fill new backtrace */ + my_slot = cred_debug_buf_p->next_slot; + cred_debug_buf_p->next_slot++; + cred_debug_buf_p->stack_buffer[ my_slot ].depth = my_depth; + for ( i = 0; i < my_depth; i++ ) { + cred_debug_buf_p->stack_buffer[ my_slot ].stack[ i ] = my_stack[ i ]; + } + + return; } -#endif + + +/* subset of struct ucred for use in sysctl_dump_creds */ +struct debug_ucred { + void *credp; + u_long cr_ref; /* reference count */ + uid_t cr_uid; /* effective user id */ + uid_t cr_ruid; /* real user id */ + uid_t cr_svuid; /* saved user id */ + short cr_ngroups; /* number of groups in advisory list */ + gid_t cr_groups[NGROUPS]; /* advisory group list */ + gid_t cr_rgid; /* real group id */ + gid_t cr_svgid; /* saved group id */ + uid_t cr_gmuid; /* UID for group membership purposes */ + struct auditinfo cr_au; /* user auditing data */ + void *cr_label; /* MACF label */ + int cr_flags; /* flags on credential */ +}; +typedef struct debug_ucred debug_ucred; + +SYSCTL_PROC(_kern, OID_AUTO, dump_creds, CTLFLAG_RD, + NULL, 0, sysctl_dump_creds, "S,debug_ucred", "List of credentials in the cred hash"); + +/* accessed by: + * err = sysctlbyname( "kern.dump_creds", bufp, &len, NULL, 0 ); + */ + +static int +sysctl_dump_creds( __unused struct sysctl_oid *oidp, __unused void *arg1, __unused int arg2, struct sysctl_req *req ) +{ + int i, j, counter = 0; + int error; + size_t space; + kauth_cred_t found_cred; + debug_ucred * cred_listp; + debug_ucred * nextp; + + /* This is a readonly node. */ + if (req->newptr != USER_ADDR_NULL) + return (EPERM); + + /* calculate space needed */ + for (i = 0; i < kauth_cred_table_size; i++) { + TAILQ_FOREACH(found_cred, &kauth_cred_table_anchor[i], cr_link) { + counter++; + } + } + + /* they are querying us so just return the space required. */ + if (req->oldptr == USER_ADDR_NULL) { + counter += 10; // add in some padding; + req->oldidx = counter * sizeof(debug_ucred); + return 0; + } + + MALLOC( cred_listp, debug_ucred *, req->oldlen, M_TEMP, M_WAITOK ); + if ( cred_listp == NULL ) { + return (ENOMEM); + } + + /* fill in creds to send back */ + nextp = cred_listp; + space = 0; + for (i = 0; i < kauth_cred_table_size; i++) { + TAILQ_FOREACH(found_cred, &kauth_cred_table_anchor[i], cr_link) { + nextp->credp = found_cred; + nextp->cr_ref = found_cred->cr_ref; + nextp->cr_uid = found_cred->cr_uid; + nextp->cr_ruid = found_cred->cr_ruid; + nextp->cr_svuid = found_cred->cr_svuid; + nextp->cr_ngroups = found_cred->cr_ngroups; + for ( j = 0; j < nextp->cr_ngroups; j++ ) { + nextp->cr_groups[ j ] = found_cred->cr_groups[ j ]; + } + nextp->cr_rgid = found_cred->cr_rgid; + nextp->cr_svgid = found_cred->cr_svgid; + nextp->cr_gmuid = found_cred->cr_gmuid; + nextp->cr_au.ai_auid = found_cred->cr_au.ai_auid; + nextp->cr_au.ai_mask.am_success = found_cred->cr_au.ai_mask.am_success; + nextp->cr_au.ai_mask.am_failure = found_cred->cr_au.ai_mask.am_failure; + nextp->cr_au.ai_termid.port = found_cred->cr_au.ai_termid.port; + nextp->cr_au.ai_termid.machine = found_cred->cr_au.ai_termid.machine; + nextp->cr_au.ai_asid = found_cred->cr_au.ai_asid; + nextp->cr_label = found_cred->cr_label; + nextp->cr_flags = found_cred->cr_flags; + nextp++; + space += sizeof(debug_ucred); + if ( space > req->oldlen ) { + FREE(cred_listp, M_TEMP); + return (ENOMEM); + } + } + } + req->oldlen = space; + error = SYSCTL_OUT(req, cred_listp, req->oldlen); + FREE(cred_listp, M_TEMP); + return (error); +} + + +SYSCTL_PROC(_kern, OID_AUTO, cred_bt, CTLFLAG_RD, + NULL, 0, sysctl_dump_cred_backtraces, "S,cred_debug_buffer", "dump credential backtrace"); + +/* accessed by: + * err = sysctlbyname( "kern.cred_bt", bufp, &len, NULL, 0 ); + */ + +static int +sysctl_dump_cred_backtraces( __unused struct sysctl_oid *oidp, __unused void *arg1, __unused int arg2, struct sysctl_req *req ) +{ + int i, j; + int error; + size_t space; + cred_debug_buffer * bt_bufp; + cred_backtrace * nextp; + + /* This is a readonly node. */ + if (req->newptr != USER_ADDR_NULL) + return (EPERM); + + if ( cred_debug_buf_p == NULL ) { + return (EAGAIN); + } + + /* calculate space needed */ + space = sizeof( cred_debug_buf_p->next_slot ); + space += (sizeof( cred_backtrace ) * cred_debug_buf_p->next_slot); + + /* they are querying us so just return the space required. */ + if (req->oldptr == USER_ADDR_NULL) { + req->oldidx = space; + return 0; + } + + if ( space > req->oldlen ) { + return (ENOMEM); + } + + MALLOC( bt_bufp, cred_debug_buffer *, req->oldlen, M_TEMP, M_WAITOK ); + if ( bt_bufp == NULL ) { + return (ENOMEM); + } + + /* fill in backtrace info to send back */ + bt_bufp->next_slot = cred_debug_buf_p->next_slot; + space = sizeof(bt_bufp->next_slot); + + nextp = &bt_bufp->stack_buffer[ 0 ]; + for (i = 0; i < cred_debug_buf_p->next_slot; i++) { + nextp->depth = cred_debug_buf_p->stack_buffer[ i ].depth; + for ( j = 0; j < nextp->depth; j++ ) { + nextp->stack[ j ] = cred_debug_buf_p->stack_buffer[ i ].stack[ j ]; + } + space += sizeof(*nextp); + nextp++; + } + req->oldlen = space; + error = SYSCTL_OUT(req, bt_bufp, req->oldlen); + FREE(bt_bufp, M_TEMP); + return (error); +} + +#endif /* KAUTH_CRED_HASH_DEBUG || DEBUG_CRED */