4770 lines
129 KiB
C
4770 lines
129 KiB
C
/*
|
|
* Copyright (c) 2004-2020 Apple Inc. All rights reserved.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
|
|
*
|
|
* This file contains Original Code and/or Modifications of Original Code
|
|
* as defined in and that are subject to the Apple Public Source License
|
|
* Version 2.0 (the 'License'). You may not use this file except in
|
|
* 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.
|
|
*
|
|
* 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, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
* Please see the License for the specific language governing rights and
|
|
* limitations under the License.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
|
|
*/
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
#include <sys/param.h> /* XXX trim includes */
|
|
#include <sys/acct.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/ucred.h>
|
|
#include <sys/proc_internal.h>
|
|
#include <sys/user.h>
|
|
#include <sys/timeb.h>
|
|
#include <sys/times.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/kauth.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/sdt.h>
|
|
|
|
#include <security/audit/audit.h>
|
|
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h> /* For manifest constants in posix_cred_access */
|
|
#include <sys/sysproto.h>
|
|
#include <mach/message.h>
|
|
|
|
#include <machine/atomic.h>
|
|
#include <libkern/OSByteOrder.h>
|
|
|
|
#include <kern/smr_hash.h>
|
|
#include <kern/task.h>
|
|
#include <kern/locks.h>
|
|
#ifdef MACH_ASSERT
|
|
# undef MACH_ASSERT
|
|
#endif
|
|
#define MACH_ASSERT 1 /* XXX so bogus */
|
|
#include <kern/assert.h>
|
|
|
|
#if CONFIG_MACF
|
|
#include <security/mac.h>
|
|
#include <security/mac_policy.h>
|
|
#include <security/mac_framework.h>
|
|
#include <security/_label.h>
|
|
#endif
|
|
|
|
#include <os/hash.h>
|
|
#include <IOKit/IOBSD.h>
|
|
|
|
/* Set to 1 to turn on KAUTH_DEBUG for kern_credential.c */
|
|
#if 0
|
|
#ifdef KAUTH_DEBUG
|
|
#undef KAUTH_DEBUG
|
|
#endif
|
|
|
|
#ifdef K_UUID_FMT
|
|
#undef K_UUID_FMT
|
|
#endif
|
|
|
|
#ifdef K_UUID_ARG
|
|
#undef K_UUID_ARG
|
|
#endif
|
|
|
|
# define K_UUID_FMT "%08x:%08x:%08x:%08x"
|
|
# define K_UUID_ARG(_u) &_u.g_guid_asint[0],&_u.g_guid_asint[1],&_u.g_guid_asint[2],&_u.g_guid_asint[3]
|
|
# define KAUTH_DEBUG(fmt, args...) do { printf("%s:%d: " fmt "\n", __PRETTY_FUNCTION__, __LINE__ , ##args); } while (0)
|
|
#endif
|
|
|
|
#if CONFIG_EXT_RESOLVER
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
static LCK_MTX_DECLARE(kauth_resolver_mtx, &kauth_lck_grp);
|
|
#define KAUTH_RESOLVER_LOCK() lck_mtx_lock(&kauth_resolver_mtx);
|
|
#define KAUTH_RESOLVER_UNLOCK() lck_mtx_unlock(&kauth_resolver_mtx);
|
|
|
|
static volatile pid_t kauth_resolver_identity;
|
|
static int kauth_identitysvc_has_registered;
|
|
static int kauth_resolver_registered;
|
|
static uint32_t kauth_resolver_sequence = 31337;
|
|
static int kauth_resolver_timeout = 30; /* default: 30 seconds */
|
|
|
|
struct kauth_resolver_work {
|
|
TAILQ_ENTRY(kauth_resolver_work) kr_link;
|
|
struct kauth_identity_extlookup kr_work;
|
|
uint64_t kr_extend;
|
|
uint32_t kr_seqno;
|
|
int kr_refs;
|
|
int kr_flags;
|
|
#define KAUTH_REQUEST_UNSUBMITTED (1<<0)
|
|
#define KAUTH_REQUEST_SUBMITTED (1<<1)
|
|
#define KAUTH_REQUEST_DONE (1<<2)
|
|
int kr_result;
|
|
};
|
|
|
|
TAILQ_HEAD(kauth_resolver_unsubmitted_head, kauth_resolver_work) kauth_resolver_unsubmitted =
|
|
TAILQ_HEAD_INITIALIZER(kauth_resolver_unsubmitted);
|
|
TAILQ_HEAD(kauth_resolver_submitted_head, kauth_resolver_work) kauth_resolver_submitted =
|
|
TAILQ_HEAD_INITIALIZER(kauth_resolver_submitted);
|
|
TAILQ_HEAD(kauth_resolver_done_head, kauth_resolver_work) kauth_resolver_done =
|
|
TAILQ_HEAD_INITIALIZER(kauth_resolver_done);
|
|
|
|
/* Number of resolver timeouts between logged complaints */
|
|
#define KAUTH_COMPLAINT_INTERVAL 1000
|
|
int kauth_resolver_timeout_cnt = 0;
|
|
|
|
#if DEVELOPMENT || DEBUG
|
|
/* Internal builds get different (less ambiguous) breadcrumbs. */
|
|
#define KAUTH_RESOLVER_FAILED_ERRCODE EOWNERDEAD
|
|
#else
|
|
/* But non-Internal builds get errors that are allowed by standards. */
|
|
#define KAUTH_RESOLVER_FAILED_ERRCODE EIO
|
|
#endif /* DEVELOPMENT || DEBUG */
|
|
|
|
int kauth_resolver_failed_cnt = 0;
|
|
#define RESOLVER_FAILED_MESSAGE(fmt, args...) \
|
|
do { \
|
|
if (!(kauth_resolver_failed_cnt++ % 100)) { \
|
|
printf("%s: " fmt "\n", __PRETTY_FUNCTION__, ##args); \
|
|
} \
|
|
} while (0)
|
|
|
|
static int kauth_resolver_submit(struct kauth_identity_extlookup *lkp, uint64_t extend_data);
|
|
static int kauth_resolver_complete(user_addr_t message);
|
|
static int kauth_resolver_getwork(user_addr_t message);
|
|
static int kauth_resolver_getwork2(user_addr_t message);
|
|
static __attribute__((noinline)) int __KERNEL_IS_WAITING_ON_EXTERNAL_CREDENTIAL_RESOLVER__(
|
|
struct kauth_resolver_work *);
|
|
|
|
#define KAUTH_CACHES_MAX_SIZE 10000 /* Max # entries for both groups and id caches */
|
|
|
|
struct kauth_identity {
|
|
TAILQ_ENTRY(kauth_identity) ki_link;
|
|
int ki_valid;
|
|
uid_t ki_uid;
|
|
gid_t ki_gid;
|
|
uint32_t ki_supgrpcnt;
|
|
gid_t ki_supgrps[NGROUPS];
|
|
guid_t ki_guid;
|
|
ntsid_t ki_ntsid;
|
|
const char *ki_name; /* string name from string cache */
|
|
/*
|
|
* Expiry times are the earliest time at which we will disregard the
|
|
* cached state and go to userland. Before then if the valid bit is
|
|
* set, we will return the cached value. If it's not set, we will
|
|
* not go to userland to resolve, just assume that there is no answer
|
|
* available.
|
|
*/
|
|
time_t ki_groups_expiry;
|
|
time_t ki_guid_expiry;
|
|
time_t ki_ntsid_expiry;
|
|
};
|
|
|
|
static TAILQ_HEAD(kauth_identity_head, kauth_identity) kauth_identities =
|
|
TAILQ_HEAD_INITIALIZER(kauth_identities);
|
|
static LCK_MTX_DECLARE(kauth_identity_mtx, &kauth_lck_grp);
|
|
#define KAUTH_IDENTITY_LOCK() lck_mtx_lock(&kauth_identity_mtx);
|
|
#define KAUTH_IDENTITY_UNLOCK() lck_mtx_unlock(&kauth_identity_mtx);
|
|
#define KAUTH_IDENTITY_CACHEMAX_DEFAULT 100 /* XXX default sizing? */
|
|
static int kauth_identity_cachemax = KAUTH_IDENTITY_CACHEMAX_DEFAULT;
|
|
static int kauth_identity_count;
|
|
|
|
static struct kauth_identity *kauth_identity_alloc(uid_t uid, gid_t gid, guid_t *guidp, time_t guid_expiry,
|
|
ntsid_t *ntsidp, time_t ntsid_expiry, size_t supgrpcnt, gid_t *supgrps, time_t groups_expiry,
|
|
const char *name, int nametype);
|
|
static void kauth_identity_register_and_free(struct kauth_identity *kip);
|
|
static void kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_identity *kip, uint64_t extend_data);
|
|
static void kauth_identity_trimcache(int newsize);
|
|
static void kauth_identity_lru(struct kauth_identity *kip);
|
|
static int kauth_identity_guid_expired(struct kauth_identity *kip);
|
|
static int kauth_identity_ntsid_expired(struct kauth_identity *kip);
|
|
static int kauth_identity_find_uid(uid_t uid, struct kauth_identity *kir, char *getname);
|
|
static int kauth_identity_find_gid(gid_t gid, struct kauth_identity *kir, char *getname);
|
|
static int kauth_identity_find_guid(guid_t *guidp, struct kauth_identity *kir, char *getname);
|
|
static int kauth_identity_find_ntsid(ntsid_t *ntsid, struct kauth_identity *kir, char *getname);
|
|
static int kauth_identity_find_nam(char *name, int valid, struct kauth_identity *kir);
|
|
|
|
struct kauth_group_membership {
|
|
TAILQ_ENTRY(kauth_group_membership) gm_link;
|
|
uid_t gm_uid; /* the identity whose membership we're recording */
|
|
gid_t gm_gid; /* group of which they are a member */
|
|
time_t gm_expiry; /* TTL for the membership, or 0 for persistent entries */
|
|
int gm_flags;
|
|
#define KAUTH_GROUP_ISMEMBER (1<<0)
|
|
};
|
|
|
|
TAILQ_HEAD(kauth_groups_head, kauth_group_membership) kauth_groups =
|
|
TAILQ_HEAD_INITIALIZER(kauth_groups);
|
|
static LCK_MTX_DECLARE(kauth_groups_mtx, &kauth_lck_grp);
|
|
#define KAUTH_GROUPS_LOCK() lck_mtx_lock(&kauth_groups_mtx);
|
|
#define KAUTH_GROUPS_UNLOCK() lck_mtx_unlock(&kauth_groups_mtx);
|
|
#define KAUTH_GROUPS_CACHEMAX_DEFAULT 100 /* XXX default sizing? */
|
|
static int kauth_groups_cachemax = KAUTH_GROUPS_CACHEMAX_DEFAULT;
|
|
static int kauth_groups_count;
|
|
|
|
static int kauth_groups_expired(struct kauth_group_membership *gm);
|
|
static void kauth_groups_lru(struct kauth_group_membership *gm);
|
|
static void kauth_groups_updatecache(struct kauth_identity_extlookup *el);
|
|
static void kauth_groups_trimcache(int newsize);
|
|
|
|
/*
|
|
* __KERNEL_IS_WAITING_ON_EXTERNAL_CREDENTIAL_RESOLVER__
|
|
*
|
|
* Description: Waits for the user space daemon to respond to the request
|
|
* we made. Function declared non inline to be visible in
|
|
* stackshots and spindumps as well as debugging.
|
|
*
|
|
* Parameters: workp Work queue entry.
|
|
*
|
|
* Returns: 0 on Success.
|
|
* EIO if Resolver is dead.
|
|
* EINTR thread interrupted in msleep
|
|
* EWOULDBLOCK thread timed out in msleep
|
|
* ERESTART returned by msleep.
|
|
*
|
|
*/
|
|
static __attribute__((noinline)) int
|
|
__KERNEL_IS_WAITING_ON_EXTERNAL_CREDENTIAL_RESOLVER__(
|
|
struct kauth_resolver_work *workp)
|
|
{
|
|
int error = 0;
|
|
struct timespec ts;
|
|
for (;;) {
|
|
/* we could compute a better timeout here */
|
|
ts.tv_sec = kauth_resolver_timeout;
|
|
ts.tv_nsec = 0;
|
|
error = msleep(workp, &kauth_resolver_mtx, PCATCH, "kr_submit", &ts);
|
|
/* request has been completed? */
|
|
if ((error == 0) && (workp->kr_flags & KAUTH_REQUEST_DONE)) {
|
|
break;
|
|
}
|
|
/* woken because the resolver has died? */
|
|
if (kauth_resolver_identity == 0) {
|
|
RESOLVER_FAILED_MESSAGE("kauth external resolver died while while waiting for work to complete");
|
|
error = KAUTH_RESOLVER_FAILED_ERRCODE;
|
|
break;
|
|
}
|
|
/* an error? */
|
|
if (error != 0) {
|
|
break;
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_resolver_identity_reset
|
|
*
|
|
* Description: Reset the identity of the external resolver in certain
|
|
* controlled circumstances.
|
|
*
|
|
* Parameters: None.
|
|
*
|
|
* Returns: Nothing.
|
|
*/
|
|
void
|
|
kauth_resolver_identity_reset(void)
|
|
{
|
|
KAUTH_RESOLVER_LOCK();
|
|
if (kauth_resolver_identity != 0) {
|
|
printf("kauth external resolver %d failed to de-register.\n",
|
|
kauth_resolver_identity);
|
|
kauth_resolver_identity = 0;
|
|
kauth_resolver_registered = 0;
|
|
}
|
|
KAUTH_RESOLVER_UNLOCK();
|
|
}
|
|
|
|
/*
|
|
* kauth_resolver_submit
|
|
*
|
|
* Description: Submit an external credential identity resolution request to
|
|
* the user space daemon.
|
|
*
|
|
* Parameters: lkp A pointer to an external
|
|
* lookup request
|
|
* extend_data extended data for kr_extend
|
|
*
|
|
* Returns: 0 Success
|
|
* EWOULDBLOCK No resolver registered
|
|
* EINTR Operation interrupted (e.g. by
|
|
* a signal)
|
|
* ENOMEM Could not allocate work item
|
|
* copyinstr:EFAULT Bad message from user space
|
|
* workp->kr_result:??? An error from the user space
|
|
* daemon (includes ENOENT!)
|
|
*
|
|
* Implicit returns:
|
|
* *lkp Modified
|
|
*
|
|
* Notes: Allocate a work queue entry, submit the work and wait for
|
|
* the operation to either complete or time out. Outstanding
|
|
* operations may also be cancelled.
|
|
*
|
|
* Submission is by means of placing the item on a work queue
|
|
* which is serviced by an external resolver thread calling
|
|
* into the kernel. The caller then sleeps until timeout,
|
|
* cancellation, or an external resolver thread calls in with
|
|
* a result message to kauth_resolver_complete(). All of these
|
|
* events wake the caller back up.
|
|
*
|
|
* This code is called from either kauth_cred_ismember_gid()
|
|
* for a group membership request, or it is called from
|
|
* kauth_cred_cache_lookup() when we get a cache miss.
|
|
*/
|
|
static int
|
|
kauth_resolver_submit(struct kauth_identity_extlookup *lkp, uint64_t extend_data)
|
|
{
|
|
struct kauth_resolver_work *workp, *killp;
|
|
struct timespec ts;
|
|
int error, shouldfree;
|
|
|
|
/* no point actually blocking if the resolver isn't up yet */
|
|
if (kauth_resolver_identity == 0) {
|
|
/*
|
|
* We've already waited an initial <kauth_resolver_timeout>
|
|
* seconds with no result.
|
|
*
|
|
* Sleep on a stack address so no one wakes us before timeout;
|
|
* we sleep a half a second in case we are a high priority
|
|
* process, so that memberd doesn't starve while we are in a
|
|
* tight loop between user and kernel, eating all the CPU.
|
|
*/
|
|
error = tsleep(&ts, PZERO | PCATCH, "kr_submit", hz / 2);
|
|
if (kauth_resolver_identity == 0) {
|
|
/*
|
|
* if things haven't changed while we were asleep,
|
|
* tell the caller we couldn't get an authoritative
|
|
* answer.
|
|
*/
|
|
return EWOULDBLOCK;
|
|
}
|
|
}
|
|
|
|
workp = kalloc_type(struct kauth_resolver_work, Z_WAITOK | Z_NOFAIL);
|
|
|
|
workp->kr_work = *lkp;
|
|
workp->kr_extend = extend_data;
|
|
workp->kr_refs = 1;
|
|
workp->kr_flags = KAUTH_REQUEST_UNSUBMITTED;
|
|
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.
|
|
*/
|
|
KAUTH_RESOLVER_LOCK();
|
|
workp->kr_seqno = workp->kr_work.el_seqno = kauth_resolver_sequence++;
|
|
workp->kr_work.el_result = KAUTH_EXTLOOKUP_INPROG;
|
|
|
|
/*
|
|
* XXX We *MUST NOT* attempt to coalesce identical work items due to
|
|
* XXX the inability to ensure order of update of the request item
|
|
* XXX extended data vs. the wakeup; instead, we let whoever is waiting
|
|
* XXX for each item repeat the update when they wake up.
|
|
*/
|
|
TAILQ_INSERT_TAIL(&kauth_resolver_unsubmitted, workp, kr_link);
|
|
|
|
/*
|
|
* Wake up an external resolver thread to deal with the new work; one
|
|
* may not be available, and if not, then the request will be grabbed
|
|
* when a resolver thread comes back into the kernel to request new
|
|
* work.
|
|
*/
|
|
wakeup_one((caddr_t)&kauth_resolver_unsubmitted);
|
|
error = __KERNEL_IS_WAITING_ON_EXTERNAL_CREDENTIAL_RESOLVER__(workp);
|
|
|
|
/* if the request was processed, copy the result */
|
|
if (error == 0) {
|
|
*lkp = workp->kr_work;
|
|
}
|
|
|
|
if (error == EWOULDBLOCK) {
|
|
if ((kauth_resolver_timeout_cnt++ % KAUTH_COMPLAINT_INTERVAL) == 0) {
|
|
printf("kauth external resolver timed out (%d timeout(s) of %d seconds).\n",
|
|
kauth_resolver_timeout_cnt, kauth_resolver_timeout);
|
|
}
|
|
|
|
if (workp->kr_flags & KAUTH_REQUEST_UNSUBMITTED) {
|
|
/*
|
|
* If the request timed out and was never collected, the resolver
|
|
* is dead and probably not coming back anytime soon. In this
|
|
* case we revert to no-resolver behaviour, and punt all the other
|
|
* sleeping requests to clear the backlog.
|
|
*/
|
|
KAUTH_DEBUG("RESOLVER - request timed out without being collected for processing, resolver dead");
|
|
|
|
/*
|
|
* Make the current resolver non-authoritative, and mark it as
|
|
* no longer registered to prevent kauth_cred_ismember_gid()
|
|
* enqueueing more work until a new one is registered. This
|
|
* mitigates the damage a crashing resolver may inflict.
|
|
*/
|
|
kauth_resolver_identity = 0;
|
|
kauth_resolver_registered = 0;
|
|
|
|
/* kill all the other requestes that are waiting as well */
|
|
TAILQ_FOREACH(killp, &kauth_resolver_submitted, kr_link)
|
|
wakeup(killp);
|
|
TAILQ_FOREACH(killp, &kauth_resolver_unsubmitted, kr_link)
|
|
wakeup(killp);
|
|
/* Cause all waiting-for-work threads to return EIO */
|
|
wakeup((caddr_t)&kauth_resolver_unsubmitted);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* drop our reference on the work item, and note whether we should
|
|
* free it or not
|
|
*/
|
|
if (--workp->kr_refs <= 0) {
|
|
/* work out which list we have to remove it from */
|
|
if (workp->kr_flags & KAUTH_REQUEST_DONE) {
|
|
TAILQ_REMOVE(&kauth_resolver_done, workp, kr_link);
|
|
} else if (workp->kr_flags & KAUTH_REQUEST_SUBMITTED) {
|
|
TAILQ_REMOVE(&kauth_resolver_submitted, workp, kr_link);
|
|
} else if (workp->kr_flags & KAUTH_REQUEST_UNSUBMITTED) {
|
|
TAILQ_REMOVE(&kauth_resolver_unsubmitted, workp, kr_link);
|
|
} else {
|
|
KAUTH_DEBUG("RESOLVER - completed request has no valid queue");
|
|
}
|
|
shouldfree = 1;
|
|
} else {
|
|
/* someone else still has a reference on this request */
|
|
shouldfree = 0;
|
|
}
|
|
|
|
/* collect request result */
|
|
if (error == 0) {
|
|
error = workp->kr_result;
|
|
}
|
|
KAUTH_RESOLVER_UNLOCK();
|
|
|
|
/*
|
|
* If we dropped the last reference, free the request.
|
|
*/
|
|
if (shouldfree) {
|
|
kfree_type(struct kauth_resolver_work, workp);
|
|
}
|
|
|
|
KAUTH_DEBUG("RESOLVER - returning %d", error);
|
|
return error;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 int32_t *retval)
|
|
{
|
|
int opcode = uap->opcode;
|
|
user_addr_t message = uap->message;
|
|
struct kauth_resolver_work *workp;
|
|
struct kauth_cache_sizes sz_arg = {};
|
|
int error;
|
|
pid_t new_id;
|
|
|
|
if (!IOCurrentTaskHasEntitlement(IDENTITYSVC_ENTITLEMENT)) {
|
|
KAUTH_DEBUG("RESOLVER - pid %d not entitled to call identitysvc", proc_getpid(current_proc()));
|
|
return EPERM;
|
|
}
|
|
|
|
/*
|
|
* New server registering itself.
|
|
*/
|
|
if (opcode == KAUTH_EXTLOOKUP_REGISTER) {
|
|
new_id = proc_getpid(current_proc());
|
|
if ((error = kauth_authorize_generic(kauth_cred_get(), KAUTH_GENERIC_ISSUSER)) != 0) {
|
|
KAUTH_DEBUG("RESOLVER - pid %d refused permission to become identity resolver", new_id);
|
|
return error;
|
|
}
|
|
KAUTH_RESOLVER_LOCK();
|
|
if (kauth_resolver_identity != new_id) {
|
|
KAUTH_DEBUG("RESOLVER - new resolver %d taking over from old %d", new_id, kauth_resolver_identity);
|
|
/*
|
|
* We have a new server, so assume that all the old requests have been lost.
|
|
*/
|
|
while ((workp = TAILQ_LAST(&kauth_resolver_submitted, kauth_resolver_submitted_head)) != NULL) {
|
|
TAILQ_REMOVE(&kauth_resolver_submitted, workp, kr_link);
|
|
workp->kr_flags &= ~KAUTH_REQUEST_SUBMITTED;
|
|
workp->kr_flags |= KAUTH_REQUEST_UNSUBMITTED;
|
|
TAILQ_INSERT_HEAD(&kauth_resolver_unsubmitted, workp, kr_link);
|
|
}
|
|
/*
|
|
* Allow user space resolver to override the
|
|
* external resolution timeout
|
|
*/
|
|
if (message > 30 && message < 10000) {
|
|
kauth_resolver_timeout = (int)message;
|
|
KAUTH_DEBUG("RESOLVER - new resolver changes timeout to %d seconds\n", (int)message);
|
|
}
|
|
kauth_resolver_identity = new_id;
|
|
kauth_resolver_registered = 1;
|
|
kauth_identitysvc_has_registered = 1;
|
|
wakeup(&kauth_resolver_unsubmitted);
|
|
}
|
|
KAUTH_RESOLVER_UNLOCK();
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Beyond this point, we must be the resolver process. We verify this
|
|
* by confirming the resolver credential and pid.
|
|
*/
|
|
if ((kauth_cred_getuid(kauth_cred_get()) != 0) || (proc_getpid(current_proc()) != kauth_resolver_identity)) {
|
|
KAUTH_DEBUG("RESOLVER - call from bogus resolver %d\n", proc_getpid(current_proc()));
|
|
return EPERM;
|
|
}
|
|
|
|
if (opcode == KAUTH_GET_CACHE_SIZES) {
|
|
KAUTH_IDENTITY_LOCK();
|
|
sz_arg.kcs_id_size = kauth_identity_cachemax;
|
|
KAUTH_IDENTITY_UNLOCK();
|
|
|
|
KAUTH_GROUPS_LOCK();
|
|
sz_arg.kcs_group_size = kauth_groups_cachemax;
|
|
KAUTH_GROUPS_UNLOCK();
|
|
|
|
if ((error = copyout(&sz_arg, uap->message, sizeof(sz_arg))) != 0) {
|
|
return error;
|
|
}
|
|
|
|
return 0;
|
|
} else if (opcode == KAUTH_SET_CACHE_SIZES) {
|
|
if ((error = copyin(uap->message, &sz_arg, sizeof(sz_arg))) != 0) {
|
|
return error;
|
|
}
|
|
|
|
if ((sz_arg.kcs_group_size > KAUTH_CACHES_MAX_SIZE) ||
|
|
(sz_arg.kcs_id_size > KAUTH_CACHES_MAX_SIZE)) {
|
|
return EINVAL;
|
|
}
|
|
|
|
KAUTH_IDENTITY_LOCK();
|
|
kauth_identity_cachemax = sz_arg.kcs_id_size;
|
|
kauth_identity_trimcache(kauth_identity_cachemax);
|
|
KAUTH_IDENTITY_UNLOCK();
|
|
|
|
KAUTH_GROUPS_LOCK();
|
|
kauth_groups_cachemax = sz_arg.kcs_group_size;
|
|
kauth_groups_trimcache(kauth_groups_cachemax);
|
|
KAUTH_GROUPS_UNLOCK();
|
|
|
|
return 0;
|
|
} else if (opcode == KAUTH_CLEAR_CACHES) {
|
|
KAUTH_IDENTITY_LOCK();
|
|
kauth_identity_trimcache(0);
|
|
KAUTH_IDENTITY_UNLOCK();
|
|
|
|
KAUTH_GROUPS_LOCK();
|
|
kauth_groups_trimcache(0);
|
|
KAUTH_GROUPS_UNLOCK();
|
|
} else if (opcode == KAUTH_EXTLOOKUP_DEREGISTER) {
|
|
/*
|
|
* Terminate outstanding requests; without an authoritative
|
|
* resolver, we are now back on our own authority.
|
|
*/
|
|
struct kauth_resolver_work *killp;
|
|
|
|
KAUTH_RESOLVER_LOCK();
|
|
|
|
/*
|
|
* Clear the identity, but also mark it as unregistered so
|
|
* there is no explicit future expectation of us getting a
|
|
* new resolver any time soon.
|
|
*/
|
|
kauth_resolver_identity = 0;
|
|
kauth_resolver_registered = 0;
|
|
|
|
TAILQ_FOREACH(killp, &kauth_resolver_submitted, kr_link)
|
|
wakeup(killp);
|
|
TAILQ_FOREACH(killp, &kauth_resolver_unsubmitted, kr_link)
|
|
wakeup(killp);
|
|
/* Cause all waiting-for-work threads to return EIO */
|
|
wakeup((caddr_t)&kauth_resolver_unsubmitted);
|
|
KAUTH_RESOLVER_UNLOCK();
|
|
}
|
|
|
|
/*
|
|
* Got a result returning?
|
|
*/
|
|
if (opcode & KAUTH_EXTLOOKUP_RESULT) {
|
|
if ((error = kauth_resolver_complete(message)) != 0) {
|
|
return error;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Caller wants to take more work?
|
|
*/
|
|
if (opcode & KAUTH_EXTLOOKUP_WORKER) {
|
|
if ((error = kauth_resolver_getwork(message)) != 0) {
|
|
return error;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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_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);
|
|
/*
|
|
* If this is a wakeup from another thread in the resolver
|
|
* deregistering it, error out the request-for-work thread
|
|
*/
|
|
if (!kauth_resolver_identity) {
|
|
RESOLVER_FAILED_MESSAGE("external resolver died");
|
|
error = KAUTH_RESOLVER_FAILED_ERRCODE;
|
|
}
|
|
KAUTH_RESOLVER_UNLOCK();
|
|
return error;
|
|
}
|
|
|
|
thread = current_thread();
|
|
ut = get_bsdthread_info(thread);
|
|
message = ut->uu_save.uus_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 resolution process. This frees up the stack
|
|
* while we are waiting for the user space resolver to complete
|
|
* a request. This is specifically used so that our per thread
|
|
* cost can be small, and we will therefore be willing to run a
|
|
* 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;
|
|
|
|
/*
|
|
* 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);
|
|
|
|
/*
|
|
* Copy out the external lookup structure for the request, not
|
|
* including the el_extend field, which contains the address of the
|
|
* external buffer provided by the external resolver into which we
|
|
* copy the extension request information.
|
|
*/
|
|
/* BEFORE FIELD */
|
|
if ((error = copyout(&workp->kr_work, message, offsetof(struct kauth_identity_extlookup, el_extend))) != 0) {
|
|
KAUTH_DEBUG("RESOLVER - error submitting work to resolve");
|
|
goto out;
|
|
}
|
|
/* AFTER FIELD */
|
|
if ((error = copyout(&workp->kr_work.el_info_reserved_1,
|
|
message + offsetof(struct kauth_identity_extlookup, el_info_reserved_1),
|
|
sizeof(struct kauth_identity_extlookup) - offsetof(struct kauth_identity_extlookup, el_info_reserved_1))) != 0) {
|
|
KAUTH_DEBUG("RESOLVER - error submitting work to resolve");
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Handle extended requests here; if we have a request of a type where
|
|
* the kernel wants a translation of extended information, then we need
|
|
* to copy it out into the extended buffer, assuming the buffer is
|
|
* valid; we only attempt to get the buffer address if we have request
|
|
* data to copy into it.
|
|
*/
|
|
|
|
/*
|
|
* translate a user@domain string into a uid/gid/whatever
|
|
*/
|
|
if (workp->kr_work.el_flags & (KAUTH_EXTLOOKUP_VALID_PWNAM | KAUTH_EXTLOOKUP_VALID_GRNAM)) {
|
|
uint64_t uaddr;
|
|
|
|
error = copyin(message + offsetof(struct kauth_identity_extlookup, el_extend), &uaddr, sizeof(uaddr));
|
|
if (!error) {
|
|
size_t actual; /* not used */
|
|
/*
|
|
* Use copyoutstr() to reduce the copy size; we let
|
|
* this catch a NULL uaddr because we shouldn't be
|
|
* asking in that case anyway.
|
|
*/
|
|
error = copyoutstr(CAST_DOWN(void *, workp->kr_extend), uaddr, MAXPATHLEN, &actual);
|
|
}
|
|
if (error) {
|
|
KAUTH_DEBUG("RESOLVER - error submitting work to resolve");
|
|
goto out;
|
|
}
|
|
}
|
|
TAILQ_REMOVE(&kauth_resolver_unsubmitted, workp, kr_link);
|
|
workp->kr_flags &= ~KAUTH_REQUEST_UNSUBMITTED;
|
|
workp->kr_flags |= KAUTH_REQUEST_SUBMITTED;
|
|
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
|
|
* actual 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_save.uus_kauth.message = message;
|
|
error = msleep0(&kauth_resolver_unsubmitted, &kauth_resolver_mtx, PCATCH, "GRGetWork", 0, kauth_resolver_getwork_continue);
|
|
KAUTH_RESOLVER_UNLOCK();
|
|
/*
|
|
* If this is a wakeup from another thread in the resolver
|
|
* deregistering it, error out the request-for-work thread
|
|
*/
|
|
if (!kauth_resolver_identity) {
|
|
printf("external resolver died");
|
|
error = KAUTH_RESOLVER_FAILED_ERRCODE;
|
|
}
|
|
return error;
|
|
}
|
|
return kauth_resolver_getwork2(message);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
struct kauth_identity_extlookup extl;
|
|
struct kauth_resolver_work *workp;
|
|
struct kauth_resolver_work *killp;
|
|
int error, result, want_extend_data;
|
|
|
|
/*
|
|
* Copy in the mesage, including the extension field, since we are
|
|
* copying into a local variable.
|
|
*/
|
|
if ((error = copyin(message, &extl, sizeof(extl))) != 0) {
|
|
KAUTH_DEBUG("RESOLVER - error getting completed work\n");
|
|
return error;
|
|
}
|
|
|
|
KAUTH_RESOLVER_LOCK();
|
|
|
|
error = 0;
|
|
result = 0;
|
|
switch (extl.el_result) {
|
|
case KAUTH_EXTLOOKUP_INPROG:
|
|
{
|
|
static int once = 0;
|
|
|
|
/* XXX this should go away once memberd is updated */
|
|
if (!once) {
|
|
printf("kauth_resolver: memberd is not setting valid result codes (assuming always successful)\n");
|
|
once = 1;
|
|
}
|
|
}
|
|
OS_FALLTHROUGH;
|
|
|
|
case KAUTH_EXTLOOKUP_SUCCESS:
|
|
break;
|
|
|
|
case KAUTH_EXTLOOKUP_FATAL:
|
|
/* fatal error means the resolver is dead */
|
|
KAUTH_DEBUG("RESOLVER - resolver %d died, waiting for a new one", kauth_resolver_identity);
|
|
RESOLVER_FAILED_MESSAGE("resolver %d died, waiting for a new one", kauth_resolver_identity);
|
|
/*
|
|
* Terminate outstanding requests; without an authoritative
|
|
* resolver, we are now back on our own authority. Tag the
|
|
* resolver unregistered to prevent kauth_cred_ismember_gid()
|
|
* enqueueing more work until a new one is registered. This
|
|
* mitigates the damage a crashing resolver may inflict.
|
|
*/
|
|
kauth_resolver_identity = 0;
|
|
kauth_resolver_registered = 0;
|
|
|
|
TAILQ_FOREACH(killp, &kauth_resolver_submitted, kr_link)
|
|
wakeup(killp);
|
|
TAILQ_FOREACH(killp, &kauth_resolver_unsubmitted, kr_link)
|
|
wakeup(killp);
|
|
/* Cause all waiting-for-work threads to return EIO */
|
|
wakeup((caddr_t)&kauth_resolver_unsubmitted);
|
|
/* and return EIO to the caller */
|
|
error = KAUTH_RESOLVER_FAILED_ERRCODE;
|
|
break;
|
|
|
|
case KAUTH_EXTLOOKUP_BADRQ:
|
|
KAUTH_DEBUG("RESOLVER - resolver reported invalid request %d", extl.el_seqno);
|
|
result = EINVAL;
|
|
break;
|
|
|
|
case KAUTH_EXTLOOKUP_FAILURE:
|
|
KAUTH_DEBUG("RESOLVER - resolver reported transient failure for request %d", extl.el_seqno);
|
|
RESOLVER_FAILED_MESSAGE("resolver reported transient failure for request %d", extl.el_seqno);
|
|
result = KAUTH_RESOLVER_FAILED_ERRCODE;
|
|
break;
|
|
|
|
default:
|
|
KAUTH_DEBUG("RESOLVER - resolver returned unexpected status %d", extl.el_result);
|
|
RESOLVER_FAILED_MESSAGE("resolver returned unexpected status %d", extl.el_result);
|
|
result = KAUTH_RESOLVER_FAILED_ERRCODE;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* In the case of a fatal error, we assume that the resolver will
|
|
* restart quickly and re-collect all of the outstanding requests.
|
|
* Thus, we don't complete the request which returned the fatal
|
|
* error status.
|
|
*/
|
|
if (extl.el_result != KAUTH_EXTLOOKUP_FATAL) {
|
|
/* scan our list for this request */
|
|
TAILQ_FOREACH(workp, &kauth_resolver_submitted, kr_link) {
|
|
/* found it? */
|
|
if (workp->kr_seqno == extl.el_seqno) {
|
|
/*
|
|
* Do we want extend_data?
|
|
*/
|
|
want_extend_data = (workp->kr_work.el_flags & (KAUTH_EXTLOOKUP_WANT_PWNAM | KAUTH_EXTLOOKUP_WANT_GRNAM));
|
|
|
|
/*
|
|
* Get the request of the submitted queue so
|
|
* that it is not cleaned up out from under
|
|
* us by a timeout.
|
|
*/
|
|
TAILQ_REMOVE(&kauth_resolver_submitted, workp, kr_link);
|
|
workp->kr_flags &= ~KAUTH_REQUEST_SUBMITTED;
|
|
workp->kr_flags |= KAUTH_REQUEST_DONE;
|
|
workp->kr_result = result;
|
|
|
|
/* Copy the result message to the work item. */
|
|
memcpy(&workp->kr_work, &extl, sizeof(struct kauth_identity_extlookup));
|
|
|
|
/*
|
|
* Check if we have a result in the extension
|
|
* field; if we do, then we need to separately
|
|
* copy the data from the message el_extend
|
|
* into the request buffer that's in the work
|
|
* item. We have to do it here because we do
|
|
* not want to wake up the waiter until the
|
|
* data is in their buffer, and because the
|
|
* actual request response may be destroyed
|
|
* by the time the requester wakes up, and they
|
|
* do not have access to the user space buffer
|
|
* address.
|
|
*
|
|
* It is safe to drop and reacquire the lock
|
|
* here because we've already removed the item
|
|
* from the submission queue, but have not yet
|
|
* moved it to the completion queue. Note that
|
|
* near simultaneous requests may result in
|
|
* duplication of requests for items in this
|
|
* window. This should not be a performance
|
|
* issue and is easily detectable by comparing
|
|
* time to live on last response vs. time of
|
|
* next request in the resolver logs.
|
|
*
|
|
* A malicious/faulty resolver could overwrite
|
|
* part of a user's address space if they return
|
|
* flags that mismatch the original request's flags.
|
|
*/
|
|
if (want_extend_data && (extl.el_flags & (KAUTH_EXTLOOKUP_VALID_PWNAM | KAUTH_EXTLOOKUP_VALID_GRNAM))) {
|
|
size_t actual; /* notused */
|
|
|
|
KAUTH_RESOLVER_UNLOCK();
|
|
error = copyinstr(extl.el_extend, CAST_DOWN(void *, workp->kr_extend), MAXPATHLEN, &actual);
|
|
KAUTH_DEBUG("RESOLVER - resolver got name :%*s: len = %d\n", (int)actual,
|
|
actual ? "null" : (char *)extl.el_extend, actual);
|
|
KAUTH_RESOLVER_LOCK();
|
|
} else if (extl.el_flags & (KAUTH_EXTLOOKUP_VALID_PWNAM | KAUTH_EXTLOOKUP_VALID_GRNAM)) {
|
|
error = EFAULT;
|
|
KAUTH_DEBUG("RESOLVER - resolver returned mismatching extension flags (%d), request contained (%d)",
|
|
extl.el_flags, want_extend_data);
|
|
}
|
|
|
|
/*
|
|
* Move the completed work item to the
|
|
* completion queue and wake up requester(s)
|
|
*/
|
|
TAILQ_INSERT_TAIL(&kauth_resolver_done, workp, kr_link);
|
|
wakeup(workp);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* Note that it's OK for us not to find anything; if the request has
|
|
* timed out the work record will be gone.
|
|
*/
|
|
KAUTH_RESOLVER_UNLOCK();
|
|
|
|
return error;
|
|
}
|
|
#endif /* CONFIG_EXT_RESOLVER */
|
|
|
|
|
|
/*
|
|
* Identity cache.
|
|
*/
|
|
|
|
#define KI_VALID_UID (1<<0) /* UID and GID are mutually exclusive */
|
|
#define KI_VALID_GID (1<<1)
|
|
#define KI_VALID_GUID (1<<2)
|
|
#define KI_VALID_NTSID (1<<3)
|
|
#define KI_VALID_PWNAM (1<<4) /* Used for translation */
|
|
#define KI_VALID_GRNAM (1<<5) /* Used for translation */
|
|
#define KI_VALID_GROUPS (1<<6)
|
|
|
|
#if CONFIG_EXT_RESOLVER
|
|
/*
|
|
* kauth_identity_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 or bad parameters
|
|
* !NULL A pointer to the allocated
|
|
* structure, filled in
|
|
*
|
|
* Notes: It is illegal to translate between UID and GID; any given UUID
|
|
* or NTSID can only refer to an NTSID or UUID (respectively),
|
|
* and *either* a UID *or* a GID, but not both.
|
|
*/
|
|
static struct kauth_identity *
|
|
kauth_identity_alloc(uid_t uid, gid_t gid, guid_t *guidp, time_t guid_expiry,
|
|
ntsid_t *ntsidp, time_t ntsid_expiry, size_t supgrpcnt, gid_t *supgrps, time_t groups_expiry,
|
|
const char *name, int nametype)
|
|
{
|
|
struct kauth_identity *kip;
|
|
|
|
/* get and fill in a new identity */
|
|
kip = kalloc_type(struct kauth_identity, Z_WAITOK | Z_ZERO | Z_NOFAIL);
|
|
if (gid != KAUTH_GID_NONE) {
|
|
kip->ki_gid = gid;
|
|
kip->ki_valid = KI_VALID_GID;
|
|
}
|
|
if (uid != KAUTH_UID_NONE) {
|
|
if (kip->ki_valid & KI_VALID_GID) {
|
|
panic("can't allocate kauth identity with both uid and gid");
|
|
}
|
|
kip->ki_uid = uid;
|
|
kip->ki_valid = KI_VALID_UID;
|
|
}
|
|
if (supgrpcnt) {
|
|
/*
|
|
* A malicious/faulty resolver could return bad values
|
|
*/
|
|
assert(supgrpcnt <= NGROUPS);
|
|
assert(supgrps != NULL);
|
|
|
|
if ((supgrpcnt > NGROUPS) || (supgrps == NULL)) {
|
|
return NULL;
|
|
}
|
|
if (kip->ki_valid & KI_VALID_GID) {
|
|
panic("can't allocate kauth identity with both gid and supplementary groups");
|
|
}
|
|
kip->ki_supgrpcnt = (uint32_t)supgrpcnt;
|
|
memcpy(kip->ki_supgrps, supgrps, sizeof(supgrps[0]) * supgrpcnt);
|
|
kip->ki_valid |= KI_VALID_GROUPS;
|
|
}
|
|
kip->ki_groups_expiry = groups_expiry;
|
|
if (guidp != NULL) {
|
|
kip->ki_guid = *guidp;
|
|
kip->ki_valid |= KI_VALID_GUID;
|
|
}
|
|
kip->ki_guid_expiry = guid_expiry;
|
|
if (ntsidp != NULL) {
|
|
kip->ki_ntsid = *ntsidp;
|
|
kip->ki_valid |= KI_VALID_NTSID;
|
|
}
|
|
kip->ki_ntsid_expiry = ntsid_expiry;
|
|
if (name != NULL) {
|
|
kip->ki_name = name;
|
|
kip->ki_valid |= nametype;
|
|
}
|
|
return kip;
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_identity_register_and_free
|
|
*
|
|
* Description: Register an association between identity tokens. The passed
|
|
* 'kip' is consumed 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_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.
|
|
*/
|
|
ip = NULL;
|
|
KAUTH_IDENTITY_LOCK();
|
|
if (kip->ki_valid & KI_VALID_UID) {
|
|
if (kip->ki_valid & KI_VALID_GID) {
|
|
panic("kauth_identity: can't insert record with both UID and GID as key");
|
|
}
|
|
TAILQ_FOREACH(ip, &kauth_identities, ki_link)
|
|
if ((ip->ki_valid & KI_VALID_UID) && (ip->ki_uid == kip->ki_uid)) {
|
|
break;
|
|
}
|
|
} else if (kip->ki_valid & KI_VALID_GID) {
|
|
TAILQ_FOREACH(ip, &kauth_identities, ki_link)
|
|
if ((ip->ki_valid & KI_VALID_GID) && (ip->ki_gid == kip->ki_gid)) {
|
|
break;
|
|
}
|
|
} else {
|
|
panic("kauth_identity: can't insert record without UID or GID as key");
|
|
}
|
|
|
|
if (ip != NULL) {
|
|
/* we already have an entry, merge/overwrite */
|
|
if (kip->ki_valid & KI_VALID_GUID) {
|
|
ip->ki_guid = kip->ki_guid;
|
|
ip->ki_valid |= KI_VALID_GUID;
|
|
}
|
|
ip->ki_guid_expiry = kip->ki_guid_expiry;
|
|
if (kip->ki_valid & KI_VALID_NTSID) {
|
|
ip->ki_ntsid = kip->ki_ntsid;
|
|
ip->ki_valid |= KI_VALID_NTSID;
|
|
}
|
|
ip->ki_ntsid_expiry = kip->ki_ntsid_expiry;
|
|
/* a valid ki_name field overwrites the previous name field */
|
|
if (kip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM)) {
|
|
/* if there's an old one, discard it */
|
|
const char *oname = NULL;
|
|
if (ip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM)) {
|
|
oname = ip->ki_name;
|
|
}
|
|
ip->ki_name = kip->ki_name;
|
|
kip->ki_name = oname;
|
|
}
|
|
/* and discard the incoming entry */
|
|
ip = kip;
|
|
} else {
|
|
/*
|
|
* if we don't have any information on this identity, add it;
|
|
* if it pushes us over our limit, discard the oldest one.
|
|
*/
|
|
TAILQ_INSERT_HEAD(&kauth_identities, kip, ki_link);
|
|
if (++kauth_identity_count > kauth_identity_cachemax) {
|
|
ip = TAILQ_LAST(&kauth_identities, kauth_identity_head);
|
|
TAILQ_REMOVE(&kauth_identities, ip, ki_link);
|
|
kauth_identity_count--;
|
|
}
|
|
}
|
|
KAUTH_IDENTITY_UNLOCK();
|
|
/* have to drop lock before freeing expired entry (it may be in use) */
|
|
if (ip != NULL) {
|
|
/* if the ki_name field is used, clear it first */
|
|
if (ip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM)) {
|
|
vfs_removename(ip->ki_name);
|
|
}
|
|
/* free the expired entry */
|
|
kfree_type(struct kauth_identity, ip);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_identity_updatecache
|
|
*
|
|
* Description: Given a lookup result, add any associations that we don't
|
|
* currently have; replace ones which have changed.
|
|
*
|
|
* Parameters: elp External lookup result from
|
|
* user space daemon to kernel
|
|
* rkip pointer to returned kauth
|
|
* identity, or NULL
|
|
* extend_data Extended data (can vary)
|
|
*
|
|
* Returns: (void)
|
|
*
|
|
* Implicit returns:
|
|
* *rkip Modified (if non-NULL)
|
|
*
|
|
* Notes: For extended information requests, this code relies on the fact
|
|
* that elp->el_flags is never used as an rvalue, and is only
|
|
* ever bit-tested for valid lookup information we are willing
|
|
* to cache.
|
|
*
|
|
* XXX: We may have to do the same in the case that extended data was
|
|
* passed out to user space to ensure that the request string
|
|
* gets cached; we may also be able to use the rkip as an
|
|
* input to avoid this. The jury is still out.
|
|
*
|
|
* XXX: This codes performance could be improved for multiple valid
|
|
* results by combining the loop iteration in a single loop.
|
|
*/
|
|
static void
|
|
kauth_identity_updatecache(struct kauth_identity_extlookup *elp, struct kauth_identity *rkip, uint64_t extend_data)
|
|
{
|
|
struct timeval tv;
|
|
struct kauth_identity *kip;
|
|
const char *speculative_name = NULL;
|
|
|
|
microuptime(&tv);
|
|
|
|
/*
|
|
* If there is extended data, and that data represents a name rather
|
|
* than something else, speculatively create an entry for it in the
|
|
* string cache. We do this to avoid holding the KAUTH_IDENTITY_LOCK
|
|
* over the allocation later.
|
|
*/
|
|
if (elp->el_flags & (KAUTH_EXTLOOKUP_VALID_PWNAM | KAUTH_EXTLOOKUP_VALID_GRNAM)) {
|
|
const char *tmp = CAST_DOWN(const char *, extend_data);
|
|
speculative_name = vfs_addname(tmp, (uint32_t)strnlen(tmp, MAXPATHLEN - 1), 0, 0);
|
|
}
|
|
|
|
/* user identity? */
|
|
if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_UID) {
|
|
KAUTH_IDENTITY_LOCK();
|
|
TAILQ_FOREACH(kip, &kauth_identities, ki_link) {
|
|
/* matching record */
|
|
if ((kip->ki_valid & KI_VALID_UID) && (kip->ki_uid == elp->el_uid)) {
|
|
if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_SUPGRPS) {
|
|
assert(elp->el_sup_grp_cnt <= NGROUPS);
|
|
if (elp->el_sup_grp_cnt > NGROUPS) {
|
|
KAUTH_DEBUG("CACHE - invalid sup_grp_cnt provided (%d), truncating to %d",
|
|
elp->el_sup_grp_cnt, NGROUPS);
|
|
elp->el_sup_grp_cnt = NGROUPS;
|
|
}
|
|
kip->ki_supgrpcnt = elp->el_sup_grp_cnt;
|
|
memcpy(kip->ki_supgrps, elp->el_sup_groups, sizeof(elp->el_sup_groups[0]) * kip->ki_supgrpcnt);
|
|
kip->ki_valid |= KI_VALID_GROUPS;
|
|
kip->ki_groups_expiry = (elp->el_member_valid) ? tv.tv_sec + elp->el_member_valid : 0;
|
|
}
|
|
if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_UGUID) {
|
|
kip->ki_guid = elp->el_uguid;
|
|
kip->ki_valid |= KI_VALID_GUID;
|
|
}
|
|
kip->ki_guid_expiry = (elp->el_uguid_valid) ? tv.tv_sec + elp->el_uguid_valid : 0;
|
|
if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_USID) {
|
|
kip->ki_ntsid = elp->el_usid;
|
|
kip->ki_valid |= KI_VALID_NTSID;
|
|
}
|
|
kip->ki_ntsid_expiry = (elp->el_usid_valid) ? tv.tv_sec + elp->el_usid_valid : 0;
|
|
if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_PWNAM) {
|
|
const char *oname = kip->ki_name;
|
|
kip->ki_name = speculative_name;
|
|
speculative_name = NULL;
|
|
kip->ki_valid |= KI_VALID_PWNAM;
|
|
if (oname) {
|
|
/*
|
|
* free oname (if any) outside
|
|
* the lock
|
|
*/
|
|
speculative_name = oname;
|
|
}
|
|
}
|
|
kauth_identity_lru(kip);
|
|
if (rkip != NULL) {
|
|
*rkip = *kip;
|
|
}
|
|
KAUTH_DEBUG("CACHE - refreshed %d is " K_UUID_FMT, kip->ki_uid, K_UUID_ARG(kip->ki_guid));
|
|
break;
|
|
}
|
|
}
|
|
KAUTH_IDENTITY_UNLOCK();
|
|
/* not found in cache, add new record */
|
|
if (kip == NULL) {
|
|
kip = kauth_identity_alloc(elp->el_uid, KAUTH_GID_NONE,
|
|
(elp->el_flags & KAUTH_EXTLOOKUP_VALID_UGUID) ? &elp->el_uguid : NULL,
|
|
(elp->el_uguid_valid) ? tv.tv_sec + elp->el_uguid_valid : 0,
|
|
(elp->el_flags & KAUTH_EXTLOOKUP_VALID_USID) ? &elp->el_usid : NULL,
|
|
(elp->el_usid_valid) ? tv.tv_sec + elp->el_usid_valid : 0,
|
|
(elp->el_flags & KAUTH_EXTLOOKUP_VALID_SUPGRPS) ? elp->el_sup_grp_cnt : 0,
|
|
(elp->el_flags & KAUTH_EXTLOOKUP_VALID_SUPGRPS) ? elp->el_sup_groups : NULL,
|
|
(elp->el_member_valid) ? tv.tv_sec + elp->el_member_valid : 0,
|
|
(elp->el_flags & KAUTH_EXTLOOKUP_VALID_PWNAM) ? speculative_name : NULL,
|
|
KI_VALID_PWNAM);
|
|
if (kip != NULL) {
|
|
if (rkip != NULL) {
|
|
*rkip = *kip;
|
|
}
|
|
if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_PWNAM) {
|
|
speculative_name = NULL;
|
|
}
|
|
KAUTH_DEBUG("CACHE - learned %d is " K_UUID_FMT, kip->ki_uid, K_UUID_ARG(kip->ki_guid));
|
|
kauth_identity_register_and_free(kip);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* group identity? (ignore, if we already processed it as a user) */
|
|
if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GID && !(elp->el_flags & KAUTH_EXTLOOKUP_VALID_UID)) {
|
|
KAUTH_IDENTITY_LOCK();
|
|
TAILQ_FOREACH(kip, &kauth_identities, ki_link) {
|
|
/* matching record */
|
|
if ((kip->ki_valid & KI_VALID_GID) && (kip->ki_gid == elp->el_gid)) {
|
|
if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GGUID) {
|
|
kip->ki_guid = elp->el_gguid;
|
|
kip->ki_valid |= KI_VALID_GUID;
|
|
}
|
|
kip->ki_guid_expiry = (elp->el_gguid_valid) ? tv.tv_sec + elp->el_gguid_valid : 0;
|
|
if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GSID) {
|
|
kip->ki_ntsid = elp->el_gsid;
|
|
kip->ki_valid |= KI_VALID_NTSID;
|
|
}
|
|
kip->ki_ntsid_expiry = (elp->el_gsid_valid) ? tv.tv_sec + elp->el_gsid_valid : 0;
|
|
if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GRNAM) {
|
|
const char *oname = kip->ki_name;
|
|
kip->ki_name = speculative_name;
|
|
speculative_name = NULL;
|
|
kip->ki_valid |= KI_VALID_GRNAM;
|
|
if (oname) {
|
|
/*
|
|
* free oname (if any) outside
|
|
* the lock
|
|
*/
|
|
speculative_name = oname;
|
|
}
|
|
}
|
|
kauth_identity_lru(kip);
|
|
if (rkip != NULL) {
|
|
*rkip = *kip;
|
|
}
|
|
KAUTH_DEBUG("CACHE - refreshed %d is " K_UUID_FMT, kip->ki_uid, K_UUID_ARG(kip->ki_guid));
|
|
break;
|
|
}
|
|
}
|
|
KAUTH_IDENTITY_UNLOCK();
|
|
/* not found in cache, add new record */
|
|
if (kip == NULL) {
|
|
kip = kauth_identity_alloc(KAUTH_UID_NONE, elp->el_gid,
|
|
(elp->el_flags & KAUTH_EXTLOOKUP_VALID_GGUID) ? &elp->el_gguid : NULL,
|
|
(elp->el_gguid_valid) ? tv.tv_sec + elp->el_gguid_valid : 0,
|
|
(elp->el_flags & KAUTH_EXTLOOKUP_VALID_GSID) ? &elp->el_gsid : NULL,
|
|
(elp->el_gsid_valid) ? tv.tv_sec + elp->el_gsid_valid : 0,
|
|
(elp->el_flags & KAUTH_EXTLOOKUP_VALID_SUPGRPS) ? elp->el_sup_grp_cnt : 0,
|
|
(elp->el_flags & KAUTH_EXTLOOKUP_VALID_SUPGRPS) ? elp->el_sup_groups : NULL,
|
|
(elp->el_member_valid) ? tv.tv_sec + elp->el_member_valid : 0,
|
|
(elp->el_flags & KAUTH_EXTLOOKUP_VALID_GRNAM) ? speculative_name : NULL,
|
|
KI_VALID_GRNAM);
|
|
if (kip != NULL) {
|
|
if (rkip != NULL) {
|
|
*rkip = *kip;
|
|
}
|
|
if (elp->el_flags & KAUTH_EXTLOOKUP_VALID_GRNAM) {
|
|
speculative_name = NULL;
|
|
}
|
|
KAUTH_DEBUG("CACHE - learned %d is " K_UUID_FMT, kip->ki_uid, K_UUID_ARG(kip->ki_guid));
|
|
kauth_identity_register_and_free(kip);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If we have a name reference to drop, drop it here */
|
|
if (speculative_name != NULL) {
|
|
vfs_removename(speculative_name);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Trim older entries from the identity cache.
|
|
*
|
|
* Must be called with the identity cache lock held.
|
|
*/
|
|
static void
|
|
kauth_identity_trimcache(int newsize)
|
|
{
|
|
struct kauth_identity *kip;
|
|
|
|
lck_mtx_assert(&kauth_identity_mtx, LCK_MTX_ASSERT_OWNED);
|
|
|
|
while (kauth_identity_count > newsize) {
|
|
kip = TAILQ_LAST(&kauth_identities, kauth_identity_head);
|
|
TAILQ_REMOVE(&kauth_identities, kip, ki_link);
|
|
kauth_identity_count--;
|
|
kfree_type(struct kauth_identity, kip);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* kauth_identity_lru
|
|
*
|
|
* Description: Promote the entry to the head of the LRU, assumes the cache
|
|
* is locked.
|
|
*
|
|
* Parameters: kip kauth identity to move to the
|
|
* head of the LRU list, if it's
|
|
* 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)
|
|
{
|
|
if (kip != TAILQ_FIRST(&kauth_identities)) {
|
|
TAILQ_REMOVE(&kauth_identities, kip, ki_link);
|
|
TAILQ_INSERT_HEAD(&kauth_identities, kip, ki_link);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
struct timeval tv;
|
|
|
|
/*
|
|
* Expiration time of 0 means this entry is persistent.
|
|
*/
|
|
if (kip->ki_guid_expiry == 0) {
|
|
return 0;
|
|
}
|
|
|
|
microuptime(&tv);
|
|
KAUTH_DEBUG("CACHE - GUID expires @ %ld now %ld", kip->ki_guid_expiry, tv.tv_sec);
|
|
|
|
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)
|
|
{
|
|
struct timeval tv;
|
|
|
|
/*
|
|
* Expiration time of 0 means this entry is persistent.
|
|
*/
|
|
if (kip->ki_ntsid_expiry == 0) {
|
|
return 0;
|
|
}
|
|
|
|
microuptime(&tv);
|
|
KAUTH_DEBUG("CACHE - NTSID expires @ %ld now %ld", kip->ki_ntsid_expiry, tv.tv_sec);
|
|
|
|
return (kip->ki_ntsid_expiry <= tv.tv_sec) ? 1 : 0;
|
|
}
|
|
|
|
/*
|
|
* kauth_identity_groups_expired
|
|
*
|
|
* Description: Handle lazy expiration of supplemental group translations.
|
|
*
|
|
* Parameters: kip kauth identity to check for
|
|
* groups expiration
|
|
*
|
|
* Returns: 1 Expired
|
|
* 0 Not expired
|
|
*/
|
|
static int
|
|
kauth_identity_groups_expired(struct kauth_identity *kip)
|
|
{
|
|
struct timeval tv;
|
|
|
|
/*
|
|
* Expiration time of 0 means this entry is persistent.
|
|
*/
|
|
if (kip->ki_groups_expiry == 0) {
|
|
return 0;
|
|
}
|
|
|
|
microuptime(&tv);
|
|
KAUTH_DEBUG("CACHE - GROUPS expires @ %ld now %ld\n", kip->ki_groups_expiry, tv.tv_sec);
|
|
|
|
return (kip->ki_groups_expiry <= tv.tv_sec) ? 1 : 0;
|
|
}
|
|
|
|
/*
|
|
* kauth_identity_find_uid
|
|
*
|
|
* Description: Search for an entry by UID
|
|
*
|
|
* Parameters: uid UID to find
|
|
* kir Pointer to return area
|
|
* getname Name buffer, if ki_name wanted
|
|
*
|
|
* 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, char *getname)
|
|
{
|
|
struct kauth_identity *kip;
|
|
|
|
KAUTH_IDENTITY_LOCK();
|
|
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;
|
|
/* If a name is wanted and one exists, copy it out */
|
|
if (getname != NULL && (kip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM))) {
|
|
strlcpy(getname, kip->ki_name, MAXPATHLEN);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
KAUTH_IDENTITY_UNLOCK();
|
|
return (kip == NULL) ? ENOENT : 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_identity_find_gid
|
|
*
|
|
* Description: Search for an entry by GID
|
|
*
|
|
* Parameters: gid GID to find
|
|
* kir Pointer to return area
|
|
* getname Name buffer, if ki_name wanted
|
|
*
|
|
* Returns: 0 Found
|
|
* ENOENT Not found
|
|
*
|
|
* Implicit returns:
|
|
* *klr Modified, if found
|
|
*/
|
|
static int
|
|
kauth_identity_find_gid(uid_t gid, struct kauth_identity *kir, char *getname)
|
|
{
|
|
struct kauth_identity *kip;
|
|
|
|
KAUTH_IDENTITY_LOCK();
|
|
TAILQ_FOREACH(kip, &kauth_identities, ki_link) {
|
|
if ((kip->ki_valid & KI_VALID_GID) && (gid == kip->ki_gid)) {
|
|
kauth_identity_lru(kip);
|
|
/* Copy via structure assignment */
|
|
*kir = *kip;
|
|
/* If a name is wanted and one exists, copy it out */
|
|
if (getname != NULL && (kip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM))) {
|
|
strlcpy(getname, kip->ki_name, MAXPATHLEN);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
KAUTH_IDENTITY_UNLOCK();
|
|
return (kip == NULL) ? ENOENT : 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_identity_find_guid
|
|
*
|
|
* Description: Search for an entry by GUID
|
|
*
|
|
* Parameters: guidp Pointer to GUID to find
|
|
* kir Pointer to return area
|
|
* getname Name buffer, if ki_name wanted
|
|
*
|
|
* Returns: 0 Found
|
|
* ENOENT Not found
|
|
*
|
|
* 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, char *getname)
|
|
{
|
|
struct kauth_identity *kip;
|
|
|
|
KAUTH_IDENTITY_LOCK();
|
|
TAILQ_FOREACH(kip, &kauth_identities, ki_link) {
|
|
if ((kip->ki_valid & KI_VALID_GUID) && (kauth_guid_equal(guidp, &kip->ki_guid))) {
|
|
kauth_identity_lru(kip);
|
|
/* Copy via structure assignment */
|
|
*kir = *kip;
|
|
/* If a name is wanted and one exists, copy it out */
|
|
if (getname != NULL && (kip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM))) {
|
|
strlcpy(getname, kip->ki_name, MAXPATHLEN);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
KAUTH_IDENTITY_UNLOCK();
|
|
return (kip == NULL) ? ENOENT : 0;
|
|
}
|
|
|
|
/*
|
|
* kauth_identity_find_nam
|
|
*
|
|
* Description: Search for an entry by name
|
|
*
|
|
* Parameters: name Pointer to name to find
|
|
* valid KI_VALID_PWNAM or KI_VALID_GRNAM
|
|
* kir Pointer to return area
|
|
*
|
|
* Returns: 0 Found
|
|
* ENOENT Not found
|
|
*
|
|
* Implicit returns:
|
|
* *klr Modified, if found
|
|
*/
|
|
static int
|
|
kauth_identity_find_nam(char *name, int valid, struct kauth_identity *kir)
|
|
{
|
|
struct kauth_identity *kip;
|
|
|
|
KAUTH_IDENTITY_LOCK();
|
|
TAILQ_FOREACH(kip, &kauth_identities, ki_link) {
|
|
if ((kip->ki_valid & valid) && !strcmp(name, kip->ki_name)) {
|
|
kauth_identity_lru(kip);
|
|
/* Copy via structure assignment */
|
|
*kir = *kip;
|
|
break;
|
|
}
|
|
}
|
|
KAUTH_IDENTITY_UNLOCK();
|
|
return (kip == NULL) ? ENOENT : 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_identity_find_ntsid
|
|
*
|
|
* Description: Search for an entry by NTSID
|
|
*
|
|
* Parameters: ntsid Pointer to NTSID to find
|
|
* kir Pointer to return area
|
|
* getname Name buffer, if ki_name wanted
|
|
*
|
|
* Returns: 0 Found
|
|
* ENOENT Not found
|
|
*
|
|
* 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, char *getname)
|
|
{
|
|
struct kauth_identity *kip;
|
|
|
|
KAUTH_IDENTITY_LOCK();
|
|
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;
|
|
/* If a name is wanted and one exists, copy it out */
|
|
if (getname != NULL && (kip->ki_valid & (KI_VALID_PWNAM | KI_VALID_GRNAM))) {
|
|
strlcpy(getname, kip->ki_name, MAXPATHLEN);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
KAUTH_IDENTITY_UNLOCK();
|
|
return (kip == NULL) ? ENOENT : 0;
|
|
}
|
|
#endif /* CONFIG_EXT_RESOLVER */
|
|
|
|
|
|
/*
|
|
* 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 unequal
|
|
* !0 If GUIDs are equal
|
|
*/
|
|
int
|
|
kauth_guid_equal(guid_t *guid1, guid_t *guid2)
|
|
{
|
|
return bcmp(guid1, guid2, sizeof(*guid1)) == 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 well 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)
|
|
{
|
|
static char fingerprint[] = {0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef};
|
|
uint32_t code;
|
|
/*
|
|
* All WKGs begin with the same 12 bytes.
|
|
*/
|
|
if (bcmp((void *)guid, fingerprint, 12) == 0) {
|
|
/*
|
|
* The final 4 bytes are our code (in network byte order).
|
|
*/
|
|
code = OSSwapHostToBigInt32(*(uint32_t *)&guid->g_guid[12]);
|
|
switch (code) {
|
|
case 0x0000000c:
|
|
return KAUTH_WKG_EVERYBODY;
|
|
case 0xfffffffe:
|
|
return KAUTH_WKG_NOBODY;
|
|
case 0x0000000a:
|
|
return KAUTH_WKG_OWNER;
|
|
case 0x00000010:
|
|
return KAUTH_WKG_GROUP;
|
|
}
|
|
}
|
|
return KAUTH_WKG_NOT;
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_ntsid_equal
|
|
*
|
|
* Description: Determine the equality of two NTSIDs (NT Security Identifiers)
|
|
*
|
|
* Parameters: sid1 Pointer to first NTSID
|
|
* sid2 Pointer to second NTSID
|
|
*
|
|
* Returns: 0 If GUIDs are unequal
|
|
* !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)) == 0) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Identity KPI
|
|
*
|
|
* We support four tokens representing identity:
|
|
* - Credential reference
|
|
* - UID
|
|
* - GUID
|
|
* - NT security identifier
|
|
*
|
|
* Of these, the UID is the ubiquitous identifier; cross-referencing should
|
|
* be done using it.
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
* 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 = kauth_cred_getgid(cred);
|
|
posix_cred_t pcred = posix_cred_get(cred);
|
|
|
|
/* Ignoring the first entry, scan for a match for the new egid */
|
|
for (i = 1; i < pcred->cr_ngroups; i++) {
|
|
/*
|
|
* If we find a match, swap them so we don't lose overall
|
|
* group information
|
|
*/
|
|
if (pcred->cr_groups[i] == new_egid) {
|
|
pcred->cr_groups[i] = old_egid;
|
|
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 && !(pcred->cr_flags & CRF_NOMEMBERD) &&
|
|
kauth_cred_ismember_gid(cred, new_egid, &is_member) == 0 &&
|
|
is_member) {
|
|
displaced = 0;
|
|
}
|
|
#endif /* radar_4600026 */
|
|
|
|
/* set the new EGID into the old spot */
|
|
pcred->cr_groups[0] = new_egid;
|
|
|
|
return displaced;
|
|
}
|
|
|
|
|
|
uid_t
|
|
kauth_cred_getuid(kauth_cred_t cred)
|
|
{
|
|
return posix_cred_get(cred)->cr_uid;
|
|
}
|
|
|
|
uid_t
|
|
kauth_cred_getruid(kauth_cred_t cred)
|
|
{
|
|
return posix_cred_get(cred)->cr_ruid;
|
|
}
|
|
|
|
uid_t
|
|
kauth_cred_getsvuid(kauth_cred_t cred)
|
|
{
|
|
return posix_cred_get(cred)->cr_svuid;
|
|
}
|
|
|
|
|
|
gid_t
|
|
kauth_cred_getgid(kauth_cred_t cred)
|
|
{
|
|
return posix_cred_get(cred)->cr_gid;
|
|
}
|
|
|
|
gid_t
|
|
kauth_cred_getrgid(kauth_cred_t cred)
|
|
{
|
|
return posix_cred_get(cred)->cr_rgid;
|
|
}
|
|
|
|
gid_t
|
|
kauth_cred_getsvgid(kauth_cred_t cred)
|
|
{
|
|
return posix_cred_get(cred)->cr_svgid;
|
|
}
|
|
|
|
|
|
static int kauth_cred_cache_lookup(int from, int to, void *src, void *dst);
|
|
|
|
#if CONFIG_EXT_RESOLVER == 0
|
|
/*
|
|
* If there's no resolver, only support a subset of the kauth_cred_x2y() lookups.
|
|
*/
|
|
static __inline int
|
|
kauth_cred_cache_lookup(int from, int to, void *src, void *dst)
|
|
{
|
|
/* NB: These must match the definitions used by Libinfo's mbr_identifier_translate(). */
|
|
static const uuid_t _user_compat_prefix = {0xff, 0xff, 0xee, 0xee, 0xdd, 0xdd, 0xcc, 0xcc, 0xbb, 0xbb, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00};
|
|
static const uuid_t _group_compat_prefix = {0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0x00};
|
|
#define COMPAT_PREFIX_LEN (sizeof(uuid_t) - sizeof(id_t))
|
|
|
|
assert(from != to);
|
|
|
|
switch (from) {
|
|
case KI_VALID_UID: {
|
|
id_t uid = htonl(*(id_t *)src);
|
|
|
|
if (to == KI_VALID_GUID) {
|
|
uint8_t *uu = dst;
|
|
memcpy(uu, _user_compat_prefix, sizeof(_user_compat_prefix));
|
|
memcpy(&uu[COMPAT_PREFIX_LEN], &uid, sizeof(uid));
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
case KI_VALID_GID: {
|
|
id_t gid = htonl(*(id_t *)src);
|
|
|
|
if (to == KI_VALID_GUID) {
|
|
uint8_t *uu = dst;
|
|
memcpy(uu, _group_compat_prefix, sizeof(_group_compat_prefix));
|
|
memcpy(&uu[COMPAT_PREFIX_LEN], &gid, sizeof(gid));
|
|
return 0;
|
|
}
|
|
break;
|
|
}
|
|
case KI_VALID_GUID: {
|
|
const uint8_t *uu = src;
|
|
|
|
if (to == KI_VALID_UID) {
|
|
if (memcmp(uu, _user_compat_prefix, COMPAT_PREFIX_LEN) == 0) {
|
|
id_t uid;
|
|
memcpy(&uid, &uu[COMPAT_PREFIX_LEN], sizeof(uid));
|
|
*(id_t *)dst = ntohl(uid);
|
|
return 0;
|
|
}
|
|
} else if (to == KI_VALID_GID) {
|
|
if (memcmp(uu, _group_compat_prefix, COMPAT_PREFIX_LEN) == 0) {
|
|
id_t gid;
|
|
memcpy(&gid, &uu[COMPAT_PREFIX_LEN], sizeof(gid));
|
|
*(id_t *)dst = ntohl(gid);
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
/* NOT IMPLEMENTED */
|
|
break;
|
|
}
|
|
return ENOENT;
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_EXT_RESOLVER) && (CONFIG_EXT_RESOLVER)
|
|
/*
|
|
* Structure to hold supplemental groups. Used for impedance matching with
|
|
* kauth_cred_cache_lookup below.
|
|
*/
|
|
struct supgroups {
|
|
size_t *count;
|
|
gid_t *groups;
|
|
};
|
|
|
|
/*
|
|
* kauth_cred_uid2groups
|
|
*
|
|
* Description: Fetch supplemental GROUPS from UID
|
|
*
|
|
* Parameters: uid UID to examine
|
|
* groups pointer to an array of gid_ts
|
|
* gcount pointer to the number of groups wanted/returned
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *groups Modified, if successful
|
|
* *gcount Modified, if successful
|
|
*
|
|
*/
|
|
static int
|
|
kauth_cred_uid2groups(uid_t *uid, gid_t *groups, size_t *gcount)
|
|
{
|
|
int rv;
|
|
|
|
struct supgroups supgroups;
|
|
supgroups.count = gcount;
|
|
supgroups.groups = groups;
|
|
|
|
rv = kauth_cred_cache_lookup(KI_VALID_UID, KI_VALID_GROUPS, uid, &supgroups);
|
|
|
|
return rv;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* kauth_cred_guid2pwnam
|
|
*
|
|
* Description: Fetch PWNAM from GUID
|
|
*
|
|
* Parameters: guidp Pointer to GUID to examine
|
|
* pwnam Pointer to user@domain buffer
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *pwnam Modified, if successful
|
|
*
|
|
* Notes: pwnam is assumed to point to a buffer of MAXPATHLEN in size
|
|
*/
|
|
int
|
|
kauth_cred_guid2pwnam(guid_t *guidp, char *pwnam)
|
|
{
|
|
return kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_PWNAM, guidp, pwnam);
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_guid2grnam
|
|
*
|
|
* Description: Fetch GRNAM from GUID
|
|
*
|
|
* Parameters: guidp Pointer to GUID to examine
|
|
* grnam Pointer to group@domain buffer
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *grnam Modified, if successful
|
|
*
|
|
* Notes: grnam is assumed to point to a buffer of MAXPATHLEN in size
|
|
*/
|
|
int
|
|
kauth_cred_guid2grnam(guid_t *guidp, char *grnam)
|
|
{
|
|
return kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_GRNAM, guidp, grnam);
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_pwnam2guid
|
|
*
|
|
* Description: Fetch PWNAM from GUID
|
|
*
|
|
* Parameters: pwnam String containing user@domain
|
|
* guidp Pointer to buffer for GUID
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *guidp Modified, if successful
|
|
*
|
|
* Notes: pwnam should not point to a request larger than MAXPATHLEN
|
|
* bytes in size, including the NUL termination of the string.
|
|
*/
|
|
int
|
|
kauth_cred_pwnam2guid(char *pwnam, guid_t *guidp)
|
|
{
|
|
return kauth_cred_cache_lookup(KI_VALID_PWNAM, KI_VALID_GUID, pwnam, guidp);
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_grnam2guid
|
|
*
|
|
* Description: Fetch GRNAM from GUID
|
|
*
|
|
* Parameters: grnam String containing group@domain
|
|
* guidp Pointer to buffer for GUID
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *guidp Modified, if successful
|
|
*
|
|
* Notes: grnam should not point to a request larger than MAXPATHLEN
|
|
* bytes in size, including the NUL termination of the string.
|
|
*/
|
|
int
|
|
kauth_cred_grnam2guid(char *grnam, guid_t *guidp)
|
|
{
|
|
return kauth_cred_cache_lookup(KI_VALID_GRNAM, KI_VALID_GUID, grnam, guidp);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
/*
|
|
* kauth_cred_nfs4domain2dsnode
|
|
*
|
|
* Description: Fetch dsnode from nfs4domain
|
|
*
|
|
* Parameters: nfs4domain Pointer to a string nfs4 domain
|
|
* dsnode Pointer to buffer for dsnode
|
|
*
|
|
* Returns: 0 Success
|
|
* ENOENT For now just a stub that always fails
|
|
*
|
|
* Implicit returns:
|
|
* *dsnode Modified, if successuful
|
|
*/
|
|
int
|
|
kauth_cred_nfs4domain2dsnode(__unused char *nfs4domain, __unused char *dsnode)
|
|
{
|
|
return ENOENT;
|
|
}
|
|
|
|
/*
|
|
* kauth_cred_dsnode2nfs4domain
|
|
*
|
|
* Description: Fetch nfs4domain from dsnode
|
|
*
|
|
* Parameters: nfs4domain Pointer to string dsnode
|
|
* dsnode Pointer to buffer for nfs4domain
|
|
*
|
|
* Returns: 0 Success
|
|
* ENOENT For now just a stub that always fails
|
|
*
|
|
* Implicit returns:
|
|
* *nfs4domain Modified, if successuful
|
|
*/
|
|
int
|
|
kauth_cred_dsnode2nfs4domain(__unused char *dsnode, __unused char *nfs4domain)
|
|
{
|
|
return ENOENT;
|
|
}
|
|
|
|
/*
|
|
* kauth_cred_ntsid2uid
|
|
*
|
|
* Description: Fetch UID from NTSID
|
|
*
|
|
* Parameters: sidp Pointer to NTSID to examine
|
|
* uidp Pointer to buffer for UID
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *uidp Modified, if successful
|
|
*/
|
|
int
|
|
kauth_cred_ntsid2uid(ntsid_t *sidp, uid_t *uidp)
|
|
{
|
|
return kauth_cred_cache_lookup(KI_VALID_NTSID, KI_VALID_UID, sidp, uidp);
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_ntsid2gid
|
|
*
|
|
* Description: Fetch GID from NTSID
|
|
*
|
|
* Parameters: sidp Pointer to NTSID to examine
|
|
* gidp Pointer to buffer for GID
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *gidp Modified, if successful
|
|
*/
|
|
int
|
|
kauth_cred_ntsid2gid(ntsid_t *sidp, gid_t *gidp)
|
|
{
|
|
return kauth_cred_cache_lookup(KI_VALID_NTSID, KI_VALID_GID, sidp, gidp);
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_ntsid2guid
|
|
*
|
|
* Description: Fetch GUID from NTSID
|
|
*
|
|
* Parameters: sidp Pointer to NTSID to examine
|
|
* guidp Pointer to buffer for GUID
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *guidp Modified, if successful
|
|
*/
|
|
int
|
|
kauth_cred_ntsid2guid(ntsid_t *sidp, guid_t *guidp)
|
|
{
|
|
return kauth_cred_cache_lookup(KI_VALID_NTSID, KI_VALID_GUID, sidp, guidp);
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_uid2guid
|
|
*
|
|
* Description: Fetch GUID from UID
|
|
*
|
|
* Parameters: uid UID to examine
|
|
* guidp Pointer to buffer for GUID
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *guidp Modified, if successful
|
|
*/
|
|
int
|
|
kauth_cred_uid2guid(uid_t uid, guid_t *guidp)
|
|
{
|
|
return kauth_cred_cache_lookup(KI_VALID_UID, KI_VALID_GUID, &uid, guidp);
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_getguid
|
|
*
|
|
* Description: Fetch GUID from credential
|
|
*
|
|
* Parameters: cred Credential to examine
|
|
* guidp Pointer to buffer for GUID
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *guidp Modified, if successful
|
|
*/
|
|
int
|
|
kauth_cred_getguid(kauth_cred_t cred, guid_t *guidp)
|
|
{
|
|
return kauth_cred_uid2guid(kauth_cred_getuid(cred), guidp);
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_getguid
|
|
*
|
|
* Description: Fetch GUID from GID
|
|
*
|
|
* Parameters: gid GID to examine
|
|
* guidp Pointer to buffer for GUID
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *guidp Modified, if successful
|
|
*/
|
|
int
|
|
kauth_cred_gid2guid(gid_t gid, guid_t *guidp)
|
|
{
|
|
return kauth_cred_cache_lookup(KI_VALID_GID, KI_VALID_GUID, &gid, guidp);
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_uid2ntsid
|
|
*
|
|
* Description: Fetch NTSID from UID
|
|
*
|
|
* Parameters: uid UID to examine
|
|
* sidp Pointer to buffer for NTSID
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *sidp Modified, if successful
|
|
*/
|
|
int
|
|
kauth_cred_uid2ntsid(uid_t uid, ntsid_t *sidp)
|
|
{
|
|
return kauth_cred_cache_lookup(KI_VALID_UID, KI_VALID_NTSID, &uid, sidp);
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_getntsid
|
|
*
|
|
* Description: Fetch NTSID from credential
|
|
*
|
|
* Parameters: cred Credential to examine
|
|
* sidp Pointer to buffer for NTSID
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *sidp Modified, if successful
|
|
*/
|
|
int
|
|
kauth_cred_getntsid(kauth_cred_t cred, ntsid_t *sidp)
|
|
{
|
|
return kauth_cred_uid2ntsid(kauth_cred_getuid(cred), sidp);
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_gid2ntsid
|
|
*
|
|
* Description: Fetch NTSID from GID
|
|
*
|
|
* Parameters: gid GID to examine
|
|
* sidp Pointer to buffer for NTSID
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *sidp Modified, if successful
|
|
*/
|
|
int
|
|
kauth_cred_gid2ntsid(gid_t gid, ntsid_t *sidp)
|
|
{
|
|
return kauth_cred_cache_lookup(KI_VALID_GID, KI_VALID_NTSID, &gid, sidp);
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_guid2ntsid
|
|
*
|
|
* Description: Fetch NTSID from GUID
|
|
*
|
|
* Parameters: guidp Pointer to GUID to examine
|
|
* sidp Pointer to buffer for NTSID
|
|
*
|
|
* Returns: 0 Success
|
|
* kauth_cred_cache_lookup:EINVAL
|
|
*
|
|
* Implicit returns:
|
|
* *sidp Modified, if successful
|
|
*/
|
|
int
|
|
kauth_cred_guid2ntsid(guid_t *guidp, ntsid_t *sidp)
|
|
{
|
|
return kauth_cred_cache_lookup(KI_VALID_GUID, KI_VALID_NTSID, guidp, sidp);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
#if CONFIG_EXT_RESOLVER
|
|
static int
|
|
kauth_cred_cache_lookup(int from, int to, void *src, void *dst)
|
|
{
|
|
struct kauth_identity ki;
|
|
struct kauth_identity_extlookup el;
|
|
int error;
|
|
uint64_t extend_data = 0ULL;
|
|
int (* expired)(struct kauth_identity *kip);
|
|
char *namebuf = NULL;
|
|
|
|
KAUTH_DEBUG("CACHE - translate %d to %d", from, to);
|
|
|
|
/*
|
|
* Look for an existing cache entry for this association.
|
|
* If the entry has not expired, return the cached information.
|
|
* We do not cache user@domain translations here; they use too
|
|
* much memory to hold onto forever, and can not be updated
|
|
* atomically.
|
|
*/
|
|
if (to == KI_VALID_PWNAM || to == KI_VALID_GRNAM) {
|
|
if (dst == NULL) {
|
|
return EINVAL;
|
|
}
|
|
namebuf = dst;
|
|
*namebuf = '\0';
|
|
}
|
|
ki.ki_valid = 0;
|
|
switch (from) {
|
|
case KI_VALID_UID:
|
|
error = kauth_identity_find_uid(*(uid_t *)src, &ki, namebuf);
|
|
break;
|
|
case KI_VALID_GID:
|
|
error = kauth_identity_find_gid(*(gid_t *)src, &ki, namebuf);
|
|
break;
|
|
case KI_VALID_GUID:
|
|
error = kauth_identity_find_guid((guid_t *)src, &ki, namebuf);
|
|
break;
|
|
case KI_VALID_NTSID:
|
|
error = kauth_identity_find_ntsid((ntsid_t *)src, &ki, namebuf);
|
|
break;
|
|
case KI_VALID_PWNAM:
|
|
case KI_VALID_GRNAM:
|
|
/* Names are unique in their 'from' space */
|
|
error = kauth_identity_find_nam((char *)src, from, &ki);
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
/* If we didn't get what we're asking for. Call the resolver */
|
|
if (!error && !(to & ki.ki_valid)) {
|
|
error = ENOENT;
|
|
}
|
|
/* lookup failure or error */
|
|
if (error != 0) {
|
|
/* any other error is fatal */
|
|
if (error != ENOENT) {
|
|
/* XXX bogus check - this is not possible */
|
|
KAUTH_DEBUG("CACHE - cache search error %d", error);
|
|
return error;
|
|
}
|
|
} else {
|
|
/* found a valid cached entry, check expiry */
|
|
switch (to) {
|
|
case KI_VALID_GUID:
|
|
expired = kauth_identity_guid_expired;
|
|
break;
|
|
case KI_VALID_NTSID:
|
|
expired = kauth_identity_ntsid_expired;
|
|
break;
|
|
case KI_VALID_GROUPS:
|
|
expired = kauth_identity_groups_expired;
|
|
break;
|
|
default:
|
|
switch (from) {
|
|
case KI_VALID_GUID:
|
|
expired = kauth_identity_guid_expired;
|
|
break;
|
|
case KI_VALID_NTSID:
|
|
expired = kauth_identity_ntsid_expired;
|
|
break;
|
|
default:
|
|
expired = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If no expiry function, or not expired, we have found
|
|
* a hit.
|
|
*/
|
|
if (expired) {
|
|
if (!expired(&ki)) {
|
|
KAUTH_DEBUG("CACHE - entry valid, unexpired");
|
|
expired = NULL; /* must clear it is used as a flag */
|
|
} else {
|
|
/*
|
|
* We leave ki_valid set here; it contains a
|
|
* translation but the TTL has expired. If we can't
|
|
* get a result from the resolver, we will use it as
|
|
* a better-than nothing alternative.
|
|
*/
|
|
|
|
KAUTH_DEBUG("CACHE - expired entry found");
|
|
}
|
|
} else {
|
|
KAUTH_DEBUG("CACHE - no expiry function");
|
|
}
|
|
|
|
if (!expired) {
|
|
/* do we have a translation? */
|
|
if (ki.ki_valid & to) {
|
|
KAUTH_DEBUG("CACHE - found matching entry with valid 0x%08x", ki.ki_valid);
|
|
DTRACE_PROC4(kauth__identity__cache__hit, int, from, int, to, void *, src, void *, dst);
|
|
goto found;
|
|
} else {
|
|
/*
|
|
* GUIDs and NTSIDs map to either a UID or a GID, but not both.
|
|
* If we went looking for a translation from GUID or NTSID and
|
|
* found a translation that wasn't for our desired type, then
|
|
* don't bother calling the resolver. We know that this
|
|
* GUID/NTSID can't translate to our desired type.
|
|
*/
|
|
switch (from) {
|
|
case KI_VALID_GUID:
|
|
case KI_VALID_NTSID:
|
|
switch (to) {
|
|
case KI_VALID_GID:
|
|
if ((ki.ki_valid & KI_VALID_UID)) {
|
|
KAUTH_DEBUG("CACHE - unexpected entry 0x%08x & %x", ki.ki_valid, KI_VALID_GID);
|
|
return ENOENT;
|
|
}
|
|
break;
|
|
case KI_VALID_UID:
|
|
if ((ki.ki_valid & KI_VALID_GID)) {
|
|
KAUTH_DEBUG("CACHE - unexpected entry 0x%08x & %x", ki.ki_valid, KI_VALID_UID);
|
|
return ENOENT;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We failed to find a cache entry; call the resolver.
|
|
*
|
|
* Note: We ask for as much non-extended data as we can get,
|
|
* and only provide (or ask for) extended information if
|
|
* we have a 'from' (or 'to') which requires it. This
|
|
* way we don't pay for the extra transfer overhead for
|
|
* data we don't need.
|
|
*/
|
|
bzero(&el, sizeof(el));
|
|
el.el_info_pid = proc_getpid(current_proc());
|
|
switch (from) {
|
|
case KI_VALID_UID:
|
|
el.el_flags = KAUTH_EXTLOOKUP_VALID_UID;
|
|
el.el_uid = *(uid_t *)src;
|
|
break;
|
|
case KI_VALID_GID:
|
|
el.el_flags = KAUTH_EXTLOOKUP_VALID_GID;
|
|
el.el_gid = *(gid_t *)src;
|
|
break;
|
|
case KI_VALID_GUID:
|
|
el.el_flags = KAUTH_EXTLOOKUP_VALID_UGUID | KAUTH_EXTLOOKUP_VALID_GGUID;
|
|
el.el_uguid = *(guid_t *)src;
|
|
el.el_gguid = *(guid_t *)src;
|
|
break;
|
|
case KI_VALID_NTSID:
|
|
el.el_flags = KAUTH_EXTLOOKUP_VALID_USID | KAUTH_EXTLOOKUP_VALID_GSID;
|
|
el.el_usid = *(ntsid_t *)src;
|
|
el.el_gsid = *(ntsid_t *)src;
|
|
break;
|
|
case KI_VALID_PWNAM:
|
|
/* extra overhead */
|
|
el.el_flags = KAUTH_EXTLOOKUP_VALID_PWNAM;
|
|
extend_data = CAST_USER_ADDR_T(src);
|
|
break;
|
|
case KI_VALID_GRNAM:
|
|
/* extra overhead */
|
|
el.el_flags = KAUTH_EXTLOOKUP_VALID_GRNAM;
|
|
extend_data = CAST_USER_ADDR_T(src);
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
/*
|
|
* Here we ask for everything all at once, to avoid having to work
|
|
* out what we really want now, or might want soon.
|
|
*
|
|
* Asking for SID translations when we don't know we need them right
|
|
* now is going to cause excess work to be done if we're connected
|
|
* to a network that thinks it can translate them. This list needs
|
|
* to get smaller/smarter.
|
|
*/
|
|
el.el_flags |= KAUTH_EXTLOOKUP_WANT_UID | KAUTH_EXTLOOKUP_WANT_GID |
|
|
KAUTH_EXTLOOKUP_WANT_UGUID | KAUTH_EXTLOOKUP_WANT_GGUID |
|
|
KAUTH_EXTLOOKUP_WANT_USID | KAUTH_EXTLOOKUP_WANT_GSID;
|
|
if (to == KI_VALID_PWNAM) {
|
|
/* extra overhead */
|
|
el.el_flags |= KAUTH_EXTLOOKUP_WANT_PWNAM;
|
|
extend_data = CAST_USER_ADDR_T(dst);
|
|
}
|
|
if (to == KI_VALID_GRNAM) {
|
|
/* extra overhead */
|
|
el.el_flags |= KAUTH_EXTLOOKUP_WANT_GRNAM;
|
|
extend_data = CAST_USER_ADDR_T(dst);
|
|
}
|
|
if (to == KI_VALID_GROUPS) {
|
|
/* Expensive and only useful for an NFS client not using kerberos */
|
|
el.el_flags |= KAUTH_EXTLOOKUP_WANT_SUPGRPS;
|
|
if (ki.ki_valid & KI_VALID_GROUPS) {
|
|
/*
|
|
* Copy the current supplemental groups for the resolver.
|
|
* The resolver should check these groups first and if
|
|
* the user (uid) is still a member it should endeavor to
|
|
* keep them in the list. Otherwise NFS clients could get
|
|
* changing access to server file system objects on each
|
|
* expiration.
|
|
*/
|
|
if (ki.ki_supgrpcnt > NGROUPS) {
|
|
panic("kauth data structure corrupted. kauth identity 0x%p with %u groups, greater than max of %d",
|
|
&ki, ki.ki_supgrpcnt, NGROUPS);
|
|
}
|
|
|
|
el.el_sup_grp_cnt = (uint32_t)ki.ki_supgrpcnt;
|
|
|
|
memcpy(el.el_sup_groups, ki.ki_supgrps, sizeof(el.el_sup_groups[0]) * ki.ki_supgrpcnt);
|
|
/* Let the resolver know these were the previous valid groups */
|
|
el.el_flags |= KAUTH_EXTLOOKUP_VALID_SUPGRPS;
|
|
KAUTH_DEBUG("GROUPS: Sending previously valid GROUPS");
|
|
} else {
|
|
KAUTH_DEBUG("GROUPS: no valid groups to send");
|
|
}
|
|
}
|
|
|
|
/* Call resolver */
|
|
KAUTH_DEBUG("CACHE - calling resolver for %x", el.el_flags);
|
|
|
|
DTRACE_PROC3(kauth__id__resolver__submitted, int, from, int, to, uintptr_t, src);
|
|
|
|
error = kauth_resolver_submit(&el, extend_data);
|
|
|
|
DTRACE_PROC2(kauth__id__resolver__returned, int, error, struct kauth_identity_extlookup *, &el)
|
|
|
|
KAUTH_DEBUG("CACHE - resolver returned %d", error);
|
|
|
|
/* was the external lookup successful? */
|
|
if (error == 0) {
|
|
/*
|
|
* Save the results from the lookup - we may have other
|
|
* information, even if we didn't get a guid or the
|
|
* extended data.
|
|
*
|
|
* If we came from a name, we know the extend_data is valid.
|
|
*/
|
|
if (from == KI_VALID_PWNAM) {
|
|
el.el_flags |= KAUTH_EXTLOOKUP_VALID_PWNAM;
|
|
} else if (from == KI_VALID_GRNAM) {
|
|
el.el_flags |= KAUTH_EXTLOOKUP_VALID_GRNAM;
|
|
}
|
|
|
|
kauth_identity_updatecache(&el, &ki, extend_data);
|
|
|
|
/*
|
|
* Check to see if we have a valid cache entry
|
|
* originating from the result.
|
|
*/
|
|
if (!(ki.ki_valid & to)) {
|
|
error = ENOENT;
|
|
}
|
|
}
|
|
if (error) {
|
|
return error;
|
|
}
|
|
found:
|
|
/*
|
|
* Copy from the appropriate struct kauth_identity cache entry
|
|
* structure into the destination buffer area.
|
|
*/
|
|
switch (to) {
|
|
case KI_VALID_UID:
|
|
*(uid_t *)dst = ki.ki_uid;
|
|
break;
|
|
case KI_VALID_GID:
|
|
*(gid_t *)dst = ki.ki_gid;
|
|
break;
|
|
case KI_VALID_GUID:
|
|
*(guid_t *)dst = ki.ki_guid;
|
|
break;
|
|
case KI_VALID_NTSID:
|
|
*(ntsid_t *)dst = ki.ki_ntsid;
|
|
break;
|
|
case KI_VALID_GROUPS: {
|
|
struct supgroups *gp = (struct supgroups *)dst;
|
|
size_t limit = ki.ki_supgrpcnt;
|
|
|
|
if (gp->count) {
|
|
limit = MIN(ki.ki_supgrpcnt, *gp->count);
|
|
*gp->count = limit;
|
|
}
|
|
|
|
memcpy(gp->groups, ki.ki_supgrps, sizeof(gid_t) * limit);
|
|
}
|
|
break;
|
|
case KI_VALID_PWNAM:
|
|
case KI_VALID_GRNAM:
|
|
/* handled in kauth_resolver_complete() */
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
KAUTH_DEBUG("CACHE - returned successfully");
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Group membership cache.
|
|
*
|
|
* XXX the linked-list implementation here needs to be optimized.
|
|
*/
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
struct timeval tv;
|
|
|
|
/*
|
|
* Expiration time of 0 means this entry is persistent.
|
|
*/
|
|
if (gm->gm_expiry == 0) {
|
|
return 0;
|
|
}
|
|
|
|
microuptime(&tv);
|
|
|
|
return (gm->gm_expiry <= tv.tv_sec) ? 1 : 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
if (gm != TAILQ_FIRST(&kauth_groups)) {
|
|
TAILQ_REMOVE(&kauth_groups, gm, gm_link);
|
|
TAILQ_INSERT_HEAD(&kauth_groups, gm, gm_link);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
struct kauth_group_membership *gm;
|
|
struct timeval tv;
|
|
|
|
/* need a valid response if we are to cache anything */
|
|
if ((el->el_flags &
|
|
(KAUTH_EXTLOOKUP_VALID_UID | KAUTH_EXTLOOKUP_VALID_GID | KAUTH_EXTLOOKUP_VALID_MEMBERSHIP)) !=
|
|
(KAUTH_EXTLOOKUP_VALID_UID | KAUTH_EXTLOOKUP_VALID_GID | KAUTH_EXTLOOKUP_VALID_MEMBERSHIP)) {
|
|
return;
|
|
}
|
|
|
|
microuptime(&tv);
|
|
|
|
/*
|
|
* 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) &&
|
|
(el->el_gid == gm->gm_gid)) {
|
|
if (el->el_flags & KAUTH_EXTLOOKUP_ISMEMBER) {
|
|
gm->gm_flags |= KAUTH_GROUP_ISMEMBER;
|
|
} else {
|
|
gm->gm_flags &= ~KAUTH_GROUP_ISMEMBER;
|
|
}
|
|
gm->gm_expiry = (el->el_member_valid) ? el->el_member_valid + tv.tv_sec : 0;
|
|
kauth_groups_lru(gm);
|
|
break;
|
|
}
|
|
}
|
|
KAUTH_GROUPS_UNLOCK();
|
|
|
|
/* if we found an entry to update, stop here */
|
|
if (gm != NULL) {
|
|
return;
|
|
}
|
|
|
|
/* allocate a new record */
|
|
gm = kalloc_type(struct kauth_group_membership, Z_WAITOK | Z_NOFAIL);
|
|
gm->gm_uid = el->el_uid;
|
|
gm->gm_gid = el->el_gid;
|
|
if (el->el_flags & KAUTH_EXTLOOKUP_ISMEMBER) {
|
|
gm->gm_flags |= KAUTH_GROUP_ISMEMBER;
|
|
} else {
|
|
gm->gm_flags &= ~KAUTH_GROUP_ISMEMBER;
|
|
}
|
|
gm->gm_expiry = (el->el_member_valid) ? el->el_member_valid + tv.tv_sec : 0;
|
|
|
|
/*
|
|
* Insert the new entry. Note that it's possible to race ourselves
|
|
* 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);
|
|
if (++kauth_groups_count > kauth_groups_cachemax) {
|
|
gm = TAILQ_LAST(&kauth_groups, kauth_groups_head);
|
|
TAILQ_REMOVE(&kauth_groups, gm, gm_link);
|
|
kauth_groups_count--;
|
|
} else {
|
|
gm = NULL;
|
|
}
|
|
KAUTH_GROUPS_UNLOCK();
|
|
|
|
/* free expired cache entry */
|
|
kfree_type(struct kauth_group_membership, gm);
|
|
}
|
|
|
|
/*
|
|
* Trim older entries from the group membership cache.
|
|
*
|
|
* Must be called with the group cache lock held.
|
|
*/
|
|
static void
|
|
kauth_groups_trimcache(int new_size)
|
|
{
|
|
struct kauth_group_membership *gm;
|
|
|
|
lck_mtx_assert(&kauth_groups_mtx, LCK_MTX_ASSERT_OWNED);
|
|
|
|
while (kauth_groups_count > new_size) {
|
|
gm = TAILQ_LAST(&kauth_groups, kauth_groups_head);
|
|
TAILQ_REMOVE(&kauth_groups, gm, gm_link);
|
|
kauth_groups_count--;
|
|
kfree_type(struct kauth_group_membership, gm);
|
|
}
|
|
}
|
|
#endif /* CONFIG_EXT_RESOLVER */
|
|
|
|
/*
|
|
* Group membership KPI
|
|
*/
|
|
|
|
/*
|
|
* 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 perform lookup
|
|
* kauth_resolver_submit:EWOULDBLOCK
|
|
* kauth_resolver_submit:EINTR
|
|
* kauth_resolver_submit:ENOMEM
|
|
* kauth_resolver_submit:ENOENT User space daemon did not vend
|
|
* this credential.
|
|
* kauth_resolver_submit:??? Unlikely error from user space
|
|
*
|
|
* Implicit returns:
|
|
* *resultp (modified) 1 Is member
|
|
* 0 Is not member
|
|
*
|
|
* Notes: This function guarantees not to modify resultp when returning
|
|
* an error.
|
|
*
|
|
* This function effectively checks the EGID as well, since the
|
|
* EGID is cr_groups[0] as an implementation detail.
|
|
*/
|
|
int
|
|
kauth_cred_ismember_gid(kauth_cred_t cred, gid_t gid, int *resultp)
|
|
{
|
|
posix_cred_t pcred = posix_cred_get(cred);
|
|
int i;
|
|
|
|
/*
|
|
* Check the per-credential list of override groups.
|
|
*
|
|
* We can conditionalise this on cred->cr_gmuid == KAUTH_UID_NONE since
|
|
* the cache should be used for that case.
|
|
*/
|
|
for (i = 0; i < pcred->cr_ngroups; i++) {
|
|
if (gid == pcred->cr_groups[i]) {
|
|
*resultp = 1;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we don't have a UID for group membership checks, the in-cred list
|
|
* was authoritative and we can stop here.
|
|
*/
|
|
if (pcred->cr_gmuid == KAUTH_UID_NONE) {
|
|
*resultp = 0;
|
|
return 0;
|
|
}
|
|
|
|
#if CONFIG_EXT_RESOLVER
|
|
struct kauth_group_membership *gm;
|
|
struct kauth_identity_extlookup el;
|
|
int error;
|
|
|
|
/*
|
|
* If the resolver hasn't checked in yet, we are early in the boot
|
|
* phase and the local group list is complete and authoritative.
|
|
*/
|
|
if (!kauth_resolver_registered) {
|
|
*resultp = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* TODO: */
|
|
/* XXX check supplementary groups */
|
|
/* XXX check whiteout groups */
|
|
/* XXX nesting of supplementary/whiteout groups? */
|
|
|
|
/*
|
|
* Check the group cache.
|
|
*/
|
|
KAUTH_GROUPS_LOCK();
|
|
TAILQ_FOREACH(gm, &kauth_groups, gm_link) {
|
|
if ((gm->gm_uid == pcred->cr_gmuid) && (gm->gm_gid == gid) && !kauth_groups_expired(gm)) {
|
|
kauth_groups_lru(gm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* did we find a membership entry? */
|
|
if (gm != NULL) {
|
|
*resultp = (gm->gm_flags & KAUTH_GROUP_ISMEMBER) ? 1 : 0;
|
|
}
|
|
KAUTH_GROUPS_UNLOCK();
|
|
|
|
/* if we did, we can return now */
|
|
if (gm != NULL) {
|
|
DTRACE_PROC2(kauth__group__cache__hit, int, pcred->cr_gmuid, int, gid);
|
|
return 0;
|
|
}
|
|
|
|
/* nothing in the cache, need to go to userland */
|
|
bzero(&el, sizeof(el));
|
|
el.el_info_pid = proc_getpid(current_proc());
|
|
el.el_flags = KAUTH_EXTLOOKUP_VALID_UID | KAUTH_EXTLOOKUP_VALID_GID | KAUTH_EXTLOOKUP_WANT_MEMBERSHIP;
|
|
el.el_uid = pcred->cr_gmuid;
|
|
el.el_gid = gid;
|
|
el.el_member_valid = 0; /* XXX set by resolver? */
|
|
|
|
DTRACE_PROC2(kauth__group__resolver__submitted, int, el.el_uid, int, el.el_gid);
|
|
|
|
error = kauth_resolver_submit(&el, 0ULL);
|
|
|
|
DTRACE_PROC2(kauth__group__resolver__returned, int, error, int, el.el_flags);
|
|
|
|
if (error != 0) {
|
|
return error;
|
|
}
|
|
/* save the results from the lookup */
|
|
kauth_groups_updatecache(&el);
|
|
|
|
/* if we successfully ascertained membership, report */
|
|
if (el.el_flags & KAUTH_EXTLOOKUP_VALID_MEMBERSHIP) {
|
|
*resultp = (el.el_flags & KAUTH_EXTLOOKUP_ISMEMBER) ? 1 : 0;
|
|
return 0;
|
|
}
|
|
|
|
return ENOENT;
|
|
#else
|
|
*resultp = 0;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* 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_resolver_submit:ENOENT User space daemon did not vend
|
|
* this credential.
|
|
* kauth_cred_ismember_gid:EWOULDBLOCK
|
|
* kauth_cred_ismember_gid:EINTR
|
|
* kauth_cred_ismember_gid:ENOMEM
|
|
* 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(__unused kauth_cred_t cred, guid_t *guidp, int *resultp)
|
|
{
|
|
int error = 0;
|
|
|
|
switch (kauth_wellknown_guid(guidp)) {
|
|
case KAUTH_WKG_NOBODY:
|
|
*resultp = 0;
|
|
break;
|
|
case KAUTH_WKG_EVERYBODY:
|
|
*resultp = 1;
|
|
break;
|
|
default:
|
|
{
|
|
gid_t gid;
|
|
#if CONFIG_EXT_RESOLVER
|
|
struct kauth_identity ki;
|
|
|
|
/*
|
|
* Grovel the identity cache looking for this GUID.
|
|
* If we find it, and it is for a user record, return
|
|
* false because it's not a group.
|
|
*
|
|
* This is necessary because we don't have -ve caching
|
|
* of group memberships, and we really want to avoid
|
|
* calling out to the resolver if at all possible.
|
|
*
|
|
* Because we're called by the ACL evaluator, and the
|
|
* ACL evaluator is likely to encounter ACEs for users,
|
|
* this is expected to be a common case.
|
|
*/
|
|
ki.ki_valid = 0;
|
|
if ((error = kauth_identity_find_guid(guidp, &ki, NULL)) == 0 &&
|
|
!kauth_identity_guid_expired(&ki)) {
|
|
if (ki.ki_valid & KI_VALID_GID) {
|
|
/* It's a group after all... */
|
|
gid = ki.ki_gid;
|
|
goto do_check;
|
|
}
|
|
if (ki.ki_valid & KI_VALID_UID) {
|
|
*resultp = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
#endif /* CONFIG_EXT_RESOLVER */
|
|
/*
|
|
* Attempt to translate the GUID to a GID. Even if
|
|
* this fails, we will have primed the cache if it is
|
|
* a user record and we'll see it above the next time
|
|
* we're asked.
|
|
*/
|
|
if ((error = kauth_cred_guid2gid(guidp, &gid)) != 0) {
|
|
/*
|
|
* If we have no guid -> gid translation, it's not a group and
|
|
* thus the cred can't be a member.
|
|
*/
|
|
if (error == ENOENT) {
|
|
*resultp = 0;
|
|
error = 0;
|
|
}
|
|
} else {
|
|
#if CONFIG_EXT_RESOLVER
|
|
do_check:
|
|
#endif /* CONFIG_EXT_RESOLVER */
|
|
error = kauth_cred_ismember_gid(cred, gid, resultp);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* kauth_cred_gid_subset
|
|
*
|
|
* Description: Given two credentials, determine if all GIDs associated with
|
|
* the first are also associated with the second
|
|
*
|
|
* 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;
|
|
posix_cred_t pcred1 = posix_cred_get(cred1);
|
|
posix_cred_t pcred2 = posix_cred_get(cred2);
|
|
|
|
/* First, check the local list of groups */
|
|
for (i = 0; i < pcred1->cr_ngroups; i++) {
|
|
gid = pcred1->cr_groups[i];
|
|
if ((err = kauth_cred_ismember_gid(cred2, gid, &res)) != 0) {
|
|
return err;
|
|
}
|
|
|
|
if (!res && gid != pcred2->cr_rgid && gid != pcred2->cr_svgid) {
|
|
*resultp = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Check real gid */
|
|
if ((err = kauth_cred_ismember_gid(cred2, pcred1->cr_rgid, &res)) != 0) {
|
|
return err;
|
|
}
|
|
|
|
if (!res && pcred1->cr_rgid != pcred2->cr_rgid &&
|
|
pcred1->cr_rgid != pcred2->cr_svgid) {
|
|
*resultp = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Finally, check saved gid */
|
|
if ((err = kauth_cred_ismember_gid(cred2, pcred1->cr_svgid, &res)) != 0) {
|
|
return err;
|
|
}
|
|
|
|
if (!res && pcred1->cr_svgid != pcred2->cr_rgid &&
|
|
pcred1->cr_svgid != pcred2->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 kauth_cred_getuid(cred) == 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Credential KPI
|
|
*/
|
|
|
|
static smrh_key_t kauth_cred_key(kauth_cred_t cred);
|
|
static uint32_t kauth_cred_key_hash(smrh_key_t key, uint32_t seed);
|
|
static bool kauth_cred_key_equ(smrh_key_t k1, smrh_key_t k2);
|
|
static uint32_t kauth_cred_obj_hash(const struct smrq_slink *, uint32_t seed);
|
|
static bool kauth_cred_obj_equ(const struct smrq_slink *, smrh_key_t);
|
|
static bool kauth_cred_obj_try_get(void *);
|
|
|
|
struct ucred_rw {
|
|
os_ref_atomic_t crw_weak_ref;
|
|
struct ucred *crw_cred;
|
|
struct smrq_slink crw_link;
|
|
struct smr_node crw_node;
|
|
};
|
|
|
|
#define KAUTH_CRED_REF_MAX 0x0ffffffful
|
|
|
|
ZONE_DEFINE_ID(ZONE_ID_KAUTH_CRED, "cred", struct ucred, ZC_READONLY | ZC_ZFREE_CLEARMEM);
|
|
static KALLOC_TYPE_DEFINE(ucred_rw_zone, struct ucred_rw, KT_DEFAULT);
|
|
os_refgrp_decl(static, ucred_ref_grp, "ucred_rw", NULL);
|
|
|
|
SMRH_TRAITS_DEFINE(kauth_cred_traits, struct ucred_rw, crw_link,
|
|
.domain = &smr_proc_task,
|
|
.key_hash = kauth_cred_key_hash,
|
|
.key_equ = kauth_cred_key_equ,
|
|
.obj_hash = kauth_cred_obj_hash,
|
|
.obj_equ = kauth_cred_obj_equ,
|
|
.obj_try_get = kauth_cred_obj_try_get);
|
|
|
|
static struct smr_shash kauth_cred_hash;
|
|
|
|
static inline void
|
|
ucred_rw_ref(struct ucred_rw *rw)
|
|
{
|
|
os_ref_retain_raw(&rw->crw_weak_ref, &ucred_ref_grp);
|
|
}
|
|
|
|
static inline bool
|
|
ucred_rw_tryref(struct ucred_rw *rw)
|
|
{
|
|
return os_ref_retain_try_raw(&rw->crw_weak_ref, &ucred_ref_grp);
|
|
}
|
|
|
|
static inline os_ref_count_t
|
|
ucred_rw_unref(struct ucred_rw *rw)
|
|
{
|
|
return os_ref_release_raw(&rw->crw_weak_ref, &ucred_ref_grp);
|
|
}
|
|
|
|
static inline void
|
|
ucred_rw_unref_live(struct ucred_rw *rw)
|
|
{
|
|
os_ref_release_live_raw(&rw->crw_weak_ref, &ucred_ref_grp);
|
|
}
|
|
|
|
__abortlike
|
|
static void
|
|
kauth_cred_panic_over_released(kauth_cred_t cred)
|
|
{
|
|
panic("kauth_cred_unref: cred %p over-released", cred);
|
|
}
|
|
|
|
__abortlike
|
|
static void
|
|
kauth_cred_panic_over_retain(kauth_cred_t cred)
|
|
{
|
|
panic("kauth_cred_ref: cred %p over-retained", cred);
|
|
}
|
|
|
|
static void
|
|
kauth_cred_hold(kauth_cred_t cred)
|
|
{
|
|
unsigned long ref;
|
|
|
|
ref = zalloc_ro_update_field_atomic(ZONE_ID_KAUTH_CRED,
|
|
cred, cr_ref, ZRO_ATOMIC_ADD_LONG, 1);
|
|
if (ref >= KAUTH_CRED_REF_MAX) {
|
|
kauth_cred_panic_over_retain(cred);
|
|
}
|
|
}
|
|
|
|
static void
|
|
kauth_cred_drop(kauth_cred_t cred)
|
|
{
|
|
unsigned long ref;
|
|
|
|
ref = zalloc_ro_update_field_atomic(ZONE_ID_KAUTH_CRED,
|
|
cred, cr_ref, ZRO_ATOMIC_ADD_LONG, -1);
|
|
if (__improbable(ref == 0 || ref > KAUTH_CRED_REF_MAX)) {
|
|
kauth_cred_panic_over_released(cred);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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).
|
|
*
|
|
* We also create the kernel and init creds before lockdown
|
|
* so that vfs_context0 and initcred pointers can be made constant.
|
|
*
|
|
* We do this in the "ZALLOC" stage because we need
|
|
* the kauth_cred_hash_mtx to be initialized,
|
|
* and to allocate the kernel cred.
|
|
*/
|
|
__startup_func
|
|
static void
|
|
kauth_cred_init(void)
|
|
{
|
|
struct posix_cred kernel_cred_template = {
|
|
.cr_ngroups = 1,
|
|
.cr_flags = CRF_NOMEMBERD,
|
|
};
|
|
|
|
smr_shash_init(&kauth_cred_hash, SMRSH_BALANCED, maxproc / 4);
|
|
vfs_context0.vc_ucred = posix_cred_create(&kernel_cred_template);
|
|
}
|
|
STARTUP(ZALLOC, STARTUP_RANK_LAST, kauth_cred_init);
|
|
|
|
uid_t
|
|
kauth_getuid(void)
|
|
{
|
|
return kauth_cred_getuid(kauth_cred_get());
|
|
}
|
|
|
|
uid_t
|
|
kauth_getruid(void)
|
|
{
|
|
return kauth_cred_getruid(kauth_cred_get());
|
|
}
|
|
|
|
gid_t
|
|
kauth_getgid(void)
|
|
{
|
|
return kauth_cred_getgid(kauth_cred_get());
|
|
}
|
|
|
|
gid_t
|
|
kauth_getrgid(void)
|
|
{
|
|
return kauth_cred_getrgid(kauth_cred_get());
|
|
}
|
|
|
|
kauth_cred_t
|
|
kauth_cred_get(void)
|
|
{
|
|
return current_thread_ro()->tro_cred;
|
|
}
|
|
|
|
intptr_t
|
|
current_thread_cred_label_get(int slot)
|
|
{
|
|
#if CONFIG_MACF
|
|
return mac_label_get(kauth_cred_get()->cr_label, slot);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
__abortlike
|
|
static void
|
|
current_cached_proc_cred_panic(proc_t p)
|
|
{
|
|
panic("current_cached_proc_cred(%p) called but current_proc() is %p",
|
|
p, current_proc());
|
|
}
|
|
|
|
kauth_cred_t
|
|
current_cached_proc_cred(proc_t p)
|
|
{
|
|
thread_ro_t tro = current_thread_ro();
|
|
|
|
if (tro->tro_proc != p && p != PROC_NULL) {
|
|
current_cached_proc_cred_panic(p);
|
|
}
|
|
return tro->tro_realcred;
|
|
}
|
|
|
|
intptr_t
|
|
current_cached_proc_label_get(int slot)
|
|
{
|
|
#if CONFIG_MACF
|
|
return mac_label_get(current_cached_proc_cred(PROC_NULL)->cr_label, slot);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
kauth_cred_t
|
|
current_cached_proc_cred_ref(proc_t p)
|
|
{
|
|
kauth_cred_t cred = current_cached_proc_cred(p);
|
|
|
|
kauth_cred_ref(cred);
|
|
return cred;
|
|
}
|
|
|
|
__attribute__((noinline))
|
|
static void
|
|
kauth_cred_thread_update_slow(thread_ro_t tro, proc_t proc)
|
|
{
|
|
struct ucred *cred = kauth_cred_proc_ref(proc);
|
|
struct thread_ro_creds my_creds = tro->tro_creds;
|
|
|
|
if (my_creds.tro_realcred != cred) {
|
|
if (my_creds.tro_realcred == my_creds.tro_cred) {
|
|
kauth_cred_set(&my_creds.tro_cred, cred);
|
|
}
|
|
kauth_cred_set(&my_creds.tro_realcred, cred);
|
|
zalloc_ro_update_field(ZONE_ID_THREAD_RO,
|
|
tro, tro_creds, &my_creds);
|
|
}
|
|
kauth_cred_unref(&cred);
|
|
}
|
|
|
|
/*
|
|
* current_cached_proc_cred_update
|
|
*
|
|
* 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.
|
|
*/
|
|
__attribute__((always_inline))
|
|
void
|
|
current_cached_proc_cred_update(void)
|
|
{
|
|
thread_ro_t tro = current_thread_ro();
|
|
proc_t proc = tro->tro_proc;
|
|
|
|
if (__improbable(tro->tro_task != kernel_task &&
|
|
tro->tro_realcred != proc_ucred_unsafe(proc))) {
|
|
kauth_cred_thread_update_slow(tro, proc);
|
|
}
|
|
}
|
|
|
|
kauth_cred_t
|
|
kauth_cred_get_with_ref(void)
|
|
{
|
|
struct ucred *ucred = kauth_cred_get();
|
|
kauth_cred_ref(ucred);
|
|
return ucred;
|
|
}
|
|
|
|
__abortlike
|
|
static void
|
|
kauth_cred_verify_panic(kauth_cred_t cred, struct ucred_rw *cred_rw)
|
|
{
|
|
panic("kauth_cred_t backref mismatch: cred:%p cred->cr_rw:%p "
|
|
"cred_rw:%p", cred, cred->cr_rw, cred_rw);
|
|
}
|
|
|
|
__pure2
|
|
static struct ucred_rw *
|
|
kauth_cred_rw(kauth_cred_t cred)
|
|
{
|
|
struct ucred_rw *rw = kauth_cred_require(cred)->cr_rw;
|
|
|
|
if (__improbable(rw->crw_cred != cred)) {
|
|
kauth_cred_verify_panic(cred, rw);
|
|
}
|
|
|
|
return rw;
|
|
}
|
|
|
|
|
|
kauth_cred_t
|
|
kauth_cred_proc_ref(proc_t procp)
|
|
{
|
|
kauth_cred_t cred;
|
|
|
|
smr_proc_task_enter();
|
|
cred = proc_ucred_smr(procp);
|
|
if (!ucred_rw_tryref(kauth_cred_rw(cred))) {
|
|
cred = NOCRED;
|
|
}
|
|
smr_proc_task_leave();
|
|
|
|
if (__improbable(cred == NOCRED)) {
|
|
proc_ucred_lock(procp);
|
|
cred = proc_ucred_locked(procp);
|
|
kauth_cred_ref(cred);
|
|
proc_ucred_unlock(procp);
|
|
}
|
|
return cred;
|
|
}
|
|
|
|
static kauth_cred_t
|
|
__kauth_cred_proc_ref_for_pidversion_slow(pid_t pid, uint32_t pidvers, bool dovers)
|
|
{
|
|
kauth_cred_t cred = NOCRED;
|
|
proc_t procp;
|
|
|
|
procp = proc_find(pid);
|
|
if (procp == PROC_NULL) {
|
|
return NOCRED;
|
|
}
|
|
|
|
if (dovers && proc_get_ro(procp)->p_idversion != pidvers) {
|
|
proc_rele(procp);
|
|
return NOCRED;
|
|
}
|
|
|
|
cred = kauth_cred_proc_ref(procp);
|
|
proc_rele(procp);
|
|
|
|
return cred;
|
|
}
|
|
|
|
static inline kauth_cred_t
|
|
__kauth_cred_proc_ref_for_pidversion(pid_t pid, uint32_t pidvers, bool dovers)
|
|
{
|
|
kauth_cred_t cred = NOCRED;
|
|
struct proc_ro *pro;
|
|
proc_t procp;
|
|
int err;
|
|
|
|
smr_proc_task_enter();
|
|
procp = proc_find_noref_smr(pid);
|
|
if (procp == PROC_NULL) {
|
|
err = ESRCH;
|
|
} else {
|
|
pro = proc_get_ro(procp);
|
|
cred = proc_ucred_smr(procp);
|
|
if (dovers && pro->p_idversion != pidvers) {
|
|
err = ESRCH;
|
|
} else if (!ucred_rw_tryref(kauth_cred_rw(cred))) {
|
|
err = EAGAIN;
|
|
} else {
|
|
err = 0;
|
|
}
|
|
}
|
|
smr_proc_task_leave();
|
|
|
|
if (__probable(err == 0)) {
|
|
return cred;
|
|
}
|
|
|
|
if (err == EAGAIN) {
|
|
return __kauth_cred_proc_ref_for_pidversion_slow(pid, pidvers, dovers);
|
|
}
|
|
|
|
return NOCRED;
|
|
}
|
|
|
|
kauth_cred_t
|
|
kauth_cred_proc_ref_for_pid(pid_t pid)
|
|
{
|
|
return __kauth_cred_proc_ref_for_pidversion(pid, 0, false);
|
|
}
|
|
|
|
kauth_cred_t
|
|
kauth_cred_proc_ref_for_pidversion(pid_t pid, uint32_t pidvers)
|
|
{
|
|
return __kauth_cred_proc_ref_for_pidversion(pid, pidvers, true);
|
|
}
|
|
|
|
/*
|
|
* kauth_cred_alloc
|
|
*
|
|
* Description: Create a deduplicated credential optionally derived
|
|
* from a parent credential, according to the specified template.
|
|
*
|
|
* Parameters: parent_cred the parent cred the model is
|
|
* derived from (or NOCRED for
|
|
* a creation)
|
|
*
|
|
* model_cred the (mutable) template of the
|
|
* cred to add to the hash table.
|
|
*
|
|
* Returns: (kauth_thread_t) The inserted cred, or the
|
|
* collision that was found.
|
|
*/
|
|
static kauth_cred_t
|
|
kauth_cred_alloc(kauth_cred_t parent_cred, kauth_cred_t model_cred)
|
|
{
|
|
struct ucred_rw *found_rw;
|
|
struct ucred_rw *new_rw;
|
|
struct ucred *newcred;
|
|
|
|
/*
|
|
* Step 1: find if there's a duplicate entry
|
|
*/
|
|
|
|
found_rw = smr_shash_get(&kauth_cred_hash, kauth_cred_key(model_cred),
|
|
&kauth_cred_traits);
|
|
|
|
if (found_rw) {
|
|
/* found a duplicate, free the label if the model owned it */
|
|
#if CONFIG_MACF
|
|
if (!parent_cred || model_cred->cr_label != parent_cred->cr_label) {
|
|
mac_cred_label_destroy(model_cred);
|
|
}
|
|
#endif
|
|
|
|
/* smr_hash_get() already did a kauth_cred_ro() */
|
|
return found_rw->crw_cred;
|
|
}
|
|
|
|
/*
|
|
* Step 2: create a fresh new kauth_cred.
|
|
*
|
|
* give it ownership of the label and audit session,
|
|
* if it doesn't have it already.
|
|
*/
|
|
#if CONFIG_MACF
|
|
if (parent_cred && model_cred->cr_label == parent_cred->cr_label) {
|
|
mac_cred_label_init(model_cred);
|
|
mac_cred_label_associate(parent_cred, model_cred);
|
|
}
|
|
mac_cred_label_seal(model_cred);
|
|
#else
|
|
(void)parent_cred;
|
|
#endif
|
|
AUDIT_SESSION_REF(model_cred);
|
|
|
|
new_rw = zalloc_flags(ucred_rw_zone, Z_WAITOK | Z_ZERO | Z_NOFAIL);
|
|
os_ref_init_raw(&new_rw->crw_weak_ref, &ucred_ref_grp);
|
|
|
|
model_cred->cr_rw = new_rw;
|
|
model_cred->cr_unused = NULL;
|
|
model_cred->cr_ref = 0;
|
|
|
|
newcred = zalloc_ro(ZONE_ID_KAUTH_CRED, Z_WAITOK | Z_ZERO | Z_NOFAIL);
|
|
new_rw->crw_cred = newcred;
|
|
|
|
#if HAS_APPLE_PAC
|
|
{
|
|
void *naked_ptr = model_cred->cr_label;
|
|
void *signed_ptr;
|
|
signed_ptr = ptrauth_sign_unauthenticated(naked_ptr,
|
|
ptrauth_key_process_independent_data,
|
|
ptrauth_blend_discriminator(&newcred->cr_label,
|
|
OS_PTRAUTH_DISCRIMINATOR("ucred.cr_label")));
|
|
memcpy((void *)&model_cred->cr_label, &signed_ptr, sizeof(void *));
|
|
}
|
|
#endif
|
|
|
|
zalloc_ro_update_elem(ZONE_ID_KAUTH_CRED, newcred, model_cred);
|
|
|
|
/*
|
|
* Step 3: try to insert in the hash table,
|
|
* and deal someone else racing us.
|
|
*/
|
|
found_rw = smr_shash_get_or_insert(&kauth_cred_hash,
|
|
kauth_cred_key(newcred), &new_rw->crw_link, &kauth_cred_traits);
|
|
if (__probable(!found_rw)) {
|
|
return newcred;
|
|
}
|
|
|
|
#if CONFIG_MACF
|
|
mac_cred_label_free(newcred->cr_label);
|
|
#endif
|
|
AUDIT_SESSION_UNREF(newcred);
|
|
zfree(ucred_rw_zone, new_rw);
|
|
zfree_ro(ZONE_ID_KAUTH_CRED, newcred);
|
|
|
|
/* smr_shash_get_or_insert() already did a kauth_cred_ro() */
|
|
return found_rw->crw_cred;
|
|
}
|
|
|
|
kauth_cred_t
|
|
kauth_cred_require(kauth_cred_t cred)
|
|
{
|
|
zone_require_ro(ZONE_ID_KAUTH_CRED, sizeof(struct ucred), cred);
|
|
return cred;
|
|
}
|
|
|
|
__abortlike
|
|
static void
|
|
kauth_cred_rw_verify_panic(const struct ucred_rw *cred_rw, kauth_cred_t cred)
|
|
{
|
|
panic("ucred_rw backref mismatch: cred_rw:%p cred_rw->crw_cred:%p "
|
|
"cred: %p", cred_rw, cred_rw->crw_cred, cred);
|
|
}
|
|
|
|
__pure2
|
|
static kauth_cred_t
|
|
kauth_cred_ro(const struct ucred_rw *cred_rw)
|
|
{
|
|
kauth_cred_t cred = kauth_cred_require(cred_rw->crw_cred);
|
|
|
|
if (__improbable(cred->cr_rw != cred_rw)) {
|
|
kauth_cred_rw_verify_panic(cred_rw, cred);
|
|
}
|
|
|
|
return cred;
|
|
}
|
|
|
|
__attribute__((noinline))
|
|
static void
|
|
kauth_cred_free(struct smr_node *node)
|
|
{
|
|
struct ucred_rw *rw = __container_of(node, struct ucred_rw, crw_node);
|
|
struct ucred *cred = kauth_cred_ro(rw);
|
|
|
|
if (cred == vfs_context0.vc_ucred) {
|
|
panic("Over-release of the kernel credentials");
|
|
}
|
|
if (os_atomic_load(&cred->cr_ref, relaxed) != 0) {
|
|
panic("%s: freeing credential with active long-term ref", __func__);
|
|
}
|
|
|
|
#if CONFIG_MACF
|
|
mac_cred_label_free(cred->cr_label);
|
|
#endif
|
|
|
|
zfree(ucred_rw_zone, rw);
|
|
zfree_ro(ZONE_ID_KAUTH_CRED, cred);
|
|
}
|
|
|
|
__attribute__((noinline))
|
|
static void
|
|
kauth_cred_retire(struct ucred_rw *rw, struct ucred *cred __unused)
|
|
{
|
|
vm_size_t size = sizeof(struct ucred_rw) +
|
|
#if CONFIG_MACF
|
|
sizeof(struct label) +
|
|
#endif
|
|
sizeof(struct ucred);
|
|
|
|
smr_shash_remove(&kauth_cred_hash, &rw->crw_link, &kauth_cred_traits);
|
|
AUDIT_SESSION_UNREF(cred); /* uses SMR, safe to do immediately */
|
|
smr_call(&smr_proc_task, &rw->crw_node, size, kauth_cred_free);
|
|
}
|
|
|
|
static kauth_cred_t
|
|
posix_cred_create_internal(posix_cred_t pcred, struct au_session audit)
|
|
{
|
|
struct ucred model = {
|
|
.cr_posix = *pcred,
|
|
.cr_label = NULL,
|
|
.cr_audit = audit,
|
|
};
|
|
int is_member = 0;
|
|
|
|
if (pcred->cr_ngroups < 1) {
|
|
return NOCRED;
|
|
}
|
|
|
|
if (pcred->cr_flags & CRF_NOMEMBERD) {
|
|
pcred->cr_gmuid = KAUTH_UID_NONE;
|
|
} else {
|
|
/*
|
|
* If the template credential is not opting out of external
|
|
* group membership resolution, then we need to check that
|
|
* the UID we will be using is resolvable by the external
|
|
* resolver. If it's not, then we opt it out anyway, since
|
|
* all future external resolution requests will be failing
|
|
* anyway, and potentially taking a long time to do it. We
|
|
* use gid 0 because we always know it will exist and not
|
|
* trigger additional lookups. This is OK, because we end up
|
|
* precatching the information here as a result.
|
|
*/
|
|
if (!kauth_cred_ismember_gid(&model, 0, &is_member)) {
|
|
/*
|
|
* It's a recognized value; we don't really care about
|
|
* the answer, so long as it's something the external
|
|
* resolver could have vended.
|
|
*/
|
|
pcred->cr_gmuid = pcred->cr_uid;
|
|
} else {
|
|
/*
|
|
* It's not something the external resolver could
|
|
* have vended, so we don't want to ask it more
|
|
* questions about the credential in the future. This
|
|
* speeds up future lookups, as long as the caller
|
|
* caches results; otherwise, it the same recurring
|
|
* cost. Since most credentials are used multiple
|
|
* times, we still get some performance win from this.
|
|
*/
|
|
pcred->cr_gmuid = KAUTH_UID_NONE;
|
|
pcred->cr_flags |= CRF_NOMEMBERD;
|
|
}
|
|
}
|
|
|
|
mac_cred_label_init(&model);
|
|
return kauth_cred_alloc(NOCRED, &model);
|
|
}
|
|
|
|
/*
|
|
* kauth_cred_create
|
|
*
|
|
* Description: Obsolete function that is unfortunately exported,
|
|
* but that no one should use directly.
|
|
*
|
|
* 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.
|
|
*/
|
|
kauth_cred_t
|
|
kauth_cred_create(kauth_cred_t cred)
|
|
{
|
|
return posix_cred_create_internal(&cred->cr_posix, cred->cr_audit);
|
|
}
|
|
|
|
kauth_cred_t
|
|
kauth_cred_derive(kauth_cred_t cred, kauth_cred_derive_t derive_fn)
|
|
{
|
|
struct ucred model = {
|
|
.cr_posix = cred->cr_posix,
|
|
.cr_label = cred->cr_label,
|
|
.cr_audit = cred->cr_audit,
|
|
};
|
|
|
|
if (derive_fn(cred, &model)) {
|
|
return kauth_cred_alloc(cred, &model);
|
|
}
|
|
|
|
kauth_cred_ref(cred);
|
|
return cred;
|
|
}
|
|
|
|
|
|
bool
|
|
kauth_cred_proc_update(
|
|
proc_t p,
|
|
proc_settoken_t action,
|
|
kauth_cred_derive_t derive_fn)
|
|
{
|
|
kauth_cred_t cur_cred, free_cred, new_cred;
|
|
|
|
cur_cred = kauth_cred_proc_ref(p);
|
|
|
|
for (;;) {
|
|
new_cred = kauth_cred_derive(cur_cred, derive_fn);
|
|
if (new_cred == cur_cred) {
|
|
if (action == PROC_SETTOKEN_ALWAYS) {
|
|
set_security_token(p, cur_cred);
|
|
}
|
|
kauth_cred_unref(&new_cred);
|
|
kauth_cred_unref(&cur_cred);
|
|
return false;
|
|
}
|
|
|
|
proc_ucred_lock(p);
|
|
if (__probable(proc_ucred_locked(p) == cur_cred)) {
|
|
kauth_cred_ref(new_cred);
|
|
kauth_cred_hold(new_cred);
|
|
|
|
zalloc_ro_mut(ZONE_ID_PROC_RO, proc_get_ro(p),
|
|
offsetof(struct proc_ro, p_ucred),
|
|
&new_cred, sizeof(struct ucred *));
|
|
|
|
kauth_cred_drop(cur_cred);
|
|
ucred_rw_unref_live(cur_cred->cr_rw);
|
|
|
|
proc_update_creds_onproc(p, new_cred);
|
|
proc_ucred_unlock(p);
|
|
|
|
if (action == PROC_SETTOKEN_SETUGID) {
|
|
OSBitOrAtomic(P_SUGID, &p->p_flag);
|
|
}
|
|
if (action != PROC_SETTOKEN_NONE) {
|
|
set_security_token(p, new_cred);
|
|
}
|
|
|
|
kauth_cred_unref(&new_cred);
|
|
kauth_cred_unref(&cur_cred);
|
|
return true;
|
|
}
|
|
|
|
free_cred = cur_cred;
|
|
cur_cred = proc_ucred_locked(p);
|
|
kauth_cred_ref(cur_cred);
|
|
proc_ucred_unlock(p);
|
|
|
|
kauth_cred_unref(&free_cred);
|
|
kauth_cred_unref(&new_cred);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_model_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: model The model 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.
|
|
*/
|
|
bool
|
|
kauth_cred_model_setresuid(
|
|
kauth_cred_t model,
|
|
uid_t ruid,
|
|
uid_t euid,
|
|
uid_t svuid,
|
|
uid_t gmuid)
|
|
{
|
|
posix_cred_t pcred = posix_cred_get(model);
|
|
bool updated = false;
|
|
|
|
/*
|
|
* We don't need to do anything if the UIDs we are changing are
|
|
* already the same as the UIDs passed in
|
|
*/
|
|
if (euid != KAUTH_UID_NONE && pcred->cr_uid != euid) {
|
|
pcred->cr_uid = euid;
|
|
updated = true;
|
|
}
|
|
|
|
if (ruid != KAUTH_UID_NONE && pcred->cr_ruid != ruid) {
|
|
pcred->cr_ruid = ruid;
|
|
updated = true;
|
|
}
|
|
|
|
if (svuid != KAUTH_UID_NONE && pcred->cr_svuid != svuid) {
|
|
pcred->cr_svuid = svuid;
|
|
updated = true;
|
|
}
|
|
|
|
if (pcred->cr_gmuid != gmuid) {
|
|
/*
|
|
* If we are setting the gmuid to KAUTH_UID_NONE, then we want
|
|
* to opt out of participation in external group resolution,
|
|
* unless we explicitly opt back in later.
|
|
*/
|
|
pcred->cr_gmuid = gmuid;
|
|
if (gmuid == KAUTH_UID_NONE) {
|
|
pcred->cr_flags |= CRF_NOMEMBERD;
|
|
}
|
|
updated = true;
|
|
}
|
|
|
|
return updated;
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_model_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: model The model credential
|
|
* rgid The new real GID
|
|
* egid The new effective GID
|
|
* svgid The new saved GID
|
|
*
|
|
* Returns: (kauth_cred_t) The updated credential
|
|
*/
|
|
bool
|
|
kauth_cred_model_setresgid(
|
|
kauth_cred_t model,
|
|
gid_t rgid,
|
|
gid_t egid,
|
|
gid_t svgid)
|
|
{
|
|
posix_cred_t pcred = posix_cred_get(model);
|
|
bool updated = false;
|
|
|
|
if (egid != KAUTH_GID_NONE && pcred->cr_gid != egid) {
|
|
if (kauth_cred_change_egid(model, egid)) {
|
|
pcred->cr_flags |= CRF_NOMEMBERD;
|
|
pcred->cr_gmuid = KAUTH_UID_NONE;
|
|
}
|
|
updated = true;
|
|
}
|
|
|
|
if (rgid != KAUTH_GID_NONE && pcred->cr_rgid != rgid) {
|
|
pcred->cr_rgid = rgid;
|
|
updated = true;
|
|
}
|
|
if (svgid != KAUTH_GID_NONE && pcred->cr_svgid != svgid) {
|
|
pcred->cr_svgid = svgid;
|
|
updated = true;
|
|
}
|
|
|
|
return updated;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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_model_setgroups
|
|
*
|
|
* Description: Update the given credential using the provide supplementary
|
|
* group list and group membership UID
|
|
*
|
|
* Parameters: cred The model credential
|
|
* groups Pointer to gid_t array which
|
|
* contains the new group list
|
|
* groupcount The count 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.
|
|
*
|
|
* XXX: Changes are determined in ordinal order - if the caller passes
|
|
* in the same groups list that is already present in the
|
|
* credential, but the members are in a different order, even if
|
|
* the EGID is not modified (i.e. cr_groups[0] is the same), it
|
|
* 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.
|
|
*/
|
|
bool
|
|
kauth_cred_model_setgroups(
|
|
kauth_cred_t model,
|
|
gid_t *groups,
|
|
size_t groupcount,
|
|
uid_t gmuid)
|
|
{
|
|
posix_cred_t pcred = posix_cred_get(model);
|
|
|
|
assert(groupcount <= NGROUPS);
|
|
groupcount = MIN(groupcount, NGROUPS);
|
|
|
|
/*
|
|
* We don't need to do anything if the given list of groups does not
|
|
* change.
|
|
*/
|
|
if (pcred->cr_gmuid == gmuid &&
|
|
pcred->cr_ngroups == groupcount &&
|
|
memcmp(pcred->cr_groups, groups, groupcount * sizeof(gid_t)) == 0) {
|
|
return false;
|
|
}
|
|
|
|
pcred->cr_gmuid = gmuid;
|
|
pcred->cr_ngroups = (short)groupcount;
|
|
memcpy(pcred->cr_groups, groups, groupcount * sizeof(gid_t));
|
|
if (gmuid == KAUTH_UID_NONE) {
|
|
pcred->cr_flags |= CRF_NOMEMBERD;
|
|
} else {
|
|
pcred->cr_flags &= ~CRF_NOMEMBERD;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Notes: The return value exists to account for the possibility of a
|
|
* kauth_cred_t without a POSIX label. This will be the case in
|
|
* the future (see posix_cred_get() below, for more details).
|
|
*/
|
|
#if CONFIG_EXT_RESOLVER
|
|
int kauth_external_supplementary_groups_supported = 1;
|
|
|
|
SYSCTL_INT(_kern, OID_AUTO, ds_supgroups_supported, CTLFLAG_RW | CTLFLAG_LOCKED, &kauth_external_supplementary_groups_supported, 0, "");
|
|
#endif
|
|
|
|
int
|
|
kauth_cred_getgroups(kauth_cred_t cred, gid_t *grouplist, size_t *countp)
|
|
{
|
|
size_t limit = NGROUPS;
|
|
posix_cred_t pcred;
|
|
|
|
if (cred == NULL) {
|
|
KAUTH_DEBUG("kauth_cred_getgroups got NULL credential");
|
|
return EINVAL;
|
|
}
|
|
|
|
if (grouplist == NULL) {
|
|
KAUTH_DEBUG("kauth_cred_getgroups got NULL group list");
|
|
return EINVAL;
|
|
}
|
|
|
|
pcred = posix_cred_get(cred);
|
|
|
|
#if CONFIG_EXT_RESOLVER
|
|
/*
|
|
* If we've not opted out of using the resolver, then convert the cred to a list
|
|
* of supplemental groups. We do this only if there has been a resolver to talk to,
|
|
* since we may be too early in boot, or in an environment that isn't using DS.
|
|
*/
|
|
if (kauth_identitysvc_has_registered && kauth_external_supplementary_groups_supported && (pcred->cr_flags & CRF_NOMEMBERD) == 0) {
|
|
uid_t uid = kauth_cred_getuid(cred);
|
|
int err;
|
|
|
|
err = kauth_cred_uid2groups(&uid, grouplist, countp);
|
|
if (!err) {
|
|
return 0;
|
|
}
|
|
|
|
/* On error just fall through */
|
|
KAUTH_DEBUG("kauth_cred_getgroups failed %d\n", err);
|
|
}
|
|
#endif /* CONFIG_EXT_RESOLVER */
|
|
|
|
/*
|
|
* If they just want a copy of the groups list, they may not care
|
|
* about the actual count. If they specify an input count, however,
|
|
* treat it as an indicator of the buffer size available in grouplist,
|
|
* and limit the returned list to that size.
|
|
*/
|
|
if (countp) {
|
|
limit = MIN(*countp, pcred->cr_ngroups);
|
|
*countp = limit;
|
|
}
|
|
|
|
memcpy(grouplist, pcred->cr_groups, sizeof(gid_t) * limit);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_model_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: model The model 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.
|
|
*/
|
|
bool
|
|
kauth_cred_model_setuidgid(kauth_cred_t model, uid_t uid, gid_t gid)
|
|
{
|
|
struct posix_cred pcred = {
|
|
.cr_uid = uid,
|
|
.cr_ruid = uid,
|
|
.cr_svuid = uid,
|
|
|
|
.cr_ngroups = 1,
|
|
.cr_gid = gid,
|
|
.cr_rgid = gid,
|
|
.cr_svgid = gid,
|
|
|
|
.cr_flags = model->cr_posix.cr_flags,
|
|
};
|
|
|
|
/* inherit the opt-out of memberd */
|
|
if (pcred.cr_flags & CRF_NOMEMBERD) {
|
|
pcred.cr_gmuid = KAUTH_UID_NONE;
|
|
} else {
|
|
pcred.cr_gmuid = uid;
|
|
}
|
|
|
|
if (memcmp(&model->cr_posix, &pcred, sizeof(struct posix_cred)) != 0) {
|
|
model->cr_posix = pcred;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* kauth_cred_model_setauditinfo
|
|
*
|
|
* Description: Update the given credential using the given au_session_t.
|
|
*
|
|
* Parameters: model The model credential
|
|
* auditinfo_p Pointer to ne audit information
|
|
*
|
|
* Returns: (kauth_cred_t) The updated credential
|
|
*/
|
|
bool
|
|
kauth_cred_model_setauditinfo(kauth_cred_t model, au_session_t *auditinfo_p)
|
|
{
|
|
if (memcmp(&model->cr_audit, auditinfo_p, sizeof(model->cr_audit)) != 0) {
|
|
model->cr_audit = *auditinfo_p;
|
|
return true;
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
#if CONFIG_MACF
|
|
kauth_cred_t
|
|
kauth_cred_label_update(kauth_cred_t cred, struct label *label)
|
|
{
|
|
kauth_cred_t new_cred;
|
|
|
|
new_cred = kauth_cred_derive(cred,
|
|
^bool (kauth_cred_t parent, kauth_cred_t model) {
|
|
mac_cred_label_init(model);
|
|
mac_cred_label_associate(parent, model);
|
|
mac_cred_label_update(model, label);
|
|
return true;
|
|
});
|
|
|
|
kauth_cred_unref(&cred);
|
|
return new_cred;
|
|
}
|
|
|
|
int
|
|
kauth_proc_label_update(struct proc *p, struct label *label)
|
|
{
|
|
kauth_cred_proc_update(p, PROC_SETTOKEN_NONE,
|
|
^bool (kauth_cred_t parent, kauth_cred_t model) {
|
|
mac_cred_label_init(model);
|
|
mac_cred_label_associate(parent, model);
|
|
mac_cred_label_update(model, label);
|
|
return true;
|
|
});
|
|
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
|
|
* lupdateerror The error place holder for MAC label authority
|
|
* to update about possible termination
|
|
*
|
|
* Returns: 0 Label update did not make credential
|
|
* disjoint
|
|
* 1 Label update caused credential to be
|
|
* disjoint
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
void
|
|
kauth_proc_label_update_execve(struct proc *p, vfs_context_t ctx,
|
|
struct vnode *vp, off_t offset, struct vnode *scriptvp, struct label *scriptl,
|
|
struct label *execl, unsigned int *csflags, void *macextensions, int *disjoint, int *update_return)
|
|
{
|
|
kauth_cred_proc_update(p, PROC_SETTOKEN_NONE,
|
|
^bool (kauth_cred_t parent, kauth_cred_t model) {
|
|
mac_cred_label_init(model);
|
|
mac_cred_label_associate(parent, model);
|
|
mac_cred_label_update_execve(ctx, model,
|
|
vp, offset, scriptvp, scriptl, execl, csflags,
|
|
macextensions, disjoint, update_return);
|
|
return true;
|
|
});
|
|
}
|
|
#else
|
|
kauth_cred_t
|
|
kauth_cred_label_update(__unused kauth_cred_t cred, __unused struct label *label)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
int
|
|
kauth_proc_label_update(__unused struct proc *p, __unused struct label *label)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
|
|
void
|
|
kauth_cred_ref(kauth_cred_t cred)
|
|
{
|
|
ucred_rw_ref(kauth_cred_rw(cred));
|
|
}
|
|
|
|
void
|
|
(kauth_cred_unref)(kauth_cred_t * credp)
|
|
{
|
|
struct ucred *cred = *credp;
|
|
struct ucred_rw *rw = kauth_cred_rw(cred);
|
|
|
|
*credp = NOCRED;
|
|
|
|
if (ucred_rw_unref(rw) == 0) {
|
|
kauth_cred_retire(rw, cred);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* kauth_cred_set
|
|
*
|
|
* Description: Store a long-term credential reference to a credential pointer,
|
|
* dropping the long-term reference on any previous credential held
|
|
* at the address.
|
|
*
|
|
* Parameters: credp Pointer to the credential
|
|
* storage field. If *credp points
|
|
* to a valid credential before
|
|
* this call, its long-term
|
|
* reference will be dropped.
|
|
* new_cred The new credential to take a
|
|
* long-term reference to and
|
|
* assign to *credp. May be
|
|
* NOCRED.
|
|
*
|
|
* Returns: (void)
|
|
*
|
|
* Notes: Taking/dropping a long-term reference is costly in terms of
|
|
* performance.
|
|
*/
|
|
void
|
|
(kauth_cred_set)(kauth_cred_t * credp, kauth_cred_t new_cred)
|
|
{
|
|
kauth_cred_t old_cred = *credp;
|
|
|
|
if (old_cred != new_cred) {
|
|
if (IS_VALID_CRED(new_cred)) {
|
|
kauth_cred_ref(new_cred);
|
|
kauth_cred_hold(new_cred);
|
|
}
|
|
|
|
*credp = new_cred;
|
|
|
|
if (IS_VALID_CRED(old_cred)) {
|
|
kauth_cred_drop(old_cred);
|
|
kauth_cred_unref(&old_cred);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 kauth_cred_unref() KPI.
|
|
*/
|
|
kauth_cred_t
|
|
kauth_cred_copy_real(kauth_cred_t cred)
|
|
{
|
|
kauth_cred_derive_t fn = ^bool (kauth_cred_t parent __unused, kauth_cred_t model) {
|
|
posix_cred_t pcred = posix_cred_get(model);
|
|
|
|
/* if the credential is already 'real', just take a reference */
|
|
if ((pcred->cr_ruid == pcred->cr_uid) &&
|
|
(pcred->cr_rgid == pcred->cr_gid)) {
|
|
return false;
|
|
}
|
|
|
|
pcred->cr_uid = pcred->cr_ruid;
|
|
/* displacing a supplementary group opts us out of memberd */
|
|
if (kauth_cred_change_egid(model, pcred->cr_rgid)) {
|
|
pcred->cr_flags |= CRF_NOMEMBERD;
|
|
pcred->cr_gmuid = KAUTH_UID_NONE;
|
|
}
|
|
/*
|
|
* If the cred is not opted out, make sure we are using the r/euid
|
|
* for group checks
|
|
*/
|
|
if (pcred->cr_gmuid != KAUTH_UID_NONE) {
|
|
pcred->cr_gmuid = pcred->cr_ruid;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
return kauth_cred_derive(cred, fn);
|
|
}
|
|
|
|
/*
|
|
* Hash table traits methods
|
|
*/
|
|
static smrh_key_t
|
|
kauth_cred_key(kauth_cred_t cred)
|
|
{
|
|
return (smrh_key_t){ .smrk_opaque = cred };
|
|
}
|
|
|
|
static uint32_t
|
|
kauth_cred_ro_hash(const struct ucred *cred, uint32_t seed)
|
|
{
|
|
uint32_t hash = seed;
|
|
|
|
hash = os_hash_jenkins_update(&cred->cr_posix,
|
|
sizeof(struct posix_cred), hash);
|
|
hash = os_hash_jenkins_update(&cred->cr_audit,
|
|
sizeof(struct au_session), hash);
|
|
#if CONFIG_MACF
|
|
if (cred->cr_posix.cr_flags & CRF_MAC_ENFORCE) {
|
|
hash = mac_cred_label_hash_update(cred->cr_label, hash);
|
|
}
|
|
#endif /* CONFIG_MACF */
|
|
|
|
return hash;
|
|
}
|
|
static uint32_t
|
|
kauth_cred_key_hash(smrh_key_t key, uint32_t seed)
|
|
{
|
|
return kauth_cred_ro_hash(key.smrk_opaque, seed);
|
|
}
|
|
static uint32_t
|
|
kauth_cred_obj_hash(const struct smrq_slink *link, uint32_t seed)
|
|
{
|
|
const struct ucred_rw *rw;
|
|
|
|
rw = __container_of(link, struct ucred_rw, crw_link);
|
|
/* this is used during rehash, re-auth the objects as we do */
|
|
return kauth_cred_ro_hash(kauth_cred_ro(rw), seed);
|
|
}
|
|
|
|
static bool
|
|
kauth_cred_key_equ(smrh_key_t k1, smrh_key_t k2)
|
|
{
|
|
const struct ucred *cred1 = k1.smrk_opaque;
|
|
const struct ucred *cred2 = k2.smrk_opaque;
|
|
const struct posix_cred *pcred1 = &cred1->cr_posix;
|
|
const struct posix_cred *pcred2 = &cred2->cr_posix;
|
|
|
|
/*
|
|
* don't worry about the label unless the flags in
|
|
* either credential tell us to.
|
|
*/
|
|
if (memcmp(pcred1, pcred2, sizeof(*pcred1))) {
|
|
return false;
|
|
}
|
|
if (memcmp(&cred1->cr_audit, &cred2->cr_audit, sizeof(cred1->cr_audit))) {
|
|
return false;
|
|
}
|
|
#if CONFIG_MACF
|
|
/* Note: we know the flags are equal, so we only need to test one */
|
|
if (pcred1->cr_flags & CRF_MAC_ENFORCE) {
|
|
if (!mac_cred_label_is_equal(cred1->cr_label, cred2->cr_label)) {
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
static bool
|
|
kauth_cred_obj_equ(const struct smrq_slink *link, smrh_key_t key)
|
|
{
|
|
const struct ucred_rw *rw;
|
|
|
|
rw = __container_of(link, struct ucred_rw, crw_link);
|
|
/* only do the kauth_cred_ro() check in try_get() */
|
|
return kauth_cred_key_equ(kauth_cred_key(rw->crw_cred), key);
|
|
}
|
|
|
|
static bool
|
|
kauth_cred_obj_try_get(void *obj)
|
|
{
|
|
struct ucred_rw *rw = obj;
|
|
kauth_cred_t cred = kauth_cred_require(rw->crw_cred);
|
|
|
|
if (__improbable(cred->cr_rw != rw)) {
|
|
kauth_cred_rw_verify_panic(rw, cred);
|
|
}
|
|
|
|
return ucred_rw_tryref(rw);
|
|
}
|
|
|
|
/*
|
|
**********************************************************************
|
|
* The following routines will be moved to a policy_posix.c module at
|
|
* some future point.
|
|
**********************************************************************
|
|
*/
|
|
|
|
/*
|
|
* posix_cred_create
|
|
*
|
|
* Description: Helper function to create a kauth_cred_t credential that is
|
|
* initally labelled with a specific POSIX credential label
|
|
*
|
|
* Parameters: pcred The posix_cred_t to use as the initial
|
|
* label value
|
|
*
|
|
* Returns: (kauth_cred_t) The credential that was found in the
|
|
* hash or creates
|
|
* NULL kauth_cred_make() failed, or there was
|
|
* no egid specified, or we failed to
|
|
* attach a label to the new credential
|
|
*
|
|
* 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.
|
|
*/
|
|
kauth_cred_t
|
|
posix_cred_create(posix_cred_t pcred)
|
|
{
|
|
struct au_session audit = {
|
|
.as_aia_p = audit_default_aia_p,
|
|
};
|
|
|
|
return posix_cred_create_internal(pcred, audit);
|
|
}
|
|
|
|
|
|
/*
|
|
* posix_cred_get
|
|
*
|
|
* Description: Given a kauth_cred_t, return the POSIX credential label, if
|
|
* any, which is associated with it.
|
|
*
|
|
* Parameters: cred The credential to obtain the label from
|
|
*
|
|
* Returns: posix_cred_t The POSIX credential label
|
|
*
|
|
* Notes: In the event that the policy_posix MACF module IS NOT loaded,
|
|
* this function will return a pointer to a posix_cred_t which
|
|
* GRANTS all access (effectively, a "root" credential). This is
|
|
* necessary to support legacy code which insists on tightly
|
|
* integrating POSIX credentials into its APIs, including, but
|
|
* not limited to, System V IPC mechanisms, POSIX IPC mechanisms,
|
|
* NFSv3, signals, dtrace, and a large number of kauth routines
|
|
* used to implement POSIX permissions related system calls.
|
|
*
|
|
* In the event that the policy_posix MACF module IS loaded, and
|
|
* there is no POSIX label on the kauth_cred_t credential, this
|
|
* function will return a pointer to a posix_cred_t which DENIES
|
|
* all access (effectively, a "deny rights granted by POSIX"
|
|
* credential). This is necessary to support the concept of a
|
|
* transiently loaded POSIX policy, or kauth_cred_t credentials
|
|
* which can not be used in conjunctions with POSIX permissions
|
|
* checks.
|
|
*
|
|
* This function currently returns the address of the cr_posix
|
|
* field of the supplied kauth_cred_t credential, and as such
|
|
* currently can not fail. In the future, this will not be the
|
|
* case.
|
|
*/
|
|
posix_cred_t
|
|
posix_cred_get(kauth_cred_t cred)
|
|
{
|
|
return &cred->cr_posix;
|
|
}
|
|
|
|
|
|
/*
|
|
* posix_cred_access
|
|
*
|
|
* Description: Perform a POSIX access check for a protected object
|
|
*
|
|
* Parameters: cred The credential to check
|
|
* object_uid The POSIX UID of the protected object
|
|
* object_gid The POSIX GID of the protected object
|
|
* object_mode The POSIX mode of the protected object
|
|
* mode_req The requested POSIX access rights
|
|
*
|
|
* Returns 0 Access is granted
|
|
* EACCES Access is denied
|
|
*
|
|
* Notes: This code optimizes the case where the world and group rights
|
|
* would both grant the requested rights to avoid making a group
|
|
* membership query. This is a big performance win in the case
|
|
* where this is true.
|
|
*/
|
|
int
|
|
posix_cred_access(kauth_cred_t cred, id_t object_uid, id_t object_gid, mode_t object_mode, mode_t mode_req)
|
|
{
|
|
int is_member;
|
|
mode_t mode_owner = (object_mode & S_IRWXU);
|
|
mode_t mode_group = (mode_t)((object_mode & S_IRWXG) << 3);
|
|
mode_t mode_world = (mode_t)((object_mode & S_IRWXO) << 6);
|
|
|
|
/*
|
|
* Check first for owner rights
|
|
*/
|
|
if (kauth_cred_getuid(cred) == object_uid && (mode_req & mode_owner) == mode_req) {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Combined group and world rights check, if we don't have owner rights
|
|
*
|
|
* OPTIMIZED: If group and world rights would grant the same bits, and
|
|
* they set of requested bits is in both, then we can simply check the
|
|
* world rights, avoiding a group membership check, which is expensive.
|
|
*/
|
|
if ((mode_req & mode_group & mode_world) == mode_req) {
|
|
return 0;
|
|
} else {
|
|
/*
|
|
* NON-OPTIMIZED: requires group membership check.
|
|
*/
|
|
if ((mode_req & mode_group) != mode_req) {
|
|
/*
|
|
* exclusion group : treat errors as "is a member"
|
|
*
|
|
* NON-OPTIMIZED: +group would deny; must check group
|
|
*/
|
|
if (!kauth_cred_ismember_gid(cred, object_gid, &is_member) && is_member) {
|
|
/*
|
|
* DENY: +group denies
|
|
*/
|
|
return EACCES;
|
|
} else {
|
|
if ((mode_req & mode_world) != mode_req) {
|
|
/*
|
|
* DENY: both -group & world would deny
|
|
*/
|
|
return EACCES;
|
|
} else {
|
|
/*
|
|
* ALLOW: allowed by -group and +world
|
|
*/
|
|
return 0;
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* inclusion group; treat errors as "not a member"
|
|
*
|
|
* NON-OPTIMIZED: +group allows, world denies; must
|
|
* check group
|
|
*/
|
|
if (!kauth_cred_ismember_gid(cred, object_gid, &is_member) && is_member) {
|
|
/*
|
|
* ALLOW: allowed by +group
|
|
*/
|
|
return 0;
|
|
} else {
|
|
if ((mode_req & mode_world) != mode_req) {
|
|
/*
|
|
* DENY: both -group & world would deny
|
|
*/
|
|
return EACCES;
|
|
} else {
|
|
/*
|
|
* ALLOW: allowed by -group and +world
|
|
*/
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|