X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/13f56ec4e58bf8687e2a68032c093c0213dd519b..e8c3f78193f1895ea514044358b93b1add9322f3:/bsd/kern/kern_credential.c diff --git a/bsd/kern/kern_credential.c b/bsd/kern/kern_credential.c index 4a6ee0386..141807dc8 100644 --- a/bsd/kern/kern_credential.c +++ b/bsd/kern/kern_credential.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2010 Apple Inc. All rights reserved. + * Copyright (c) 2004-2011 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -48,25 +48,20 @@ #include #include #include +#include #include #include #include /* For manifest constants in posix_cred_access */ #include -#include #include #include -/* mach_absolute_time() */ -#include -#include -#include - #include #include -#include +#include #ifdef MACH_ASSERT # undef MACH_ASSERT #endif @@ -76,14 +71,36 @@ #if CONFIG_MACF #include #include +#include #endif +#include + void mach_kauth_cred_uthread_update( void ); #define CRED_DIAGNOSTIC 0 # 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 + +#ifdef K_UUID_ARG +#undef K_UUID_ARG +#endif + +# define K_UUID_FMT "%08x:%08x:%08x:%08x" +# define K_UUID_ARG(_u) *(int *)&_u.g_guid[0],*(int *)&_u.g_guid[4],*(int *)&_u.g_guid[8],*(int *)&_u.g_guid[12] +# 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 * change a credential, and we can track actual credential changes that @@ -130,6 +147,7 @@ cred_debug_buffer * cred_debug_buf_p = NULL; #endif /* !DEBUG_CRED */ +#if CONFIG_EXT_RESOLVER /* * Interface to external identity resolver. * @@ -144,6 +162,7 @@ static lck_mtx_t *kauth_resolver_mtx; #define KAUTH_RESOLVER_UNLOCK() lck_mtx_unlock(kauth_resolver_mtx); 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 */ @@ -153,7 +172,6 @@ struct kauth_resolver_work { struct kauth_identity_extlookup kr_work; uint64_t kr_extend; uint32_t kr_seqno; - uint64_t kr_subtime; /* submission time */ int kr_refs; int kr_flags; #define KAUTH_REQUEST_UNSUBMITTED (1<<0) @@ -166,29 +184,117 @@ TAILQ_HEAD(kauth_resolver_unsubmitted_head, kauth_resolver_work) kauth_resolver_ 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 *); -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; +#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_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); + +#endif /* CONFIG_EXT_RESOLVER */ + +#define KAUTH_CRED_TABLE_SIZE 97 TAILQ_HEAD(kauth_cred_entry_head, ucred); static struct kauth_cred_entry_head * kauth_cred_table_anchor = NULL; -/* Weighted moving average for resolver response time */ -static struct kco_moving_average resolver_ma; - #define KAUTH_CRED_HASH_DEBUG 0 static int kauth_cred_add(kauth_cred_t new_cred); -static void kauth_cred_remove(kauth_cred_t cred); +static boolean_t kauth_cred_remove(kauth_cred_t cred); static inline u_long kauth_cred_hash(const uint8_t *datap, int data_len, u_long start_key); static u_long kauth_cred_get_hashkey(kauth_cred_t cred); static kauth_cred_t kauth_cred_update(kauth_cred_t old_cred, kauth_cred_t new_cred, boolean_t retain_auditinfo); -static void kauth_cred_unref_hashlocked(kauth_cred_t *credp); +static boolean_t kauth_cred_unref_hashlocked(kauth_cred_t *credp); #if KAUTH_CRED_HASH_DEBUG static int kauth_cred_count = 0; @@ -196,6 +302,51 @@ 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; +} + /* * kauth_resolver_init @@ -206,7 +357,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.. @@ -232,11 +383,6 @@ kauth_resolver_init(void) TAILQ_INIT(&kauth_resolver_done); kauth_resolver_sequence = 31337; kauth_resolver_mtx = lck_mtx_alloc_init(kauth_lck_grp, 0/*LCK_ATTR_NULL*/); - - /* - * 110% of average response time is "too long" and should be reported - */ - kco_ma_init(&resolver_ma, 110, KCO_MA_F_WMA); } @@ -283,7 +429,6 @@ kauth_resolver_submit(struct kauth_identity_extlookup *lkp, uint64_t extend_data struct kauth_resolver_work *workp, *killp; struct timespec ts; int error, shouldfree; - uint64_t duration; /* no point actually blocking if the resolver isn't up yet */ if (kauth_resolver_identity == 0) { @@ -326,7 +471,7 @@ kauth_resolver_submit(struct kauth_identity_extlookup *lkp, uint64_t extend_data workp->kr_work.el_result = KAUTH_EXTLOOKUP_INPROG; /* - * XXX We *MUST NOT* attempt to coelesce identical work items due to + * 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. @@ -335,80 +480,51 @@ kauth_resolver_submit(struct kauth_identity_extlookup *lkp, uint64_t extend_data /* * 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 grabed + * 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 = 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) { - error = EIO; - break; - } - /* an error? */ - if (error != 0) - break; - } - - /* - * Update the moving average of how long the request took; if it - * took longer than the time threshold, then we complain about it - * being slow. - */ - duration = mach_absolute_time() - workp->kr_subtime; - if (kco_ma_addsample(&resolver_ma, duration)) { - uint64_t average; - uint64_t old_average; - int32_t threshold; - int count; - - /* If we can't get information, don't log anything */ - if (kco_ma_info(&resolver_ma, KCO_MA_F_WMA, &average, &old_average, &threshold, &count)) { - char pname[MAXCOMLEN+1] = "(NULL)"; - proc_name(kauth_resolver_identity, pname, sizeof(pname)); - // printf("kauth_resolver_submit: External resolver pid %d (name %s) response time %lld, average %lld new %lld threshold %d%% actual %d%% count %d\n", kauth_resolver_identity, pname, duration, old_average, average, threshold, (int)((duration * 100) / old_average), count); - } - } + error = __KERNEL_IS_WAITING_ON_EXTERNAL_CREDENTIAL_RESOLVER__(workp); /* if the request was processed, copy the result */ 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"); + 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); + } + } - /* - * 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 @@ -477,9 +593,15 @@ identitysvc(__unused struct proc *p, struct identitysvc_args *uap, __unused int3 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. */ @@ -511,6 +633,7 @@ identitysvc(__unused struct proc *p, struct identitysvc_args *uap, __unused int3 } kauth_resolver_identity = new_id; kauth_resolver_registered = 1; + kauth_identitysvc_has_registered = 1; wakeup(&kauth_resolver_unsubmitted); } KAUTH_RESOLVER_UNLOCK(); @@ -518,14 +641,58 @@ identitysvc(__unused struct proc *p, struct identitysvc_args *uap, __unused int3 } /* - * 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); } + + 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 (opcode == KAUTH_EXTLOOKUP_DEREGISTER) { + 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. @@ -612,15 +779,17 @@ kauth_resolver_getwork_continue(int result) * If this is a wakeup from another thread in the resolver * deregistering it, error out the request-for-work thread */ - if (!kauth_resolver_identity) - error = EIO; + if (!kauth_resolver_identity) { + RESOLVER_FAILED_MESSAGE("external resolver died"); + error = KAUTH_RESOLVER_FAILED_ERRCODE; + } KAUTH_RESOLVER_UNLOCK(); return(error); } thread = current_thread(); ut = get_bsdthread_info(thread); - message = ut->uu_kauth.message; + message = ut->uu_save.uus_kauth.message; return(kauth_resolver_getwork2(message)); } @@ -638,7 +807,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 @@ -708,7 +877,6 @@ kauth_resolver_getwork2(user_addr_t message) TAILQ_REMOVE(&kauth_resolver_unsubmitted, workp, kr_link); workp->kr_flags &= ~KAUTH_REQUEST_UNSUBMITTED; workp->kr_flags |= KAUTH_REQUEST_SUBMITTED; - workp->kr_subtime = mach_absolute_time(); TAILQ_INSERT_TAIL(&kauth_resolver_submitted, workp, kr_link); out: @@ -734,7 +902,7 @@ 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) @@ -748,15 +916,17 @@ 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(); /* * If this is a wakeup from another thread in the resolver * deregistering it, error out the request-for-work thread */ - if (!kauth_resolver_identity) - error = EIO; + if (!kauth_resolver_identity) { + printf("external resolver died"); + error = KAUTH_RESOLVER_FAILED_ERRCODE; + } return(error); } return kauth_resolver_getwork2(message); @@ -780,7 +950,7 @@ kauth_resolver_complete(user_addr_t message) struct kauth_identity_extlookup extl; struct kauth_resolver_work *workp; struct kauth_resolver_work *killp; - int error, result; + int error, result, want_extend_data; /* * Copy in the mesage, including the extension field, since we are @@ -814,6 +984,7 @@ kauth_resolver_complete(user_addr_t message) 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 @@ -831,7 +1002,7 @@ kauth_resolver_complete(user_addr_t message) /* Cause all waiting-for-work threads to return EIO */ wakeup((caddr_t)&kauth_resolver_unsubmitted); /* and return EIO to the caller */ - error = EIO; + error = KAUTH_RESOLVER_FAILED_ERRCODE; break; case KAUTH_EXTLOOKUP_BADRQ: @@ -841,12 +1012,14 @@ kauth_resolver_complete(user_addr_t message) 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; } @@ -861,6 +1034,10 @@ kauth_resolver_complete(user_addr_t message) TAILQ_FOREACH(workp, &kauth_resolver_submitted, kr_link) { /* found it? */ if (workp->kr_seqno == extl.el_seqno) { + /* + * 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 @@ -898,13 +1075,23 @@ kauth_resolver_complete(user_addr_t message) * 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 (extl.el_flags & (KAUTH_EXTLOOKUP_VALID_PWNAM|KAUTH_EXTLOOKUP_VALID_GRNAM)) { + 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, request_flags); } /* @@ -925,60 +1112,22 @@ kauth_resolver_complete(user_addr_t message) 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) #define KI_VALID_PWNAM (1<<4) /* Used for translation */ #define KI_VALID_GRNAM (1<<5) /* Used for translation */ - uid_t ki_uid; - gid_t ki_gid; - 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_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, 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_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); - +#define KI_VALID_GROUPS (1<<6) +#if CONFIG_EXT_RESOLVER /* * kauth_identity_init * @@ -988,7 +1137,7 @@ static int kauth_identity_find_nam(char *name, int valid, struct kauth_identity * * 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. * @@ -1012,8 +1161,8 @@ 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 @@ -1021,7 +1170,9 @@ kauth_identity_init(void) * 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, const char *name, int nametype) +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; @@ -1038,6 +1189,24 @@ kauth_identity_alloc(uid_t uid, gid_t gid, guid_t *guidp, time_t guid_expiry, nt 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; @@ -1125,7 +1294,7 @@ kauth_identity_register_and_free(struct kauth_identity *kip) * 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--; @@ -1199,16 +1368,28 @@ kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_id 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; @@ -1234,9 +1415,12 @@ 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) { @@ -1260,12 +1444,12 @@ 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; @@ -1291,9 +1475,12 @@ 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) { @@ -1314,6 +1501,25 @@ kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_id } +/* + * 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 * @@ -1357,8 +1563,15 @@ 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); + 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); } @@ -1379,11 +1592,45 @@ 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); + 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 @@ -1505,7 +1752,7 @@ kauth_identity_find_guid(guid_t *guidp, struct kauth_identity *kir, char *getnam * * Parameters: name Pointer to name to find * valid KI_VALID_PWNAM or KI_VALID_GRNAM - * kir Pointer to return aread + * kir Pointer to return area * * Returns: 0 Found * ENOENT Not found @@ -1570,6 +1817,7 @@ kauth_identity_find_ntsid(ntsid_t *ntsid, struct kauth_identity *kir, char *getn KAUTH_IDENTITY_UNLOCK(); return((kip == NULL) ? ENOENT : 0); } +#endif /* CONFIG_EXT_RESOLVER */ /* @@ -1586,7 +1834,7 @@ 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 @@ -1603,7 +1851,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" @@ -1642,10 +1890,10 @@ kauth_wellknown_guid(guid_t *guid) * * 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 @@ -1673,7 +1921,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); /* @@ -1860,6 +2107,115 @@ kauth_cred_getsvgid(kauth_cred_t cred) } +static int kauth_cred_cache_lookup(int from, int to, void *src, void *dst); + +#if CONFIG_EXT_RESOLVER == 0 +/* + * 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 supplemental GROUPS from UID + * + * 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: + * *groups Modified, if successful + * *gcount Modified, if successful + * + */ +static int +kauth_cred_uid2groups(uid_t *uid, gid_t *groups, int *gcount) +{ + 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_guid2pwnam * @@ -1995,6 +2351,45 @@ kauth_cred_guid2gid(guid_t *guidp, gid_t *gidp) return(kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_GID, guidp, gidp)); } +/* + * kauth_cred_nfs4domain2dsnode + * + * Description: Fetch dsnode from nfs4domain + * + * Parameters: nfs4domain Pointer to a string nfs4 domain + * dsnode Pointer to buffer for dsnode + * + * Returns: 0 Success + * ENOENT For now just a stub that always fails + * + * Implicit returns: + * *dsnode Modified, if successuful + */ +int +kauth_cred_nfs4domain2dsnode(__unused char *nfs4domain, __unused char *dsnode) +{ + return(ENOENT); +} + +/* + * kauth_cred_dsnode2nfs4domain + * + * Description: Fetch nfs4domain from dsnode + * + * Parameters: nfs4domain Pointer to string dsnode + * dsnode Pointer to buffer for nfs4domain + * + * Returns: 0 Success + * ENOENT For now just a stub that always fails + * + * Implicit returns: + * *nfs4domain Modified, if successuful + */ +int +kauth_cred_dsnode2nfs4domain(__unused char *dsnode, __unused char *nfs4domain) +{ + return(ENOENT); +} /* * kauth_cred_ntsid2uid @@ -2225,6 +2620,7 @@ 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) { @@ -2245,7 +2641,10 @@ kauth_cred_cache_lookup(int from, int to, void *src, void *dst) * 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) { @@ -2269,6 +2668,9 @@ kauth_cred_cache_lookup(int from, int to, void *src, void *dst) default: 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) { /* any other error is fatal */ @@ -2286,6 +2688,9 @@ kauth_cred_cache_lookup(int from, int to, void *src, void *dst) 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: @@ -2325,6 +2730,7 @@ kauth_cred_cache_lookup(int from, int to, void *src, void *dst) /* 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 { /* @@ -2422,10 +2828,42 @@ kauth_cred_cache_lookup(int from, int to, void *src, void *dst) 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); + + 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 external lookup successful? */ @@ -2472,6 +2910,18 @@ 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() */ @@ -2490,28 +2940,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 * @@ -2521,7 +2949,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 @@ -2551,7 +2979,14 @@ 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); } @@ -2623,7 +3058,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; } @@ -2644,7 +3079,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; } /* @@ -2655,7 +3090,7 @@ 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--; @@ -2669,6 +3104,25 @@ kauth_groups_updatecache(struct kauth_identity_extlookup *el) 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--; + FREE(gm, M_KAUTH); + } +} +#endif /* CONFIG_EXT_RESOLVER */ /* * Group membership KPI @@ -2687,7 +3141,7 @@ 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 @@ -2702,16 +3156,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) { posix_cred_t pcred = posix_cred_get(cred); - struct kauth_group_membership *gm; - struct kauth_identity_extlookup el; - int i, error; + int i; /* * Check the per-credential list of override groups. @@ -2735,7 +3187,11 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) 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. @@ -2744,7 +3200,7 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) *resultp = 0; return(0); } - + /* TODO: */ /* XXX check supplementary groups */ /* XXX check whiteout groups */ @@ -2767,9 +3223,11 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) KAUTH_GROUPS_UNLOCK(); /* if we did, we can return now */ - if (gm != NULL) + 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; @@ -2777,7 +3235,13 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) el.el_uid = pcred->cr_gmuid; el.el_gid = gid; 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 */ @@ -2790,9 +3254,12 @@ kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp) } return(ENOENT); +#else + *resultp = 0; + return(0); +#endif } - /* * kauth_cred_ismember_guid * @@ -2820,15 +3287,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) { - struct kauth_identity ki; - 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; @@ -2836,7 +3299,11 @@ kauth_cred_ismember_guid(kauth_cred_t cred, guid_t *guidp, int *resultp) *resultp = 1; break; default: -#if 6603280 + { + 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 @@ -2863,7 +3330,7 @@ kauth_cred_ismember_guid(kauth_cred_t cred, guid_t *guidp, int *resultp) return (0); } } -#endif /* 6603280 */ +#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 @@ -2880,9 +3347,13 @@ 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); } + } + break; } return(error); } @@ -3030,15 +3501,14 @@ 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), + (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++) { + for (i = 0; i < KAUTH_CRED_TABLE_SIZE; i++) { TAILQ_INIT(&kauth_cred_table_anchor[i]); } } @@ -3267,7 +3737,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. * @@ -3289,10 +3759,10 @@ kauth_cred_proc_ref(proc_t procp) { kauth_cred_t cred; - proc_lock(procp); + proc_ucred_lock(procp); cred = proc_ucred(procp); kauth_cred_ref(cred); - proc_unlock(procp); + proc_ucred_unlock(procp); return(cred); } @@ -3670,7 +4140,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 @@ -3693,7 +4163,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 @@ -3750,17 +4220,42 @@ kauth_cred_setgroups(kauth_cred_t cred, gid_t *groups, int groupcount, uid_t gmu } /* - * XXX temporary, for NFS support until we can come up with a better - * XXX enumeration/comparison mechanism - * - * Notes: The return value exists to account for the possbility of a + * 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 @@ -3769,11 +4264,11 @@ kauth_cred_getgroups(kauth_cred_t cred, gid_t *grouplist, int *countp) * and limit the returned list to that size. */ if (countp) { - limit = MIN(*countp, cred->cr_posix.cr_ngroups); + limit = MIN(*countp, pcred->cr_ngroups); *countp = limit; } - memcpy(grouplist, cred->cr_posix.cr_groups, sizeof(gid_t) * limit); + memcpy(grouplist, pcred->cr_groups, sizeof(gid_t) * limit); return 0; } @@ -4042,11 +4537,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; @@ -4055,8 +4551,9 @@ 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); @@ -4098,7 +4595,7 @@ int kauth_proc_label_update(struct proc *p, struct label *label) 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 @@ -4106,7 +4603,7 @@ 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 */ @@ -4116,8 +4613,7 @@ int kauth_proc_label_update(struct proc *p, struct label *label) /* update cred on proc */ PROC_UPDATE_CREDS_ONPROC(p); - mac_proc_set_enforce(p, MAC_ALL_ENFORCE); - proc_unlock(p); + proc_ucred_unlock(p); } break; } @@ -4140,6 +4636,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 @@ -4150,13 +4648,13 @@ 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); @@ -4171,12 +4669,12 @@ kauth_proc_label_update_execve(struct proc *p, vfs_context_t ctx, * 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 @@ -4184,7 +4682,7 @@ 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 */ @@ -4193,15 +4691,12 @@ kauth_proc_label_update_execve(struct proc *p, vfs_context_t ctx, p->p_ucred = my_new_cred; /* update cred on proc */ PROC_UPDATE_CREDS_ONPROC(p); - mac_proc_set_enforce(p, MAC_ALL_ENFORCE); - proc_unlock(p); + proc_ucred_unlock(p); } break; } /* Drop old proc reference or our extra reference */ kauth_cred_unref(&my_cred); - - return (disjoint); } #if 1 @@ -4315,7 +4810,8 @@ kauth_cred_ref(kauth_cred_t cred) * Parameters: credp Pointer to address containing * credential to be freed * - * Returns: (void) + * Returns: TRUE if the credential must be destroyed by the caller. + * FALSE otherwise. * * Implicit returns: * *credp Set to NOCRED @@ -4326,18 +4822,19 @@ kauth_cred_ref(kauth_cred_t cred) * scoped to this compilation unit. * * This function destroys the contents of the pointer passed by - * the caller to prevent the caller accidently attempting to + * the caller to prevent the caller accidentally 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 + * intended effect, to take into account the reference held by * the credential hash, which is released at the same time. */ -static void +static boolean_t kauth_cred_unref_hashlocked(kauth_cred_t *credp) { int old_value; + boolean_t destroy_it = FALSE; KAUTH_CRED_HASH_LOCK_ASSERT(); NULLCRED_CHECK(*credp); @@ -4363,9 +4860,14 @@ kauth_cred_unref_hashlocked(kauth_cred_t *credp) */ if (old_value < 3) { /* The last absolute reference is our credential hash table */ - kauth_cred_remove(*credp); + destroy_it = kauth_cred_remove(*credp); } - *credp = NOCRED; + + if (destroy_it == FALSE) { + *credp = NOCRED; + } + + return (destroy_it); } @@ -4390,9 +4892,23 @@ kauth_cred_unref_hashlocked(kauth_cred_t *credp) void kauth_cred_unref(kauth_cred_t *credp) { + boolean_t destroy_it; + KAUTH_CRED_HASH_LOCK(); - kauth_cred_unref_hashlocked(credp); + destroy_it = kauth_cred_unref_hashlocked(credp); KAUTH_CRED_HASH_UNLOCK(); + + if (destroy_it == TRUE) { + assert(*credp != NOCRED); +#if CONFIG_MACF + mac_cred_label_destroy(*credp); +#endif + AUDIT_SESSION_UNREF(*credp); + + (*credp)->cr_ref = 0; + FREE_ZONE(*credp, sizeof(*(*credp)), M_CRED); + *credp = NOCRED; + } } @@ -4447,11 +4963,11 @@ kauth_cred_rele(kauth_cred_t cred) * 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 @@ -4502,7 +5018,7 @@ 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) @@ -4636,17 +5152,31 @@ kauth_cred_update(kauth_cred_t old_cred, kauth_cred_t model_cred, return(old_cred); } if (found_cred != NULL) { + boolean_t destroy_it; + 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); + destroy_it = kauth_cred_unref_hashlocked(&old_cred); KAUTH_CRED_HASH_UNLOCK(); + if (destroy_it == TRUE) { + assert(old_cred != NOCRED); +#if CONFIG_MACF + mac_cred_label_destroy(old_cred); +#endif + AUDIT_SESSION_UNREF(old_cred); + + old_cred->cr_ref = 0; + FREE_ZONE(old_cred, sizeof(*old_cred), M_CRED); + old_cred = NOCRED; + + } return(found_cred); } - + /* * Must allocate a new credential using the model. also * adds the new credential to the credential hash table. @@ -4699,7 +5229,7 @@ kauth_cred_add(kauth_cred_t new_cred) KAUTH_CRED_HASH_LOCK_ASSERT(); hash_key = kauth_cred_get_hashkey(new_cred); - hash_key %= kauth_cred_table_size; + hash_key %= KAUTH_CRED_TABLE_SIZE; /* race fix - there is a window where another matching credential * could have been inserted between the time this one was created and we @@ -4728,7 +5258,7 @@ kauth_cred_add(kauth_cred_t new_cred) * Parameters: cred Credential to remove from cred * hash cache * - * Returns: (void) + * Returns: TRUE if the cred was found & removed from the hash; FALSE if not. * * Locks: Caller is expected to hold KAUTH_CRED_HASH_LOCK * @@ -4737,43 +5267,36 @@ kauth_cred_add(kauth_cred_t new_cred) * following code occurs with the hash lock held; in theory, this * protects us from the 2->1 reference that gets us here. */ -static void +static boolean_t kauth_cred_remove(kauth_cred_t cred) { u_long hash_key; kauth_cred_t found_cred; hash_key = kauth_cred_get_hashkey(cred); - hash_key %= kauth_cred_table_size; + 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 */ + return (FALSE); /* someone else got a ref */ /* 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 - AUDIT_SESSION_UNREF(cred); - - cred->cr_ref = 0; - FREE_ZONE(cred, sizeof(*cred), M_CRED); #if KAUTH_CRED_HASH_DEBUG kauth_cred_count--; #endif - return; + return (TRUE); } } /* 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 (FALSE); } @@ -4787,7 +5310,7 @@ kauth_cred_remove(kauth_cred_t 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 @@ -4811,7 +5334,7 @@ kauth_cred_find(kauth_cred_t cred) #endif hash_key = kauth_cred_get_hashkey(cred); - hash_key %= kauth_cred_table_size; + 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) { @@ -4822,21 +5345,16 @@ kauth_cred_find(kauth_cred_t cred) * don't worry about the label unless the flags in * either credential tell us to. */ - if ((found_pcred->cr_flags & CRF_MAC_ENFORCE) != 0 || - (pcred->cr_flags & CRF_MAC_ENFORCE) != 0) { - /* include the label pointer in the compare */ - match = (bcmp(&found_pcred->cr_uid, &pcred->cr_uid, - (sizeof(struct ucred) - - offsetof(struct ucred, cr_posix))) == 0); - } else { - /* flags have to match, but skip the label in bcmp */ - match = (found_pcred->cr_flags == pcred->cr_flags && - bcmp(&found_pcred->cr_uid, &pcred->cr_uid, - sizeof(struct posix_cred)) == 0 && - bcmp(&found_cred->cr_audit, &cred->cr_audit, - sizeof(cred->cr_audit)) == 0); - + match = (bcmp(found_pcred, pcred, sizeof (*pcred)) == 0) ? TRUE : FALSE; + match = match && ((bcmp(&found_cred->cr_audit, &cred->cr_audit, + sizeof(cred->cr_audit)) == 0) ? TRUE : FALSE); +#if CONFIG_MACF + if (((found_pcred->cr_flags & CRF_MAC_ENFORCE) != 0) || + ((pcred->cr_flags & CRF_MAC_ENFORCE) != 0)) { + match = match && mac_cred_label_compare(found_cred->cr_label, + cred->cr_label); } +#endif if (match) { /* found a match */ return(found_cred); @@ -4898,22 +5416,24 @@ 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) { +#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_posix, - sizeof(struct ucred) - offsetof(struct ucred, cr_posix), - hash_key); - } else { - /* skip label */ - 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 = kauth_cred_hash((uint8_t *)cred->cr_label, + sizeof (struct label), hash_key); } +#endif return(hash_key); } @@ -4939,7 +5459,7 @@ kauth_cred_hash_print(void) 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++) { + 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) { @@ -5124,7 +5644,7 @@ sysctl_dump_creds( __unused struct sysctl_oid *oidp, __unused void *arg1, __unus 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++; } @@ -5137,7 +5657,7 @@ 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 ); + MALLOC( cred_listp, debug_ucred *, req->oldlen, M_TEMP, M_WAITOK | M_ZERO); if ( cred_listp == NULL ) { return (ENOMEM); } @@ -5145,7 +5665,7 @@ sysctl_dump_creds( __unused struct sysctl_oid *oidp, __unused void *arg1, __unus /* 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; @@ -5236,7 +5756,7 @@ sysctl_dump_cred_backtraces( __unused struct sysctl_oid *oidp, __unused void *ar return (ENOMEM); } - MALLOC( bt_bufp, cred_debug_buffer *, req->oldlen, M_TEMP, M_WAITOK ); + MALLOC( bt_bufp, cred_debug_buffer *, req->oldlen, M_TEMP, M_WAITOK | M_ZERO); if ( bt_bufp == NULL ) { return (ENOMEM); } @@ -5286,9 +5806,9 @@ sysctl_dump_cred_backtraces( __unused struct sysctl_oid *oidp, __unused void *ar * attach a label to the new credential * * Notes: This function currently wraps kauth_cred_create(), and is the - * only consume of tht ill-fated function, apart from bsd_init(). + * 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 cals containing + * credentials based on the over-the-wire RPC calls containing * traditional POSIX credential information being tunneled to * the server host from the client machine. * @@ -5296,7 +5816,7 @@ sysctl_dump_cred_backtraces( __unused struct sysctl_oid *oidp, __unused void *ar * * 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 implementaiton detail. + * 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 @@ -5332,7 +5852,7 @@ posix_cred_create(posix_cred_t pcred) * 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 credentails into its APIs, including, but + * 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. @@ -5369,13 +5889,13 @@ posix_cred_get(kauth_cred_t cred) * Returns: (void) * * Notes: This function is currently void in order to permit it to fit - * in with the currrent MACF framework label methods which allow - * labelling to fail silently. This is like acceptable for + * 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 currenty can not fail, as currently the POSIX + * 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. */