--- /dev/null
+/*-
+ * Copyright (c) 2008-2009 Apple Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/kernel.h>
+#include <sys/event.h>
+#include <sys/kauth.h>
+#include <sys/queue.h>
+#include <sys/syscall.h>
+#include <sys/sysent.h>
+#include <sys/sysproto.h>
+#include <sys/systm.h>
+#include <sys/ucred.h>
+
+#include <libkern/OSAtomic.h>
+
+#include <bsm/audit.h>
+#include <security/audit/audit.h>
+#include <security/audit/audit_bsd.h>
+#include <security/audit/audit_private.h>
+
+#include <vm/vm_protos.h>
+#include <kern/audit_sessionport.h>
+
+kern_return_t ipc_object_copyin(ipc_space_t, mach_port_name_t,
+ mach_msg_type_name_t, ipc_port_t *);
+void ipc_port_release_send(ipc_port_t);
+
+/*
+ * The default auditinfo_addr entry for ucred.
+ */
+struct auditinfo_addr audit_default_aia = {
+ .ai_auid = AU_DEFAUDITID,
+ .ai_asid = AU_DEFAUDITSID,
+ .ai_termid = { .at_type = AU_IPv4, },
+};
+
+#if CONFIG_AUDIT
+
+/*
+ * Currently the hash table is a fixed size.
+ */
+#define HASH_TABLE_SIZE 97
+#define HASH_ASID(asid) (audit_session_hash(asid) % HASH_TABLE_SIZE)
+
+/*
+ * Audit Session Entry. This is treated as an object with public and private
+ * data. The se_auinfo field is the only information that is public and
+ * needs to be the first entry.
+ */
+struct au_sentry {
+ auditinfo_addr_t se_auinfo; /* Public audit session data. */
+#define se_asid se_auinfo.ai_asid
+#define se_auid se_auinfo.ai_auid
+#define se_mask se_auinfo.ai_mask
+#define se_termid se_auinfo.ai_termid
+#define se_flags se_auinfo.ai_flags
+
+ long se_refcnt; /* Reference count. */
+ long se_procnt; /* Processes in session. */
+ ipc_port_t se_port; /* Session port. */
+ struct klist se_klist; /* Knotes for session */
+ struct mtx se_klist_mtx; /* se_klist mutex */
+ LIST_ENTRY(au_sentry) se_link; /* Hash bucket link list (1) */
+};
+typedef struct au_sentry au_sentry_t;
+
+#define AU_SENTRY_PTR(aia_p) ((au_sentry_t *)(aia_p))
+
+static struct rwlock se_entry_lck; /* (1) lock for se_link above */
+
+LIST_HEAD(au_sentry_head, au_sentry);
+static struct au_sentry_head *au_sentry_bucket = NULL;
+
+/*
+ * Audit Propagation Knote List is a list of kevent knotes that are assosiated
+ * with an any ASID knote. If the any ASID gets modified or deleted these are
+ * modified or deleted as well.
+ */
+struct au_plist {
+ struct knote *pl_knote; /* ptr to per-session knote */
+ LIST_ENTRY(au_plist) pl_link; /* list link (2) */
+};
+typedef struct au_plist au_plist_t;
+
+struct au_plisthead {
+ struct rlck ph_rlck; /* (2) lock for pl_link list */
+ LIST_HEAD(au_plhead, au_plist) ph_head; /* list head */
+};
+typedef struct au_plisthead au_plisthead_t;
+
+#define EV_ANY_ASID EV_FLAG0
+
+MALLOC_DEFINE(M_AU_SESSION, "audit_session", "Audit session data");
+MALLOC_DEFINE(M_AU_EV_PLIST, "audit_ev_plist", "Audit session event plist");
+
+/*
+ * Kevent filters.
+ */
+static int audit_filt_sessionattach(struct knote *kn);
+static void audit_filt_sessiondetach(struct knote *kn);
+static void audit_filt_sessiontouch(struct knote *kn,
+ struct kevent64_s *kev, long type);
+static int audit_filt_session(struct knote *kn, long hint);
+
+static void audit_register_kevents(uint32_t asid, uint32_t auid);
+
+struct filterops audit_session_filtops = {
+ .f_attach = audit_filt_sessionattach,
+ .f_detach = audit_filt_sessiondetach,
+ .f_touch = audit_filt_sessiontouch,
+ .f_event = audit_filt_session,
+};
+
+/*
+ * The klist for consumers that are interested in any session (ASID). This list
+ * is not associated with any data structure but is used for registering
+ * new kevents when sessions are created. This klist is lock by
+ * anyas_klist_mtx.
+ */
+static struct klist anyas_klist;
+struct mtx anyas_klist_mtx;
+
+#define AUDIT_ANYAS_KLIST_LOCK_INIT() mtx_init(&anyas_klist_mtx, \
+ "audit anyas_klist_mtx", NULL, MTX_DEF)
+#define AUDIT_ANYAS_KLIST_LOCK() mtx_lock(&anyas_klist_mtx)
+#define AUDIT_ANYAS_KLIST_UNLOCK() mtx_unlock(&anyas_klist_mtx)
+#define AUDIT_ANYAS_KLIST_LOCK_ASSERT() mtx_assert(&anyas_klist_mtx, MA_OWNED)
+
+#define AUDIT_SENTRY_RWLOCK_INIT() rw_init(&se_entry_lck, \
+ "audit se_entry_lck")
+#define AUDIT_SENTRY_RLOCK() rw_rlock(&se_entry_lck)
+#define AUDIT_SENTRY_WLOCK() rw_wlock(&se_entry_lck)
+#define AUDIT_SENTRY_RWLOCK_ASSERT() rw_assert(&se_entry_lck, RA_LOCKED)
+#define AUDIT_SENTRY_RUNLOCK() rw_runlock(&se_entry_lck)
+#define AUDIT_SENTRY_WUNLOCK() rw_wunlock(&se_entry_lck)
+
+#define AUDIT_SE_KLIST_LOCK_INIT(se, n) mtx_init(&(se)->se_klist_mtx, \
+ n, NULL, MTX_DEF)
+#define AUDIT_SE_KLIST_LOCK(se) mtx_lock(&(se)->se_klist_mtx)
+#define AUDIT_SE_KLIST_UNLOCK(se) mtx_unlock(&(se)->se_klist_mtx)
+#define AUDIT_SE_KLIST_LOCK_DESTROY(se) mtx_destroy(&(se)->se_klist_mtx)
+#define AUDIT_SE_KLIST_LOCK_ASSERT(se) mtx_assert(&(se)->se_klist_mtx, \
+ MA_OWNED)
+
+#define AUDIT_PLIST_LOCK_INIT(pl) rlck_init(&(pl)->ph_rlck, \
+ "audit ph_rlck")
+#define AUDIT_PLIST_LOCK(pl) rlck_lock(&(pl)->ph_rlck)
+#define AUDIT_PLIST_UNLOCK(pl) rlck_unlock(&(pl)->ph_rlck)
+#define AUDIT_PLIST_LOCK_DESTROY(pl) rlck_destroy(&(pl)->ph_rlck)
+
+#if AUDIT_SESSION_DEBUG
+#include <kern/kalloc.h>
+
+struct au_sentry_debug {
+ auditinfo_addr_t se_auinfo;
+ long se_refcnt;
+ long se_procnt;
+};
+typedef struct au_sentry_debug au_sentry_debug_t;
+
+static int audit_sysctl_session_debug(struct sysctl_oid *oidp, void *arg1,
+ int arg2, struct sysctl_req *req);
+
+SYSCTL_PROC(_kern, OID_AUTO, audit_session_debug, CTLFLAG_RD, NULL, 0,
+ audit_sysctl_session_debug, "S,audit_session_debug",
+ "Current session debug info for auditing.");
+
+/*
+ * Copy out the session debug info via the sysctl interface. The userland code
+ * is something like the following:
+ *
+ * error = sysctlbyname("kern.audit_session_debug", buffer_ptr, &buffer_len,
+ * NULL, 0);
+ */
+static int
+audit_sysctl_session_debug(__unused struct sysctl_oid *oidp,
+ __unused void *arg1, __unused int arg2, struct sysctl_req *req)
+{
+ au_sentry_t *se;
+ au_sentry_debug_t *sed_tab, *next_sed;
+ int i, entry_cnt = 0;
+ size_t sz;
+ int err = 0;
+
+ /*
+ * This provides a read-only node.
+ */
+ if (req->newptr != USER_ADDR_NULL)
+ return (EPERM);
+
+ /*
+ * Walk the audit session hash table to determine the size.
+ */
+ AUDIT_SENTRY_RLOCK();
+ for(i = 0; i < HASH_TABLE_SIZE; i++)
+ LIST_FOREACH(se, &au_sentry_bucket[i], se_link)
+ if (se != NULL)
+ entry_cnt++;
+
+ /*
+ * If just querying then return the space required. There is an
+ * obvious race condition here so we just fudge this by 3 in case
+ * the audit session table grows.
+ */
+ if (req->oldptr == USER_ADDR_NULL) {
+ req->oldidx = (entry_cnt + 3) * sizeof(au_sentry_debug_t);
+ AUDIT_SENTRY_RUNLOCK();
+ return (0);
+ }
+
+ /*
+ * Alloc a temporary buffer.
+ */
+ if (req->oldlen < (entry_cnt * sizeof(au_sentry_debug_t))) {
+ AUDIT_SENTRY_RUNLOCK();
+ return (ENOMEM);
+ }
+ /*
+ * We hold the lock over the alloc since we don't want the table to
+ * grow on us. Therefore, use the non-blocking version of kalloc().
+ */
+ sed_tab = (au_sentry_debug_t *)kalloc_noblock(entry_cnt *
+ sizeof(au_sentry_debug_t));
+ if (sed_tab == NULL) {
+ AUDIT_SENTRY_RUNLOCK();
+ return (ENOMEM);
+ }
+ bzero(sed_tab, entry_cnt * sizeof(au_sentry_debug_t));
+
+ /*
+ * Walk the audit session hash table and build the record array.
+ */
+ sz = 0;
+ next_sed = sed_tab;
+ for(i = 0; i < HASH_TABLE_SIZE; i++) {
+ LIST_FOREACH(se, &au_sentry_bucket[i], se_link) {
+ if (se != NULL) {
+ bcopy(se, next_sed, sizeof(next_sed));
+ next_sed++;
+ sz += sizeof(au_sentry_debug_t);
+ }
+ }
+ }
+ AUDIT_SENTRY_RUNLOCK();
+
+ req->oldlen = sz;
+ err = SYSCTL_OUT(req, sed_tab, sz);
+ kfree(sed_tab, entry_cnt * sizeof(au_sentry_debug_t));
+
+ return (err);
+}
+
+#endif /* AUDIT_SESSION_DEBUG */
+
+/*
+ * Hash the audit session ID using a simple 32-bit mix.
+ */
+static inline uint32_t
+audit_session_hash(au_asid_t asid)
+{
+ uint32_t a = (uint32_t) asid;
+
+ a = (a - (a << 6)) ^ (a >> 17);
+ a = (a - (a << 9)) ^ (a << 4);
+ a = (a - (a << 3)) ^ (a << 10);
+ a = a ^ (a >> 15);
+
+ return (a);
+}
+
+/*
+ * Do an hash lookup and find the session entry for a given ASID. Return NULL
+ * if not found.
+ */
+static au_sentry_t *
+audit_session_find(au_asid_t asid)
+{
+ uint32_t hkey;
+ au_sentry_t *found_se;
+
+ AUDIT_SENTRY_RWLOCK_ASSERT();
+
+ hkey = HASH_ASID(asid);
+
+ LIST_FOREACH(found_se, &au_sentry_bucket[hkey], se_link)
+ if (found_se->se_asid == asid)
+ return (found_se);
+ return (NULL);
+}
+
+/*
+ * Call kqueue knote while holding the session entry klist lock.
+ */
+static void
+audit_session_knote(au_sentry_t *se, long hint)
+{
+
+ AUDIT_SE_KLIST_LOCK(se);
+ KNOTE(&se->se_klist, hint);
+ AUDIT_SE_KLIST_UNLOCK(se);
+}
+
+/*
+ * Remove the given audit_session entry from the hash table.
+ */
+static void
+audit_session_remove(au_sentry_t *se)
+{
+ uint32_t hkey;
+ au_sentry_t *found_se, *tmp_se;
+
+ KASSERT(se->se_refcnt == 0, ("audit_session_remove: ref count != 0"));
+
+ hkey = HASH_ASID(se->se_asid);
+
+ AUDIT_SENTRY_WLOCK();
+ LIST_FOREACH_SAFE(found_se, &au_sentry_bucket[hkey], se_link, tmp_se) {
+ if (found_se == se) {
+
+ audit_session_knote(found_se, NOTE_AS_CLOSE);
+
+ LIST_REMOVE(found_se, se_link);
+ AUDIT_SENTRY_WUNLOCK();
+ AUDIT_SE_KLIST_LOCK_DESTROY(found_se);
+ found_se->se_refcnt = 0;
+ free(found_se, M_AU_SESSION);
+
+ return;
+ }
+ }
+ AUDIT_SENTRY_WUNLOCK();
+}
+
+/*
+ * Reference the session by incrementing the sentry ref count.
+ */
+static void
+audit_ref_session(au_sentry_t *se)
+{
+ long old_val;
+
+ old_val = OSAddAtomicLong(1, &se->se_refcnt);
+ KASSERT(old_val < 100000,
+ ("audit_ref_session: Too many references on session."));
+}
+
+/*
+ * Decrement the sentry ref count and remove the session entry if last one.
+ */
+static void
+audit_unref_session(au_sentry_t *se)
+{
+ long old_val;
+
+ old_val = OSAddAtomicLong(-1, &se->se_refcnt);
+ if (old_val == 1)
+ audit_session_remove(se);
+ KASSERT(old_val > 0,
+ ("audit_unref_session: Too few references on session."));
+}
+
+/*
+ * Increment the process count in the session.
+ */
+static void
+audit_inc_procount(au_sentry_t *se)
+{
+ long old_val;
+
+ old_val = OSAddAtomicLong(1, &se->se_procnt);
+ KASSERT(old_val <= PID_MAX,
+ ("audit_inc_procount: proc count > PID_MAX"));
+}
+
+/*
+ * Decrement the process count and add a knote if it is the last process
+ * to exit the session.
+ */
+static void
+audit_dec_procount(au_sentry_t *se)
+{
+ long old_val;
+
+ old_val = OSAddAtomicLong(-1, &se->se_procnt);
+ if (old_val == 1)
+ audit_session_knote(se, NOTE_AS_END);
+ KASSERT(old_val >= 1,
+ ("audit_dec_procount: proc count < 0"));
+}
+
+/*
+ * Update the session entry and check to see if anything was updated.
+ * Returns:
+ * 0 Nothing was updated (We don't care about process preselection masks)
+ * 1 Something was updated.
+ */
+static int
+audit_update_sentry(au_sentry_t *se, auditinfo_addr_t *new_aia)
+{
+ auditinfo_addr_t *aia = &se->se_auinfo;
+ int update;
+
+ KASSERT(new_aia != &audit_default_aia,
+ ("audit_update_sentry: Trying to update the default aia."));
+
+ update = (aia->ai_auid != new_aia->ai_auid ||
+ bcmp(&aia->ai_termid, &new_aia->ai_termid,
+ sizeof(new_aia->ai_termid)) ||
+ aia->ai_flags != new_aia->ai_flags);
+
+ if (update)
+ bcopy(new_aia, aia, sizeof(*aia));
+
+ return (update);
+}
+
+/*
+ * Return the next session ID. The range of kernel generated audit session IDs
+ * is ASSIGNED_ASID_MIN to ASSIGNED_ASID_MAX.
+ */
+static uint32_t
+audit_session_nextid(void)
+{
+ static uint32_t next_asid = ASSIGNED_ASID_MIN;
+
+ AUDIT_SENTRY_RWLOCK_ASSERT();
+
+ if (next_asid > ASSIGNED_ASID_MAX)
+ next_asid = ASSIGNED_ASID_MIN;
+
+ return (next_asid++);
+}
+
+/*
+ * Allocated a new audit_session entry and add it to the hash table. If the
+ * given ASID is set to AU_ASSIGN_ASID then audit_session_new() will pick an
+ * audit session ID. Otherwise, it attempts use the one given. It creates a
+ * reference to the entry that must be unref'ed.
+ */
+static auditinfo_addr_t *
+audit_session_new(auditinfo_addr_t *new_aia, int newprocess)
+{
+ au_asid_t asid;
+ au_sentry_t *se = NULL;
+ auditinfo_addr_t *aia = NULL;
+ char nm[LOCK_MAX_NAME];
+
+ KASSERT(new_aia != NULL, ("audit_session_new: new_aia == NULL"));
+
+ asid = new_aia->ai_asid;
+
+#if 0 /* XXX this assertion is currently broken by securityd/LoginWindow */
+ KASSERT((asid != AU_ASSIGN_ASID && asid <= PID_MAX),
+ ("audit_session_new: illegal ASID value: %d", asid));
+#endif
+
+ /*
+ * Alloc a new session entry now so we don't wait holding the lock.
+ */
+ se = malloc(sizeof(au_sentry_t), M_AU_SESSION, M_WAITOK | M_ZERO);
+
+ snprintf(nm, sizeof(nm), "audit se_klist_mtx %d", asid);
+ AUDIT_SE_KLIST_LOCK_INIT(se, nm);
+
+ /*
+ * Find an unique session ID, if desired.
+ */
+ AUDIT_SENTRY_WLOCK();
+ if (asid == AU_ASSIGN_ASID) {
+ do {
+ asid = (au_asid_t)audit_session_nextid();
+ } while(audit_session_find(asid) != NULL);
+ } else {
+ au_sentry_t *found_se = NULL;
+
+ /*
+ * Check to see if the requested ASID is already in the
+ * hash table. If so, update it with the new auditinfo.
+ */
+ if ((found_se = audit_session_find(asid)) != NULL) {
+ int updated;
+
+ updated = audit_update_sentry(found_se, new_aia);
+ audit_ref_session(found_se);
+
+ AUDIT_SENTRY_WUNLOCK();
+ AUDIT_SE_KLIST_LOCK_DESTROY(se);
+ free(se, M_AU_SESSION);
+
+ if (updated)
+ audit_session_knote(found_se, NOTE_AS_UPDATE);
+
+ /*
+ * If this is a new process joining this session then
+ * we need to update the proc count.
+ */
+ if (newprocess)
+ audit_inc_procount(found_se);
+
+ return (&found_se->se_auinfo);
+ }
+ }
+
+ /*
+ * Start the reference and proc count at 1 to account for the process
+ * that invoked this via setaudit_addr() (or friends).
+ */
+ se->se_refcnt = se->se_procnt = 1;
+
+ /*
+ * Populate the new session entry. Note that process masks are stored
+ * in kauth ucred so just zero them here.
+ */
+ se->se_port = IPC_PORT_NULL;
+ aia = &se->se_auinfo;
+ aia->ai_asid = asid;
+ aia->ai_auid = new_aia->ai_auid;
+ bzero(&new_aia->ai_mask, sizeof(new_aia->ai_mask));
+ bcopy(&new_aia->ai_termid, &aia->ai_termid, sizeof(aia->ai_termid));
+ aia->ai_flags = new_aia->ai_flags;
+
+ /*
+ * Add it to the hash table.
+ */
+ LIST_INSERT_HEAD(&au_sentry_bucket[HASH_ASID(asid)], se, se_link);
+ AUDIT_SENTRY_WUNLOCK();
+
+ /*
+ * Register kevents for consumers wanting events for any ASID
+ * and knote the event.
+ */
+ audit_register_kevents(se->se_asid, se->se_auid);
+ audit_session_knote(se, NOTE_AS_START);
+
+ return (aia);
+}
+
+/*
+ * Lookup an existing session. A copy of the audit session info for a given
+ * ASID is returned in ret_aia. Returns 0 on success.
+ */
+int
+audit_session_lookup(au_asid_t asid, auditinfo_addr_t *ret_aia)
+{
+ au_sentry_t *se = NULL;
+
+ if ((uint32_t)asid > ASSIGNED_ASID_MAX)
+ return (-1);
+ AUDIT_SENTRY_RLOCK();
+ if ((se = audit_session_find(asid)) == NULL) {
+ AUDIT_SENTRY_RUNLOCK();
+ return (1);
+ }
+ if (ret_aia != NULL)
+ bcopy(&se->se_auinfo, ret_aia, sizeof(*ret_aia));
+ AUDIT_SENTRY_RUNLOCK();
+
+ return (0);
+}
+
+/*
+ * Add a reference to the session entry.
+ */
+void
+audit_session_ref(kauth_cred_t cred)
+{
+ auditinfo_addr_t *aia_p;
+
+ KASSERT(IS_VALID_CRED(cred),
+ ("audit_session_ref: Invalid kauth_cred."));
+
+ aia_p = cred->cr_audit.as_aia_p;
+
+ if (IS_VALID_SESSION(aia_p))
+ audit_ref_session(AU_SENTRY_PTR(aia_p));
+}
+
+/*
+ * Remove a reference to the session entry.
+ */
+void
+audit_session_unref(kauth_cred_t cred)
+{
+ auditinfo_addr_t *aia_p;
+
+ KASSERT(IS_VALID_CRED(cred),
+ ("audit_session_unref: Invalid kauth_cred."));
+
+ aia_p = cred->cr_audit.as_aia_p;
+
+ if (IS_VALID_SESSION(aia_p))
+ audit_unref_session(AU_SENTRY_PTR(aia_p));
+}
+
+void
+audit_session_procnew(kauth_cred_t cred)
+{
+ auditinfo_addr_t *aia_p;
+
+ KASSERT(IS_VALID_CRED(cred),
+ ("audit_session_procnew: Invalid kauth_cred."));
+
+ aia_p = cred->cr_audit.as_aia_p;
+
+ if (IS_VALID_SESSION(aia_p))
+ audit_inc_procount(AU_SENTRY_PTR(aia_p));
+}
+
+void
+audit_session_procexit(kauth_cred_t cred)
+{
+ auditinfo_addr_t *aia_p;
+
+ KASSERT(IS_VALID_CRED(cred),
+ ("audit_session_procexit: Invalid kauth_cred."));
+
+ aia_p = cred->cr_audit.as_aia_p;
+
+ if (IS_VALID_SESSION(aia_p))
+ audit_dec_procount(AU_SENTRY_PTR(aia_p));
+}
+
+/*
+ * Init the audit session code.
+ */
+void
+audit_session_init(void)
+{
+ int i;
+
+ KASSERT((ASSIGNED_ASID_MAX - ASSIGNED_ASID_MIN) > PID_MAX,
+ ("audit_session_init: ASSIGNED_ASID_MAX is not large enough."));
+
+ AUDIT_SENTRY_RWLOCK_INIT();
+ AUDIT_ANYAS_KLIST_LOCK_INIT();
+
+ au_sentry_bucket = malloc( sizeof(struct au_sentry) *
+ HASH_TABLE_SIZE, M_AU_SESSION, M_WAITOK | M_ZERO);
+
+ for (i = 0; i < HASH_TABLE_SIZE; i++)
+ LIST_INIT(&au_sentry_bucket[i]);
+}
+
+/*
+ * Allocate a new kevent propagation list (plist).
+ */
+static caddr_t
+audit_new_plist(void)
+{
+ au_plisthead_t *plhead;
+
+ plhead = malloc(sizeof(au_plisthead_t), M_AU_EV_PLIST, M_WAITOK |
+ M_ZERO);
+
+ LIST_INIT(&plhead->ph_head);
+ AUDIT_PLIST_LOCK_INIT(plhead);
+
+ return ((caddr_t) plhead);
+}
+
+/*
+ * Destroy a kevent propagation list (plist). The anyas_klist_mtx mutex must be
+ * held by the caller.
+ */
+static void
+audit_destroy_plist(struct knote *anyas_kn)
+{
+ au_plisthead_t *plhead;
+ au_plist_t *plentry, *ple_tmp;
+ struct kevent64_s kev;
+
+ KASSERT(anyas_kn != NULL, ("audit_destroy_plist: anyas = NULL"));
+ plhead = (au_plisthead_t *)anyas_kn->kn_hook;
+ KASSERT(plhead != NULL, ("audit_destroy_plist: plhead = NULL"));
+
+ /*
+ * Delete everything in the propagation list.
+ */
+ AUDIT_PLIST_LOCK(plhead);
+ LIST_FOREACH_SAFE(plentry, &plhead->ph_head, pl_link, ple_tmp) {
+ struct kqueue *kq = plentry->pl_knote->kn_kq;
+
+ kev.ident = plentry->pl_knote->kn_id;
+ kev.filter = EVFILT_SESSION;
+ kev.flags = EV_DELETE;
+
+ /*
+ * The plist entry gets removed in rm_from_plist() which is
+ * called indirectly by kevent_register().
+ */
+ kevent_register(kq, &kev, NULL);
+ }
+ AUDIT_PLIST_UNLOCK(plhead);
+
+ /*
+ * Remove the head.
+ */
+ AUDIT_PLIST_LOCK_DESTROY(plhead);
+ free(plhead, M_AU_EV_PLIST);
+}
+
+/*
+ * Add a knote pointer entry to the kevent propagation list.
+ */
+static void
+audit_add_to_plist(struct knote *anyas_kn, struct knote *kn)
+{
+ au_plisthead_t *plhead;
+ au_plist_t *plentry;
+
+ KASSERT(anyas_kn != NULL, ("audit_add_to_plist: anyas = NULL"));
+ plhead = (au_plisthead_t *)anyas_kn->kn_hook;
+ KASSERT(plhead != NULL, ("audit_add_to_plist: plhead = NULL"));
+
+ plentry = malloc(sizeof(au_plist_t), M_AU_EV_PLIST, M_WAITOK | M_ZERO);
+
+ plentry->pl_knote = kn;
+ AUDIT_PLIST_LOCK(plhead);
+ LIST_INSERT_HEAD(&plhead->ph_head, plentry, pl_link);
+ AUDIT_PLIST_UNLOCK(plhead);
+}
+
+/*
+ * Remote a knote pointer entry from the kevent propagation list. The lock
+ * on the plist may already be head (by audit_destroy_plist() above) so we use
+ * a recursive lock.
+ */
+static void
+audit_rm_from_plist(struct knote *kn)
+{
+ struct knote *anyas_kn;
+ au_plisthead_t *plhd;
+ au_plist_t *plentry, *ple_tmp;
+
+ KASSERT(kn != NULL, ("audit_rm_from_plist: kn = NULL"));
+ anyas_kn = (struct knote *)kn->kn_hook;
+ KASSERT(anyas_kn != NULL, ("audit_rm_to_plist: anyas = NULL"));
+ plhd = (au_plisthead_t *)anyas_kn->kn_hook;
+
+ AUDIT_PLIST_LOCK(plhd);
+ LIST_FOREACH_SAFE(plentry, &plhd->ph_head, pl_link, ple_tmp) {
+ if (plentry->pl_knote == kn) {
+ LIST_REMOVE(plentry, pl_link);
+ free(plentry, M_AU_EV_PLIST);
+ AUDIT_PLIST_UNLOCK(plhd);
+ return;
+ }
+ }
+ AUDIT_PLIST_UNLOCK(plhd);
+}
+
+/*
+ * The attach filter for EVFILT_SESSION.
+ */
+static int
+audit_filt_sessionattach(struct knote *kn)
+{
+ au_sentry_t *se = NULL;
+
+ /*
+ * Check flags for the events we currently support.
+ */
+ if ((kn->kn_sfflags & (NOTE_AS_START | NOTE_AS_END | NOTE_AS_CLOSE
+ | NOTE_AS_UPDATE | NOTE_AS_ERR)) == 0)
+ return (ENOTSUP);
+
+ /*
+ * If the interest is in any session then add to the any ASID knote
+ * list. Otherwise, add it to the knote list assosiated with the
+ * given session.
+ */
+ if (kn->kn_id == AS_ANY_ASID) {
+
+ kn->kn_flags |= EV_CLEAR;
+ kn->kn_ptr.p_se = NULL;
+
+ /*
+ * Attach a kevent propagation list for any kevents that get
+ * added.
+ */
+ kn->kn_hook = audit_new_plist();
+
+ AUDIT_ANYAS_KLIST_LOCK();
+ KNOTE_ATTACH(&anyas_klist, kn);
+ AUDIT_ANYAS_KLIST_UNLOCK();
+
+ return (0);
+ } else {
+
+ /*
+ * NOTE: The anyas klist lock will be held in this
+ * part of the code when indirectly called from
+ * audit_register_kevents() below.
+ */
+
+ /*
+ * Check to make sure it is a valid ASID.
+ */
+ if (kn->kn_id > ASSIGNED_ASID_MAX)
+ return (EINVAL);
+
+ AUDIT_SENTRY_RLOCK();
+ se = audit_session_find(kn->kn_id);
+ AUDIT_SENTRY_RUNLOCK();
+ if (se == NULL)
+ return (EINVAL);
+
+ AUDIT_SE_KLIST_LOCK(se);
+ kn->kn_flags |= EV_CLEAR;
+ kn->kn_ptr.p_se = se;
+
+ /*
+ * If this attach is the result of an "any ASID" (pseudo)
+ * kevent then attach the any session knote ptr to this knote.
+ * Also, add this knote to the its propagation list.
+ */
+ if (kn->kn_flags & EV_ANY_ASID) {
+ struct knote *anyas_kn =
+ (struct knote *)((uintptr_t)kn->kn_kevent.ext[0]);
+ kn->kn_hook = (caddr_t) anyas_kn;
+ kn->kn_flags &= ~EV_ANY_ASID;
+ audit_add_to_plist(anyas_kn, kn);
+ } else
+ kn->kn_hook = NULL;
+ KNOTE_ATTACH(&se->se_klist, kn);
+ AUDIT_SE_KLIST_UNLOCK(se);
+
+ return (0);
+ }
+}
+
+/*
+ * The detach filter for EVFILT_SESSION.
+ */
+static void
+audit_filt_sessiondetach(struct knote *kn)
+{
+ au_sentry_t *se = NULL;
+
+ if (kn->kn_id == AS_ANY_ASID) {
+
+ AUDIT_ANYAS_KLIST_LOCK();
+ audit_destroy_plist(kn);
+ KNOTE_DETACH(&anyas_klist, kn);
+ AUDIT_ANYAS_KLIST_UNLOCK();
+
+ } else {
+ /*
+ * If this knote was created by any ASID kevent then remove
+ * from kevent propagation list.
+ */
+ if (kn->kn_hook != NULL) {
+ audit_rm_from_plist(kn);
+ kn->kn_hook = NULL;
+ }
+
+ /*
+ * Check to see if already detached.
+ */
+ se = kn->kn_ptr.p_se;
+ if (se != NULL) {
+ AUDIT_SE_KLIST_LOCK(se);
+ kn->kn_ptr.p_se = NULL;
+ KNOTE_DETACH(&se->se_klist, kn);
+ AUDIT_SE_KLIST_UNLOCK(se);
+ }
+ }
+}
+
+/*
+ * The touch filter for EVFILT_SESSION. Check for any ASID kevent updates and
+ * propagate the change.
+ */
+static void
+audit_filt_sessiontouch(struct knote *kn, struct kevent64_s *kev, long type)
+{
+ struct knote *ple_kn;
+ struct kqueue *kq;
+ au_sentry_t *se;
+ au_plisthead_t *plhead;
+ au_plist_t *plentry;
+ struct kevent64_s newkev;
+
+ switch (type) {
+ case EVENT_REGISTER:
+ kn->kn_sfflags = kev->fflags;
+ kn->kn_sdata = kev->data;
+ /*
+ * If an any ASID kevent was updated then we may need to
+ * propagate the update.
+ */
+ if (kev->ident == AS_ANY_ASID && kn->kn_hook != NULL) {
+
+ /*
+ * Propagate the change to each of the session kevents
+ * that were created by this any ASID kevent.
+ */
+ plhead = (au_plisthead_t *)kn->kn_hook;
+ AUDIT_PLIST_LOCK(plhead);
+ LIST_FOREACH(plentry, &plhead->ph_head, pl_link) {
+
+ if ((ple_kn = plentry->pl_knote) == NULL)
+ continue;
+ if ((se = ple_kn->kn_ptr.p_se) == NULL)
+ continue;
+ if ((kq = ple_kn->kn_kq) == NULL)
+ continue;
+
+ newkev.ident = plentry->pl_knote->kn_id;
+ newkev.filter = EVFILT_SESSION;
+ newkev.flags = kev->flags;
+ newkev.fflags = kev->fflags;
+ newkev.data = kev->data;
+ newkev.udata = kev->udata;
+ kevent_register(kq, &newkev, NULL);
+ }
+ AUDIT_PLIST_UNLOCK(plhead);
+ }
+ break;
+
+ case EVENT_PROCESS:
+ *kev = kn->kn_kevent;
+ if (kn->kn_flags & EV_CLEAR) {
+ kn->kn_data = 0;
+ kn->kn_fflags = 0;
+ }
+ break;
+
+ default:
+ KASSERT((type == EVENT_REGISTER || type == EVENT_PROCESS),
+ ("filt_sessiontouch(): invalid type (%ld)", type));
+ break;
+ }
+}
+
+/*
+ * Event filter for EVFILT_SESSION. The AUDIT_SE_KLIST_LOCK should be held
+ * by audit_session_knote().
+ */
+static int
+audit_filt_session(struct knote *kn, long hint)
+{
+ int events = (int)hint;
+ au_sentry_t *se = kn->kn_ptr.p_se;
+
+ if (hint != 0 && se != NULL) {
+
+ if (kn->kn_sfflags & events) {
+ kn->kn_fflags |= events;
+ kn->kn_data = se->se_auid;
+ }
+
+ /*
+ * If this is the last possible event for the knote,
+ * detach the knote from the audit session before the
+ * session goes away.
+ */
+ if (events & NOTE_AS_CLOSE) {
+
+ /*
+ * If created by any ASID kevent then remove from
+ * propagation list.
+ */
+ if (kn->kn_hook != NULL) {
+ audit_rm_from_plist(kn);
+ kn->kn_hook = NULL;
+ }
+ kn->kn_flags |= (EV_EOF | EV_ONESHOT);
+ kn->kn_ptr.p_se = NULL;
+ AUDIT_SE_KLIST_LOCK_ASSERT(se);
+ KNOTE_DETACH(&se->se_klist, kn);
+
+ return (1);
+ }
+ }
+ return (kn->kn_fflags != 0);
+}
+
+/*
+ * For all the consumers wanting events for all sessions, register new
+ * kevents associated with the session for the given ASID. The actual
+ * attachment is done by the EVFILT_SESSION attach filter above.
+ */
+static void
+audit_register_kevents(uint32_t asid, uint32_t auid)
+{
+ struct knote *kn;
+
+ AUDIT_ANYAS_KLIST_LOCK();
+ SLIST_FOREACH(kn, &anyas_klist, kn_selnext) {
+ struct kqueue *kq = kn->kn_kq;
+ struct kevent64_s kev;
+ int err;
+
+ kev.ident = asid;
+ kev.filter = EVFILT_SESSION;
+ kev.flags = kn->kn_flags | EV_ADD | EV_ENABLE | EV_ANY_ASID;
+ kev.fflags = kn->kn_sfflags;
+ kev.data = auid;
+ kev.udata = kn->kn_kevent.udata;
+
+ /*
+ * Save the knote ptr for this "any ASID" knote for the attach
+ * filter.
+ */
+ kev.ext[0] = (uint64_t)((uintptr_t)kn);
+
+ /*
+ * XXX kevent_register() may block here alloc'ing a new knote.
+ * We may want to think about using a lockless linked list or
+ * at least a sleep rwlock for the anyas_klist.
+ */
+ err = kevent_register(kq, &kev, NULL);
+ if (err)
+ kn->kn_fflags |= NOTE_AS_ERR;
+ }
+ AUDIT_ANYAS_KLIST_UNLOCK();
+}
+
+/*
+ * Safely update kauth cred of the given process with new the given audit info.
+ * If the newprocess flag is set then we need to account for this process in
+ * the proc count.
+ */
+int
+audit_session_setaia(proc_t p, auditinfo_addr_t *aia_p, int newprocess)
+{
+ kauth_cred_t my_cred, my_new_cred;
+ struct au_session as;
+ struct au_session tmp_as;
+ auditinfo_addr_t caia;
+
+ /*
+ * If this is going to modify an existing session then do some
+ * immutable checks.
+ */
+ if (audit_session_lookup(aia_p->ai_asid, &caia) == 0) {
+
+ /*
+ * If the current audit ID is not the default then it is
+ * immutable.
+ */
+ if (caia.ai_auid != AU_DEFAUDITID &&
+ caia.ai_auid != aia_p->ai_auid)
+ return (EINVAL);
+
+ /*
+ * If the current termid is not the default then it is
+ * immutable.
+ */
+ if ((caia.ai_termid.at_type != AU_IPv4 ||
+ caia.ai_termid.at_port != 0 ||
+ caia.ai_termid.at_addr[0] != 0) &&
+ (caia.ai_termid.at_port != aia_p->ai_termid.at_port ||
+ caia.ai_termid.at_type != aia_p->ai_termid.at_type ||
+ bcmp(&caia.ai_termid.at_addr, &aia_p->ai_termid.at_addr,
+ sizeof (caia.ai_termid.at_addr) )) )
+ return (EINVAL);
+
+ /* The audit flags are immutable. */
+ if (caia.ai_flags != aia_p->ai_flags)
+ return (EINVAL);
+
+ /* The audit masks are mutable. */
+ }
+
+ my_cred = kauth_cred_proc_ref(p);
+ bcopy(&aia_p->ai_mask, &as.as_mask, sizeof(as.as_mask));
+ as.as_aia_p = audit_session_new(aia_p, newprocess);
+
+ /*
+ * We are modifying the audit info in a credential so we need a new
+ * credential (or take another reference on an existing credential that
+ * matches our new one). We must do this because the audit info in the
+ * credential is used as part of our hash key. Get current credential
+ * in the target process and take a reference while we muck with it.
+ */
+ for (;;) {
+
+ /*
+ * Set the credential with new info. If there is no change,
+ * we get back the same credential we passed in; if there is
+ * a change, we drop the reference on the credential we
+ * passed in. The subsequent compare is safe, because it is
+ * a pointer compare rather than a contents compare.
+ */
+ bcopy(&as, &tmp_as, sizeof(tmp_as));
+ my_new_cred = kauth_cred_setauditinfo(my_cred, &tmp_as);
+
+ if (my_cred != my_new_cred) {
+ proc_lock(p);
+ /* Need to protect for a race where another thread also
+ * changed the credential after we took our reference.
+ * If p_ucred has changed then we should restart this
+ * again with the new cred.
+ */
+ if (p->p_ucred != my_cred) {
+ proc_unlock(p);
+ audit_session_unref(my_new_cred);
+ kauth_cred_unref(&my_new_cred);
+ /* try again */
+ my_cred = kauth_cred_proc_ref(p);
+ continue;
+ }
+ p->p_ucred = my_new_cred;
+ proc_unlock(p);
+ }
+ /*
+ * Drop old proc reference or our extra reference.
+ */
+ kauth_cred_unref(&my_cred);
+ break;
+ }
+ audit_session_unref(my_new_cred);
+
+ /*
+ * Propagate the change from the process to the Mach task.
+ */
+ set_security_token(p);
+
+ return (0);
+}
+
+/*
+ * audit_session_self (system call)
+ *
+ * Description: Obtain a Mach send right for the current session.
+ *
+ * Parameters: p Process calling audit_session_self().
+ *
+ * Returns: *ret_port Named Mach send right, which may be
+ * MACH_PORT_NULL in the failure case.
+ *
+ * Errno: 0 Success
+ * EINVAL The calling process' session has not be set.
+ * ESRCH Bad process, can't get valid cred for process.
+ * ENOMEM Port allocation failed due to no free memory.
+ */
+int
+audit_session_self(proc_t p, __unused struct audit_session_self_args *uap,
+ mach_port_name_t *ret_port)
+{
+ ipc_port_t sendport = IPC_PORT_NULL;
+ kauth_cred_t cred = NULL;
+ auditinfo_addr_t *aia_p;
+ au_sentry_t *se;
+ int err = 0;
+
+ cred = kauth_cred_proc_ref(p);
+ if (!IS_VALID_CRED(cred)) {
+ err = ESRCH;
+ goto done;
+ }
+
+ aia_p = cred->cr_audit.as_aia_p;
+ if (!IS_VALID_SESSION(aia_p)) {
+ err = EINVAL;
+ goto done;
+ }
+
+ se = AU_SENTRY_PTR(aia_p);
+
+ /*
+ * Processes that join using this mach port will inherit this process'
+ * pre-selection masks.
+ */
+ if (se->se_port == IPC_PORT_NULL)
+ bcopy(&cred->cr_audit.as_mask, &se->se_mask,
+ sizeof(se->se_mask));
+
+ if ((sendport = audit_session_mksend(aia_p, &se->se_port)) == NULL) {
+ /* failed to alloc new port */
+ err = ENOMEM;
+ goto done;
+ }
+
+ /*
+ * This reference on the session is unref'ed in
+ * audit_session_port_destory(). This reference is needed so the
+ * session doesn't get dropped until the session join is done.
+ */
+ audit_ref_session(se);
+
+
+done:
+ if (cred != NULL)
+ kauth_cred_unref(&cred);
+ if (err == 0)
+ *ret_port = ipc_port_copyout_send(sendport,
+ get_task_ipcspace(p->task));
+ else
+ *ret_port = MACH_PORT_NULL;
+
+ return (err);
+}
+
+void
+audit_session_portaiadestroy(struct auditinfo_addr *port_aia_p)
+{
+ au_sentry_t *se;
+
+ KASSERT(port_aia_p != NULL,
+ ("audit_session_infodestroy: port_aia_p = NULL"));
+
+ se = AU_SENTRY_PTR(port_aia_p);
+
+ /*
+ * Drop the reference added in audit_session_self().
+ */
+ if (se != NULL) {
+ se->se_port = IPC_PORT_NULL;
+ audit_unref_session(se);
+ }
+
+}
+
+static int
+audit_session_join_internal(proc_t p, ipc_port_t port, au_asid_t *new_asid)
+{
+ auditinfo_addr_t *port_aia_p, *old_aia_p;
+ kauth_cred_t cred = NULL;
+ au_asid_t old_asid;
+ int err = 0;
+
+ *new_asid = AU_DEFAUDITSID;
+
+ if ((port_aia_p = audit_session_porttoaia(port)) == NULL) {
+ err = EINVAL;
+ goto done;
+ }
+ *new_asid = port_aia_p->ai_asid;
+
+ cred = kauth_cred_proc_ref(p);
+ if (!IS_VALID_CRED(cred)) {
+ kauth_cred_unref(&cred);
+ err = ESRCH;
+ goto done;
+ }
+ old_aia_p = cred->cr_audit.as_aia_p;
+ old_asid = old_aia_p->ai_asid;
+
+ /*
+ * Add process in if not already in the session.
+ */
+ if (*new_asid != old_asid) {
+ audit_session_setaia(p, port_aia_p, 1);
+ /*
+ * If this process was in a valid session before then we
+ * need to decrement the process count of the session it
+ * came from.
+ */
+ if (IS_VALID_SESSION(old_aia_p))
+ audit_dec_procount(AU_SENTRY_PTR(old_aia_p));
+ }
+ kauth_cred_unref(&cred);
+
+done:
+ if (port != IPC_PORT_NULL)
+ ipc_port_release_send(port);
+
+ return (err);
+}
+
+/*
+ * audit_session_spawnjoin
+ *
+ * Description: posix_spawn() interface to audit_session_join_internal().
+ *
+ * Returns: 0 Success
+ * EINVAL Invalid Mach port name.
+ * ESRCH Invalid calling process/cred.
+ */
+int
+audit_session_spawnjoin(proc_t p, ipc_port_t port)
+{
+ au_asid_t new_asid;
+
+ return (audit_session_join_internal(p, port, &new_asid));
+}
+
+/*
+ * audit_session_join (system call)
+ *
+ * Description: Join the session for a given Mach port send right.
+ *
+ * Parameters: p Process calling session join.
+ * uap->port A Mach send right.
+ *
+ * Returns: *ret_asid Audit session ID of new session, which may
+ * be AU_DEFAUDITSID in the failure case.
+ *
+ * Errno: 0 Success
+ * EINVAL Invalid Mach port name.
+ * ESRCH Invalid calling process/cred.
+ */
+int
+audit_session_join(proc_t p, struct audit_session_join_args *uap,
+ au_asid_t *ret_asid)
+{
+ ipc_port_t port = IPC_PORT_NULL;
+ mach_port_name_t send = uap->port;
+ int err = 0;
+
+
+ if (ipc_object_copyin(get_task_ipcspace(p->task), send,
+ MACH_MSG_TYPE_COPY_SEND, &port) != KERN_SUCCESS) {
+ *ret_asid = AU_DEFAUDITSID;
+ err = EINVAL;
+ } else
+ err = audit_session_join_internal(p, port, ret_asid);
+
+ return (err);
+}
+
+#else
+
+int
+audit_session_self(proc_t p, struct audit_session_self_args *uap,
+ mach_port_name_t *ret_port)
+{
+#pragma unused(p, uap, ret_port)
+
+ return (ENOSYS);
+}
+
+int
+audit_session_join(proc_t p, struct audit_session_join_args *uap,
+ au_asid_t *ret_asid)
+{
+#pragma unused(p, uap, ret_asid)
+
+ return (ENOSYS);
+}
+
+#endif /* CONFIG_AUDIT */