/*
- * 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
* 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,
* 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@
*/
/*
* and identity information.
*/
-
-#include <sys/param.h> /* XXX trim includes */
+#include <sys/param.h> /* XXX trim includes */
#include <sys/acct.h>
#include <sys/systm.h>
#include <sys/ucred.h>
#include <sys/malloc.h>
#include <sys/kauth.h>
#include <sys/kernel.h>
+#include <sys/sdt.h>
-#include <bsm/audit_kernel.h>
+#include <security/audit/audit.h>
#include <sys/mount.h>
+#include <sys/stat.h> /* For manifest constants in posix_cred_access */
#include <sys/sysproto.h>
#include <mach/message.h>
#include <mach/host_security.h>
-#include <libkern/OSAtomic.h>
+#include <machine/atomic.h>
#include <kern/task.h>
-#include <kern/lock.h>
+#include <kern/locks.h>
#ifdef MACH_ASSERT
# undef MACH_ASSERT
#endif
-#define MACH_ASSERT 1 /* XXX so bogus */
+#define MACH_ASSERT 1 /* XXX so bogus */
#include <kern/assert.h>
#if CONFIG_MACF
#include <security/mac.h>
#include <security/mac_framework.h>
+#include <security/_label.h>
+#endif
+
+#include <IOKit/IOBSD.h>
+
+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
*/
#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 <libkern/OSDebug.h> /* needed for get_backtrace( ) */
+#include <libkern/OSDebug.h> /* 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.
*
*/
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;
+}
/*
*
* 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..
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*/);
}
*
* 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 <kauth_resolver_timeout>
+ * 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;
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
/* 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;
}
* 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.
*/
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) {
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
if (result) {
KAUTH_RESOLVER_UNLOCK();
- return(result);
+ return 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);
}
* 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
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
*/
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;
out:
KAUTH_RESOLVER_UNLOCK();
- return(error);
+ return error;
}
* 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;
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);
}
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();
}
}
/* 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;
* 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
*
*
* 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.
*
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*/);
}
* 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) {
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;
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;
}
* 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
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) {
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);
+ }
}
* 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;
}
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 */
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;
}
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
{
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;
}
{
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
*
* Parameters: uid UID to find
* kir Pointer to return area
+ * getname Name buffer, if ki_name wanted
*
* Returns: 0 Found
* ENOENT Not found
* *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;
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
* *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;
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;
}
*
* 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
* 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;
}
}
KAUTH_IDENTITY_UNLOCK();
- return((kip == NULL) ? ENOENT : 0);
+ return (kip == NULL) ? ENOENT : 0;
}
*
* 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
* 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;
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 */
/*
* 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;
}
*
* 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"
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.
*/
/*
* 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
/* 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;
}
* be done using it.
*/
-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;
#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
*
* 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;
}
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
*
* 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
*
* 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);
}
* 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) {
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;
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;
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
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;
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;
}
* 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
*
*
* 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
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*/);
}
{
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;
}
{
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);
} 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;
}
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);
} 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
*/
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
* 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:
* 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.
* 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;
}
}
* 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 */
*/
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
*
* 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
* 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;
*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
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
*
* 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;
}
int
kauth_cred_issuser(kauth_cred_t cred)
{
- return(cred->cr_uid == 0);
+ return kauth_cred_getuid(cred) == 0;
}
*/
/* 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)
/*
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]);
}
}
uid_t
kauth_getuid(void)
{
- return(kauth_cred_get()->cr_uid);
+ return kauth_cred_getuid(kauth_cred_get());
}
uid_t
kauth_getruid(void)
{
- return(kauth_cred_get()->cr_ruid);
+ return kauth_cred_getruid(kauth_cred_get());
}
gid_t
kauth_getgid(void)
{
- return(kauth_cred_get()->cr_groups[0]);
+ return kauth_cred_getgid(kauth_cred_get());
}
gid_t
kauth_getrgid(void)
{
- return(kauth_cred_get()->cr_rgid);
+ return kauth_cred_getrgid(kauth_cred_get());
}
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.
* 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
(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);
+ }
}
}
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
*/
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;
}
* 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.
*
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
*
*
* 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
* 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
*
* 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;
}
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);
* 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;
}
/*
*/
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;
}
/*
* 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);
}
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;
}
/*
/* 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.
* 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
* 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
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;
}
}
* 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;
}
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;
}
/*
* 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);
}
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);
/*
* 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");
* 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
* 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;
* 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
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
*
* 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;
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;
}
/*
* 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;
/* 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
* 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;
}
/*
* 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
* 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
* 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)
{
#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)
{
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
*
* 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
* 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
*
{
kauth_cred_unref(&cred);
}
+#endif /* !__LP64__ */
/*
*
* 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
* 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;
}
/*
* 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;
}
/*
* 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);
}
*/
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;
}
* 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
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;
}
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++;
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
*
* 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;
/* 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++;
}
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;
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) {
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++;
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;
+ }
+ }
+ }
+ }
+}