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