X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/c910b4d9d2451126ae3917b931cd4390c11e1d52..c6bf4f310a33a9262d455ea4d3f0630b1255e3fe:/bsd/kern/kern_credential.c diff --git a/bsd/kern/kern_credential.c b/bsd/kern/kern_credential.c index c7b4ca7a8..643b1cebb 100644 --- a/bsd/kern/kern_credential.c +++ b/bsd/kern/kern_credential.c @@ -1,8 +1,8 @@ /* - * Copyright (c) 2004-2007 Apple Inc. All rights reserved. + * Copyright (c) 2004-2011 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ - * + * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in @@ -11,10 +11,10 @@ * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. - * + * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. - * + * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, @@ -22,7 +22,7 @@ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. - * + * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* @@ -37,8 +37,7 @@ * and identity information. */ - -#include /* XXX trim includes */ +#include /* XXX trim includes */ #include #include #include @@ -49,32 +48,56 @@ #include #include #include +#include -#include +#include #include +#include /* For manifest constants in posix_cred_access */ #include #include #include -#include +#include #include -#include +#include #ifdef MACH_ASSERT # undef MACH_ASSERT #endif -#define MACH_ASSERT 1 /* XXX so bogus */ +#define MACH_ASSERT 1 /* XXX so bogus */ #include #if CONFIG_MACF #include #include +#include +#endif + +#include + +void mach_kauth_cred_uthread_update( void ); + +# define NULLCRED_CHECK(_c) do {if (!IS_VALID_CRED(_c)) panic("%s: bad credential %p", __FUNCTION__,_c);} while(0) + +/* Set to 1 to turn on KAUTH_DEBUG for kern_credential.c */ +#if 0 +#ifdef KAUTH_DEBUG +#undef KAUTH_DEBUG +#endif + +#ifdef K_UUID_FMT +#undef K_UUID_FMT #endif -#define CRED_DIAGNOSTIC 0 +#ifdef K_UUID_ARG +#undef K_UUID_ARG +#endif -# define NULLCRED_CHECK(_c) do {if (!IS_VALID_CRED(_c)) panic("%s: bad credential %p", __FUNCTION__,_c);} while(0) +# define K_UUID_FMT "%08x:%08x:%08x:%08x" +# define K_UUID_ARG(_u) &_u.g_guid_asint[0],&_u.g_guid_asint[1],&_u.g_guid_asint[2],&_u.g_guid_asint[3] +# define KAUTH_DEBUG(fmt, args...) do { printf("%s:%d: " fmt "\n", __PRETTY_FUNCTION__, __LINE__ , ##args); } while (0) +#endif /* * Credential debugging; we can track entry into a function that might @@ -85,43 +108,44 @@ */ #if DEBUG_CRED -#define DEBUG_CRED_ENTER printf -#define DEBUG_CRED_CHANGE printf +#define DEBUG_CRED_ENTER printf +#define DEBUG_CRED_CHANGE printf extern void kauth_cred_print(kauth_cred_t cred); -#include /* needed for get_backtrace( ) */ +#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_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 ); +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 ]; + 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 ]; + 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 */ +#else /* !DEBUG_CRED */ -#define DEBUG_CRED_ENTER(fmt, ...) do {} while (0) -#define DEBUG_CRED_CHANGE(fmt, ...) do {} while (0) +#define DEBUG_CRED_ENTER(fmt, ...) do {} while (0) +#define DEBUG_CRED_CHANGE(fmt, ...) do {} while (0) -#endif /* !DEBUG_CRED */ +#endif /* !DEBUG_CRED */ +#if CONFIG_EXT_RESOLVER /* * Interface to external identity resolver. * @@ -132,55 +156,190 @@ cred_debug_buffer * cred_debug_buf_p = NULL; */ static lck_mtx_t *kauth_resolver_mtx; -#define KAUTH_RESOLVER_LOCK() lck_mtx_lock(kauth_resolver_mtx); -#define KAUTH_RESOLVER_UNLOCK() lck_mtx_unlock(kauth_resolver_mtx); +#define KAUTH_RESOLVER_LOCK() lck_mtx_lock(kauth_resolver_mtx); +#define KAUTH_RESOLVER_UNLOCK() lck_mtx_unlock(kauth_resolver_mtx); -static volatile pid_t kauth_resolver_identity; -static int kauth_resolver_registered; -static uint32_t kauth_resolver_sequence; +static volatile pid_t kauth_resolver_identity; +static int kauth_identitysvc_has_registered; +static int kauth_resolver_registered; +static uint32_t kauth_resolver_sequence; +static int kauth_resolver_timeout = 30; /* default: 30 seconds */ struct kauth_resolver_work { TAILQ_ENTRY(kauth_resolver_work) kr_link; struct kauth_identity_extlookup kr_work; - uint32_t kr_seqno; - int kr_refs; - int kr_flags; -#define KAUTH_REQUEST_UNSUBMITTED (1<<0) -#define KAUTH_REQUEST_SUBMITTED (1<<1) -#define KAUTH_REQUEST_DONE (1<<2) - int kr_result; + uint64_t kr_extend; + uint32_t kr_seqno; + int kr_refs; + int kr_flags; +#define KAUTH_REQUEST_UNSUBMITTED (1<<0) +#define KAUTH_REQUEST_SUBMITTED (1<<1) +#define KAUTH_REQUEST_DONE (1<<2) + int kr_result; }; TAILQ_HEAD(kauth_resolver_unsubmitted_head, kauth_resolver_work) kauth_resolver_unsubmitted; -TAILQ_HEAD(kauth_resolver_submitted_head, kauth_resolver_work) kauth_resolver_submitted; -TAILQ_HEAD(kauth_resolver_done_head, kauth_resolver_work) kauth_resolver_done; +TAILQ_HEAD(kauth_resolver_submitted_head, kauth_resolver_work) kauth_resolver_submitted; +TAILQ_HEAD(kauth_resolver_done_head, kauth_resolver_work) kauth_resolver_done; + +/* Number of resolver timeouts between logged complaints */ +#define KAUTH_COMPLAINT_INTERVAL 1000 +int kauth_resolver_timeout_cnt = 0; + +#if DEVELOPMENT || DEBUG +/* Internal builds get different (less ambiguous) breadcrumbs. */ +#define KAUTH_RESOLVER_FAILED_ERRCODE EOWNERDEAD +#else +/* But non-Internal builds get errors that are allowed by standards. */ +#define KAUTH_RESOLVER_FAILED_ERRCODE EIO +#endif /* DEVELOPMENT || DEBUG */ + +int kauth_resolver_failed_cnt = 0; +#define RESOLVER_FAILED_MESSAGE(fmt, args...) \ +do { \ + if (!(kauth_resolver_failed_cnt++ % 100)) { \ + printf("%s: " fmt "\n", __PRETTY_FUNCTION__, ##args); \ + } \ +} while (0) + +static int kauth_resolver_submit(struct kauth_identity_extlookup *lkp, uint64_t extend_data); +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); +static __attribute__((noinline)) int __KERNEL_IS_WAITING_ON_EXTERNAL_CREDENTIAL_RESOLVER__( + struct kauth_resolver_work *); + +#define KAUTH_CACHES_MAX_SIZE 10000 /* Max # entries for both groups and id caches */ + +struct kauth_identity { + TAILQ_ENTRY(kauth_identity) ki_link; + int ki_valid; + uid_t ki_uid; + gid_t ki_gid; + int ki_supgrpcnt; + gid_t ki_supgrps[NGROUPS]; + guid_t ki_guid; + ntsid_t ki_ntsid; + const char *ki_name; /* string name from string cache */ + /* + * Expiry times are the earliest time at which we will disregard the + * cached state and go to userland. Before then if the valid bit is + * set, we will return the cached value. If it's not set, we will + * not go to userland to resolve, just assume that there is no answer + * available. + */ + time_t ki_groups_expiry; + time_t ki_guid_expiry; + time_t ki_ntsid_expiry; +}; + +static TAILQ_HEAD(kauth_identity_head, kauth_identity) kauth_identities; +static lck_mtx_t *kauth_identity_mtx; +#define KAUTH_IDENTITY_LOCK() lck_mtx_lock(kauth_identity_mtx); +#define KAUTH_IDENTITY_UNLOCK() lck_mtx_unlock(kauth_identity_mtx); +#define KAUTH_IDENTITY_CACHEMAX_DEFAULT 100 /* XXX default sizing? */ +static int kauth_identity_cachemax = KAUTH_IDENTITY_CACHEMAX_DEFAULT; +static int kauth_identity_count; + +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, int supgrpcnt, gid_t *supgrps, time_t groups_expiry, + const char *name, int nametype); +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, uint64_t extend_data); +static void kauth_identity_trimcache(int newsize); +static void kauth_identity_lru(struct kauth_identity *kip); +static int kauth_identity_guid_expired(struct kauth_identity *kip); +static int kauth_identity_ntsid_expired(struct kauth_identity *kip); +static int kauth_identity_find_uid(uid_t uid, struct kauth_identity *kir, char *getname); +static int kauth_identity_find_gid(gid_t gid, struct kauth_identity *kir, char *getname); +static int kauth_identity_find_guid(guid_t *guidp, struct kauth_identity *kir, char *getname); +static int kauth_identity_find_ntsid(ntsid_t *ntsid, struct kauth_identity *kir, char *getname); +static int kauth_identity_find_nam(char *name, int valid, struct kauth_identity *kir); + +struct kauth_group_membership { + TAILQ_ENTRY(kauth_group_membership) gm_link; + uid_t gm_uid; /* the identity whose membership we're recording */ + gid_t gm_gid; /* group of which they are a member */ + time_t gm_expiry; /* TTL for the membership, or 0 for persistent entries */ + int gm_flags; +#define KAUTH_GROUP_ISMEMBER (1<<0) +}; + +TAILQ_HEAD(kauth_groups_head, kauth_group_membership) kauth_groups; +static lck_mtx_t *kauth_groups_mtx; +#define KAUTH_GROUPS_LOCK() lck_mtx_lock(kauth_groups_mtx); +#define KAUTH_GROUPS_UNLOCK() lck_mtx_unlock(kauth_groups_mtx); +#define KAUTH_GROUPS_CACHEMAX_DEFAULT 100 /* XXX default sizing? */ +static int kauth_groups_cachemax = KAUTH_GROUPS_CACHEMAX_DEFAULT; +static int kauth_groups_count; -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); +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); +static void kauth_groups_trimcache(int newsize); -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; +#endif /* CONFIG_EXT_RESOLVER */ -TAILQ_HEAD(kauth_cred_entry_head, ucred); -static struct kauth_cred_entry_head * kauth_cred_table_anchor = NULL; +#define KAUTH_CRED_TABLE_SIZE 128 -#define KAUTH_CRED_HASH_DEBUG 0 +LIST_HEAD(kauth_cred_entry_head, ucred); +static struct kauth_cred_entry_head + kauth_cred_table_anchor[KAUTH_CRED_TABLE_SIZE]; -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 struct kauth_cred_entry_head *kauth_cred_get_bucket(kauth_cred_t cred); +static kauth_cred_t kauth_cred_add(kauth_cred_t new_cred, struct kauth_cred_entry_head *bucket); +static void kauth_cred_remove_locked(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); +static kauth_cred_t kauth_cred_find_and_ref(kauth_cred_t cred, + struct kauth_cred_entry_head *bucket); +static bool kauth_cred_is_equal(kauth_cred_t cred1, kauth_cred_t cred2); -#if KAUTH_CRED_HASH_DEBUG -static int kauth_cred_count = 0; -static void kauth_cred_hash_print(void); -static void kauth_cred_print(kauth_cred_t cred); -#endif +#if CONFIG_EXT_RESOLVER + +/* + * __KERNEL_IS_WAITING_ON_EXTERNAL_CREDENTIAL_RESOLVER__ + * + * Description: Waits for the user space daemon to respond to the request + * we made. Function declared non inline to be visible in + * stackshots and spindumps as well as debugging. + * + * Parameters: workp Work queue entry. + * + * Returns: 0 on Success. + * EIO if Resolver is dead. + * EINTR thread interrupted in msleep + * EWOULDBLOCK thread timed out in msleep + * ERESTART returned by msleep. + * + */ +static __attribute__((noinline)) int +__KERNEL_IS_WAITING_ON_EXTERNAL_CREDENTIAL_RESOLVER__( + struct kauth_resolver_work *workp) +{ + int error = 0; + struct timespec ts; + for (;;) { + /* we could compute a better timeout here */ + ts.tv_sec = kauth_resolver_timeout; + ts.tv_nsec = 0; + error = msleep(workp, kauth_resolver_mtx, PCATCH, "kr_submit", &ts); + /* request has been completed? */ + if ((error == 0) && (workp->kr_flags & KAUTH_REQUEST_DONE)) { + break; + } + /* woken because the resolver has died? */ + if (kauth_resolver_identity == 0) { + RESOLVER_FAILED_MESSAGE("kauth external resolver died while while waiting for work to complete"); + error = KAUTH_RESOLVER_FAILED_ERRCODE; + break; + } + /* an error? */ + if (error != 0) { + break; + } + } + return error; +} /* @@ -192,7 +351,7 @@ static void kauth_cred_print(kauth_cred_t cred); * * Returns: (void) * - * Notes: Intialize the credential identity resolver for use; the + * Notes: Initialize 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.. @@ -217,7 +376,7 @@ kauth_resolver_init(void) TAILQ_INIT(&kauth_resolver_submitted); TAILQ_INIT(&kauth_resolver_done); kauth_resolver_sequence = 31337; - kauth_resolver_mtx = lck_mtx_alloc_init(kauth_lck_grp, 0/*LCK_ATTR_NULL*/); + kauth_resolver_mtx = lck_mtx_alloc_init(kauth_lck_grp, 0 /*LCK_ATTR_NULL*/); } @@ -229,51 +388,71 @@ kauth_resolver_init(void) * * Parameters: lkp A pointer to an external * lookup request + * extend_data extended data for kr_extend * * 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 + * copyinstr:EFAULT Bad message from user space + * workp->kr_result:??? An error from the user space + * daemon (includes ENOENT!) + * + * Implicit returns: + * *lkp Modified * * 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. + * + * Submission is by means of placing the item on a work queue + * which is serviced by an external resolver thread calling + * into the kernel. The caller then sleeps until timeout, + * cancellation, or an external resolver thread calls in with + * a result message to kauth_resolver_complete(). All of these + * events wake the caller back up. + * + * This code is called from either kauth_cred_ismember_gid() + * for a group membership request, or it is called from + * kauth_cred_cache_lookup() when we get a cache miss. */ static int -kauth_resolver_submit(struct kauth_identity_extlookup *lkp) +kauth_resolver_submit(struct kauth_identity_extlookup *lkp, uint64_t extend_data) { struct kauth_resolver_work *workp, *killp; struct timespec ts; - int error, shouldfree; - + int error, shouldfree; + /* no point actually blocking if the resolver isn't up yet */ if (kauth_resolver_identity == 0) { /* - * We've already waited an initial 30 seconds with no result. + * We've already waited an initial + * seconds with no result. + * * Sleep on a stack address so no one wakes us before timeout; * we sleep a half a second in case we are a high priority * process, so that memberd doesn't starve while we are in a * tight loop between user and kernel, eating all the CPU. */ - error = tsleep(&ts, PZERO | PCATCH, "kr_submit", hz/2); + error = tsleep(&ts, PZERO | PCATCH, "kr_submit", hz / 2); if (kauth_resolver_identity == 0) { /* * if things haven't changed while we were asleep, * tell the caller we couldn't get an authoritative * answer. */ - return(EWOULDBLOCK); + return EWOULDBLOCK; } } - + MALLOC(workp, struct kauth_resolver_work *, sizeof(*workp), M_KAUTH, M_WAITOK); - if (workp == NULL) - return(ENOMEM); + if (workp == NULL) { + return ENOMEM; + } workp->kr_work = *lkp; + workp->kr_extend = extend_data; workp->kr_refs = 1; workp->kr_flags = KAUTH_REQUEST_UNSUBMITTED; workp->kr_result = 0; @@ -287,49 +466,61 @@ kauth_resolver_submit(struct kauth_identity_extlookup *lkp) workp->kr_work.el_result = KAUTH_EXTLOOKUP_INPROG; /* - * XXX As an optimisation, we could check the queue for identical - * XXX items and coalesce them + * XXX We *MUST NOT* attempt to coalesce identical work items due to + * XXX the inability to ensure order of update of the request item + * XXX extended data vs. the wakeup; instead, we let whoever is waiting + * XXX for each item repeat the update when they wake up. */ TAILQ_INSERT_TAIL(&kauth_resolver_unsubmitted, workp, kr_link); + /* + * Wake up an external resolver thread to deal with the new work; one + * may not be available, and if not, then the request will be grabbed + * when a resolver thread comes back into the kernel to request new + * work. + */ wakeup_one((caddr_t)&kauth_resolver_unsubmitted); - for (;;) { - /* we could compute a better timeout here */ - ts.tv_sec = 30; - ts.tv_nsec = 0; - error = msleep(workp, kauth_resolver_mtx, PCATCH, "kr_submit", &ts); - /* request has been completed? */ - if ((error == 0) && (workp->kr_flags & KAUTH_REQUEST_DONE)) - break; - /* woken because the resolver has died? */ - if (kauth_resolver_identity == 0) { - error = EIO; - break; - } - /* an error? */ - if (error != 0) - break; - } + error = __KERNEL_IS_WAITING_ON_EXTERNAL_CREDENTIAL_RESOLVER__(workp); + /* if the request was processed, copy the result */ - if (error == 0) + if (error == 0) { *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 ((error == EWOULDBLOCK) && (workp->kr_flags & KAUTH_REQUEST_UNSUBMITTED)) { - KAUTH_DEBUG("RESOLVER - request timed out without being collected for processing, resolver dead"); - kauth_resolver_identity = 0; - /* kill all the other requestes that are waiting as well */ - TAILQ_FOREACH(killp, &kauth_resolver_submitted, kr_link) - wakeup(killp); - TAILQ_FOREACH(killp, &kauth_resolver_unsubmitted, kr_link) - wakeup(killp); } - + + if (error == EWOULDBLOCK) { + if ((kauth_resolver_timeout_cnt++ % KAUTH_COMPLAINT_INTERVAL) == 0) { + printf("kauth external resolver timed out (%d timeout(s) of %d seconds).\n", + kauth_resolver_timeout_cnt, kauth_resolver_timeout); + } + + if (workp->kr_flags & KAUTH_REQUEST_UNSUBMITTED) { + /* + * 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. + */ + KAUTH_DEBUG("RESOLVER - request timed out without being collected for processing, resolver dead"); + + /* + * Make the current resolver non-authoritative, and mark it as + * no longer registered to prevent kauth_cred_ismember_gid() + * enqueueing more work until a new one is registered. This + * mitigates the damage a crashing resolver may inflict. + */ + kauth_resolver_identity = 0; + kauth_resolver_registered = 0; + + /* kill all the other requestes that are waiting as well */ + TAILQ_FOREACH(killp, &kauth_resolver_submitted, kr_link) + wakeup(killp); + TAILQ_FOREACH(killp, &kauth_resolver_unsubmitted, kr_link) + wakeup(killp); + /* Cause all waiting-for-work threads to return EIO */ + wakeup((caddr_t)&kauth_resolver_unsubmitted); + } + } + /* * drop our reference on the work item, and note whether we should * free it or not @@ -350,18 +541,22 @@ kauth_resolver_submit(struct kauth_identity_extlookup *lkp) /* someone else still has a reference on this request */ shouldfree = 0; } + /* collect request result */ - if (error == 0) + if (error == 0) { error = workp->kr_result; + } KAUTH_RESOLVER_UNLOCK(); + /* * If we dropped the last reference, free the request. */ - if (shouldfree) + if (shouldfree) { FREE(workp, M_KAUTH); + } KAUTH_DEBUG("RESOLVER - returning %d", error); - return(error); + return error; } @@ -389,14 +584,20 @@ kauth_resolver_submit(struct kauth_identity_extlookup *lkp) * for the next request. */ int -identitysvc(__unused struct proc *p, struct identitysvc_args *uap, __unused register_t *retval) +identitysvc(__unused struct proc *p, struct identitysvc_args *uap, __unused int32_t *retval) { int opcode = uap->opcode; user_addr_t message = uap->message; struct kauth_resolver_work *workp; + struct kauth_cache_sizes sz_arg = {}; int error; pid_t new_id; + if (!IOTaskHasEntitlement(current_task(), IDENTITYSVC_ENTITLEMENT)) { + KAUTH_DEBUG("RESOLVER - pid %d not entitled to call identitysvc", current_proc()->p_pid); + return EPERM; + } + /* * New server registering itself. */ @@ -404,7 +605,7 @@ identitysvc(__unused struct proc *p, struct identitysvc_args *uap, __unused regi new_id = current_proc()->p_pid; if ((error = kauth_authorize_generic(kauth_cred_get(), KAUTH_GENERIC_ISSUSER)) != 0) { KAUTH_DEBUG("RESOLVER - pid %d refused permission to become identity resolver", new_id); - return(error); + return error; } KAUTH_RESOLVER_LOCK(); if (kauth_resolver_identity != new_id) { @@ -418,45 +619,126 @@ identitysvc(__unused struct proc *p, struct identitysvc_args *uap, __unused regi workp->kr_flags |= KAUTH_REQUEST_UNSUBMITTED; TAILQ_INSERT_HEAD(&kauth_resolver_unsubmitted, workp, kr_link); } + /* + * Allow user space resolver to override the + * external resolution timeout + */ + if (message > 30 && message < 10000) { + kauth_resolver_timeout = message; + KAUTH_DEBUG("RESOLVER - new resolver changes timeout to %d seconds\n", (int)message); + } kauth_resolver_identity = new_id; kauth_resolver_registered = 1; + kauth_identitysvc_has_registered = 1; wakeup(&kauth_resolver_unsubmitted); } KAUTH_RESOLVER_UNLOCK(); - return(0); + return 0; } /* - * Beyond this point, we must be the resolver process. + * Beyond this point, we must be the resolver process. We verify this + * by confirming the resolver credential and pid. */ - if (current_proc()->p_pid != kauth_resolver_identity) { + if ((kauth_cred_getuid(kauth_cred_get()) != 0) || (current_proc()->p_pid != kauth_resolver_identity)) { KAUTH_DEBUG("RESOLVER - call from bogus resolver %d\n", current_proc()->p_pid); - return(EPERM); + return EPERM; + } + + if (opcode == KAUTH_GET_CACHE_SIZES) { + KAUTH_IDENTITY_LOCK(); + sz_arg.kcs_id_size = kauth_identity_cachemax; + KAUTH_IDENTITY_UNLOCK(); + + KAUTH_GROUPS_LOCK(); + sz_arg.kcs_group_size = kauth_groups_cachemax; + KAUTH_GROUPS_UNLOCK(); + + if ((error = copyout(&sz_arg, uap->message, sizeof(sz_arg))) != 0) { + return error; + } + + return 0; + } else if (opcode == KAUTH_SET_CACHE_SIZES) { + if ((error = copyin(uap->message, &sz_arg, sizeof(sz_arg))) != 0) { + return error; + } + + if ((sz_arg.kcs_group_size > KAUTH_CACHES_MAX_SIZE) || + (sz_arg.kcs_id_size > KAUTH_CACHES_MAX_SIZE)) { + return EINVAL; + } + + KAUTH_IDENTITY_LOCK(); + kauth_identity_cachemax = sz_arg.kcs_id_size; + kauth_identity_trimcache(kauth_identity_cachemax); + KAUTH_IDENTITY_UNLOCK(); + + KAUTH_GROUPS_LOCK(); + kauth_groups_cachemax = sz_arg.kcs_group_size; + kauth_groups_trimcache(kauth_groups_cachemax); + KAUTH_GROUPS_UNLOCK(); + + return 0; + } else if (opcode == KAUTH_CLEAR_CACHES) { + KAUTH_IDENTITY_LOCK(); + kauth_identity_trimcache(0); + KAUTH_IDENTITY_UNLOCK(); + + KAUTH_GROUPS_LOCK(); + kauth_groups_trimcache(0); + KAUTH_GROUPS_UNLOCK(); + } else if (opcode == KAUTH_EXTLOOKUP_DEREGISTER) { + /* + * Terminate outstanding requests; without an authoritative + * resolver, we are now back on our own authority. + */ + struct kauth_resolver_work *killp; + + KAUTH_RESOLVER_LOCK(); + + /* + * Clear the identity, but also mark it as unregistered so + * there is no explicit future expectation of us getting a + * new resolver any time soon. + */ + kauth_resolver_identity = 0; + kauth_resolver_registered = 0; + + TAILQ_FOREACH(killp, &kauth_resolver_submitted, kr_link) + wakeup(killp); + TAILQ_FOREACH(killp, &kauth_resolver_unsubmitted, kr_link) + wakeup(killp); + /* Cause all waiting-for-work threads to return EIO */ + wakeup((caddr_t)&kauth_resolver_unsubmitted); + KAUTH_RESOLVER_UNLOCK(); } - + /* * Got a result returning? */ if (opcode & KAUTH_EXTLOOKUP_RESULT) { - if ((error = kauth_resolver_complete(message)) != 0) - return(error); + if ((error = kauth_resolver_complete(message)) != 0) { + return error; + } } /* * Caller wants to take more work? */ if (opcode & KAUTH_EXTLOOKUP_WORKER) { - if ((error = kauth_resolver_getwork(message)) != 0) - return(error); + if ((error = kauth_resolver_getwork(message)) != 0) { + return error; + } } - return(0); + return 0; } /* * kauth_resolver_getwork_continue - * + * * Description: Continuation for kauth_resolver_getwork * * Parameters: result Error code or 0 for the sleep @@ -478,7 +760,7 @@ kauth_resolver_getwork_continue(int result) if (result) { KAUTH_RESOLVER_UNLOCK(); - return(result); + return result; } /* @@ -491,14 +773,22 @@ kauth_resolver_getwork_continue(int result) int error; error = msleep0(&kauth_resolver_unsubmitted, kauth_resolver_mtx, PCATCH, "GRGetWork", 0, kauth_resolver_getwork_continue); + /* + * If this is a wakeup from another thread in the resolver + * deregistering it, error out the request-for-work thread + */ + if (!kauth_resolver_identity) { + RESOLVER_FAILED_MESSAGE("external resolver died"); + error = KAUTH_RESOLVER_FAILED_ERRCODE; + } KAUTH_RESOLVER_UNLOCK(); - return(error); + return error; } thread = current_thread(); ut = get_bsdthread_info(thread); - message = ut->uu_kauth.message; - return(kauth_resolver_getwork2(message)); + message = ut->uu_save.uus_kauth.message; + return kauth_resolver_getwork2(message); } @@ -515,7 +805,7 @@ kauth_resolver_getwork_continue(int result) * 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 + * in the identity resolution 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 @@ -525,7 +815,7 @@ static int kauth_resolver_getwork2(user_addr_t message) { struct kauth_resolver_work *workp; - int error; + int error; /* * Note: We depend on the caller protecting us from a NULL work item @@ -534,10 +824,54 @@ kauth_resolver_getwork2(user_addr_t message) */ workp = TAILQ_FIRST(&kauth_resolver_unsubmitted); - if ((error = copyout(&workp->kr_work, message, sizeof(workp->kr_work))) != 0) { + /* + * Copy out the external lookup structure for the request, not + * including the el_extend field, which contains the address of the + * external buffer provided by the external resolver into which we + * copy the extension request information. + */ + /* BEFORE FIELD */ + if ((error = copyout(&workp->kr_work, message, offsetof(struct kauth_identity_extlookup, el_extend))) != 0) { KAUTH_DEBUG("RESOLVER - error submitting work to resolve"); goto out; } + /* AFTER FIELD */ + if ((error = copyout(&workp->kr_work.el_info_reserved_1, + message + offsetof(struct kauth_identity_extlookup, el_info_reserved_1), + sizeof(struct kauth_identity_extlookup) - offsetof(struct kauth_identity_extlookup, el_info_reserved_1))) != 0) { + KAUTH_DEBUG("RESOLVER - error submitting work to resolve"); + goto out; + } + + /* + * Handle extended requests here; if we have a request of a type where + * the kernel wants a translation of extended information, then we need + * to copy it out into the extended buffer, assuming the buffer is + * valid; we only attempt to get the buffer address if we have request + * data to copy into it. + */ + + /* + * translate a user@domain string into a uid/gid/whatever + */ + if (workp->kr_work.el_flags & (KAUTH_EXTLOOKUP_VALID_PWNAM | KAUTH_EXTLOOKUP_VALID_GRNAM)) { + uint64_t uaddr; + + error = copyin(message + offsetof(struct kauth_identity_extlookup, el_extend), &uaddr, sizeof(uaddr)); + if (!error) { + size_t actual; /* not used */ + /* + * Use copyoutstr() to reduce the copy size; we let + * this catch a NULL uaddr because we shouldn't be + * asking in that case anyway. + */ + error = copyoutstr(CAST_DOWN(void *, workp->kr_extend), uaddr, MAXPATHLEN, &actual); + } + if (error) { + 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; @@ -545,7 +879,7 @@ kauth_resolver_getwork2(user_addr_t message) out: KAUTH_RESOLVER_UNLOCK(); - return(error); + return error; } @@ -566,13 +900,13 @@ out: * 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. + * actual request outstanding. */ static int kauth_resolver_getwork(user_addr_t message) { struct kauth_resolver_work *workp; - int error; + int error; KAUTH_RESOLVER_LOCK(); error = 0; @@ -580,10 +914,18 @@ kauth_resolver_getwork(user_addr_t message) thread_t thread = current_thread(); struct uthread *ut = get_bsdthread_info(thread); - ut->uu_kauth.message = message; + ut->uu_save.uus_kauth.message = message; error = msleep0(&kauth_resolver_unsubmitted, kauth_resolver_mtx, PCATCH, "GRGetWork", 0, kauth_resolver_getwork_continue); KAUTH_RESOLVER_UNLOCK(); - return(error); + /* + * If this is a wakeup from another thread in the resolver + * deregistering it, error out the request-for-work thread + */ + if (!kauth_resolver_identity) { + printf("external resolver died"); + error = KAUTH_RESOLVER_FAILED_ERRCODE; + } + return error; } return kauth_resolver_getwork2(message); } @@ -603,13 +945,18 @@ kauth_resolver_getwork(user_addr_t message) static int kauth_resolver_complete(user_addr_t message) { - struct kauth_identity_extlookup extl; + struct kauth_identity_extlookup extl; struct kauth_resolver_work *workp; - int error, result; + struct kauth_resolver_work *killp; + int error, result, want_extend_data; + /* + * Copy in the mesage, including the extension field, since we are + * copying into a local variable. + */ if ((error = copyin(message, &extl, sizeof(extl))) != 0) { KAUTH_DEBUG("RESOLVER - error getting completed work\n"); - return(error); + return error; } KAUTH_RESOLVER_LOCK(); @@ -628,47 +975,127 @@ kauth_resolver_complete(user_addr_t message) } } /* FALLTHROUGH */ + case KAUTH_EXTLOOKUP_SUCCESS: break; case KAUTH_EXTLOOKUP_FATAL: /* fatal error means the resolver is dead */ KAUTH_DEBUG("RESOLVER - resolver %d died, waiting for a new one", kauth_resolver_identity); + RESOLVER_FAILED_MESSAGE("resolver %d died, waiting for a new one", kauth_resolver_identity); + /* + * Terminate outstanding requests; without an authoritative + * resolver, we are now back on our own authority. Tag the + * resolver unregistered to prevent kauth_cred_ismember_gid() + * enqueueing more work until a new one is registered. This + * mitigates the damage a crashing resolver may inflict. + */ kauth_resolver_identity = 0; - /* XXX should we terminate all outstanding requests? */ - error = EIO; + kauth_resolver_registered = 0; + + TAILQ_FOREACH(killp, &kauth_resolver_submitted, kr_link) + wakeup(killp); + TAILQ_FOREACH(killp, &kauth_resolver_unsubmitted, kr_link) + wakeup(killp); + /* Cause all waiting-for-work threads to return EIO */ + wakeup((caddr_t)&kauth_resolver_unsubmitted); + /* and return EIO to the caller */ + error = KAUTH_RESOLVER_FAILED_ERRCODE; break; + case KAUTH_EXTLOOKUP_BADRQ: KAUTH_DEBUG("RESOLVER - resolver reported invalid request %d", extl.el_seqno); result = EINVAL; break; + case KAUTH_EXTLOOKUP_FAILURE: KAUTH_DEBUG("RESOLVER - resolver reported transient failure for request %d", extl.el_seqno); - result = EIO; + RESOLVER_FAILED_MESSAGE("resolver reported transient failure for request %d", extl.el_seqno); + result = KAUTH_RESOLVER_FAILED_ERRCODE; break; + default: KAUTH_DEBUG("RESOLVER - resolver returned unexpected status %d", extl.el_result); - result = EIO; + RESOLVER_FAILED_MESSAGE("resolver returned unexpected status %d", extl.el_result); + result = KAUTH_RESOLVER_FAILED_ERRCODE; break; } /* - * In the case of a fatal error, we assume that the resolver will restart - * quickly and re-collect all of the outstanding requests. Thus, we don't - * complete the request which returned the fatal error status. + * In the case of a fatal error, we assume that the resolver will + * restart quickly and re-collect all of the outstanding requests. + * Thus, we don't complete the request which returned the fatal + * error status. */ if (extl.el_result != KAUTH_EXTLOOKUP_FATAL) { /* scan our list for this request */ TAILQ_FOREACH(workp, &kauth_resolver_submitted, kr_link) { /* found it? */ if (workp->kr_seqno == extl.el_seqno) { - /* copy result */ - workp->kr_work = extl; - /* move onto completed list and wake up requester(s) */ + /* + * Do we want extend_data? + */ + want_extend_data = (workp->kr_work.el_flags & (KAUTH_EXTLOOKUP_WANT_PWNAM | KAUTH_EXTLOOKUP_WANT_GRNAM)); + + /* + * Get the request of the submitted queue so + * that it is not cleaned up out from under + * us by a timeout. + */ TAILQ_REMOVE(&kauth_resolver_submitted, workp, kr_link); workp->kr_flags &= ~KAUTH_REQUEST_SUBMITTED; workp->kr_flags |= KAUTH_REQUEST_DONE; workp->kr_result = result; + + /* Copy the result message to the work item. */ + memcpy(&workp->kr_work, &extl, sizeof(struct kauth_identity_extlookup)); + + /* + * Check if we have a result in the extension + * field; if we do, then we need to separately + * copy the data from the message el_extend + * into the request buffer that's in the work + * item. We have to do it here because we do + * not want to wake up the waiter until the + * data is in their buffer, and because the + * actual request response may be destroyed + * by the time the requester wakes up, and they + * do not have access to the user space buffer + * address. + * + * It is safe to drop and reacquire the lock + * here because we've already removed the item + * from the submission queue, but have not yet + * moved it to the completion queue. Note that + * near simultaneous requests may result in + * duplication of requests for items in this + * window. This should not be a performance + * issue and is easily detectable by comparing + * time to live on last response vs. time of + * next request in the resolver logs. + * + * A malicious/faulty resolver could overwrite + * part of a user's address space if they return + * flags that mismatch the original request's flags. + */ + if (want_extend_data && (extl.el_flags & (KAUTH_EXTLOOKUP_VALID_PWNAM | KAUTH_EXTLOOKUP_VALID_GRNAM))) { + size_t actual; /* notused */ + + KAUTH_RESOLVER_UNLOCK(); + error = copyinstr(extl.el_extend, CAST_DOWN(void *, workp->kr_extend), MAXPATHLEN, &actual); + KAUTH_DEBUG("RESOLVER - resolver got name :%*s: len = %d\n", (int)actual, + actual ? "null" : (char *)extl.el_extend, actual); + KAUTH_RESOLVER_LOCK(); + } else if (extl.el_flags & (KAUTH_EXTLOOKUP_VALID_PWNAM | KAUTH_EXTLOOKUP_VALID_GRNAM)) { + error = EFAULT; + KAUTH_DEBUG("RESOLVER - resolver returned mismatching extension flags (%d), request contained (%d)", + extl.el_flags, want_extend_data); + } + + /* + * Move the completed work item to the + * completion queue and wake up requester(s) + */ TAILQ_INSERT_TAIL(&kauth_resolver_done, workp, kr_link); wakeup(workp); break; @@ -680,58 +1107,25 @@ kauth_resolver_complete(user_addr_t message) * timed out the work record will be gone. */ KAUTH_RESOLVER_UNLOCK(); - - return(error); + + return error; } +#endif /* CONFIG_EXT_RESOLVER */ /* * Identity cache. */ -struct kauth_identity { - TAILQ_ENTRY(kauth_identity) ki_link; - int ki_valid; -#define KI_VALID_UID (1<<0) /* UID and GID are mutually exclusive */ -#define KI_VALID_GID (1<<1) -#define KI_VALID_GUID (1<<2) -#define KI_VALID_NTSID (1<<3) - uid_t ki_uid; - gid_t ki_gid; - guid_t ki_guid; - ntsid_t ki_ntsid; - /* - * Expiry times are the earliest time at which we will disregard the cached state and go to - * userland. Before then if the valid bit is set, we will return the cached value. If it's - * not set, we will not go to userland to resolve, just assume that there is no answer - * available. - */ - time_t ki_guid_expiry; - time_t ki_ntsid_expiry; -}; - -static TAILQ_HEAD(kauth_identity_head, kauth_identity) kauth_identities; -#define KAUTH_IDENTITY_CACHEMAX 100 /* XXX sizing? */ -static int kauth_identity_count; - -static lck_mtx_t *kauth_identity_mtx; -#define KAUTH_IDENTITY_LOCK() lck_mtx_lock(kauth_identity_mtx); -#define KAUTH_IDENTITY_UNLOCK() lck_mtx_unlock(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_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); -static int kauth_identity_ntsid_expired(struct kauth_identity *kip); -static int kauth_identity_find_uid(uid_t uid, struct kauth_identity *kir); -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); - +#define KI_VALID_UID (1<<0) /* UID and GID are mutually exclusive */ +#define KI_VALID_GID (1<<1) +#define KI_VALID_GUID (1<<2) +#define KI_VALID_NTSID (1<<3) +#define KI_VALID_PWNAM (1<<4) /* Used for translation */ +#define KI_VALID_GRNAM (1<<5) /* Used for translation */ +#define KI_VALID_GROUPS (1<<6) +#if CONFIG_EXT_RESOLVER /* * kauth_identity_init * @@ -741,7 +1135,7 @@ static int kauth_identity_find_ntsid(ntsid_t *ntsid, struct kauth_identity *kir) * * Returns: (void) * - * Notes: Intialize the credential identity resolver for use; the + * Notes: Initialize the credential identity resolver for use; the * credential identity resolver is the KPI used to communicate * with a user space credential identity resolver daemon. * @@ -752,7 +1146,7 @@ void kauth_identity_init(void) { TAILQ_INIT(&kauth_identities); - kauth_identity_mtx = lck_mtx_alloc_init(kauth_lck_grp, 0/*LCK_ATTR_NULL*/); + kauth_identity_mtx = lck_mtx_alloc_init(kauth_lck_grp, 0 /*LCK_ATTR_NULL*/); } @@ -765,19 +1159,21 @@ kauth_identity_init(void) * Parameters: uid * * Returns: NULL Insufficient memory to satisfy - * the request - * !NULL A pointer to the applocated + * the request or bad parameters + * !NULL A pointer to the allocated * 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), + * or NTSID can only refer to an NTSID 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) +kauth_identity_alloc(uid_t uid, gid_t gid, guid_t *guidp, time_t guid_expiry, + ntsid_t *ntsidp, time_t ntsid_expiry, int supgrpcnt, gid_t *supgrps, time_t groups_expiry, + const char *name, int nametype) { struct kauth_identity *kip; - + /* get and fill in a new identity */ MALLOC(kip, struct kauth_identity *, sizeof(*kip), M_KAUTH, M_WAITOK | M_ZERO); if (kip != NULL) { @@ -786,11 +1182,31 @@ kauth_identity_alloc(uid_t uid, gid_t gid, guid_t *guidp, time_t guid_expiry, nt kip->ki_valid = KI_VALID_GID; } if (uid != KAUTH_UID_NONE) { - if (kip->ki_valid & KI_VALID_GID) + if (kip->ki_valid & KI_VALID_GID) { panic("can't allocate kauth identity with both uid and gid"); + } kip->ki_uid = uid; kip->ki_valid = KI_VALID_UID; } + if (supgrpcnt) { + /* + * A malicious/faulty resolver could return bad values + */ + assert(supgrpcnt >= 0); + assert(supgrpcnt <= NGROUPS); + assert(supgrps != NULL); + + if ((supgrpcnt < 0) || (supgrpcnt > NGROUPS) || (supgrps == NULL)) { + return NULL; + } + if (kip->ki_valid & KI_VALID_GID) { + panic("can't allocate kauth identity with both gid and supplementary groups"); + } + kip->ki_supgrpcnt = supgrpcnt; + memcpy(kip->ki_supgrps, supgrps, sizeof(supgrps[0]) * supgrpcnt); + kip->ki_valid |= KI_VALID_GROUPS; + } + kip->ki_groups_expiry = groups_expiry; if (guidp != NULL) { kip->ki_guid = *guidp; kip->ki_valid |= KI_VALID_GUID; @@ -801,8 +1217,12 @@ kauth_identity_alloc(uid_t uid, gid_t gid, guid_t *guidp, time_t guid_expiry, nt kip->ki_valid |= KI_VALID_NTSID; } kip->ki_ntsid_expiry = ntsid_expiry; + if (name != NULL) { + kip->ki_name = name; + kip->ki_valid |= nametype; + } } - return(kip); + return kip; } @@ -810,7 +1230,7 @@ kauth_identity_alloc(uid_t uid, gid_t gid, guid_t *guidp, time_t guid_expiry, nt * kauth_identity_register_and_free * * Description: Register an association between identity tokens. The passed - * 'kip' is freed by this function. + * 'kip' is consumed by this function. * * Parameters: kip Pointer to kauth_identity * structure to register @@ -832,19 +1252,22 @@ kauth_identity_register_and_free(struct kauth_identity *kip) ip = NULL; KAUTH_IDENTITY_LOCK(); if (kip->ki_valid & KI_VALID_UID) { - if (kip->ki_valid & KI_VALID_GID) + if (kip->ki_valid & KI_VALID_GID) { panic("kauth_identity: can't insert record with both UID and GID as key"); + } TAILQ_FOREACH(ip, &kauth_identities, ki_link) - if ((ip->ki_valid & KI_VALID_UID) && (ip->ki_uid == kip->ki_uid)) - break; + if ((ip->ki_valid & KI_VALID_UID) && (ip->ki_uid == kip->ki_uid)) { + break; + } } else if (kip->ki_valid & KI_VALID_GID) { TAILQ_FOREACH(ip, &kauth_identities, ki_link) - if ((ip->ki_valid & KI_VALID_GID) && (ip->ki_gid == kip->ki_gid)) - break; + if ((ip->ki_valid & KI_VALID_GID) && (ip->ki_gid == kip->ki_gid)) { + break; + } } else { panic("kauth_identity: can't insert record without UID or GID as key"); } - + if (ip != NULL) { /* we already have an entry, merge/overwrite */ if (kip->ki_valid & KI_VALID_GUID) { @@ -857,22 +1280,40 @@ kauth_identity_register_and_free(struct kauth_identity *kip) ip->ki_valid |= KI_VALID_NTSID; } ip->ki_ntsid_expiry = kip->ki_ntsid_expiry; - /* and discard the incoming identity */ - FREE(kip, M_KAUTH); - ip = NULL; + /* a valid ki_name field overwrites the previous name field */ + if (kip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM)) { + /* if there's an old one, discard it */ + const char *oname = NULL; + if (ip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM)) { + oname = ip->ki_name; + } + ip->ki_name = kip->ki_name; + kip->ki_name = oname; + } + /* and discard the incoming entry */ + ip = kip; } else { - /* don't have any information on this identity, so just add it */ + /* + * if we don't have any information on this identity, add it; + * if it pushes us over our limit, discard the oldest one. + */ TAILQ_INSERT_HEAD(&kauth_identities, kip, ki_link); - if (++kauth_identity_count > KAUTH_IDENTITY_CACHEMAX) { + if (++kauth_identity_count > kauth_identity_cachemax) { ip = TAILQ_LAST(&kauth_identities, kauth_identity_head); TAILQ_REMOVE(&kauth_identities, ip, ki_link); kauth_identity_count--; } } KAUTH_IDENTITY_UNLOCK(); - /* have to drop lock before freeing expired entry */ - if (ip != NULL) + /* have to drop lock before freeing expired entry (it may be in use) */ + if (ip != NULL) { + /* if the ki_name field is used, clear it first */ + if (ip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM)) { + vfs_removename(ip->ki_name); + } + /* free the expired entry */ FREE(ip, M_KAUTH); + } } @@ -880,45 +1321,97 @@ kauth_identity_register_and_free(struct kauth_identity *kip) * kauth_identity_updatecache * * Description: Given a lookup result, add any associations that we don't - * currently have. + * currently have; replace ones which have changed. * * Parameters: elp External lookup result from * user space daemon to kernel * rkip pointer to returned kauth * identity, or NULL + * extend_data Extended data (can vary) * * Returns: (void) * * Implicit returns: * *rkip Modified (if non-NULL) + * + * Notes: For extended information requests, this code relies on the fact + * that elp->el_flags is never used as an rvalue, and is only + * ever bit-tested for valid lookup information we are willing + * to cache. + * + * XXX: We may have to do the same in the case that extended data was + * passed out to user space to ensure that the request string + * gets cached; we may also be able to use the rkip as an + * input to avoid this. The jury is still out. + * + * XXX: This codes performance could be improved for multiple valid + * results by combining the loop iteration in a single loop. */ static void -kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_identity *rkip) +kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_identity *rkip, uint64_t extend_data) { struct timeval tv; struct kauth_identity *kip; + const char *speculative_name = NULL; microuptime(&tv); - + + /* + * If there is extended data, and that data represents a name rather + * than something else, speculatively create an entry for it in the + * string cache. We do this to avoid holding the KAUTH_IDENTITY_LOCK + * over the allocation later. + */ + if (elp->el_flags & (KAUTH_EXTLOOKUP_VALID_PWNAM | KAUTH_EXTLOOKUP_VALID_GRNAM)) { + const char *tmp = CAST_DOWN(const char *, extend_data); + speculative_name = vfs_addname(tmp, strnlen(tmp, MAXPATHLEN - 1), 0, 0); + } + /* user identity? */ if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_UID) { KAUTH_IDENTITY_LOCK(); TAILQ_FOREACH(kip, &kauth_identities, ki_link) { /* matching record */ if ((kip->ki_valid & KI_VALID_UID) && (kip->ki_uid == elp->el_uid)) { + if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_SUPGRPS) { + assert(elp->el_sup_grp_cnt <= NGROUPS); + if (elp->el_sup_grp_cnt > NGROUPS) { + KAUTH_DEBUG("CACHE - invalid sup_grp_cnt provided (%d), truncating to %d", + elp->el_sup_grp_cnt, NGROUPS); + elp->el_sup_grp_cnt = NGROUPS; + } + kip->ki_supgrpcnt = elp->el_sup_grp_cnt; + memcpy(kip->ki_supgrps, elp->el_sup_groups, sizeof(elp->el_sup_groups[0]) * kip->ki_supgrpcnt); + kip->ki_valid |= KI_VALID_GROUPS; + kip->ki_groups_expiry = (elp->el_member_valid) ? tv.tv_sec + elp->el_member_valid : 0; + } if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_UGUID) { kip->ki_guid = elp->el_uguid; kip->ki_valid |= KI_VALID_GUID; } - kip->ki_guid_expiry = tv.tv_sec + elp->el_uguid_valid; + kip->ki_guid_expiry = (elp->el_uguid_valid) ? tv.tv_sec + elp->el_uguid_valid : 0; if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_USID) { kip->ki_ntsid = elp->el_usid; kip->ki_valid |= KI_VALID_NTSID; } - kip->ki_ntsid_expiry = tv.tv_sec + elp->el_usid_valid; + kip->ki_ntsid_expiry = (elp->el_usid_valid) ? tv.tv_sec + elp->el_usid_valid : 0; + if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_PWNAM) { + const char *oname = kip->ki_name; + kip->ki_name = speculative_name; + speculative_name = NULL; + kip->ki_valid |= KI_VALID_PWNAM; + if (oname) { + /* + * free oname (if any) outside + * the lock + */ + speculative_name = oname; + } + } kauth_identity_lru(kip); - if (rkip != NULL) + if (rkip != NULL) { *rkip = *kip; + } KAUTH_DEBUG("CACHE - refreshed %d is " K_UUID_FMT, kip->ki_uid, K_UUID_ARG(kip->ki_guid)); break; } @@ -928,20 +1421,29 @@ kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_id if (kip == NULL) { kip = kauth_identity_alloc(elp->el_uid, KAUTH_GID_NONE, (elp->el_flags & KAUTH_EXTLOOKUP_VALID_UGUID) ? &elp->el_uguid : NULL, - tv.tv_sec + elp->el_uguid_valid, + (elp->el_uguid_valid) ? tv.tv_sec + elp->el_uguid_valid : 0, (elp->el_flags & KAUTH_EXTLOOKUP_VALID_USID) ? &elp->el_usid : NULL, - tv.tv_sec + elp->el_usid_valid); + (elp->el_usid_valid) ? tv.tv_sec + elp->el_usid_valid : 0, + (elp->el_flags & KAUTH_EXTLOOKUP_VALID_SUPGRPS) ? elp->el_sup_grp_cnt : 0, + (elp->el_flags & KAUTH_EXTLOOKUP_VALID_SUPGRPS) ? elp->el_sup_groups : NULL, + (elp->el_member_valid) ? tv.tv_sec + elp->el_member_valid : 0, + (elp->el_flags & KAUTH_EXTLOOKUP_VALID_PWNAM) ? speculative_name : NULL, + KI_VALID_PWNAM); if (kip != NULL) { - if (rkip != NULL) + if (rkip != NULL) { *rkip = *kip; + } + if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_PWNAM) { + speculative_name = NULL; + } KAUTH_DEBUG("CACHE - learned %d is " K_UUID_FMT, kip->ki_uid, K_UUID_ARG(kip->ki_guid)); kauth_identity_register_and_free(kip); } } } - /* group identity? */ - if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GID) { + /* group identity? (ignore, if we already processed it as a user) */ + if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GID && !(elp->el_flags & KAUTH_EXTLOOKUP_VALID_UID)) { KAUTH_IDENTITY_LOCK(); TAILQ_FOREACH(kip, &kauth_identities, ki_link) { /* matching record */ @@ -950,15 +1452,29 @@ kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_id kip->ki_guid = elp->el_gguid; kip->ki_valid |= KI_VALID_GUID; } - kip->ki_guid_expiry = tv.tv_sec + elp->el_gguid_valid; + kip->ki_guid_expiry = (elp->el_gguid_valid) ? tv.tv_sec + elp->el_gguid_valid : 0; if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GSID) { kip->ki_ntsid = elp->el_gsid; kip->ki_valid |= KI_VALID_NTSID; } - kip->ki_ntsid_expiry = tv.tv_sec + elp->el_gsid_valid; + kip->ki_ntsid_expiry = (elp->el_gsid_valid) ? tv.tv_sec + elp->el_gsid_valid : 0; + if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GRNAM) { + const char *oname = kip->ki_name; + kip->ki_name = speculative_name; + speculative_name = NULL; + kip->ki_valid |= KI_VALID_GRNAM; + if (oname) { + /* + * free oname (if any) outside + * the lock + */ + speculative_name = oname; + } + } kauth_identity_lru(kip); - if (rkip != NULL) + if (rkip != NULL) { *rkip = *kip; + } KAUTH_DEBUG("CACHE - refreshed %d is " K_UUID_FMT, kip->ki_uid, K_UUID_ARG(kip->ki_guid)); break; } @@ -968,26 +1484,59 @@ kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_id if (kip == NULL) { kip = kauth_identity_alloc(KAUTH_UID_NONE, elp->el_gid, (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GGUID) ? &elp->el_gguid : NULL, - tv.tv_sec + elp->el_gguid_valid, + (elp->el_gguid_valid) ? tv.tv_sec + elp->el_gguid_valid : 0, (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GSID) ? &elp->el_gsid : NULL, - tv.tv_sec + elp->el_gsid_valid); + (elp->el_gsid_valid) ? tv.tv_sec + elp->el_gsid_valid : 0, + (elp->el_flags & KAUTH_EXTLOOKUP_VALID_SUPGRPS) ? elp->el_sup_grp_cnt : 0, + (elp->el_flags & KAUTH_EXTLOOKUP_VALID_SUPGRPS) ? elp->el_sup_groups : NULL, + (elp->el_member_valid) ? tv.tv_sec + elp->el_member_valid : 0, + (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GRNAM) ? speculative_name : NULL, + KI_VALID_GRNAM); if (kip != NULL) { - if (rkip != NULL) + if (rkip != NULL) { *rkip = *kip; + } + if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GRNAM) { + speculative_name = NULL; + } KAUTH_DEBUG("CACHE - learned %d is " K_UUID_FMT, kip->ki_uid, K_UUID_ARG(kip->ki_guid)); kauth_identity_register_and_free(kip); } } } + /* If we have a name reference to drop, drop it here */ + if (speculative_name != NULL) { + vfs_removename(speculative_name); + } } /* - * kauth_identity_lru - * - * Description: Promote the entry to the head of the LRU, assumes the cache - * is locked. + * Trim older entries from the identity cache. + * + * Must be called with the identity cache lock held. + */ +static void +kauth_identity_trimcache(int newsize) +{ + struct kauth_identity *kip; + + lck_mtx_assert(kauth_identity_mtx, LCK_MTX_ASSERT_OWNED); + + while (kauth_identity_count > newsize) { + kip = TAILQ_LAST(&kauth_identities, kauth_identity_head); + TAILQ_REMOVE(&kauth_identities, kip, ki_link); + kauth_identity_count--; + FREE(kip, M_KAUTH); + } +} + +/* + * kauth_identity_lru + * + * Description: Promote the entry to the head of the LRU, assumes the cache + * is locked. * * Parameters: kip kauth identity to move to the * head of the LRU list, if it's @@ -1026,9 +1575,17 @@ kauth_identity_guid_expired(struct kauth_identity *kip) { struct timeval tv; + /* + * Expiration time of 0 means this entry is persistent. + */ + if (kip->ki_guid_expiry == 0) { + return 0; + } + microuptime(&tv); - KAUTH_DEBUG("CACHE - GUID expires @ %d now %d", kip->ki_guid_expiry, tv.tv_sec); - return((kip->ki_guid_expiry <= tv.tv_sec) ? 1 : 0); + KAUTH_DEBUG("CACHE - GUID expires @ %ld now %ld", kip->ki_guid_expiry, tv.tv_sec); + + return (kip->ki_guid_expiry <= tv.tv_sec) ? 1 : 0; } @@ -1048,11 +1605,47 @@ kauth_identity_ntsid_expired(struct kauth_identity *kip) { struct timeval tv; + /* + * Expiration time of 0 means this entry is persistent. + */ + if (kip->ki_ntsid_expiry == 0) { + return 0; + } + microuptime(&tv); - KAUTH_DEBUG("CACHE - NTSID expires @ %d now %d", kip->ki_ntsid_expiry, tv.tv_sec); - return((kip->ki_ntsid_expiry <= tv.tv_sec) ? 1 : 0); + KAUTH_DEBUG("CACHE - NTSID expires @ %ld now %ld", kip->ki_ntsid_expiry, tv.tv_sec); + + return (kip->ki_ntsid_expiry <= tv.tv_sec) ? 1 : 0; } +/* + * kauth_identity_groups_expired + * + * Description: Handle lazy expiration of supplemental group translations. + * + * Parameters: kip kauth identity to check for + * groups expiration + * + * Returns: 1 Expired + * 0 Not expired + */ +static int +kauth_identity_groups_expired(struct kauth_identity *kip) +{ + struct timeval tv; + + /* + * Expiration time of 0 means this entry is persistent. + */ + if (kip->ki_groups_expiry == 0) { + return 0; + } + + microuptime(&tv); + KAUTH_DEBUG("CACHE - GROUPS expires @ %ld now %ld\n", kip->ki_groups_expiry, tv.tv_sec); + + return (kip->ki_groups_expiry <= tv.tv_sec) ? 1 : 0; +} /* * kauth_identity_find_uid @@ -1061,6 +1654,7 @@ kauth_identity_ntsid_expired(struct kauth_identity *kip) * * Parameters: uid UID to find * kir Pointer to return area + * getname Name buffer, if ki_name wanted * * Returns: 0 Found * ENOENT Not found @@ -1069,7 +1663,7 @@ kauth_identity_ntsid_expired(struct kauth_identity *kip) * *klr Modified, if found */ static int -kauth_identity_find_uid(uid_t uid, struct kauth_identity *kir) +kauth_identity_find_uid(uid_t uid, struct kauth_identity *kir, char *getname) { struct kauth_identity *kip; @@ -1079,21 +1673,26 @@ kauth_identity_find_uid(uid_t uid, struct kauth_identity *kir) kauth_identity_lru(kip); /* Copy via structure assignment */ *kir = *kip; + /* If a name is wanted and one exists, copy it out */ + if (getname != NULL && (kip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM))) { + strlcpy(getname, kip->ki_name, MAXPATHLEN); + } break; } } KAUTH_IDENTITY_UNLOCK(); - return((kip == NULL) ? ENOENT : 0); + return (kip == NULL) ? ENOENT : 0; } /* - * kauth_identity_find_uid + * kauth_identity_find_gid * * Description: Search for an entry by GID * * Parameters: gid GID to find * kir Pointer to return area + * getname Name buffer, if ki_name wanted * * Returns: 0 Found * ENOENT Not found @@ -1102,7 +1701,7 @@ kauth_identity_find_uid(uid_t uid, struct kauth_identity *kir) * *klr Modified, if found */ static int -kauth_identity_find_gid(uid_t gid, struct kauth_identity *kir) +kauth_identity_find_gid(uid_t gid, struct kauth_identity *kir, char *getname) { struct kauth_identity *kip; @@ -1112,11 +1711,15 @@ kauth_identity_find_gid(uid_t gid, struct kauth_identity *kir) kauth_identity_lru(kip); /* Copy via structure assignment */ *kir = *kip; + /* If a name is wanted and one exists, copy it out */ + if (getname != NULL && (kip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM))) { + strlcpy(getname, kip->ki_name, MAXPATHLEN); + } break; } } KAUTH_IDENTITY_UNLOCK(); - return((kip == NULL) ? ENOENT : 0); + return (kip == NULL) ? ENOENT : 0; } @@ -1127,6 +1730,7 @@ kauth_identity_find_gid(uid_t gid, struct kauth_identity *kir) * * Parameters: guidp Pointer to GUID to find * kir Pointer to return area + * getname Name buffer, if ki_name wanted * * Returns: 0 Found * ENOENT Not found @@ -1138,13 +1742,50 @@ kauth_identity_find_gid(uid_t gid, struct kauth_identity *kir) * may elect to call out to userland to revalidate. */ static int -kauth_identity_find_guid(guid_t *guidp, struct kauth_identity *kir) +kauth_identity_find_guid(guid_t *guidp, struct kauth_identity *kir, char *getname) { struct kauth_identity *kip; KAUTH_IDENTITY_LOCK(); 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; + /* If a name is wanted and one exists, copy it out */ + if (getname != NULL && (kip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM))) { + strlcpy(getname, kip->ki_name, MAXPATHLEN); + } + break; + } + } + KAUTH_IDENTITY_UNLOCK(); + return (kip == NULL) ? ENOENT : 0; +} + +/* + * kauth_identity_find_nam + * + * Description: Search for an entry by name + * + * Parameters: name Pointer to name to find + * valid KI_VALID_PWNAM or KI_VALID_GRNAM + * kir Pointer to return area + * + * Returns: 0 Found + * ENOENT Not found + * + * Implicit returns: + * *klr Modified, if found + */ +static int +kauth_identity_find_nam(char *name, int valid, struct kauth_identity *kir) +{ + struct kauth_identity *kip; + + KAUTH_IDENTITY_LOCK(); + TAILQ_FOREACH(kip, &kauth_identities, ki_link) { + if ((kip->ki_valid & valid) && !strcmp(name, kip->ki_name)) { kauth_identity_lru(kip); /* Copy via structure assignment */ *kir = *kip; @@ -1152,7 +1793,7 @@ kauth_identity_find_guid(guid_t *guidp, struct kauth_identity *kir) } } KAUTH_IDENTITY_UNLOCK(); - return((kip == NULL) ? ENOENT : 0); + return (kip == NULL) ? ENOENT : 0; } @@ -1163,6 +1804,7 @@ kauth_identity_find_guid(guid_t *guidp, struct kauth_identity *kir) * * Parameters: ntsid Pointer to NTSID to find * kir Pointer to return area + * getname Name buffer, if ki_name wanted * * Returns: 0 Found * ENOENT Not found @@ -1174,7 +1816,7 @@ kauth_identity_find_guid(guid_t *guidp, struct kauth_identity *kir) * may elect to call out to userland to revalidate. */ static int -kauth_identity_find_ntsid(ntsid_t *ntsid, struct kauth_identity *kir) +kauth_identity_find_ntsid(ntsid_t *ntsid, struct kauth_identity *kir, char *getname) { struct kauth_identity *kip; @@ -1184,12 +1826,17 @@ kauth_identity_find_ntsid(ntsid_t *ntsid, struct kauth_identity *kir) kauth_identity_lru(kip); /* Copy via structure assignment */ *kir = *kip; + /* If a name is wanted and one exists, copy it out */ + if (getname != NULL && (kip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM))) { + strlcpy(getname, kip->ki_name, MAXPATHLEN); + } break; } } KAUTH_IDENTITY_UNLOCK(); - return((kip == NULL) ? ENOENT : 0); + return (kip == NULL) ? ENOENT : 0; } +#endif /* CONFIG_EXT_RESOLVER */ /* @@ -1206,13 +1853,13 @@ guid_t kauth_null_guid; * Parameters: guid1 Pointer to first GUID * guid2 Pointer to second GUID * - * Returns: 0 If GUIDs are inequal + * Returns: 0 If GUIDs are unequal * !0 If GUIDs are equal */ int kauth_guid_equal(guid_t *guid1, guid_t *guid2) { - return(bcmp(guid1, guid2, sizeof(*guid1)) == 0); + return bcmp(guid1, guid2, sizeof(*guid1)) == 0; } @@ -1223,7 +1870,7 @@ kauth_guid_equal(guid_t *guid1, guid_t *guid2) * * Parameters: guid Pointer to GUID to check * - * Returns: KAUTH_WKG_NOT Not a wel known GUID + * Returns: KAUTH_WKG_NOT Not a well known GUID * KAUTH_WKG_EVERYBODY "Everybody" * KAUTH_WKG_NOBODY "Nobody" * KAUTH_WKG_OWNER "Other" @@ -1232,8 +1879,8 @@ kauth_guid_equal(guid_t *guid1, guid_t *guid2) int kauth_wellknown_guid(guid_t *guid) { - static char fingerprint[] = {0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef}; - int code; + static char fingerprint[] = {0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef}; + uint32_t code; /* * All WKGs begin with the same 12 bytes. */ @@ -1241,31 +1888,31 @@ kauth_wellknown_guid(guid_t *guid) /* * The final 4 bytes are our code (in network byte order). */ - code = OSSwapHostToBigInt32(*(u_int32_t *)&guid->g_guid[12]); - switch(code) { + code = OSSwapHostToBigInt32(*(uint32_t *)&guid->g_guid[12]); + switch (code) { case 0x0000000c: - return(KAUTH_WKG_EVERYBODY); + return KAUTH_WKG_EVERYBODY; case 0xfffffffe: - return(KAUTH_WKG_NOBODY); + return KAUTH_WKG_NOBODY; case 0x0000000a: - return(KAUTH_WKG_OWNER); + return KAUTH_WKG_OWNER; case 0x00000010: - return(KAUTH_WKG_GROUP); + return KAUTH_WKG_GROUP; } } - return(KAUTH_WKG_NOT); + return KAUTH_WKG_NOT; } /* * kauth_ntsid_equal * - * Description: Determine the equality of two NTSIDs (NT Security Identifiers) + * Description: Determine the equality of two NTSIDs (NT Security Identifiers) * - * Paramters: sid1 Pointer to first NTSID + * Parameters: sid1 Pointer to first NTSID * sid2 Pointer to second NTSID * - * Returns: 0 If GUIDs are inequal + * Returns: 0 If GUIDs are unequal * !0 If GUIDs are equal */ int @@ -1274,9 +1921,10 @@ 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)) == 0) - return(1); - return(0); + bcmp(sid1, sid2, KAUTH_NTSID_SIZE(sid1)) == 0) { + return 1; + } + return 0; } @@ -1293,7 +1941,6 @@ kauth_ntsid_equal(ntsid_t *sid1, ntsid_t *sid2) * be done using it. */ -static int kauth_cred_cache_lookup(int from, int to, void *src, void *dst); /* @@ -1322,21 +1969,22 @@ static int kauth_cred_cache_lookup(int from, int to, void *src, void *dst); static int kauth_cred_change_egid(kauth_cred_t cred, gid_t new_egid) { - int i; - int displaced = 1; + int i; + int displaced = 1; #if radar_4600026 - int is_member; -#endif /* radar_4600026 */ - gid_t old_egid = cred->cr_groups[0]; + int is_member; +#endif /* radar_4600026 */ + gid_t old_egid = kauth_cred_getgid(cred); + posix_cred_t pcred = posix_cred_get(cred); /* Ignoring the first entry, scan for a match for the new egid */ - for (i = 1; i < cred->cr_ngroups; i++) { + for (i = 1; i < pcred->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; + if (pcred->cr_groups[i] == new_egid) { + pcred->cr_groups[i] = old_egid; DEBUG_CRED_CHANGE("kauth_cred_change_egid: unset displaced\n"); displaced = 0; break; @@ -1347,12 +1995,12 @@ kauth_cred_change_egid(kauth_cred_t cred, gid_t new_egid) #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). -*/ + * 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 @@ -1362,18 +2010,18 @@ conservative approach (i.e. less likely to cause things to break). * * NB: This is typically a cold code path. */ - if (displaced && !(cred->cr_flags & CRF_NOMEMBERD) && + if (displaced && !(pcred->cr_flags & CRF_NOMEMBERD) && kauth_cred_ismember_gid(cred, new_egid, &is_member) == 0 && is_member) { - displaced = 0; + displaced = 0; DEBUG_CRED_CHANGE("kauth_cred_change_egid: reset displaced\n"); } -#endif /* radar_4600026 */ +#endif /* radar_4600026 */ /* set the new EGID into the old spot */ - cred->cr_groups[0] = new_egid; + pcred->cr_groups[0] = new_egid; - return (displaced); + return displaced; } @@ -1390,159 +2038,256 @@ uid_t kauth_cred_getuid(kauth_cred_t cred) { NULLCRED_CHECK(cred); - return(cred->cr_uid); + return posix_cred_get(cred)->cr_uid; } /* - * kauth_cred_getgid + * kauth_cred_getruid * - * Description: Fetch GID from credential + * Description: Fetch RUID from credential * * Parameters: cred Credential to examine * - * Returns: (gid_t) GID associated with credential + * Returns: (uid_t) RUID associated with credential */ uid_t -kauth_cred_getgid(kauth_cred_t cred) +kauth_cred_getruid(kauth_cred_t cred) { NULLCRED_CHECK(cred); - return(cred->cr_gid); + return posix_cred_get(cred)->cr_ruid; } /* - * kauth_cred_guid2uid - * - * Description: Fetch UID from GUID + * kauth_cred_getsvuid * - * Parameters: guidp Pointer to GUID to examine - * uidp Pointer to buffer for UID + * Description: Fetch SVUID from credential * - * Returns: 0 Success - * kauth_cred_cache_lookup:EINVAL + * Parameters: cred Credential to examine * - * Implicit returns: - * *uidp Modified, if successful + * Returns: (uid_t) SVUID associated with credential */ -int -kauth_cred_guid2uid(guid_t *guidp, uid_t *uidp) +uid_t +kauth_cred_getsvuid(kauth_cred_t cred) { - return(kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_UID, guidp, uidp)); + NULLCRED_CHECK(cred); + return posix_cred_get(cred)->cr_svuid; } /* - * kauth_cred_guid2gid - * - * Description: Fetch GID from GUID + * kauth_cred_getgid * - * Parameters: guidp Pointer to GUID to examine - * gidp Pointer to buffer for GID + * Description: Fetch GID from credential * - * Returns: 0 Success - * kauth_cred_cache_lookup:EINVAL + * Parameters: cred Credential to examine * - * Implicit returns: - * *gidp Modified, if successful + * Returns: (gid_t) GID associated with credential */ -int -kauth_cred_guid2gid(guid_t *guidp, gid_t *gidp) +gid_t +kauth_cred_getgid(kauth_cred_t cred) { - return(kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_GID, guidp, gidp)); + NULLCRED_CHECK(cred); + return posix_cred_get(cred)->cr_gid; } /* - * kauth_cred_ntsid2uid + * kauth_cred_getrgid * - * Description: Fetch UID from NTSID + * Description: Fetch RGID from credential * - * Parameters: sidp Pointer to NTSID to examine - * uidp Pointer to buffer for UID + * Parameters: cred Credential to examine * - * Returns: 0 Success - * kauth_cred_cache_lookup:EINVAL + * Returns: (gid_t) RGID associated with credential + */ +gid_t +kauth_cred_getrgid(kauth_cred_t cred) +{ + NULLCRED_CHECK(cred); + return posix_cred_get(cred)->cr_rgid; +} + + +/* + * kauth_cred_getsvgid * - * Implicit returns: - * *uidp Modified, if successful + * Description: Fetch SVGID from credential + * + * Parameters: cred Credential to examine + * + * Returns: (gid_t) SVGID associated with credential */ -int -kauth_cred_ntsid2uid(ntsid_t *sidp, uid_t *uidp) +gid_t +kauth_cred_getsvgid(kauth_cred_t cred) { - return(kauth_cred_cache_lookup(KI_VALID_NTSID, KI_VALID_UID, sidp, uidp)); + NULLCRED_CHECK(cred); + return posix_cred_get(cred)->cr_svgid; } +static int kauth_cred_cache_lookup(int from, int to, void *src, void *dst); + +#if CONFIG_EXT_RESOLVER == 0 /* - * kauth_cred_ntsid2gid + * If there's no resolver, only support a subset of the kauth_cred_x2y() lookups. + */ +static __inline int +kauth_cred_cache_lookup(int from, int to, void *src, void *dst) +{ + /* NB: These must match the definitions used by Libinfo's mbr_identifier_translate(). */ + static const uuid_t _user_compat_prefix = {0xff, 0xff, 0xee, 0xee, 0xdd, 0xdd, 0xcc, 0xcc, 0xbb, 0xbb, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00}; + static const uuid_t _group_compat_prefix = {0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0x00}; +#define COMPAT_PREFIX_LEN (sizeof(uuid_t) - sizeof(id_t)) + + assert(from != to); + + switch (from) { + case KI_VALID_UID: { + id_t uid = htonl(*(id_t *)src); + + if (to == KI_VALID_GUID) { + uint8_t *uu = dst; + memcpy(uu, _user_compat_prefix, sizeof(_user_compat_prefix)); + memcpy(&uu[COMPAT_PREFIX_LEN], &uid, sizeof(uid)); + return 0; + } + break; + } + case KI_VALID_GID: { + id_t gid = htonl(*(id_t *)src); + + if (to == KI_VALID_GUID) { + uint8_t *uu = dst; + memcpy(uu, _group_compat_prefix, sizeof(_group_compat_prefix)); + memcpy(&uu[COMPAT_PREFIX_LEN], &gid, sizeof(gid)); + return 0; + } + break; + } + case KI_VALID_GUID: { + const uint8_t *uu = src; + + if (to == KI_VALID_UID) { + if (memcmp(uu, _user_compat_prefix, COMPAT_PREFIX_LEN) == 0) { + id_t uid; + memcpy(&uid, &uu[COMPAT_PREFIX_LEN], sizeof(uid)); + *(id_t *)dst = ntohl(uid); + return 0; + } + } else if (to == KI_VALID_GID) { + if (memcmp(uu, _group_compat_prefix, COMPAT_PREFIX_LEN) == 0) { + id_t gid; + memcpy(&gid, &uu[COMPAT_PREFIX_LEN], sizeof(gid)); + *(id_t *)dst = ntohl(gid); + return 0; + } + } + break; + } + default: + /* NOT IMPLEMENTED */ + break; + } + return ENOENT; +} +#endif + +#if defined(CONFIG_EXT_RESOLVER) && (CONFIG_EXT_RESOLVER) +/* + * Structure to hold supplemental groups. Used for impedance matching with + * kauth_cred_cache_lookup below. + */ +struct supgroups { + int *count; + gid_t *groups; +}; + +/* + * kauth_cred_uid2groups * - * Description: Fetch GID from NTSID + * Description: Fetch supplemental GROUPS from UID * - * Parameters: sidp Pointer to NTSID to examine - * gidp Pointer to buffer for GID + * Parameters: uid UID to examine + * groups pointer to an array of gid_ts + * gcount pointer to the number of groups wanted/returned * * Returns: 0 Success * kauth_cred_cache_lookup:EINVAL * * Implicit returns: - * *gidp Modified, if successful + * *groups Modified, if successful + * *gcount Modified, if successful + * */ -int -kauth_cred_ntsid2gid(ntsid_t *sidp, gid_t *gidp) +static int +kauth_cred_uid2groups(uid_t *uid, gid_t *groups, int *gcount) { - return(kauth_cred_cache_lookup(KI_VALID_NTSID, KI_VALID_GID, sidp, gidp)); -} + int rv; + + struct supgroups supgroups; + supgroups.count = gcount; + supgroups.groups = groups; + rv = kauth_cred_cache_lookup(KI_VALID_UID, KI_VALID_GROUPS, uid, &supgroups); + + return rv; +} +#endif /* - * kauth_cred_ntsid2guid + * kauth_cred_guid2pwnam * - * Description: Fetch GUID from NTSID + * Description: Fetch PWNAM from GUID * - * Parameters: sidp Pointer to NTSID to examine - * guidp Pointer to buffer for GUID + * Parameters: guidp Pointer to GUID to examine + * pwnam Pointer to user@domain buffer * * Returns: 0 Success * kauth_cred_cache_lookup:EINVAL * * Implicit returns: - * *guidp Modified, if successful + * *pwnam Modified, if successful + * + * Notes: pwnam is assumed to point to a buffer of MAXPATHLEN in size */ int -kauth_cred_ntsid2guid(ntsid_t *sidp, guid_t *guidp) +kauth_cred_guid2pwnam(guid_t *guidp, char *pwnam) { - return(kauth_cred_cache_lookup(KI_VALID_NTSID, KI_VALID_GUID, sidp, guidp)); + return kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_PWNAM, guidp, pwnam); } /* - * kauth_cred_uid2guid + * kauth_cred_guid2grnam * - * Description: Fetch GUID from UID + * Description: Fetch GRNAM from GUID * - * Parameters: uid UID to examine - * guidp Pointer to buffer for GUID + * Parameters: guidp Pointer to GUID to examine + * grnam Pointer to group@domain buffer * * Returns: 0 Success * kauth_cred_cache_lookup:EINVAL * * Implicit returns: - * *guidp Modified, if successful + * *grnam Modified, if successful + * + * Notes: grnam is assumed to point to a buffer of MAXPATHLEN in size */ int -kauth_cred_uid2guid(uid_t uid, guid_t *guidp) +kauth_cred_guid2grnam(guid_t *guidp, char *grnam) { - return(kauth_cred_cache_lookup(KI_VALID_UID, KI_VALID_GUID, &uid, guidp)); + return kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_GRNAM, guidp, grnam); } /* - * kauth_cred_getguid + * kauth_cred_pwnam2guid * - * Description: Fetch GUID from credential + * Description: Fetch PWNAM from GUID * - * Parameters: cred Credential to examine + * Parameters: pwnam String containing user@domain * guidp Pointer to buffer for GUID * * Returns: 0 Success @@ -1550,21 +2295,23 @@ kauth_cred_uid2guid(uid_t uid, guid_t *guidp) * * Implicit returns: * *guidp Modified, if successful + * + * Notes: pwnam should not point to a request larger than MAXPATHLEN + * bytes in size, including the NUL termination of the string. */ int -kauth_cred_getguid(kauth_cred_t cred, guid_t *guidp) +kauth_cred_pwnam2guid(char *pwnam, guid_t *guidp) { - NULLCRED_CHECK(cred); - return(kauth_cred_uid2guid(kauth_cred_getuid(cred), guidp)); + return kauth_cred_cache_lookup(KI_VALID_PWNAM, KI_VALID_GUID, pwnam, guidp); } /* - * kauth_cred_getguid + * kauth_cred_grnam2guid * - * Description: Fetch GUID from GID + * Description: Fetch GRNAM from GUID * - * Parameters: gid GID to examine + * Parameters: grnam String containing group@domain * guidp Pointer to buffer for GUID * * Returns: 0 Success @@ -1572,96 +2319,307 @@ kauth_cred_getguid(kauth_cred_t cred, guid_t *guidp) * * Implicit returns: * *guidp Modified, if successful + * + * Notes: grnam should not point to a request larger than MAXPATHLEN + * bytes in size, including the NUL termination of the string. */ int -kauth_cred_gid2guid(gid_t gid, guid_t *guidp) +kauth_cred_grnam2guid(char *grnam, guid_t *guidp) { - return(kauth_cred_cache_lookup(KI_VALID_GID, KI_VALID_GUID, &gid, guidp)); + return kauth_cred_cache_lookup(KI_VALID_GRNAM, KI_VALID_GUID, grnam, guidp); } /* - * kauth_cred_uid2ntsid + * kauth_cred_guid2uid * - * Description: Fetch NTSID from UID + * Description: Fetch UID from GUID * - * Parameters: uid UID to examine - * sidp Pointer to buffer for NTSID + * Parameters: guidp Pointer to GUID to examine + * uidp Pointer to buffer for UID * * Returns: 0 Success * kauth_cred_cache_lookup:EINVAL * * Implicit returns: - * *sidp Modified, if successful + * *uidp Modified, if successful */ int -kauth_cred_uid2ntsid(uid_t uid, ntsid_t *sidp) +kauth_cred_guid2uid(guid_t *guidp, uid_t *uidp) { - return(kauth_cred_cache_lookup(KI_VALID_UID, KI_VALID_NTSID, &uid, sidp)); + return kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_UID, guidp, uidp); } /* - * kauth_cred_getntsid + * kauth_cred_guid2gid * - * Description: Fetch NTSID from credential + * Description: Fetch GID from GUID * - * Parameters: cred Credential to examine - * sidp Pointer to buffer for NTSID + * Parameters: guidp Pointer to GUID to examine + * gidp Pointer to buffer for GID * * Returns: 0 Success * kauth_cred_cache_lookup:EINVAL * * Implicit returns: - * *sidp Modified, if successful + * *gidp Modified, if successful */ int -kauth_cred_getntsid(kauth_cred_t cred, ntsid_t *sidp) +kauth_cred_guid2gid(guid_t *guidp, gid_t *gidp) { - NULLCRED_CHECK(cred); - return(kauth_cred_uid2ntsid(kauth_cred_getuid(cred), sidp)); + return kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_GID, guidp, gidp); } - /* - * kauth_cred_gid2ntsid + * kauth_cred_nfs4domain2dsnode * - * Description: Fetch NTSID from GID + * Description: Fetch dsnode from nfs4domain * - * Parameters: gid GID to examine - * sidp Pointer to buffer for NTSID + * Parameters: nfs4domain Pointer to a string nfs4 domain + * dsnode Pointer to buffer for dsnode * * Returns: 0 Success - * kauth_cred_cache_lookup:EINVAL + * ENOENT For now just a stub that always fails * * Implicit returns: - * *sidp Modified, if successful + * *dsnode Modified, if successuful */ int -kauth_cred_gid2ntsid(gid_t gid, ntsid_t *sidp) +kauth_cred_nfs4domain2dsnode(__unused char *nfs4domain, __unused char *dsnode) { - return(kauth_cred_cache_lookup(KI_VALID_GID, KI_VALID_NTSID, &gid, sidp)); + return ENOENT; } - /* - * kauth_cred_guid2ntsid + * kauth_cred_dsnode2nfs4domain * - * Description: Fetch NTSID from GUID + * Description: Fetch nfs4domain from dsnode * - * Parameters: guidp Pointer to GUID to examine - * sidp Pointer to buffer for NTSID + * Parameters: nfs4domain Pointer to string dsnode + * dsnode Pointer to buffer for nfs4domain * * Returns: 0 Success - * kauth_cred_cache_lookup:EINVAL + * ENOENT For now just a stub that always fails * * Implicit returns: - * *sidp Modified, if successful + * *nfs4domain Modified, if successuful + */ +int +kauth_cred_dsnode2nfs4domain(__unused char *dsnode, __unused char *nfs4domain) +{ + return ENOENT; +} + +/* + * 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) +{ + return kauth_cred_cache_lookup(KI_VALID_NTSID, KI_VALID_UID, sidp, uidp); +} + + +/* + * 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) +{ + return kauth_cred_cache_lookup(KI_VALID_NTSID, KI_VALID_GID, sidp, gidp); +} + + +/* + * 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) +{ + return kauth_cred_cache_lookup(KI_VALID_NTSID, KI_VALID_GUID, sidp, guidp); +} + + +/* + * 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) +{ + return kauth_cred_cache_lookup(KI_VALID_UID, KI_VALID_GUID, &uid, guidp); +} + + +/* + * 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) +{ + NULLCRED_CHECK(cred); + return kauth_cred_uid2guid(kauth_cred_getuid(cred), guidp); +} + + +/* + * 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); +} + + +/* + * 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) +{ + return kauth_cred_cache_lookup(KI_VALID_UID, KI_VALID_NTSID, &uid, sidp); +} + + +/* + * 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) +{ + NULLCRED_CHECK(cred); + return kauth_cred_uid2ntsid(kauth_cred_getuid(cred), sidp); +} + + +/* + * 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) +{ + return kauth_cred_cache_lookup(KI_VALID_GID, KI_VALID_NTSID, &gid, sidp); +} + + +/* + * 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) { - return(kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_NTSID, guidp, sidp)); + return kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_NTSID, guidp, sidp); } @@ -1682,36 +2640,58 @@ kauth_cred_guid2ntsid(guid_t *guidp, ntsid_t *sidp) * Returns: 0 Success * EINVAL Unknown source identity type */ +#if CONFIG_EXT_RESOLVER static int kauth_cred_cache_lookup(int from, int to, void *src, void *dst) { struct kauth_identity ki; struct kauth_identity_extlookup el; int error; + uint64_t extend_data = 0ULL; int (* expired)(struct kauth_identity *kip); + char *namebuf = NULL; KAUTH_DEBUG("CACHE - translate %d to %d", from, to); - + /* * Look for an existing cache entry for this association. * If the entry has not expired, return the cached information. + * We do not cache user@domain translations here; they use too + * much memory to hold onto forever, and can not be updated + * atomically. */ + if (to == KI_VALID_PWNAM || to == KI_VALID_GRNAM) { + if (dst == NULL) { + return EINVAL; + } + namebuf = dst; + *namebuf = '\0'; + } ki.ki_valid = 0; - switch(from) { + switch (from) { case KI_VALID_UID: - error = kauth_identity_find_uid(*(uid_t *)src, &ki); + error = kauth_identity_find_uid(*(uid_t *)src, &ki, namebuf); break; case KI_VALID_GID: - error = kauth_identity_find_gid(*(gid_t *)src, &ki); + error = kauth_identity_find_gid(*(gid_t *)src, &ki, namebuf); break; case KI_VALID_GUID: - error = kauth_identity_find_guid((guid_t *)src, &ki); + error = kauth_identity_find_guid((guid_t *)src, &ki, namebuf); break; case KI_VALID_NTSID: - error = kauth_identity_find_ntsid((ntsid_t *)src, &ki); + error = kauth_identity_find_ntsid((ntsid_t *)src, &ki, namebuf); + break; + case KI_VALID_PWNAM: + case KI_VALID_GRNAM: + /* Names are unique in their 'from' space */ + error = kauth_identity_find_nam((char *)src, from, &ki); break; default: - return(EINVAL); + return EINVAL; + } + /* If we didn't get what we're asking for. Call the resolver */ + if (!error && !(to & ki.ki_valid)) { + error = ENOENT; } /* lookup failure or error */ if (error != 0) { @@ -1719,13 +2699,22 @@ kauth_cred_cache_lookup(int from, int to, void *src, void *dst) if (error != ENOENT) { /* XXX bogus check - this is not possible */ KAUTH_DEBUG("CACHE - cache search error %d", error); - return(error); + return error; } } else { - /* do we have a translation? */ - if (ki.ki_valid & to) { - /* found a valid cached entry, check expiry */ - switch(to) { + /* found a valid cached entry, check expiry */ + switch (to) { + case KI_VALID_GUID: + expired = kauth_identity_guid_expired; + break; + case KI_VALID_NTSID: + expired = kauth_identity_ntsid_expired; + break; + case KI_VALID_GROUPS: + expired = kauth_identity_groups_expired; + break; + default: + switch (from) { case KI_VALID_GUID: expired = kauth_identity_guid_expired; break; @@ -1733,46 +2722,81 @@ kauth_cred_cache_lookup(int from, int to, void *src, void *dst) expired = kauth_identity_ntsid_expired; break; default: - switch(from) { - case KI_VALID_GUID: - expired = kauth_identity_guid_expired; - break; - case KI_VALID_NTSID: - expired = kauth_identity_ntsid_expired; - break; - default: - expired = NULL; - } - } - KAUTH_DEBUG("CACHE - found matching entry with valid %d", ki.ki_valid); - /* - * If no expiry function, or not expired, we have found - * a hit. - */ - if (!expired) { - KAUTH_DEBUG("CACHE - no expiry function"); - goto found; + expired = NULL; } + } + + /* + * If no expiry function, or not expired, we have found + * a hit. + */ + if (expired) { if (!expired(&ki)) { KAUTH_DEBUG("CACHE - entry valid, unexpired"); + expired = NULL; /* must clear it is used as a flag */ + } else { + /* + * 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"); + } + } else { + KAUTH_DEBUG("CACHE - no expiry function"); + } + + if (!expired) { + /* do we have a translation? */ + if (ki.ki_valid & to) { + KAUTH_DEBUG("CACHE - found matching entry with valid 0x%08x", ki.ki_valid); + DTRACE_PROC4(kauth__identity__cache__hit, int, from, int, to, void *, src, void *, dst); goto found; + } else { + /* + * GUIDs and NTSIDs map to either a UID or a GID, but not both. + * If we went looking for a translation from GUID or NTSID and + * found a translation that wasn't for our desired type, then + * don't bother calling the resolver. We know that this + * GUID/NTSID can't translate to our desired type. + */ + switch (from) { + case KI_VALID_GUID: + case KI_VALID_NTSID: + switch (to) { + case KI_VALID_GID: + if ((ki.ki_valid & KI_VALID_UID)) { + KAUTH_DEBUG("CACHE - unexpected entry 0x%08x & %x", ki.ki_valid, KI_VALID_GID); + return ENOENT; + } + break; + case KI_VALID_UID: + if ((ki.ki_valid & KI_VALID_GID)) { + KAUTH_DEBUG("CACHE - unexpected entry 0x%08x & %x", ki.ki_valid, KI_VALID_UID); + return ENOENT; + } + break; + } + break; + } } - /* - * 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"); } } /* * We failed to find a cache entry; call the resolver. * - * Note: We ask for as much data as we can get. + * Note: We ask for as much non-extended data as we can get, + * and only provide (or ask for) extended information if + * we have a 'from' (or 'to') which requires it. This + * way we don't pay for the extra transfer overhead for + * data we don't need. */ - switch(from) { + bzero(&el, sizeof(el)); + el.el_info_pid = current_proc()->p_pid; + switch (from) { case KI_VALID_UID: el.el_flags = KAUTH_EXTLOOKUP_VALID_UID; el.el_uid = *(uid_t *)src; @@ -1791,8 +2815,18 @@ kauth_cred_cache_lookup(int from, int to, void *src, void *dst) el.el_usid = *(ntsid_t *)src; el.el_gsid = *(ntsid_t *)src; break; + case KI_VALID_PWNAM: + /* extra overhead */ + el.el_flags = KAUTH_EXTLOOKUP_VALID_PWNAM; + extend_data = CAST_USER_ADDR_T(src); + break; + case KI_VALID_GRNAM: + /* extra overhead */ + el.el_flags = KAUTH_EXTLOOKUP_VALID_GRNAM; + extend_data = CAST_USER_ADDR_T(src); + break; default: - return(EINVAL); + return EINVAL; } /* * Here we ask for everything all at once, to avoid having to work @@ -1806,26 +2840,89 @@ kauth_cred_cache_lookup(int from, int to, void *src, void *dst) el.el_flags |= KAUTH_EXTLOOKUP_WANT_UID | KAUTH_EXTLOOKUP_WANT_GID | KAUTH_EXTLOOKUP_WANT_UGUID | KAUTH_EXTLOOKUP_WANT_GGUID | KAUTH_EXTLOOKUP_WANT_USID | KAUTH_EXTLOOKUP_WANT_GSID; + if (to == KI_VALID_PWNAM) { + /* extra overhead */ + el.el_flags |= KAUTH_EXTLOOKUP_WANT_PWNAM; + extend_data = CAST_USER_ADDR_T(dst); + } + if (to == KI_VALID_GRNAM) { + /* extra overhead */ + el.el_flags |= KAUTH_EXTLOOKUP_WANT_GRNAM; + extend_data = CAST_USER_ADDR_T(dst); + } + if (to == KI_VALID_GROUPS) { + /* Expensive and only useful for an NFS client not using kerberos */ + el.el_flags |= KAUTH_EXTLOOKUP_WANT_SUPGRPS; + if (ki.ki_valid & KI_VALID_GROUPS) { + /* + * Copy the current supplemental groups for the resolver. + * The resolver should check these groups first and if + * the user (uid) is still a member it should endeavor to + * keep them in the list. Otherwise NFS clients could get + * changing access to server file system objects on each + * expiration. + */ + if (ki.ki_supgrpcnt > NGROUPS) { + panic("kauth data structure corrupted. kauth identity 0x%p with %d groups, greater than max of %d", + &ki, ki.ki_supgrpcnt, NGROUPS); + } + + el.el_sup_grp_cnt = ki.ki_supgrpcnt; + + memcpy(el.el_sup_groups, ki.ki_supgrps, sizeof(el.el_sup_groups[0]) * ki.ki_supgrpcnt); + /* Let the resolver know these were the previous valid groups */ + el.el_flags |= KAUTH_EXTLOOKUP_VALID_SUPGRPS; + KAUTH_DEBUG("GROUPS: Sending previously valid GROUPS"); + } else { + KAUTH_DEBUG("GROUPS: no valid groups to send"); + } + } + + /* Call resolver */ KAUTH_DEBUG("CACHE - calling resolver for %x", el.el_flags); - error = kauth_resolver_submit(&el); + + DTRACE_PROC3(kauth__id__resolver__submitted, int, from, int, to, uintptr_t, src); + + error = kauth_resolver_submit(&el, extend_data); + + DTRACE_PROC2(kauth__id__resolver__returned, int, error, struct kauth_identity_extlookup *, &el) + KAUTH_DEBUG("CACHE - resolver returned %d", error); - /* was the lookup successful? */ + + /* was the external 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 - we may have other + * information, even if we didn't get a guid or the + * extended data. + * + * If we came from a name, we know the extend_data is valid. + */ + if (from == KI_VALID_PWNAM) { + el.el_flags |= KAUTH_EXTLOOKUP_VALID_PWNAM; + } else if (from == KI_VALID_GRNAM) { + el.el_flags |= KAUTH_EXTLOOKUP_VALID_GRNAM; + } + + kauth_identity_updatecache(&el, &ki, extend_data); + + /* + * Check to see if we have a valid cache entry + * originating from the result. */ - kauth_identity_updatecache(&el, &ki); + if (!(ki.ki_valid & to)) { + error = ENOENT; + } + } + if (error) { + return error; } +found: /* - * Check to see if we have a valid result. + * Copy from the appropriate struct kauth_identity cache entry + * structure into the destination buffer area. */ - if (!error && !(ki.ki_valid & to)) - error = ENOENT; - if (error) - return(error); -found: - switch(to) { + switch (to) { case KI_VALID_UID: *(uid_t *)dst = ki.ki_uid; break; @@ -1838,11 +2935,27 @@ found: case KI_VALID_NTSID: *(ntsid_t *)dst = ki.ki_ntsid; break; + case KI_VALID_GROUPS: { + struct supgroups *gp = (struct supgroups *)dst; + u_int32_t limit = ki.ki_supgrpcnt; + + if (gp->count) { + limit = MIN(ki.ki_supgrpcnt, *gp->count); + *gp->count = limit; + } + + memcpy(gp->groups, ki.ki_supgrps, sizeof(gid_t) * limit); + } + break; + case KI_VALID_PWNAM: + case KI_VALID_GRNAM: + /* handled in kauth_resolver_complete() */ + break; default: - return(EINVAL); + return EINVAL; } KAUTH_DEBUG("CACHE - returned successfully"); - return(0); + return 0; } @@ -1852,28 +2965,6 @@ found: * XXX the linked-list implementation here needs to be optimized. */ -struct kauth_group_membership { - TAILQ_ENTRY(kauth_group_membership) gm_link; - uid_t gm_uid; /* the identity whose membership we're recording */ - gid_t gm_gid; /* group of which they are a member */ - time_t gm_expiry; /* TTL for the membership */ - int gm_flags; -#define KAUTH_GROUP_ISMEMBER (1<<0) -}; - -TAILQ_HEAD(kauth_groups_head, kauth_group_membership) kauth_groups; -#define KAUTH_GROUPS_CACHEMAX 100 /* XXX sizing? */ -static int kauth_groups_count; - -static lck_mtx_t *kauth_groups_mtx; -#define KAUTH_GROUPS_LOCK() lck_mtx_lock(kauth_groups_mtx); -#define KAUTH_GROUPS_UNLOCK() lck_mtx_unlock(kauth_groups_mtx); - -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 * @@ -1883,7 +2974,7 @@ static void kauth_groups_updatecache(struct kauth_identity_extlookup *el); * * Returns: (void) * - * Notes: Intialize the groups cache for use; the group cache is used + * Notes: Initialize 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 @@ -1893,7 +2984,7 @@ void kauth_groups_init(void) { TAILQ_INIT(&kauth_groups); - kauth_groups_mtx = lck_mtx_alloc_init(kauth_lck_grp, 0/*LCK_ATTR_NULL*/); + kauth_groups_mtx = lck_mtx_alloc_init(kauth_lck_grp, 0 /*LCK_ATTR_NULL*/); } @@ -1913,8 +3004,16 @@ kauth_groups_expired(struct kauth_group_membership *gm) { struct timeval tv; + /* + * Expiration time of 0 means this entry is persistent. + */ + if (gm->gm_expiry == 0) { + return 0; + } + microuptime(&tv); - return((gm->gm_expiry <= tv.tv_sec) ? 1 : 0); + + return (gm->gm_expiry <= tv.tv_sec) ? 1 : 0; } @@ -1963,12 +3062,13 @@ kauth_groups_updatecache(struct kauth_identity_extlookup *el) { struct kauth_group_membership *gm; struct timeval tv; - + /* need a valid response if we are to cache anything */ if ((el->el_flags & - (KAUTH_EXTLOOKUP_VALID_UID | KAUTH_EXTLOOKUP_VALID_GID | KAUTH_EXTLOOKUP_VALID_MEMBERSHIP)) != - (KAUTH_EXTLOOKUP_VALID_UID | KAUTH_EXTLOOKUP_VALID_GID | KAUTH_EXTLOOKUP_VALID_MEMBERSHIP)) + (KAUTH_EXTLOOKUP_VALID_UID | KAUTH_EXTLOOKUP_VALID_GID | KAUTH_EXTLOOKUP_VALID_MEMBERSHIP)) != + (KAUTH_EXTLOOKUP_VALID_UID | KAUTH_EXTLOOKUP_VALID_GID | KAUTH_EXTLOOKUP_VALID_MEMBERSHIP)) { return; + } microuptime(&tv); @@ -1985,7 +3085,7 @@ kauth_groups_updatecache(struct kauth_identity_extlookup *el) } else { gm->gm_flags &= ~KAUTH_GROUP_ISMEMBER; } - gm->gm_expiry = el->el_member_valid + tv.tv_sec; + gm->gm_expiry = (el->el_member_valid) ? el->el_member_valid + tv.tv_sec : 0; kauth_groups_lru(gm); break; } @@ -1993,8 +3093,9 @@ kauth_groups_updatecache(struct kauth_identity_extlookup *el) KAUTH_GROUPS_UNLOCK(); /* if we found an entry to update, stop here */ - if (gm != NULL) + if (gm != NULL) { return; + } /* allocate a new record */ MALLOC(gm, struct kauth_group_membership *, sizeof(*gm), M_KAUTH, M_WAITOK); @@ -2006,8 +3107,8 @@ kauth_groups_updatecache(struct kauth_identity_extlookup *el) } else { gm->gm_flags &= ~KAUTH_GROUP_ISMEMBER; } - gm->gm_expiry = el->el_member_valid + tv.tv_sec; - } + gm->gm_expiry = (el->el_member_valid) ? el->el_member_valid + tv.tv_sec : 0; + } /* * Insert the new entry. Note that it's possible to race ourselves @@ -2017,20 +3118,41 @@ kauth_groups_updatecache(struct kauth_identity_extlookup *el) */ KAUTH_GROUPS_LOCK(); TAILQ_INSERT_HEAD(&kauth_groups, gm, gm_link); - if (kauth_groups_count++ > KAUTH_GROUPS_CACHEMAX) { + if (++kauth_groups_count > kauth_groups_cachemax) { + gm = TAILQ_LAST(&kauth_groups, kauth_groups_head); + TAILQ_REMOVE(&kauth_groups, gm, gm_link); + kauth_groups_count--; + } else { + gm = NULL; + } + KAUTH_GROUPS_UNLOCK(); + + /* free expired cache entry */ + if (gm != NULL) { + FREE(gm, M_KAUTH); + } +} + +/* + * Trim older entries from the group membership cache. + * + * Must be called with the group cache lock held. + */ +static void +kauth_groups_trimcache(int new_size) +{ + struct kauth_group_membership *gm; + + lck_mtx_assert(kauth_groups_mtx, LCK_MTX_ASSERT_OWNED); + + while (kauth_groups_count > new_size) { gm = TAILQ_LAST(&kauth_groups, kauth_groups_head); TAILQ_REMOVE(&kauth_groups, gm, gm_link); kauth_groups_count--; - } else { - gm = NULL; - } - KAUTH_GROUPS_UNLOCK(); - - /* free expired cache entry */ - if (gm != NULL) FREE(gm, M_KAUTH); + } } - +#endif /* CONFIG_EXT_RESOLVER */ /* * Group membership KPI @@ -2049,10 +3171,12 @@ kauth_groups_updatecache(struct kauth_identity_extlookup *el) * result of the call * * Returns: 0 Success - * ENOENT Could not proform lookup + * ENOENT Could not perform lookup * kauth_resolver_submit:EWOULDBLOCK * kauth_resolver_submit:EINTR * kauth_resolver_submit:ENOMEM + * kauth_resolver_submit:ENOENT User space daemon did not vend + * this credential. * kauth_resolver_submit:??? Unlikely error from user space * * Implicit returns: @@ -2062,15 +3186,14 @@ kauth_groups_updatecache(struct kauth_identity_extlookup *el) * Notes: This function guarantees not to modify resultp when returning * an error. * - * This function effectively checkes the EGID as well, since the + * This function effectively checks 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) { - struct kauth_group_membership *gm; - struct kauth_identity_extlookup el; - int i, error; + posix_cred_t pcred = posix_cred_get(cred); + int i; /* * Check the per-credential list of override groups. @@ -2078,10 +3201,10 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) * We can conditionalise this on cred->cr_gmuid == KAUTH_UID_NONE since * the cache should be used for that case. */ - for (i = 0; i < cred->cr_ngroups; i++) { - if (gid == cred->cr_groups[i]) { + for (i = 0; i < pcred->cr_ngroups; i++) { + if (gid == pcred->cr_groups[i]) { *resultp = 1; - return(0); + return 0; } } @@ -2089,21 +3212,25 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) * If we don't have a UID for group membership checks, the in-cred list * was authoritative and we can stop here. */ - if (cred->cr_gmuid == KAUTH_UID_NONE) { + if (pcred->cr_gmuid == KAUTH_UID_NONE) { *resultp = 0; - return(0); + return 0; } - - + +#if CONFIG_EXT_RESOLVER + struct kauth_group_membership *gm; + struct kauth_identity_extlookup el; + int error; + /* * 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; - return(0); + return 0; } - + /* TODO: */ /* XXX check supplementary groups */ /* XXX check whiteout groups */ @@ -2114,42 +3241,57 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) */ KAUTH_GROUPS_LOCK(); TAILQ_FOREACH(gm, &kauth_groups, gm_link) { - if ((gm->gm_uid == cred->cr_gmuid) && (gm->gm_gid == gid) && !kauth_groups_expired(gm)) { + if ((gm->gm_uid == pcred->cr_gmuid) && (gm->gm_gid == gid) && !kauth_groups_expired(gm)) { kauth_groups_lru(gm); break; } } /* did we find a membership entry? */ - if (gm != NULL) + if (gm != NULL) { *resultp = (gm->gm_flags & KAUTH_GROUP_ISMEMBER) ? 1 : 0; + } KAUTH_GROUPS_UNLOCK(); /* if we did, we can return now */ - if (gm != NULL) - return(0); - + if (gm != NULL) { + DTRACE_PROC2(kauth__group__cache__hit, int, pcred->cr_gmuid, int, gid); + return 0; + } + /* nothing in the cache, need to go to userland */ + bzero(&el, sizeof(el)); + el.el_info_pid = current_proc()->p_pid; el.el_flags = KAUTH_EXTLOOKUP_VALID_UID | KAUTH_EXTLOOKUP_VALID_GID | KAUTH_EXTLOOKUP_WANT_MEMBERSHIP; - el.el_uid = cred->cr_gmuid; + el.el_uid = pcred->cr_gmuid; el.el_gid = gid; - el.el_member_valid = 0; /* XXX set by resolver? */ - error = kauth_resolver_submit(&el); - if (error != 0) - return(error); + el.el_member_valid = 0; /* XXX set by resolver? */ + + DTRACE_PROC2(kauth__group__resolver__submitted, int, el.el_uid, int, el.el_gid); + + error = kauth_resolver_submit(&el, 0ULL); + + DTRACE_PROC2(kauth__group__resolver__returned, int, error, int, el.el_flags); + + if (error != 0) { + return error; + } /* save the results from the lookup */ kauth_groups_updatecache(&el); /* if we successfully ascertained membership, report */ if (el.el_flags & KAUTH_EXTLOOKUP_VALID_MEMBERSHIP) { *resultp = (el.el_flags & KAUTH_EXTLOOKUP_ISMEMBER) ? 1 : 0; - return(0); + return 0; } - return(ENOENT); + return ENOENT; +#else + *resultp = 0; + return 0; +#endif } - /* * kauth_cred_ismember_guid * @@ -2165,6 +3307,8 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) * Returns: 0 Success * kauth_cred_guid2gid:EINVAL * kauth_cred_ismember_gid:ENOENT + * kauth_resolver_submit:ENOENT User space daemon did not vend + * this credential. * kauth_cred_ismember_gid:EWOULDBLOCK * kauth_cred_ismember_gid:EINTR * kauth_cred_ismember_gid:ENOMEM @@ -2175,14 +3319,11 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) * 0 Is not member */ int -kauth_cred_ismember_guid(kauth_cred_t cred, guid_t *guidp, int *resultp) +kauth_cred_ismember_guid(__unused kauth_cred_t cred, guid_t *guidp, int *resultp) { - gid_t gid; - int error, wkg; + int error = 0; - error = 0; - wkg = kauth_wellknown_guid(guidp); - switch(wkg) { + switch (kauth_wellknown_guid(guidp)) { case KAUTH_WKG_NOBODY: *resultp = 0; break; @@ -2190,7 +3331,44 @@ kauth_cred_ismember_guid(kauth_cred_t cred, guid_t *guidp, int *resultp) *resultp = 1; break; default: - /* translate guid to gid */ + { + gid_t gid; +#if CONFIG_EXT_RESOLVER + struct kauth_identity ki; + + /* + * Grovel the identity cache looking for this GUID. + * If we find it, and it is for a user record, return + * false because it's not a group. + * + * This is necessary because we don't have -ve caching + * of group memberships, and we really want to avoid + * calling out to the resolver if at all possible. + * + * Because we're called by the ACL evaluator, and the + * ACL evaluator is likely to encounter ACEs for users, + * this is expected to be a common case. + */ + ki.ki_valid = 0; + if ((error = kauth_identity_find_guid(guidp, &ki, NULL)) == 0 && + !kauth_identity_guid_expired(&ki)) { + if (ki.ki_valid & KI_VALID_GID) { + /* It's a group after all... */ + gid = ki.ki_gid; + goto do_check; + } + if (ki.ki_valid & KI_VALID_UID) { + *resultp = 0; + return 0; + } + } +#endif /* CONFIG_EXT_RESOLVER */ + /* + * Attempt to translate the GUID to a GID. Even if + * this fails, we will have primed the cache if it is + * a user record and we'll see it above the next time + * we're asked. + */ if ((error = kauth_cred_guid2gid(guidp, &gid)) != 0) { /* * If we have no guid -> gid translation, it's not a group and @@ -2201,20 +3379,25 @@ kauth_cred_ismember_guid(kauth_cred_t cred, guid_t *guidp, int *resultp) error = 0; } } else { +#if CONFIG_EXT_RESOLVER +do_check: +#endif /* CONFIG_EXT_RESOLVER */ error = kauth_cred_ismember_gid(cred, gid, resultp); } } - return(error); + break; + } + return error; } /* * kauth_cred_gid_subset * - * Description: Given two credentials, determine if all GIDs associated with - * the first are also associated with the second + * 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 + * cred2 Credential to check in * resultp Pointer to int to contain the * result of the call * @@ -2229,43 +3412,45 @@ kauth_cred_ismember_guid(kauth_cred_t cred, guid_t *guidp, int *resultp) * Notes: This function guarantees not to modify resultp when returning * an error. */ -int +int kauth_cred_gid_subset(kauth_cred_t cred1, kauth_cred_t cred2, int *resultp) { int i, err, res = 1; gid_t gid; + posix_cred_t pcred1 = posix_cred_get(cred1); + posix_cred_t pcred2 = posix_cred_get(cred2); /* First, check the local list of groups */ - for (i = 0; i < cred1->cr_ngroups; i++) { - gid = cred1->cr_groups[i]; + for (i = 0; i < pcred1->cr_ngroups; i++) { + gid = pcred1->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) { + if (!res && gid != pcred2->cr_rgid && gid != pcred2->cr_svgid) { *resultp = 0; return 0; } } /* Check real gid */ - if ((err = kauth_cred_ismember_gid(cred2, cred1->cr_rgid, &res)) != 0) { + if ((err = kauth_cred_ismember_gid(cred2, pcred1->cr_rgid, &res)) != 0) { return err; } - if (!res && cred1->cr_rgid != cred2->cr_rgid && - cred1->cr_rgid != cred2->cr_svgid) { + if (!res && pcred1->cr_rgid != pcred2->cr_rgid && + pcred1->cr_rgid != pcred2->cr_svgid) { *resultp = 0; return 0; } /* Finally, check saved gid */ - if ((err = kauth_cred_ismember_gid(cred2, cred1->cr_svgid, &res)) != 0){ + if ((err = kauth_cred_ismember_gid(cred2, pcred1->cr_svgid, &res)) != 0) { return err; } - if (!res && cred1->cr_svgid != cred2->cr_rgid && - cred1->cr_svgid != cred2->cr_svgid) { + if (!res && pcred1->cr_svgid != pcred2->cr_rgid && + pcred1->cr_svgid != pcred2->cr_svgid) { *resultp = 0; return 0; } @@ -2292,7 +3477,7 @@ kauth_cred_gid_subset(kauth_cred_t cred1, kauth_cred_t cred2, int *resultp) int kauth_cred_issuser(kauth_cred_t cred) { - return(cred->cr_uid == 0); + return kauth_cred_getuid(cred) == 0; } @@ -2301,14 +3486,10 @@ kauth_cred_issuser(kauth_cred_t cred) */ /* lock protecting credential hash table */ -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 */ +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); +#define KAUTH_CRED_HASH_LOCK_ASSERT() LCK_MTX_ASSERT(&kauth_cred_hash_mtx, LCK_MTX_ASSERT_OWNED) /* @@ -2345,19 +3526,10 @@ static lck_mtx_t *kauth_cred_hash_mtx; void kauth_cred_init(void) { - int i; - - kauth_cred_hash_mtx = lck_mtx_alloc_init(kauth_lck_grp, 0/*LCK_ATTR_NULL*/); - kauth_cred_table_size = kauth_cred_primes[kauth_cred_primes_index]; - - /*allocate credential hash table */ - 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]); + lck_mtx_init(&kauth_cred_hash_mtx, kauth_lck_grp, 0 /*LCK_ATTR_NULL*/); + + for (int i = 0; i < KAUTH_CRED_TABLE_SIZE; i++) { + LIST_INIT(&kauth_cred_table_anchor[i]); } } @@ -2375,7 +3547,7 @@ kauth_cred_init(void) uid_t kauth_getuid(void) { - return(kauth_cred_get()->cr_uid); + return kauth_cred_getuid(kauth_cred_get()); } @@ -2392,7 +3564,7 @@ kauth_getuid(void) uid_t kauth_getruid(void) { - return(kauth_cred_get()->cr_ruid); + return kauth_cred_getruid(kauth_cred_get()); } @@ -2409,7 +3581,7 @@ kauth_getruid(void) gid_t kauth_getgid(void) { - return(kauth_cred_get()->cr_groups[0]); + return kauth_cred_getgid(kauth_cred_get()); } @@ -2426,7 +3598,7 @@ kauth_getgid(void) gid_t kauth_getrgid(void) { - return(kauth_cred_get()->cr_rgid); + return kauth_cred_getrgid(kauth_cred_get()); } @@ -2460,8 +3632,9 @@ kauth_cred_get(void) uthread = get_bsdthread_info(current_thread()); /* sanity */ - if (uthread == NULL) + 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. @@ -2470,13 +3643,25 @@ kauth_cred_get(void) * 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) + if ((p = (proc_t) get_bsdtask_info(get_threadtask(current_thread()))) == NULL) { panic("thread wants credential but has no BSD process"); + } uthread->uu_ucred = kauth_cred_proc_ref(p); } - return(uthread->uu_ucred); + return uthread->uu_ucred; } +void +mach_kauth_cred_uthread_update(void) +{ + uthread_t uthread; + proc_t proc; + + uthread = get_bsdthread_info(current_thread()); + proc = current_proc(); + + kauth_cred_uthread_update(uthread, proc); +} /* * kauth_cred_uthread_update @@ -2502,8 +3687,9 @@ kauth_cred_uthread_update(uthread_t uthread, proc_t proc) (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)) + if (IS_VALID_CRED(old)) { kauth_cred_unref(&old); + } } } @@ -2535,15 +3721,7 @@ kauth_cred_uthread_update(uthread_t uthread, proc_t proc) kauth_cred_t kauth_cred_get_with_ref(void) { - struct proc *procp; - struct uthread *uthread; - - uthread = get_bsdthread_info(current_thread()); - /* sanity checks */ - if (uthread == NULL) - panic("%s - thread wants credential but has no BSD thread info", __FUNCTION__); - if ((procp = (proc_t) get_bsdtask_info(get_threadtask(current_thread()))) == NULL) - panic("%s - thread wants credential but has no BSD process", __FUNCTION__); + struct uthread *uthread = current_uthread(); /* * We can lazy-bind credentials to threads, as long as their processes @@ -2554,11 +3732,11 @@ kauth_cred_get_with_ref(void) */ if (uthread->uu_ucred == NOCRED) { /* take reference for new cred in thread */ - uthread->uu_ucred = kauth_cred_proc_ref(procp); + uthread->uu_ucred = kauth_cred_proc_ref(current_proc()); } /* take a reference for our caller */ kauth_cred_ref(uthread->uu_ucred); - return(uthread->uu_ucred); + return uthread->uu_ucred; } @@ -2574,7 +3752,7 @@ kauth_cred_get_with_ref(void) * Returns: (kauth_cred_t) Pointer to the process's * newly referenced credential * - * Locks: PROC_LOCK is held before taking the reference and released + * Locks: PROC_UCRED_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. * @@ -2594,16 +3772,15 @@ kauth_cred_get_with_ref(void) kauth_cred_t kauth_cred_proc_ref(proc_t procp) { - kauth_cred_t cred; - - proc_lock(procp); + kauth_cred_t cred; + + proc_ucred_lock(procp); cred = proc_ucred(procp); kauth_cred_ref(cred); - proc_unlock(procp); - return(cred); + proc_ucred_unlock(procp); + return cred; } - /* * kauth_cred_alloc * @@ -2616,8 +3793,8 @@ kauth_cred_proc_ref(proc_t procp) * * 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. + * count, which is set to 0 to indicate the caller still has + * to call kauth_cred_add(). * * Since newly allocated credentials have no external pointers * referencing them, prior to making them visible in an externally @@ -2644,35 +3821,44 @@ kauth_cred_proc_ref(proc_t procp) * than returning a NULL pointer; the code that calls this * function needs to be audited before this can be changed. */ -kauth_cred_t +static kauth_cred_t kauth_cred_alloc(void) { kauth_cred_t newcred; - - MALLOC_ZONE(newcred, kauth_cred_t, sizeof(*newcred), M_CRED, M_WAITOK); + + MALLOC_ZONE(newcred, kauth_cred_t, sizeof(*newcred), M_CRED, M_WAITOK | M_ZERO); + assert(newcred); if (newcred != 0) { - bzero(newcred, sizeof(*newcred)); - newcred->cr_ref = 1; + posix_cred_t newpcred = posix_cred_get(newcred); + newcred->cr_audit.as_aia_p = audit_default_aia_p; /* must do this, or cred has same group membership as uid 0 */ - newcred->cr_gmuid = KAUTH_UID_NONE; -#if CRED_DIAGNOSTIC - } else { - panic("kauth_cred_alloc: couldn't allocate credential"); -#endif + newpcred->cr_gmuid = KAUTH_UID_NONE; +#if CONFIG_MACF + mac_cred_label_init(newcred); +#endif } + return newcred; +} -#if KAUTH_CRED_HASH_DEBUG - kauth_cred_count++; -#endif +/* + * kauth_cred_free + * + * Description: Destroy a credential + * + * Parameters: cred Credential to destroy. + */ +static void +kauth_cred_free(kauth_cred_t cred) +{ + assert(os_atomic_load(&cred->cr_ref, relaxed) == 0); #if CONFIG_MACF - mac_cred_label_init(newcred); + mac_cred_label_destroy(cred); #endif - - return(newcred); + AUDIT_SESSION_UNREF(cred); + FREE_ZONE(cred, sizeof(*cred), M_CRED); } - /* * kauth_cred_create * @@ -2693,75 +3879,78 @@ kauth_cred_alloc(void) * 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; - - KAUTH_CRED_HASH_LOCK_ASSERT(); + kauth_cred_t found_cred, new_cred = NULL; + posix_cred_t pcred = posix_cred_get(cred); + int is_member = 0; - 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) { + if (pcred->cr_flags & CRF_NOMEMBERD) { + pcred->cr_gmuid = KAUTH_UID_NONE; + } else { + /* + * If the template credential is not opting out of external + * group membership resolution, then we need to check that + * the UID we will be using is resolvable by the external + * resolver. If it's not, then we opt it out anyway, since + * all future external resolution requests will be failing + * anyway, and potentially taking a long time to do it. We + * use gid 0 because we always know it will exist and not + * trigger additional lookups. This is OK, because we end up + * precatching the information here as a result. + */ + if (!kauth_cred_ismember_gid(cred, 0, &is_member)) { + /* + * It's a recognized value; we don't really care about + * the answer, so long as it's something the external + * resolver could have vended. + */ + pcred->cr_gmuid = pcred->cr_uid; + } else { /* - * Found an existing credential so we'll bump - * reference count and return + * It's not something the external resolver could + * have vended, so we don't want to ask it more + * questions about the credential in the future. This + * speeds up future lookups, as long as the caller + * caches results; otherwise, it the same recurring + * cost. Since most credentials are used multiple + * times, we still get some performance win from this. */ - kauth_cred_ref(found_cred); - KAUTH_CRED_HASH_UNLOCK(); - return(found_cred); + pcred->cr_gmuid = KAUTH_UID_NONE; + pcred->cr_flags |= CRF_NOMEMBERD; } - KAUTH_CRED_HASH_UNLOCK(); - - /* - * No existing credential found. Create one and add it to - * our hash table. - */ - new_cred = kauth_cred_alloc(); - if (new_cred != NULL) { - int err; - new_cred->cr_uid = cred->cr_uid; - new_cred->cr_ruid = cred->cr_ruid; - new_cred->cr_svuid = cred->cr_svuid; - new_cred->cr_rgid = cred->cr_rgid; - new_cred->cr_svgid = cred->cr_svgid; - 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 */ - if (err == 0) - break; -#if CONFIG_MACF - mac_cred_label_destroy(new_cred); + } + + /* Caller *must* specify at least the egid in cr_groups[0] */ + if (pcred->cr_ngroups < 1) { + return NULL; + } + + struct kauth_cred_entry_head *bucket = kauth_cred_get_bucket(cred); + + KAUTH_CRED_HASH_LOCK(); + found_cred = kauth_cred_find_and_ref(cred, bucket); + KAUTH_CRED_HASH_UNLOCK(); + if (found_cred != NULL) { + return found_cred; + } + + /* + * No existing credential found. Create one and add it to + * our hash table. + */ + new_cred = kauth_cred_alloc(); + if (new_cred != NULL) { + *posix_cred_get(new_cred) = *pcred; +#if CONFIG_AUDIT + new_cred->cr_audit = cred->cr_audit; #endif - FREE_ZONE(new_cred, sizeof(*new_cred), M_CRED); - new_cred = NULL; - } + new_cred = kauth_cred_add(new_cred, bucket); } - return(new_cred); + return new_cred; } @@ -2801,6 +3990,8 @@ kauth_cred_t kauth_cred_setresuid(kauth_cred_t cred, uid_t ruid, uid_t euid, uid_t svuid, uid_t gmuid) { struct ucred temp_cred; + posix_cred_t temp_pcred = posix_cred_get(&temp_cred); + posix_cred_t pcred = posix_cred_get(cred); NULLCRED_CHECK(cred); @@ -2808,12 +3999,12 @@ kauth_cred_setresuid(kauth_cred_t cred, uid_t ruid, uid_t euid, uid_t svuid, uid * We don't need to do anything if the UIDs we are changing are * already the same as the UIDs passed in */ - 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)) { + if ((euid == KAUTH_UID_NONE || pcred->cr_uid == euid) && + (ruid == KAUTH_UID_NONE || pcred->cr_ruid == ruid) && + (svuid == KAUTH_UID_NONE || pcred->cr_svuid == svuid) && + (pcred->cr_gmuid == gmuid)) { /* no change needed */ - return(cred); + return cred; } /* @@ -2822,13 +4013,13 @@ kauth_cred_setresuid(kauth_cred_t cred, uid_t ruid, uid_t euid, uid_t svuid, uid */ bcopy(cred, &temp_cred, sizeof(temp_cred)); if (euid != KAUTH_UID_NONE) { - temp_cred.cr_uid = euid; + temp_pcred->cr_uid = euid; } if (ruid != KAUTH_UID_NONE) { - temp_cred.cr_ruid = ruid; + temp_pcred->cr_ruid = ruid; } if (svuid != KAUTH_UID_NONE) { - temp_cred.cr_svuid = svuid; + temp_pcred->cr_svuid = svuid; } /* @@ -2836,11 +4027,11 @@ kauth_cred_setresuid(kauth_cred_t cred, uid_t ruid, uid_t euid, uid_t svuid, uid * 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; + if ((temp_pcred->cr_gmuid = gmuid) == KAUTH_UID_NONE) { + temp_pcred->cr_flags |= CRF_NOMEMBERD; } - return(kauth_cred_update(cred, &temp_cred, TRUE)); + return kauth_cred_update(cred, &temp_cred, TRUE); } @@ -2873,20 +4064,22 @@ kauth_cred_setresuid(kauth_cred_t cred, uid_t ruid, uid_t euid, uid_t svuid, uid kauth_cred_t kauth_cred_setresgid(kauth_cred_t cred, gid_t rgid, gid_t egid, gid_t svgid) { - struct ucred temp_cred; + struct ucred temp_cred; + posix_cred_t temp_pcred = posix_cred_get(&temp_cred); + posix_cred_t pcred = posix_cred_get(cred); NULLCRED_CHECK(cred); DEBUG_CRED_ENTER("kauth_cred_setresgid %p %d %d %d\n", cred, rgid, egid, svgid); /* - * We don't need to do anything if the given GID are already the + * We don't need to do anything if the given GID are already the * same as the GIDs in the credential. */ - if (cred->cr_groups[0] == egid && - cred->cr_rgid == rgid && - cred->cr_svgid == svgid) { + if (pcred->cr_groups[0] == egid && + pcred->cr_rgid == rgid && + pcred->cr_svgid == svgid) { /* no change needed */ - return(cred); + return cred; } /* @@ -2898,26 +4091,26 @@ kauth_cred_setresgid(kauth_cred_t cred, gid_t rgid, gid_t egid, gid_t svgid) /* 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; + temp_pcred->cr_flags |= CRF_NOMEMBERD; + temp_pcred->cr_gmuid = KAUTH_UID_NONE; } else { DEBUG_CRED_CHANGE("not displaced\n"); } } if (rgid != KAUTH_GID_NONE) { - temp_cred.cr_rgid = rgid; + temp_pcred->cr_rgid = rgid; } if (svgid != KAUTH_GID_NONE) { - temp_cred.cr_svgid = svgid; + temp_pcred->cr_svgid = svgid; } - return(kauth_cred_update(cred, &temp_cred, TRUE)); + 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 + * 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. @@ -2931,7 +4124,7 @@ kauth_cred_setresgid(kauth_cred_t cred, gid_t rgid, gid_t egid, gid_t svgid) * Parameters: cred The original credential * groups Pointer to gid_t array which * contains the new group list - * groupcount The cound of valid groups which + * groupcount The count of valid groups which * are contained in 'groups' * gmuid KAUTH_UID_NONE -or- the new * group membership UID @@ -2954,7 +4147,7 @@ kauth_cred_setresgid(kauth_cred_t cred, gid_t rgid, gid_t egid, gid_t svgid) * 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 + * XXX: Changes are determined in ordinal order - if the caller passes * 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 @@ -2967,23 +4160,28 @@ kauth_cred_setresgid(kauth_cred_t cred, gid_t rgid, gid_t egid, gid_t svgid) kauth_cred_t kauth_cred_setgroups(kauth_cred_t cred, gid_t *groups, int groupcount, uid_t gmuid) { - int i; + int i; struct ucred temp_cred; + posix_cred_t temp_pcred = posix_cred_get(&temp_cred); + posix_cred_t pcred; NULLCRED_CHECK(cred); + pcred = posix_cred_get(cred); + /* * 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)) { + if ((pcred->cr_gmuid == gmuid) && (pcred->cr_ngroups == groupcount)) { for (i = 0; i < groupcount; i++) { - if (cred->cr_groups[i] != groups[i]) + if (pcred->cr_groups[i] != groups[i]) { break; + } } if (i == groupcount) { /* no change needed */ - return(cred); + return cred; } } @@ -2995,15 +4193,71 @@ kauth_cred_setgroups(kauth_cred_t cred, gid_t *groups, int groupcount, uid_t gmu * 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)); + temp_pcred->cr_ngroups = groupcount; + bcopy(groups, temp_pcred->cr_groups, sizeof(temp_pcred->cr_groups)); + temp_pcred->cr_gmuid = gmuid; + if (gmuid == KAUTH_UID_NONE) { + temp_pcred->cr_flags |= CRF_NOMEMBERD; + } else { + temp_pcred->cr_flags &= ~CRF_NOMEMBERD; + } + + return kauth_cred_update(cred, &temp_cred, TRUE); +} + +/* + * Notes: The return value exists to account for the possibility of a + * kauth_cred_t without a POSIX label. This will be the case in + * the future (see posix_cred_get() below, for more details). + */ +#if CONFIG_EXT_RESOLVER +int kauth_external_supplementary_groups_supported = 1; + +SYSCTL_INT(_kern, OID_AUTO, ds_supgroups_supported, CTLFLAG_RW | CTLFLAG_LOCKED, &kauth_external_supplementary_groups_supported, 0, ""); +#endif + +int +kauth_cred_getgroups(kauth_cred_t cred, gid_t *grouplist, int *countp) +{ + int limit = NGROUPS; + posix_cred_t pcred; + + pcred = posix_cred_get(cred); + +#if CONFIG_EXT_RESOLVER + /* + * If we've not opted out of using the resolver, then convert the cred to a list + * of supplemental groups. We do this only if there has been a resolver to talk to, + * since we may be too early in boot, or in an environment that isn't using DS. + */ + if (kauth_identitysvc_has_registered && kauth_external_supplementary_groups_supported && (pcred->cr_flags & CRF_NOMEMBERD) == 0) { + uid_t uid = kauth_cred_getuid(cred); + int err; + + err = kauth_cred_uid2groups(&uid, grouplist, countp); + if (!err) { + return 0; + } + + /* On error just fall through */ + KAUTH_DEBUG("kauth_cred_getgroups failed %d\n", err); + } +#endif /* CONFIG_EXT_RESOLVER */ + + /* + * If they just want a copy of the groups list, they may not care + * about the actual count. If they specify an input count, however, + * treat it as an indicator of the buffer size available in grouplist, + * and limit the returned list to that size. + */ + if (countp) { + limit = MIN(*countp, pcred->cr_ngroups); + *countp = limit; + } + + memcpy(grouplist, pcred->cr_groups, sizeof(gid_t) * limit); + + return 0; } @@ -3046,17 +4300,21 @@ kauth_cred_t kauth_cred_setuidgid(kauth_cred_t cred, uid_t uid, gid_t gid) { struct ucred temp_cred; + posix_cred_t temp_pcred = posix_cred_get(&temp_cred); + posix_cred_t pcred; NULLCRED_CHECK(cred); + pcred = posix_cred_get(cred); + /* * 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) { + if (pcred->cr_uid == uid && pcred->cr_ruid == uid && pcred->cr_svuid == uid && + pcred->cr_gid == gid && pcred->cr_rgid == gid && pcred->cr_svgid == gid) { /* no change needed */ - return(cred); + return cred; } /* @@ -3064,30 +4322,31 @@ kauth_cred_setuidgid(kauth_cred_t cred, uid_t uid, gid_t gid) * 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_pcred->cr_uid = uid; + temp_pcred->cr_ruid = uid; + temp_pcred->cr_svuid = uid; + temp_pcred->cr_flags = pcred->cr_flags; /* 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; + if (pcred->cr_flags & CRF_NOMEMBERD) { + temp_pcred->cr_gmuid = KAUTH_UID_NONE; + temp_pcred->cr_flags |= CRF_NOMEMBERD; } else { - temp_cred.cr_gmuid = uid; - temp_cred.cr_flags &= ~CRF_NOMEMBERD; + temp_pcred->cr_gmuid = uid; + temp_pcred->cr_flags &= ~CRF_NOMEMBERD; } - temp_cred.cr_ngroups = 1; + temp_pcred->cr_ngroups = 1; /* 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_pcred->cr_gmuid = KAUTH_UID_NONE; + temp_pcred->cr_flags |= CRF_NOMEMBERD; } - temp_cred.cr_rgid = gid; - temp_cred.cr_svgid = gid; + temp_pcred->cr_rgid = gid; + temp_pcred->cr_svgid = gid; #if CONFIG_MACF temp_cred.cr_label = cred->cr_label; #endif - return(kauth_cred_update(cred, &temp_cred, TRUE)); + return kauth_cred_update(cred, &temp_cred, TRUE); } @@ -3119,8 +4378,13 @@ kauth_cred_t kauth_cred_setsvuidgid(kauth_cred_t cred, uid_t uid, gid_t gid) { struct ucred temp_cred; + posix_cred_t temp_pcred = posix_cred_get(&temp_cred); + posix_cred_t pcred; NULLCRED_CHECK(cred); + + pcred = posix_cred_get(cred); + DEBUG_CRED_ENTER("kauth_cred_setsvuidgid: %p u%d->%d g%d->%d\n", cred, cred->cr_svuid, uid, cred->cr_svgid, gid); /* @@ -3128,9 +4392,9 @@ kauth_cred_setsvuidgid(kauth_cred_t cred, uid_t uid, gid_t gid) * uids are already the same as the uid provided. This check is * likely insufficient. */ - if (cred->cr_svuid == uid && cred->cr_svgid == gid) { + if (pcred->cr_svuid == uid && pcred->cr_svgid == gid) { /* no change needed */ - return(cred); + return cred; } DEBUG_CRED_CHANGE("kauth_cred_setsvuidgid: cred change\n"); @@ -3138,17 +4402,17 @@ kauth_cred_setsvuidgid(kauth_cred_t cred, uid_t uid, gid_t gid) * with new values. */ bcopy(cred, &temp_cred, sizeof(temp_cred)); - temp_cred.cr_svuid = uid; - temp_cred.cr_svgid = gid; + temp_pcred->cr_svuid = uid; + temp_pcred->cr_svgid = gid; - return(kauth_cred_update(cred, &temp_cred, TRUE)); + return kauth_cred_update(cred, &temp_cred, TRUE); } /* * kauth_cred_setauditinfo - * - * Description: Update the given credential using the given auditinfo_t. + * + * Description: Update the given credential using the given au_session_t. * * Parameters: cred The original credential * auditinfo_p Pointer to ne audit information @@ -3168,7 +4432,7 @@ kauth_cred_setsvuidgid(kauth_cred_t cred, uid_t uid, gid_t gid) * persistent reference. */ kauth_cred_t -kauth_cred_setauditinfo(kauth_cred_t cred, auditinfo_t *auditinfo_p) +kauth_cred_setauditinfo(kauth_cred_t cred, au_session_t *auditinfo_p) { struct ucred temp_cred; @@ -3178,21 +4442,21 @@ kauth_cred_setauditinfo(kauth_cred_t cred, auditinfo_t *auditinfo_p) * 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) { + if (bcmp(&cred->cr_audit, auditinfo_p, sizeof(cred->cr_audit)) == 0) { /* no change needed */ - return(cred); + return cred; } bcopy(cred, &temp_cred, sizeof(temp_cred)); - bcopy(auditinfo_p, &temp_cred.cr_au, sizeof(temp_cred.cr_au)); + bcopy(auditinfo_p, &temp_cred.cr_audit, sizeof(temp_cred.cr_audit)); - return(kauth_cred_update(cred, &temp_cred, FALSE)); + return kauth_cred_update(cred, &temp_cred, FALSE); } #if CONFIG_MACF /* * kauth_cred_label_update - * + * * Description: Update the MAC label associated with a credential * * Parameters: cred The original credential @@ -3226,12 +4490,12 @@ kauth_cred_label_update(kauth_cred_t cred, struct label *label) newcred = kauth_cred_update(cred, &temp_cred, TRUE); mac_cred_label_destroy(&temp_cred); - return (newcred); + return newcred; } /* * kauth_cred_label_update_execve - * + * * Description: Update the MAC label associated with a credential as * part of exec * @@ -3260,11 +4524,12 @@ kauth_cred_label_update(kauth_cred_t cred, struct label *label) * 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, - int *disjointp) + struct vnode *vp, off_t offset, struct vnode *scriptvp, struct label *scriptl, + struct label *execl, unsigned int *csflags, void *macextensions, int *disjointp, int *labelupdateerror) { kauth_cred_t newcred; struct ucred temp_cred; @@ -3273,12 +4538,13 @@ kauth_cred_label_update_execve(kauth_cred_t cred, vfs_context_t ctx, mac_cred_label_init(&temp_cred); mac_cred_label_associate(cred, &temp_cred); - *disjointp = mac_cred_label_update_execve(ctx, &temp_cred, - vp, scriptl, execl); + mac_cred_label_update_execve(ctx, &temp_cred, + vp, offset, scriptvp, scriptl, execl, csflags, + macextensions, disjointp, labelupdateerror); newcred = kauth_cred_update(cred, &temp_cred, TRUE); mac_cred_label_destroy(&temp_cred); - return (newcred); + return newcred; } /* @@ -3293,7 +4559,8 @@ kauth_cred_label_update_execve(kauth_cred_t cred, vfs_context_t ctx, * 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) +int +kauth_proc_label_update(struct proc *p, struct label *label) { kauth_cred_t my_cred, my_new_cred; @@ -3303,20 +4570,18 @@ int kauth_proc_label_update(struct proc *p, struct label *label) /* 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); + proc_ucred_lock(p); /* * We need to protect for a race where another thread * also changed the credential after we took our @@ -3324,22 +4589,24 @@ int kauth_proc_label_update(struct proc *p, struct label *label) * restart this again with the new cred. */ if (p->p_ucred != my_cred) { - proc_unlock(p); + proc_ucred_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); + /* update cred on proc */ + PROC_UPDATE_CREDS_ONPROC(p); + + proc_ucred_unlock(p); } break; } /* Drop old proc reference or our extra reference */ kauth_cred_unref(&my_cred); - - return (0); + + return 0; } /* @@ -3355,6 +4622,8 @@ int kauth_proc_label_update(struct proc *p, struct label *label) * vp The vnode being exec'ed * scriptl The script MAC label * execl The executable MAC label + * lupdateerror The error place holder for MAC label authority + * to update about possible termination * * Returns: 0 Label update did not make credential * disjoint @@ -3365,33 +4634,31 @@ int kauth_proc_label_update(struct proc *p, struct label *label) * result of this call. The caller should not assume the process * reference to the old credential still exists. */ -int + +void kauth_proc_label_update_execve(struct proc *p, vfs_context_t ctx, - struct vnode *vp, struct label *scriptl, struct label *execl) + struct vnode *vp, off_t offset, struct vnode *scriptvp, struct label *scriptl, + struct label *execl, unsigned int *csflags, void *macextensions, int *disjoint, int *update_return) { kauth_cred_t my_cred, my_new_cred; - int disjoint = 0; - 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, &disjoint); + */ + my_new_cred = kauth_cred_label_update_execve(my_cred, ctx, vp, offset, scriptvp, scriptl, execl, csflags, macextensions, disjoint, update_return); 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); + proc_ucred_lock(p); /* * We need to protect for a race where another thread * also changed the credential after we took our @@ -3399,29 +4666,28 @@ kauth_proc_label_update_execve(struct proc *p, vfs_context_t ctx, * restart this again with the new cred. */ if (p->p_ucred != my_cred) { - proc_unlock(p); + proc_ucred_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); + /* update cred on proc */ + PROC_UPDATE_CREDS_ONPROC(p); + proc_ucred_unlock(p); } break; } /* Drop old proc reference or our extra reference */ kauth_cred_unref(&my_cred); - - return (disjoint); } #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); kauth_cred_t kauth_cred_setlabel(kauth_cred_t cred, struct label *label) { @@ -3438,27 +4704,27 @@ kauth_proc_setlabel(struct proc *p, struct label *label) #else -/* this is a temp hack to cover us when MACF is not built in a kernel configuration. +/* 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. + * to define a stub. */ kauth_cred_t kauth_cred_label_update(__unused kauth_cred_t cred, __unused void *label) { - return(NULL); + return NULL; } int kauth_proc_label_update(__unused struct proc *p, __unused void *label) { - return (0); + 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(kauth_cred_t cred, void *label); kauth_cred_t kauth_cred_setlabel(__unused kauth_cred_t cred, __unused void *label) { @@ -3469,11 +4735,72 @@ int kauth_proc_setlabel(struct proc *p, void *label); int kauth_proc_setlabel(__unused struct proc *p, __unused void *label) { - return (0); + return 0; } #endif #endif +// TODO: move to os_refcnt once the ABI issue is resolved + +#define KAUTH_CRED_REF_MAX 0x0ffffffful + +__attribute__((noinline, cold, noreturn)) +static void +kauth_cred_panic_resurrection(kauth_cred_t cred) +{ + panic("kauth_cred_unref: cred %p resurrected", cred); + __builtin_unreachable(); +} + +__attribute__((noinline, cold, noreturn)) +static void +kauth_cred_panic_over_released(kauth_cred_t cred) +{ + panic("kauth_cred_unref: cred %p over-released", cred); + __builtin_unreachable(); +} + +__attribute__((noinline, cold, noreturn)) +static void +kauth_cred_panic_over_retain(kauth_cred_t cred) +{ + panic("kauth_cred_ref: cred %p over-retained", cred); + __builtin_unreachable(); +} + +/* + * kauth_cred_tryref + * + * Description: Tries to take a reference, used from kauth_cred_find_and_ref + * to debounce the race with kauth_cred_unref. + * + * Parameters: cred The credential to reference + * + * Returns: (bool) Whether the reference was taken + */ +static inline bool +kauth_cred_tryref(kauth_cred_t cred) +{ + u_long old_ref, new_ref; + os_atomic_rmw_loop(&cred->cr_ref, old_ref, new_ref, relaxed, { + if (old_ref == 0) { + os_atomic_rmw_loop_give_up(return false); + } + new_ref = old_ref + 1; + }); + if (__improbable(old_ref >= KAUTH_CRED_REF_MAX)) { + kauth_cred_panic_over_retain(cred); + } + +#if 0 // use this to watch a specific credential + if (is_target_cred( *credp ) != 0) { + get_backtrace(); + } +#endif + + return true; +} + /* * kauth_cred_ref * @@ -3482,114 +4809,60 @@ kauth_proc_setlabel(__unused struct proc *p, __unused void *label) * 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) { - int old_value; - - NULLCRED_CHECK(cred); + u_long old_ref = os_atomic_inc_orig(&cred->cr_ref, relaxed); - // 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 (__improbable(old_ref < 1)) { + kauth_cred_panic_resurrection(cred); + } + if (__improbable(old_ref >= KAUTH_CRED_REF_MAX)) { + kauth_cred_panic_over_retain(cred); + } #if 0 // use this to watch a specific credential - if ( is_target_cred( cred ) != 0 ) { - get_backtrace( ); + if (is_target_cred( cred ) != 0) { + get_backtrace(); } #endif - - return; } - /* - * kauth_cred_unref_hashlocked + * kauth_cred_unref_fast * - * Description: release a credential reference; when the last reference is - * released, the credential will be freed. + * Description: Release a credential reference. * * 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. + * Returns: true This was the last reference. + * false The object has more refs. * - * 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. */ -static void -kauth_cred_unref_hashlocked(kauth_cred_t *credp) +static inline bool +kauth_cred_unref_fast(kauth_cred_t cred) { - int old_value; - - KAUTH_CRED_HASH_LOCK_ASSERT(); - NULLCRED_CHECK(*credp); - - // XXX SInt32 not safe for an LP64 kernel - old_value = OSAddAtomic(-1, (SInt32 *)&(*credp)->cr_ref); - -#if DIAGNOSTIC - if (old_value == 0) - 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 + u_long old_ref = os_atomic_dec_orig(&cred->cr_ref, relaxed); #if 0 // use this to watch a specific credential - if ( is_target_cred( *credp ) != 0 ) { - get_backtrace( ); + 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 absolute reference is our credential hash table */ - kauth_cred_remove(*credp); + if (__improbable(old_ref <= 0)) { + kauth_cred_panic_over_released(cred); } - *credp = NOCRED; + return old_ref == 1; } - /* * 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. + * Description: Release a credential reference. + * Frees the credential if it is the last ref. * * Parameters: credp Pointer to address containing * credential to be freed @@ -3599,18 +4872,22 @@ kauth_cred_unref_hashlocked(kauth_cred_t *credp) * 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(); + if (kauth_cred_unref_fast(*credp)) { + KAUTH_CRED_HASH_LOCK(); + kauth_cred_remove_locked(*credp); + KAUTH_CRED_HASH_UNLOCK(); + kauth_cred_free(*credp); + } + + *credp = NOCRED; } +#ifndef __LP64__ /* * kauth_cred_rele * @@ -3631,6 +4908,7 @@ kauth_cred_rele(kauth_cred_t cred) { kauth_cred_unref(&cred); } +#endif /* !__LP64__ */ /* @@ -3653,18 +4931,18 @@ kauth_cred_rele(kauth_cred_t cred) * * 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. + * count, which is set to 0 to indicate the caller still has + * to call kauth_cred_add(). * * 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. + * can be safely initialized 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 + * cache, after which time it's frozen and becomes publicly * visible. * * The release protocol depends on kauth_hash_add() being called @@ -3672,31 +4950,24 @@ kauth_cred_rele(kauth_cred_t cred) * will trigger if this protocol is not observed). * */ -kauth_cred_t +static 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) - panic("kauth_cred_dup: bad credential"); -#endif + + assert(cred != NOCRED && cred != FSCRED); newcred = kauth_cred_alloc(); if (newcred != NULL) { -#if CONFIG_MACF - temp_label = newcred->cr_label; + newcred->cr_posix = cred->cr_posix; +#if CONFIG_AUDIT + newcred->cr_audit = cred->cr_audit; #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; + AUDIT_SESSION_REF(cred); } - return(newcred); + return newcred; } /* @@ -3714,19 +4985,21 @@ kauth_cred_dup(kauth_cred_t cred) * 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. + * via the kauth_cred_unref() KPI. */ kauth_cred_t kauth_cred_copy_real(kauth_cred_t cred) { kauth_cred_t newcred = NULL, found_cred; struct ucred temp_cred; + posix_cred_t temp_pcred = posix_cred_get(&temp_cred); + posix_cred_t pcred = posix_cred_get(cred); /* if the credential is already 'real', just take a reference */ - if ((cred->cr_ruid == cred->cr_uid) && - (cred->cr_rgid == cred->cr_gid)) { + if ((pcred->cr_ruid == pcred->cr_uid) && + (pcred->cr_rgid == pcred->cr_gid)) { kauth_cred_ref(cred); - return(cred); + return cred; } /* @@ -3734,58 +5007,36 @@ kauth_cred_copy_real(kauth_cred_t cred) * with the new values. */ bcopy(cred, &temp_cred, sizeof(temp_cred)); - temp_cred.cr_uid = cred->cr_ruid; + temp_pcred->cr_uid = pcred->cr_ruid; /* 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 (kauth_cred_change_egid(&temp_cred, pcred->cr_rgid)) { + temp_pcred->cr_flags |= CRF_NOMEMBERD; + temp_pcred->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; + if (temp_pcred->cr_gmuid != KAUTH_UID_NONE) { + temp_pcred->cr_gmuid = pcred->cr_ruid; + } - for (;;) { - int err; - - KAUTH_CRED_HASH_LOCK(); - found_cred = kauth_cred_find(&temp_cred); - if (found_cred == cred) { - /* same cred so just bail */ - KAUTH_CRED_HASH_UNLOCK(); - return(cred); - } - if (found_cred != NULL) { - /* - * 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 the real user and group IDs. - */ - newcred = kauth_cred_dup(&temp_cred); - err = kauth_cred_add(newcred); - KAUTH_CRED_HASH_UNLOCK(); + struct kauth_cred_entry_head *bucket = kauth_cred_get_bucket(cred); + + KAUTH_CRED_HASH_LOCK(); + found_cred = kauth_cred_find_and_ref(&temp_cred, bucket); + KAUTH_CRED_HASH_UNLOCK(); - /* Retry if kauth_cred_add() fails */ - if (err == 0) - break; -#if CONFIG_MACF - mac_cred_label_destroy(newcred); -#endif - FREE_ZONE(newcred, sizeof(*newcred), M_CRED); - newcred = NULL; + if (found_cred) { + return found_cred; } - - return(newcred); + + /* + * Must allocate a new credential, copy in old credential + * data and update the real user and group IDs. + */ + newcred = kauth_cred_dup(&temp_cred); + return kauth_cred_add(newcred, bucket); } @@ -3820,60 +5071,65 @@ kauth_cred_copy_real(kauth_cred_t cred) */ 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; - + boolean_t retain_auditinfo) +{ + kauth_cred_t cred; + /* * 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)); - - for (;;) { - int err; + if (retain_auditinfo) { + model_cred->cr_audit = old_cred->cr_audit; + } - KAUTH_CRED_HASH_LOCK(); - found_cred = kauth_cred_find(model_cred); - if (found_cred == old_cred) { - /* same cred so just bail */ - KAUTH_CRED_HASH_UNLOCK(); - return(old_cred); - } - if (found_cred != NULL) { - 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(); - return(found_cred); - } - + if (kauth_cred_is_equal(old_cred, model_cred)) { + return old_cred; + } + + struct kauth_cred_entry_head *bucket = kauth_cred_get_bucket(model_cred); + + KAUTH_CRED_HASH_LOCK(); + cred = kauth_cred_find_and_ref(model_cred, bucket); + if (cred != NULL) { /* - * Must allocate a new credential using the model. also - * adds the new credential to the credential hash table. + * We found a hit, so we can get rid of the old_cred. + * If we didn't, then we need to keep the old_cred around, + * because `model_cred` has copies of things such as the cr_label + * or audit session that it has not refcounts for. */ - new_cred = kauth_cred_dup(model_cred); - err = kauth_cred_add(new_cred); + bool needs_free = kauth_cred_unref_fast(old_cred); + if (needs_free) { + kauth_cred_remove_locked(old_cred); + } KAUTH_CRED_HASH_UNLOCK(); - /* retry if kauth_cred_add returns non zero value */ - if (err == 0) - break; -#if CONFIG_MACF - mac_cred_label_destroy(new_cred); -#endif - FREE_ZONE(new_cred, sizeof(*new_cred), M_CRED); - new_cred = NULL; + DEBUG_CRED_CHANGE("kauth_cred_update(cache hit): %p -> %p\n", + old_cred, cred); + if (needs_free) { + kauth_cred_free(old_cred); + } + return cred; } - DEBUG_CRED_CHANGE("kauth_cred_update(cache miss): %p -> %p\n", old_cred, new_cred); + KAUTH_CRED_HASH_UNLOCK(); + + /* + * Must allocate a new credential using the model. also + * adds the new credential to the credential hash table. + */ + cred = kauth_cred_dup(model_cred); + cred = kauth_cred_add(cred, bucket); + DEBUG_CRED_CHANGE("kauth_cred_update(cache miss): %p -> %p\n", + old_cred, cred); + + + /* + * This can't be done before the kauth_cred_dup() as the model_cred + * has pointers that old_cred owns references for. + */ kauth_cred_unref(&old_cred); - return(new_cred); + return cred; } @@ -3881,115 +5137,154 @@ kauth_cred_update(kauth_cred_t old_cred, kauth_cred_t model_cred, * 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 + * take an initial reference to account for the object being + * now valid. * * Parameters: new_cred Credential to insert into cred - * hash cache - * - * Returns: 0 Success - * -1 Hash insertion failed: caller - * should retry + * hash cache, or to destroy when + * a collision is detected. * - * Locks: Caller is expected to hold KAUTH_CRED_HASH_LOCK + * Returns: (kauth_thread_t) The inserted cred, or the + * collision that was found. * * Notes: The 'new_cred' MUST NOT already be in the cred hash cache */ -static int -kauth_cred_add(kauth_cred_t new_cred) +static kauth_cred_t +kauth_cred_add(kauth_cred_t new_cred, struct kauth_cred_entry_head *bucket) { - u_long hash_key; - - KAUTH_CRED_HASH_LOCK_ASSERT(); + kauth_cred_t found_cred; + u_long old_ref; - hash_key = kauth_cred_get_hashkey(new_cred); - hash_key %= kauth_cred_table_size; + KAUTH_CRED_HASH_LOCK(); + found_cred = kauth_cred_find_and_ref(new_cred, bucket); + if (found_cred) { + KAUTH_CRED_HASH_UNLOCK(); + kauth_cred_free(new_cred); + return found_cred; + } - /* race fix - there is a window where another matching credential - * could have been inserted between the time this one was created and we - * got the hash lock. If we find a match return an error and have the - * the caller retry. - */ - if (kauth_cred_find(new_cred) != NULL) { - return(-1); + old_ref = os_atomic_xchg(&new_cred->cr_ref, 1, relaxed); + if (old_ref != 0) { + panic("kauth_cred_add: invalid cred %p", new_cred); } - - /* take a reference for our use in credential hash table */ - kauth_cred_ref(new_cred); /* insert the credential into the hash table */ - TAILQ_INSERT_HEAD(&kauth_cred_table_anchor[hash_key], new_cred, cr_link); - - return(0); + LIST_INSERT_HEAD(bucket, new_cred, cr_link); + + KAUTH_CRED_HASH_UNLOCK(); + return new_cred; +} + +/* + * kauth_cred_remove_locked + * + * Description: Remove the given credential from our credential hash table. + * + * Parameters: cred Credential to remove. + * + * Locks: Caller is expected to hold KAUTH_CRED_HASH_LOCK + */ +static void +kauth_cred_remove_locked(kauth_cred_t cred) +{ + KAUTH_CRED_HASH_LOCK_ASSERT(); + + if (cred->cr_link.le_prev == NULL) { + panic("kauth_cred_unref: cred %p never added", cred); + } + + LIST_REMOVE(cred, cr_link); } +/* + * kauth_cred_is_equal + * + * Description: Returns whether two credentions are identical. + * + * Parameters: cred1 Credential to compare + * cred2 Credential to compare + * + * Returns: true Credentials are equal + * false Credentials are different + */ +static bool +kauth_cred_is_equal(kauth_cred_t cred1, kauth_cred_t cred2) +{ + posix_cred_t pcred1 = posix_cred_get(cred1); + posix_cred_t pcred2 = posix_cred_get(cred2); + + /* + * don't worry about the label unless the flags in + * either credential tell us to. + */ + if (memcmp(pcred1, pcred2, sizeof(*pcred1))) { + return false; + } + if (memcmp(&cred1->cr_audit, &cred2->cr_audit, sizeof(cred1->cr_audit))) { + return false; + } +#if CONFIG_MACF + /* Note: we know the flags are equal, so we only need to test one */ + if (pcred1->cr_flags & CRF_MAC_ENFORCE) { + if (!mac_cred_label_compare(cred1->cr_label, cred2->cr_label)) { + return false; + } + } +#endif + return true; +} -/* - * kauth_cred_remove +/* + * kauth_cred_find_and_ref * - * Description: Remove the given credential from our credential hash table + * Description: Using the given credential data, look for a match in our + * credential hash table * - * Parameters: cred Credential to remove from cred + * Parameters: cred Credential to lookup in cred * hash cache * - * Returns: (void) + * Returns: NULL Not found + * !NULL Matching credential already in + * cred hash cache, with a +1 ref * * 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 kauth_cred_t +kauth_cred_find_and_ref(kauth_cred_t cred, struct kauth_cred_entry_head *bucket) { - u_long hash_key; - kauth_cred_t found_cred; - - hash_key = kauth_cred_get_hashkey(cred); - hash_key %= kauth_cred_table_size; - - /* Avoid race */ - if (cred->cr_ref < 1) - panic("cred reference underflow"); - if (cred->cr_ref > 1) - return; /* someone else got a ref */ - + kauth_cred_t found_cred; + + KAUTH_CRED_HASH_LOCK_ASSERT(); + /* 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); -#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 - return; + LIST_FOREACH(found_cred, bucket, cr_link) { + if (kauth_cred_is_equal(found_cred, cred)) { + /* + * newer entries are inserted at the head, + * no hit further in the chain can possibly + * be successfully retained. + */ + if (!kauth_cred_tryref(found_cred)) { + found_cred = NULL; + } + break; } } - /* 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; + return found_cred; } - -/* +/* * kauth_cred_find * - * Description: Using the given credential data, look for a match in our - * credential hash table + * Description: This interface is sadly KPI but people can't possibly use it, + * as they need to hold a lock that isn't exposed. * * Parameters: cred Credential to lookup in cred * hash cache * * Returns: NULL Not found - * !NULL Matching cedential already in + * !NULL Matching credential already in * cred hash cache * * Locks: Caller is expected to hold KAUTH_CRED_HASH_LOCK @@ -3997,52 +5292,19 @@ kauth_cred_remove(kauth_cred_t cred) kauth_cred_t kauth_cred_find(kauth_cred_t cred) { - u_long hash_key; - kauth_cred_t found_cred; + struct kauth_cred_entry_head *bucket = kauth_cred_get_bucket(cred); + kauth_cred_t found_cred; KAUTH_CRED_HASH_LOCK_ASSERT(); -#if KAUTH_CRED_HASH_DEBUG - static int test_count = 0; - - test_count++; - if ((test_count % 200) == 0) { - kauth_cred_hash_print(); - } -#endif - - hash_key = kauth_cred_get_hashkey(cred); - hash_key %= kauth_cred_table_size; - /* Find cred in the credential hash table */ - TAILQ_FOREACH(found_cred, &kauth_cred_table_anchor[hash_key], cr_link) { - 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); + LIST_FOREACH(found_cred, bucket, cr_link) { + if (kauth_cred_is_equal(found_cred, cred)) { + break; } } - /* No match found */ - return(NULL); + return found_cred; } @@ -4061,8 +5323,8 @@ kauth_cred_find(kauth_cred_t cred) 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; + u_long hash_key = start_key; + u_long temp; while (data_len > 0) { hash_key = (hash_key << 4) + *datap++; @@ -4073,81 +5335,56 @@ kauth_cred_hash(const uint8_t *datap, int data_len, u_long start_key) hash_key &= ~temp; data_len--; } - return(hash_key); + return hash_key; } /* - * kauth_cred_get_hashkey + * kauth_cred_get_bucket * * 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). + * Returns the bucket correspondong to this hash key. * * 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 -/* - * kauth_cred_hash_print - * - * Description: Print out cred hash cache table information for debugging - * purposes, including the credential contents + * Returns: (kauth_cred_entry_head *) Returned bucket. * - * Parameters: (void) - * - * Returns: (void) - * - * Implicit returns: Results in console output + * Notes: When actually moving the POSIX credential into a real label, + * remember to update this hash computation. */ -static void -kauth_cred_hash_print(void) +static struct kauth_cred_entry_head * +kauth_cred_get_bucket(kauth_cred_t cred) { - int i, j; - kauth_cred_t found_cred; - - printf("\n\t kauth credential hash table statistics - current cred count %d \n", kauth_cred_count); - /* count slot hits, misses, collisions, and max depth */ - for (i = 0; i < kauth_cred_table_size; i++) { - printf("[%02d] ", i); - j = 0; - TAILQ_FOREACH(found_cred, &kauth_cred_table_anchor[i], cr_link) { - if (j > 0) { - printf("---- "); - } - j++; - kauth_cred_print(found_cred); - printf("\n"); - } - if (j == 0) { - printf("NOCRED \n"); - } +#if CONFIG_MACF + posix_cred_t pcred = posix_cred_get(cred); +#endif + u_long hash_key = 0; + + hash_key = kauth_cred_hash((uint8_t *)&cred->cr_posix, + sizeof(struct posix_cred), + hash_key); + hash_key = kauth_cred_hash((uint8_t *)&cred->cr_audit, + sizeof(struct au_session), + hash_key); +#if CONFIG_MACF + if (pcred->cr_flags & CRF_MAC_ENFORCE) { + hash_key = kauth_cred_hash((uint8_t *)cred->cr_label, + sizeof(struct label), + hash_key); } +#endif + + hash_key %= KAUTH_CRED_TABLE_SIZE; + return &kauth_cred_table_anchor[hash_key]; } -#endif /* KAUTH_CRED_HASH_DEBUG */ -#if (defined(KAUTH_CRED_HASH_DEBUG) && (KAUTH_CRED_HASH_DEBUG != 0)) || defined(DEBUG_CRED) +#ifdef DEBUG_CRED /* * kauth_cred_print * @@ -4161,108 +5398,134 @@ kauth_cred_hash_print(void) * Implicit returns: Results in console output */ void -kauth_cred_print(kauth_cred_t cred) +kauth_cred_print(kauth_cred_t cred) { - int i; + int i; 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) + if (i == 0) { printf("e"); + } printf("%d ", cred->cr_groups[i]); } 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); + printf("auditinfo_addr %d %d %d %d %d %d\n", + cred->cr_audit.s_aia_p->ai_auid, + cred->cr_audit.as_mask.am_success, + cred->cr_audit.as_mask.am_failure, + cred->cr_audit.as_aia_p->ai_termid.at_port, + cred->cr_audit.as_aia_p->ai_termid.at_addr[0], + cred->cr_audit.as_aia_p->ai_asid); } -int is_target_cred( kauth_cred_t the_cred ) +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_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_audit.as_aia_p->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 + * if ( the_cred->cr_audit.as_mask.am_success != 0 ) + * return( 0 ); + * if ( the_cred->cr_audit.as_mask.am_failure != 0 ) + * return( 0 ); + * if ( the_cred->cr_audit.as_aia_p->ai_termid.at_port != 0 ) + * return( 0 ); + * if ( the_cred->cr_audit.as_aia_p->ai_termid.at_addr[0] != 0 ) + * return( 0 ); + * if ( the_cred->cr_audit.as_aia_p->ai_asid != 0 ) + * return( 0 ); + * if ( the_cred->cr_flags != 0 ) + * return( 0 ); + */ + return -1; // found target cred } -void get_backtrace( void ) +void +get_backtrace( void ) { - int my_slot; - void * my_stack[ MAX_STACK_DEPTH ]; - int i, my_depth; - - if ( cred_debug_buf_p == NULL ) { + 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) ) { + 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 ) { + 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 ]; + 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; @@ -4271,45 +5534,46 @@ void get_backtrace( void ) /* 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 */ + 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_addr cr_audit; /* 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: +/* 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; + 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); + if (req->newptr != USER_ADDR_NULL) { + return EPERM; + } /* calculate space needed */ - for (i = 0; i < kauth_cred_table_size; i++) { + for (i = 0; i < KAUTH_CRED_TABLE_SIZE; i++) { TAILQ_FOREACH(found_cred, &kauth_cred_table_anchor[i], cr_link) { counter++; } @@ -4322,15 +5586,15 @@ sysctl_dump_creds( __unused struct sysctl_oid *oidp, __unused void *arg1, __unus return 0; } - MALLOC( cred_listp, debug_ucred *, req->oldlen, M_TEMP, M_WAITOK ); - if ( cred_listp == NULL ) { - return (ENOMEM); + MALLOC( cred_listp, debug_ucred *, req->oldlen, M_TEMP, M_WAITOK | M_ZERO); + 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++) { + 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; @@ -4338,62 +5602,79 @@ sysctl_dump_creds( __unused struct sysctl_oid *oidp, __unused void *arg1, __unus 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 ]; + 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_audit.ai_auid = + found_cred->cr_audit.as_aia_p->ai_auid; + nextp->cr_audit.ai_mask.am_success = + found_cred->cr_audit.as_mask.am_success; + nextp->cr_audit.ai_mask.am_failure = + found_cred->cr_audit.as_mask.am_failure; + nextp->cr_audit.ai_termid.at_port = + found_cred->cr_audit.as_aia_p->ai_termid.at_port; + nextp->cr_audit.ai_termid.at_type = + found_cred->cr_audit.as_aia_p->ai_termid.at_type; + nextp->cr_audit.ai_termid.at_addr[0] = + found_cred->cr_audit.as_aia_p->ai_termid.at_addr[0]; + nextp->cr_audit.ai_termid.at_addr[1] = + found_cred->cr_audit.as_aia_p->ai_termid.at_addr[1]; + nextp->cr_audit.ai_termid.at_addr[2] = + found_cred->cr_audit.as_aia_p->ai_termid.at_addr[2]; + nextp->cr_audit.ai_termid.at_addr[3] = + found_cred->cr_audit.as_aia_p->ai_termid.at_addr[3]; + nextp->cr_audit.ai_asid = + found_cred->cr_audit.as_aia_p->ai_asid; + nextp->cr_audit.ai_flags = + found_cred->cr_audit.as_aia_p->ai_flags; nextp->cr_label = found_cred->cr_label; nextp->cr_flags = found_cred->cr_flags; nextp++; space += sizeof(debug_ucred); - if ( space > req->oldlen ) { + if (space > req->oldlen) { FREE(cred_listp, M_TEMP); - return (ENOMEM); + return ENOMEM; } } } req->oldlen = space; error = SYSCTL_OUT(req, cred_listp, req->oldlen); FREE(cred_listp, M_TEMP); - return (error); + 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: +/* 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; + 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 (req->newptr != USER_ADDR_NULL) { + return EPERM; + } - if ( cred_debug_buf_p == NULL ) { - return (EAGAIN); + 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); + 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) { @@ -4401,24 +5682,24 @@ sysctl_dump_cred_backtraces( __unused struct sysctl_oid *oidp, __unused void *ar return 0; } - if ( space > req->oldlen ) { - return (ENOMEM); + if (space > req->oldlen) { + return ENOMEM; } - MALLOC( bt_bufp, cred_debug_buffer *, req->oldlen, M_TEMP, M_WAITOK ); - if ( bt_bufp == NULL ) { - return (ENOMEM); + MALLOC( bt_bufp, cred_debug_buffer *, req->oldlen, M_TEMP, M_WAITOK | M_ZERO); + 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 ]; + + 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 ]; + 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++; @@ -4426,7 +5707,231 @@ sysctl_dump_cred_backtraces( __unused struct sysctl_oid *oidp, __unused void *ar req->oldlen = space; error = SYSCTL_OUT(req, bt_bufp, req->oldlen); FREE(bt_bufp, M_TEMP); - return (error); + return error; +} + +#endif /* DEBUG_CRED */ + + +/* + ********************************************************************** + * The following routines will be moved to a policy_posix.c module at + * some future point. + ********************************************************************** + */ + +/* + * posix_cred_create + * + * Description: Helper function to create a kauth_cred_t credential that is + * initally labelled with a specific POSIX credential label + * + * Parameters: pcred The posix_cred_t to use as the initial + * label value + * + * Returns: (kauth_cred_t) The credential that was found in the + * hash or creates + * NULL kauth_cred_add() failed, or there was + * no egid specified, or we failed to + * attach a label to the new credential + * + * Notes: This function currently wraps kauth_cred_create(), and is the + * only consumer of that ill-fated function, apart from bsd_init(). + * It exists solely to support the NFS server code creation of + * credentials based on the over-the-wire RPC calls containing + * traditional POSIX credential information being tunneled to + * the server host from the client machine. + * + * In the future, we hope this function goes away. + * + * In the short term, it creates a temporary credential, puts + * the POSIX information from NFS into it, and then calls + * kauth_cred_create(), as an internal implementation detail. + * + * If we have to keep it around in the medium term, it will + * create a new kauth_cred_t, then label it with a POSIX label + * corresponding to the contents of the kauth_cred_t. If the + * policy_posix MACF module is not loaded, it will instead + * substitute a posix_cred_t which GRANTS all access (effectively + * a "root" credential) in order to not prevent NFS from working + * in the case that we are not supporting POSIX credentials. + */ +kauth_cred_t +posix_cred_create(posix_cred_t pcred) +{ + struct ucred temp_cred; + + bzero(&temp_cred, sizeof(temp_cred)); + temp_cred.cr_posix = *pcred; + + return kauth_cred_create(&temp_cred); +} + + +/* + * posix_cred_get + * + * Description: Given a kauth_cred_t, return the POSIX credential label, if + * any, which is associated with it. + * + * Parameters: cred The credential to obtain the label from + * + * Returns: posix_cred_t The POSIX credential label + * + * Notes: In the event that the policy_posix MACF module IS NOT loaded, + * this function will return a pointer to a posix_cred_t which + * GRANTS all access (effectively, a "root" credential). This is + * necessary to support legacy code which insists on tightly + * integrating POSIX credentials into its APIs, including, but + * not limited to, System V IPC mechanisms, POSIX IPC mechanisms, + * NFSv3, signals, dtrace, and a large number of kauth routines + * used to implement POSIX permissions related system calls. + * + * In the event that the policy_posix MACF module IS loaded, and + * there is no POSIX label on the kauth_cred_t credential, this + * function will return a pointer to a posix_cred_t which DENIES + * all access (effectively, a "deny rights granted by POSIX" + * credential). This is necessary to support the concept of a + * transiently loaded POSIX policy, or kauth_cred_t credentials + * which can not be used in conjunctions with POSIX permissions + * checks. + * + * This function currently returns the address of the cr_posix + * field of the supplied kauth_cred_t credential, and as such + * currently can not fail. In the future, this will not be the + * case. + */ +posix_cred_t +posix_cred_get(kauth_cred_t cred) +{ + return &cred->cr_posix; +} + + +/* + * posix_cred_label + * + * Description: Label a kauth_cred_t with a POSIX credential label + * + * Parameters: cred The credential to label + * pcred The POSIX credential t label it with + * + * Returns: (void) + * + * Notes: This function is currently void in order to permit it to fit + * in with the current MACF framework label methods which allow + * labeling to fail silently. This is like acceptable for + * mandatory access controls, but not for POSIX, since those + * access controls are advisory. We will need to consider a + * return value in a future version of the MACF API. + * + * This operation currently cannot fail, as currently the POSIX + * credential is a subfield of the kauth_cred_t (ucred), which + * MUST be valid. In the future, this will not be the case. + */ +void +posix_cred_label(kauth_cred_t cred, posix_cred_t pcred) +{ + cred->cr_posix = *pcred; /* structure assign for now */ } -#endif /* KAUTH_CRED_HASH_DEBUG || DEBUG_CRED */ + +/* + * posix_cred_access + * + * Description: Perform a POSIX access check for a protected object + * + * Parameters: cred The credential to check + * object_uid The POSIX UID of the protected object + * object_gid The POSIX GID of the protected object + * object_mode The POSIX mode of the protected object + * mode_req The requested POSIX access rights + * + * Returns 0 Access is granted + * EACCES Access is denied + * + * Notes: This code optimizes the case where the world and group rights + * would both grant the requested rights to avoid making a group + * membership query. This is a big performance win in the case + * where this is true. + */ +int +posix_cred_access(kauth_cred_t cred, id_t object_uid, id_t object_gid, mode_t object_mode, mode_t mode_req) +{ + int is_member; + mode_t mode_owner = (object_mode & S_IRWXU); + mode_t mode_group = (object_mode & S_IRWXG) << 3; + mode_t mode_world = (object_mode & S_IRWXO) << 6; + + /* + * Check first for owner rights + */ + if (kauth_cred_getuid(cred) == object_uid && (mode_req & mode_owner) == mode_req) { + return 0; + } + + /* + * Combined group and world rights check, if we don't have owner rights + * + * OPTIMIZED: If group and world rights would grant the same bits, and + * they set of requested bits is in both, then we can simply check the + * world rights, avoiding a group membership check, which is expensive. + */ + if ((mode_req & mode_group & mode_world) == mode_req) { + return 0; + } else { + /* + * NON-OPTIMIZED: requires group membership check. + */ + if ((mode_req & mode_group) != mode_req) { + /* + * exclusion group : treat errors as "is a member" + * + * NON-OPTIMIZED: +group would deny; must check group + */ + if (!kauth_cred_ismember_gid(cred, object_gid, &is_member) && is_member) { + /* + * DENY: +group denies + */ + return EACCES; + } else { + if ((mode_req & mode_world) != mode_req) { + /* + * DENY: both -group & world would deny + */ + return EACCES; + } else { + /* + * ALLOW: allowed by -group and +world + */ + return 0; + } + } + } else { + /* + * inclusion group; treat errors as "not a member" + * + * NON-OPTIMIZED: +group allows, world denies; must + * check group + */ + if (!kauth_cred_ismember_gid(cred, object_gid, &is_member) && is_member) { + /* + * ALLOW: allowed by +group + */ + return 0; + } else { + if ((mode_req & mode_world) != mode_req) { + /* + * DENY: both -group & world would deny + */ + return EACCES; + } else { + /* + * ALLOW: allowed by -group and +world + */ + return 0; + } + } + } + } +}