/*- * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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 */