]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/security/audit/audit_session.c
xnu-1456.1.26.tar.gz
[apple/xnu.git] / bsd / security / audit / audit_session.c
diff --git a/bsd/security/audit/audit_session.c b/bsd/security/audit/audit_session.c
new file mode 100644 (file)
index 0000000..8e05f9d
--- /dev/null
@@ -0,0 +1,1361 @@
+/*-
+ * 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 */