Index: stable/7/sys/security/audit/audit.c =================================================================== --- stable/7/sys/security/audit/audit.c (revision 178335) +++ stable/7/sys/security/audit/audit.c (revision 178336) @@ -1,575 +1,576 @@ /* * Copyright (c) 1999-2005 Apple Computer, Inc. * Copyright (c) 2006-2007 Robert N. M. Watson * 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 Computer, 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. - * - * $FreeBSD$ */ + +#include +__FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static uma_zone_t audit_record_zone; static MALLOC_DEFINE(M_AUDITCRED, "audit_cred", "Audit cred storage"); MALLOC_DEFINE(M_AUDITDATA, "audit_data", "Audit data storage"); MALLOC_DEFINE(M_AUDITPATH, "audit_path", "Audit path storage"); MALLOC_DEFINE(M_AUDITTEXT, "audit_text", "Audit text storage"); SYSCTL_NODE(_security, OID_AUTO, audit, CTLFLAG_RW, 0, "TrustedBSD audit controls"); /* * Audit control settings that are set/read by system calls and are hence * non-static. * * Define the audit control flags. */ int audit_enabled; int audit_suspended; /* * Flags controlling behavior in low storage situations. Should we panic if * a write fails? Should we fail stop if we're out of disk space? */ int audit_panic_on_write_fail; int audit_fail_stop; int audit_argv; int audit_arge; /* * Are we currently "failing stop" due to out of disk space? */ int audit_in_failure; /* * Global audit statistics. */ struct audit_fstat audit_fstat; /* * Preselection mask for non-attributable events. */ struct au_mask audit_nae_mask; /* * Mutex to protect global variables shared between various threads and * processes. */ struct mtx audit_mtx; /* * Queue of audit records ready for delivery to disk. We insert new records * at the tail, and remove records from the head. Also, a count of the * number of records used for checking queue depth. In addition, a counter * of records that we have allocated but are not yet in the queue, which is * needed to estimate the total size of the combined set of records * outstanding in the system. */ struct kaudit_queue audit_q; int audit_q_len; int audit_pre_q_len; /* * Audit queue control settings (minimum free, low/high water marks, etc.) */ struct au_qctrl audit_qctrl; /* * Condition variable to signal to the worker that it has work to do: either * new records are in the queue, or a log replacement is taking place. */ struct cv audit_worker_cv; /* * Condition variable to flag when crossing the low watermark, meaning that * threads blocked due to hitting the high watermark can wake up and continue * to commit records. */ struct cv audit_watermark_cv; /* * Condition variable for auditing threads wait on when in fail-stop mode. * Threads wait on this CV forever (and ever), never seeing the light of day * again. */ static struct cv audit_fail_cv; /* * Construct an audit record for the passed thread. */ static int audit_record_ctor(void *mem, int size, void *arg, int flags) { struct kaudit_record *ar; struct thread *td; KASSERT(sizeof(*ar) == size, ("audit_record_ctor: wrong size")); td = arg; ar = mem; bzero(ar, sizeof(*ar)); ar->k_ar.ar_magic = AUDIT_RECORD_MAGIC; nanotime(&ar->k_ar.ar_starttime); /* * Export the subject credential. */ cru2x(td->td_ucred, &ar->k_ar.ar_subj_cred); ar->k_ar.ar_subj_ruid = td->td_ucred->cr_ruid; ar->k_ar.ar_subj_rgid = td->td_ucred->cr_rgid; ar->k_ar.ar_subj_egid = td->td_ucred->cr_groups[0]; ar->k_ar.ar_subj_auid = td->td_ucred->cr_audit.ai_auid; ar->k_ar.ar_subj_asid = td->td_ucred->cr_audit.ai_asid; ar->k_ar.ar_subj_pid = td->td_proc->p_pid; ar->k_ar.ar_subj_amask = td->td_ucred->cr_audit.ai_mask; ar->k_ar.ar_subj_term_addr = td->td_ucred->cr_audit.ai_termid; return (0); } static void audit_record_dtor(void *mem, int size, void *arg) { struct kaudit_record *ar; KASSERT(sizeof(*ar) == size, ("audit_record_dtor: wrong size")); ar = mem; if (ar->k_ar.ar_arg_upath1 != NULL) free(ar->k_ar.ar_arg_upath1, M_AUDITPATH); if (ar->k_ar.ar_arg_upath2 != NULL) free(ar->k_ar.ar_arg_upath2, M_AUDITPATH); if (ar->k_ar.ar_arg_text != NULL) free(ar->k_ar.ar_arg_text, M_AUDITTEXT); if (ar->k_udata != NULL) free(ar->k_udata, M_AUDITDATA); if (ar->k_ar.ar_arg_argv != NULL) free(ar->k_ar.ar_arg_argv, M_AUDITTEXT); if (ar->k_ar.ar_arg_envv != NULL) free(ar->k_ar.ar_arg_envv, M_AUDITTEXT); } /* * Initialize the Audit subsystem: configuration state, work queue, * synchronization primitives, worker thread, and trigger device node. Also * call into the BSM assembly code to initialize it. */ static void audit_init(void) { audit_enabled = 0; audit_suspended = 0; audit_panic_on_write_fail = 0; audit_fail_stop = 0; audit_in_failure = 0; audit_argv = 0; audit_arge = 0; audit_fstat.af_filesz = 0; /* '0' means unset, unbounded. */ audit_fstat.af_currsz = 0; audit_nae_mask.am_success = 0; audit_nae_mask.am_failure = 0; TAILQ_INIT(&audit_q); audit_q_len = 0; audit_pre_q_len = 0; audit_qctrl.aq_hiwater = AQ_HIWATER; audit_qctrl.aq_lowater = AQ_LOWATER; audit_qctrl.aq_bufsz = AQ_BUFSZ; audit_qctrl.aq_minfree = AU_FS_MINFREE; mtx_init(&audit_mtx, "audit_mtx", NULL, MTX_DEF); cv_init(&audit_worker_cv, "audit_worker_cv"); cv_init(&audit_watermark_cv, "audit_watermark_cv"); cv_init(&audit_fail_cv, "audit_fail_cv"); audit_record_zone = uma_zcreate("audit_record", sizeof(struct kaudit_record), audit_record_ctor, audit_record_dtor, NULL, NULL, UMA_ALIGN_PTR, 0); /* Initialize the BSM audit subsystem. */ kau_init(); audit_trigger_init(); /* Register shutdown handler. */ EVENTHANDLER_REGISTER(shutdown_pre_sync, audit_shutdown, NULL, SHUTDOWN_PRI_FIRST); /* Start audit worker thread. */ audit_worker_init(); } SYSINIT(audit_init, SI_SUB_AUDIT, SI_ORDER_FIRST, audit_init, NULL) /* * Drain the audit queue and close the log at shutdown. Note that this can * be called both from the system shutdown path and also from audit * configuration syscalls, so 'arg' and 'howto' are ignored. */ void audit_shutdown(void *arg, int howto) { audit_rotate_vnode(NULL, NULL); } /* * Return the current thread's audit record, if any. */ struct kaudit_record * currecord(void) { return (curthread->td_ar); } /* * XXXAUDIT: There are a number of races present in the code below due to * release and re-grab of the mutex. The code should be revised to become * slightly less racy. * * XXXAUDIT: Shouldn't there be logic here to sleep waiting on available * pre_q space, suspending the system call until there is room? */ struct kaudit_record * audit_new(int event, struct thread *td) { struct kaudit_record *ar; int no_record; mtx_lock(&audit_mtx); no_record = (audit_suspended || !audit_enabled); mtx_unlock(&audit_mtx); if (no_record) return (NULL); /* * Note: the number of outstanding uncommitted audit records is * limited to the number of concurrent threads servicing system calls * in the kernel. */ ar = uma_zalloc_arg(audit_record_zone, td, M_WAITOK); ar->k_ar.ar_event = event; mtx_lock(&audit_mtx); audit_pre_q_len++; mtx_unlock(&audit_mtx); return (ar); } void audit_free(struct kaudit_record *ar) { uma_zfree(audit_record_zone, ar); } void audit_commit(struct kaudit_record *ar, int error, int retval) { au_event_t event; au_class_t class; au_id_t auid; int sorf; struct au_mask *aumask; if (ar == NULL) return; /* * Decide whether to commit the audit record by checking the error * value from the system call and using the appropriate audit mask. */ if (ar->k_ar.ar_subj_auid == AU_DEFAUDITID) aumask = &audit_nae_mask; else aumask = &ar->k_ar.ar_subj_amask; if (error) sorf = AU_PRS_FAILURE; else sorf = AU_PRS_SUCCESS; switch(ar->k_ar.ar_event) { case AUE_OPEN_RWTC: /* * The open syscall always writes a AUE_OPEN_RWTC event; * change it to the proper type of event based on the flags * and the error value. */ ar->k_ar.ar_event = audit_flags_and_error_to_openevent( ar->k_ar.ar_arg_fflags, error); break; case AUE_SYSCTL: ar->k_ar.ar_event = audit_ctlname_to_sysctlevent( ar->k_ar.ar_arg_ctlname, ar->k_ar.ar_valid_arg); break; case AUE_AUDITON: /* Convert the auditon() command to an event. */ ar->k_ar.ar_event = auditon_command_event(ar->k_ar.ar_arg_cmd); break; } auid = ar->k_ar.ar_subj_auid; event = ar->k_ar.ar_event; class = au_event_class(event); ar->k_ar_commit |= AR_COMMIT_KERNEL; if (au_preselect(event, class, aumask, sorf) != 0) ar->k_ar_commit |= AR_PRESELECT_TRAIL; if (audit_pipe_preselect(auid, event, class, sorf, ar->k_ar_commit & AR_PRESELECT_TRAIL) != 0) ar->k_ar_commit |= AR_PRESELECT_PIPE; if ((ar->k_ar_commit & (AR_PRESELECT_TRAIL | AR_PRESELECT_PIPE | AR_PRESELECT_USER_TRAIL | AR_PRESELECT_USER_PIPE)) == 0) { mtx_lock(&audit_mtx); audit_pre_q_len--; mtx_unlock(&audit_mtx); audit_free(ar); return; } ar->k_ar.ar_errno = error; ar->k_ar.ar_retval = retval; nanotime(&ar->k_ar.ar_endtime); /* * Note: it could be that some records initiated while audit was * enabled should still be committed? */ mtx_lock(&audit_mtx); if (audit_suspended || !audit_enabled) { audit_pre_q_len--; mtx_unlock(&audit_mtx); audit_free(ar); return; } /* * Constrain the number of committed audit records based on the * configurable parameter. */ while (audit_q_len >= audit_qctrl.aq_hiwater) cv_wait(&audit_watermark_cv, &audit_mtx); TAILQ_INSERT_TAIL(&audit_q, ar, k_q); audit_q_len++; audit_pre_q_len--; cv_signal(&audit_worker_cv); mtx_unlock(&audit_mtx); } /* * audit_syscall_enter() is called on entry to each system call. It is * responsible for deciding whether or not to audit the call (preselection), * and if so, allocating a per-thread audit record. audit_new() will fill in * basic thread/credential properties. */ void audit_syscall_enter(unsigned short code, struct thread *td) { struct au_mask *aumask; au_class_t class; au_event_t event; au_id_t auid; KASSERT(td->td_ar == NULL, ("audit_syscall_enter: td->td_ar != NULL")); /* * In FreeBSD, each ABI has its own system call table, and hence * mapping of system call codes to audit events. Convert the code to * an audit event identifier using the process system call table * reference. In Darwin, there's only one, so we use the global * symbol for the system call table. No audit record is generated * for bad system calls, as no operation has been performed. */ if (code >= td->td_proc->p_sysent->sv_size) return; event = td->td_proc->p_sysent->sv_table[code].sy_auevent; if (event == AUE_NULL) return; /* * Check which audit mask to use; either the kernel non-attributable * event mask or the process audit mask. */ auid = td->td_ucred->cr_audit.ai_auid; if (auid == AU_DEFAUDITID) aumask = &audit_nae_mask; else aumask = &td->td_ucred->cr_audit.ai_mask; /* * Allocate an audit record, if preselection allows it, and store in * the thread for later use. */ class = au_event_class(event); if (au_preselect(event, class, aumask, AU_PRS_BOTH)) { /* * If we're out of space and need to suspend unprivileged * processes, do that here rather than trying to allocate * another audit record. * * Note: we might wish to be able to continue here in the * future, if the system recovers. That should be possible * by means of checking the condition in a loop around * cv_wait(). It might be desirable to reevaluate whether an * audit record is still required for this event by * re-calling au_preselect(). */ if (audit_in_failure && priv_check(td, PRIV_AUDIT_FAILSTOP) != 0) { cv_wait(&audit_fail_cv, &audit_mtx); panic("audit_failing_stop: thread continued"); } td->td_ar = audit_new(event, td); } else if (audit_pipe_preselect(auid, event, class, AU_PRS_BOTH, 0)) td->td_ar = audit_new(event, td); else td->td_ar = NULL; } /* * audit_syscall_exit() is called from the return of every system call, or in * the event of exit1(), during the execution of exit1(). It is responsible * for committing the audit record, if any, along with return condition. */ void audit_syscall_exit(int error, struct thread *td) { int retval; /* * Commit the audit record as desired; once we pass the record into * audit_commit(), the memory is owned by the audit subsystem. The * return value from the system call is stored on the user thread. * If there was an error, the return value is set to -1, imitating * the behavior of the cerror routine. */ if (error) retval = -1; else retval = td->td_retval[0]; audit_commit(td->td_ar, error, retval); td->td_ar = NULL; } void audit_cred_copy(struct ucred *src, struct ucred *dest) { bcopy(&src->cr_audit, &dest->cr_audit, sizeof(dest->cr_audit)); } void audit_cred_destroy(struct ucred *cred) { } void audit_cred_init(struct ucred *cred) { bzero(&cred->cr_audit, sizeof(cred->cr_audit)); } /* * Initialize audit information for the first kernel process (proc 0) and for * the first user process (init). */ void audit_cred_kproc0(struct ucred *cred) { cred->cr_audit.ai_auid = AU_DEFAUDITID; } void audit_cred_proc1(struct ucred *cred) { cred->cr_audit.ai_auid = AU_DEFAUDITID; } void audit_thread_alloc(struct thread *td) { td->td_ar = NULL; } void audit_thread_free(struct thread *td) { KASSERT(td->td_ar == NULL, ("audit_thread_free: td_ar != NULL")); } Index: stable/7/sys/security/audit/audit_arg.c =================================================================== --- stable/7/sys/security/audit/audit_arg.c (revision 178335) +++ stable/7/sys/security/audit/audit_arg.c (revision 178336) @@ -1,856 +1,857 @@ /* * Copyright (c) 1999-2005 Apple Computer, 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 Computer, 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. - * - * $FreeBSD$ */ + +#include +__FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Calls to manipulate elements of the audit record structure from system * call code. Macro wrappers will prevent this functions from being entered * if auditing is disabled, avoiding the function call cost. We check the * thread audit record pointer anyway, as the audit condition could change, * and pre-selection may not have allocated an audit record for this event. * * XXXAUDIT: Should we assert, in each case, that this field of the record * hasn't already been filled in? */ void audit_arg_addr(void *addr) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_addr = addr; ARG_SET_VALID(ar, ARG_ADDR); } void audit_arg_exit(int status, int retval) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_exitstatus = status; ar->k_ar.ar_arg_exitretval = retval; ARG_SET_VALID(ar, ARG_EXIT); } void audit_arg_len(int len) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_len = len; ARG_SET_VALID(ar, ARG_LEN); } void audit_arg_fd(int fd) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_fd = fd; ARG_SET_VALID(ar, ARG_FD); } void audit_arg_fflags(int fflags) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_fflags = fflags; ARG_SET_VALID(ar, ARG_FFLAGS); } void audit_arg_gid(gid_t gid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_gid = gid; ARG_SET_VALID(ar, ARG_GID); } void audit_arg_uid(uid_t uid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_uid = uid; ARG_SET_VALID(ar, ARG_UID); } void audit_arg_egid(gid_t egid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_egid = egid; ARG_SET_VALID(ar, ARG_EGID); } void audit_arg_euid(uid_t euid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_euid = euid; ARG_SET_VALID(ar, ARG_EUID); } void audit_arg_rgid(gid_t rgid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_rgid = rgid; ARG_SET_VALID(ar, ARG_RGID); } void audit_arg_ruid(uid_t ruid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_ruid = ruid; ARG_SET_VALID(ar, ARG_RUID); } void audit_arg_sgid(gid_t sgid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_sgid = sgid; ARG_SET_VALID(ar, ARG_SGID); } void audit_arg_suid(uid_t suid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_suid = suid; ARG_SET_VALID(ar, ARG_SUID); } void audit_arg_groupset(gid_t *gidset, u_int gidset_size) { int i; struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; for (i = 0; i < gidset_size; i++) ar->k_ar.ar_arg_groups.gidset[i] = gidset[i]; ar->k_ar.ar_arg_groups.gidset_size = gidset_size; ARG_SET_VALID(ar, ARG_GROUPSET); } void audit_arg_login(char *login) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; strlcpy(ar->k_ar.ar_arg_login, login, MAXLOGNAME); ARG_SET_VALID(ar, ARG_LOGIN); } void audit_arg_ctlname(int *name, int namelen) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; bcopy(name, &ar->k_ar.ar_arg_ctlname, namelen * sizeof(int)); ar->k_ar.ar_arg_len = namelen; ARG_SET_VALID(ar, ARG_CTLNAME | ARG_LEN); } void audit_arg_mask(int mask) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_mask = mask; ARG_SET_VALID(ar, ARG_MASK); } void audit_arg_mode(mode_t mode) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_mode = mode; ARG_SET_VALID(ar, ARG_MODE); } void audit_arg_dev(int dev) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_dev = dev; ARG_SET_VALID(ar, ARG_DEV); } void audit_arg_value(long value) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_value = value; ARG_SET_VALID(ar, ARG_VALUE); } void audit_arg_owner(uid_t uid, gid_t gid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_uid = uid; ar->k_ar.ar_arg_gid = gid; ARG_SET_VALID(ar, ARG_UID | ARG_GID); } void audit_arg_pid(pid_t pid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_pid = pid; ARG_SET_VALID(ar, ARG_PID); } void audit_arg_process(struct proc *p) { struct kaudit_record *ar; KASSERT(p != NULL, ("audit_arg_process: p == NULL")); PROC_LOCK_ASSERT(p, MA_OWNED); ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_auid = p->p_ucred->cr_audit.ai_auid; ar->k_ar.ar_arg_euid = p->p_ucred->cr_uid; ar->k_ar.ar_arg_egid = p->p_ucred->cr_groups[0]; ar->k_ar.ar_arg_ruid = p->p_ucred->cr_ruid; ar->k_ar.ar_arg_rgid = p->p_ucred->cr_rgid; ar->k_ar.ar_arg_asid = p->p_ucred->cr_audit.ai_asid; ar->k_ar.ar_arg_termid_addr = p->p_ucred->cr_audit.ai_termid; ar->k_ar.ar_arg_pid = p->p_pid; ARG_SET_VALID(ar, ARG_AUID | ARG_EUID | ARG_EGID | ARG_RUID | ARG_RGID | ARG_ASID | ARG_TERMID_ADDR | ARG_PID | ARG_PROCESS); } void audit_arg_signum(u_int signum) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_signum = signum; ARG_SET_VALID(ar, ARG_SIGNUM); } void audit_arg_socket(int sodomain, int sotype, int soprotocol) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_sockinfo.so_domain = sodomain; ar->k_ar.ar_arg_sockinfo.so_type = sotype; ar->k_ar.ar_arg_sockinfo.so_protocol = soprotocol; ARG_SET_VALID(ar, ARG_SOCKINFO); } void audit_arg_sockaddr(struct thread *td, struct sockaddr *sa) { struct kaudit_record *ar; KASSERT(td != NULL, ("audit_arg_sockaddr: td == NULL")); KASSERT(sa != NULL, ("audit_arg_sockaddr: sa == NULL")); ar = currecord(); if (ar == NULL) return; bcopy(sa, &ar->k_ar.ar_arg_sockaddr, sa->sa_len); switch (sa->sa_family) { case AF_INET: ARG_SET_VALID(ar, ARG_SADDRINET); break; case AF_INET6: ARG_SET_VALID(ar, ARG_SADDRINET6); break; case AF_UNIX: audit_arg_upath(td, ((struct sockaddr_un *)sa)->sun_path, ARG_UPATH1); ARG_SET_VALID(ar, ARG_SADDRUNIX); break; /* XXXAUDIT: default:? */ } } void audit_arg_auid(uid_t auid) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_auid = auid; ARG_SET_VALID(ar, ARG_AUID); } void audit_arg_auditinfo(struct auditinfo *au_info) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_auid = au_info->ai_auid; ar->k_ar.ar_arg_asid = au_info->ai_asid; ar->k_ar.ar_arg_amask.am_success = au_info->ai_mask.am_success; ar->k_ar.ar_arg_amask.am_failure = au_info->ai_mask.am_failure; ar->k_ar.ar_arg_termid.port = au_info->ai_termid.port; ar->k_ar.ar_arg_termid.machine = au_info->ai_termid.machine; ARG_SET_VALID(ar, ARG_AUID | ARG_ASID | ARG_AMASK | ARG_TERMID); } void audit_arg_auditinfo_addr(struct auditinfo_addr *au_info) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_auid = au_info->ai_auid; ar->k_ar.ar_arg_asid = au_info->ai_asid; ar->k_ar.ar_arg_amask.am_success = au_info->ai_mask.am_success; ar->k_ar.ar_arg_amask.am_failure = au_info->ai_mask.am_failure; ar->k_ar.ar_arg_termid_addr.at_type = au_info->ai_termid.at_type; ar->k_ar.ar_arg_termid_addr.at_port = au_info->ai_termid.at_port; ar->k_ar.ar_arg_termid_addr.at_addr[0] = au_info->ai_termid.at_addr[0]; ar->k_ar.ar_arg_termid_addr.at_addr[1] = au_info->ai_termid.at_addr[1]; ar->k_ar.ar_arg_termid_addr.at_addr[2] = au_info->ai_termid.at_addr[2]; ar->k_ar.ar_arg_termid_addr.at_addr[3] = au_info->ai_termid.at_addr[3]; ARG_SET_VALID(ar, ARG_AUID | ARG_ASID | ARG_AMASK | ARG_TERMID_ADDR); } void audit_arg_text(char *text) { struct kaudit_record *ar; KASSERT(text != NULL, ("audit_arg_text: text == NULL")); ar = currecord(); if (ar == NULL) return; /* Invalidate the text string */ ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_TEXT); if (ar->k_ar.ar_arg_text == NULL) ar->k_ar.ar_arg_text = malloc(MAXPATHLEN, M_AUDITTEXT, M_WAITOK); strncpy(ar->k_ar.ar_arg_text, text, MAXPATHLEN); ARG_SET_VALID(ar, ARG_TEXT); } void audit_arg_cmd(int cmd) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_cmd = cmd; ARG_SET_VALID(ar, ARG_CMD); } void audit_arg_svipc_cmd(int cmd) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_svipc_cmd = cmd; ARG_SET_VALID(ar, ARG_SVIPC_CMD); } void audit_arg_svipc_perm(struct ipc_perm *perm) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; bcopy(perm, &ar->k_ar.ar_arg_svipc_perm, sizeof(ar->k_ar.ar_arg_svipc_perm)); ARG_SET_VALID(ar, ARG_SVIPC_PERM); } void audit_arg_svipc_id(int id) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_svipc_id = id; ARG_SET_VALID(ar, ARG_SVIPC_ID); } void audit_arg_svipc_addr(void * addr) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_svipc_addr = addr; ARG_SET_VALID(ar, ARG_SVIPC_ADDR); } void audit_arg_posix_ipc_perm(uid_t uid, gid_t gid, mode_t mode) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_pipc_perm.pipc_uid = uid; ar->k_ar.ar_arg_pipc_perm.pipc_gid = gid; ar->k_ar.ar_arg_pipc_perm.pipc_mode = mode; ARG_SET_VALID(ar, ARG_POSIX_IPC_PERM); } void audit_arg_auditon(union auditon_udata *udata) { struct kaudit_record *ar; ar = currecord(); if (ar == NULL) return; bcopy((void *)udata, &ar->k_ar.ar_arg_auditon, sizeof(ar->k_ar.ar_arg_auditon)); ARG_SET_VALID(ar, ARG_AUDITON); } /* * Audit information about a file, either the file's vnode info, or its * socket address info. */ void audit_arg_file(struct proc *p, struct file *fp) { struct kaudit_record *ar; struct socket *so; struct inpcb *pcb; struct vnode *vp; int vfslocked; ar = currecord(); if (ar == NULL) return; switch (fp->f_type) { case DTYPE_VNODE: case DTYPE_FIFO: /* * XXXAUDIT: Only possibly to record as first vnode? */ vp = fp->f_vnode; vfslocked = VFS_LOCK_GIANT(vp->v_mount); vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, curthread); audit_arg_vnode(vp, ARG_VNODE1); VOP_UNLOCK(vp, 0, curthread); VFS_UNLOCK_GIANT(vfslocked); break; case DTYPE_SOCKET: so = (struct socket *)fp->f_data; if (INP_CHECK_SOCKAF(so, PF_INET)) { SOCK_LOCK(so); ar->k_ar.ar_arg_sockinfo.so_type = so->so_type; ar->k_ar.ar_arg_sockinfo.so_domain = INP_SOCKAF(so); ar->k_ar.ar_arg_sockinfo.so_protocol = so->so_proto->pr_protocol; SOCK_UNLOCK(so); pcb = (struct inpcb *)so->so_pcb; INP_LOCK(pcb); ar->k_ar.ar_arg_sockinfo.so_raddr = pcb->inp_faddr.s_addr; ar->k_ar.ar_arg_sockinfo.so_laddr = pcb->inp_laddr.s_addr; ar->k_ar.ar_arg_sockinfo.so_rport = pcb->inp_fport; ar->k_ar.ar_arg_sockinfo.so_lport = pcb->inp_lport; INP_UNLOCK(pcb); ARG_SET_VALID(ar, ARG_SOCKINFO); } break; default: /* XXXAUDIT: else? */ break; } } /* * Store a path as given by the user process for auditing into the audit * record stored on the user thread. This function will allocate the memory * to store the path info if not already available. This memory will be freed * when the audit record is freed. * * XXXAUDIT: Possibly assert that the memory isn't already allocated? */ void audit_arg_upath(struct thread *td, char *upath, u_int64_t flag) { struct kaudit_record *ar; char **pathp; KASSERT(td != NULL, ("audit_arg_upath: td == NULL")); KASSERT(upath != NULL, ("audit_arg_upath: upath == NULL")); ar = currecord(); if (ar == NULL) return; KASSERT((flag == ARG_UPATH1) || (flag == ARG_UPATH2), ("audit_arg_upath: flag %llu", (unsigned long long)flag)); KASSERT((flag != ARG_UPATH1) || (flag != ARG_UPATH2), ("audit_arg_upath: flag %llu", (unsigned long long)flag)); if (flag == ARG_UPATH1) pathp = &ar->k_ar.ar_arg_upath1; else pathp = &ar->k_ar.ar_arg_upath2; if (*pathp == NULL) *pathp = malloc(MAXPATHLEN, M_AUDITPATH, M_WAITOK); audit_canon_path(td, upath, *pathp); ARG_SET_VALID(ar, flag); } /* * Function to save the path and vnode attr information into the audit * record. * * It is assumed that the caller will hold any vnode locks necessary to * perform a VOP_GETATTR() on the passed vnode. * * XXX: The attr code is very similar to vfs_vnops.c:vn_stat(), but always * provides access to the generation number as we need that to construct the * BSM file ID. * * XXX: We should accept the process argument from the caller, since it's * very likely they already have a reference. * * XXX: Error handling in this function is poor. * * XXXAUDIT: Possibly KASSERT the path pointer is NULL? */ void audit_arg_vnode(struct vnode *vp, u_int64_t flags) { struct kaudit_record *ar; struct vattr vattr; int error; struct vnode_au_info *vnp; KASSERT(vp != NULL, ("audit_arg_vnode: vp == NULL")); KASSERT((flags == ARG_VNODE1) || (flags == ARG_VNODE2), ("audit_arg_vnode: flags %jd", (intmax_t)flags)); /* * Assume that if the caller is calling audit_arg_vnode() on a * non-MPSAFE vnode, then it will have acquired Giant. */ VFS_ASSERT_GIANT(vp->v_mount); ASSERT_VOP_LOCKED(vp, "audit_arg_vnode"); ar = currecord(); if (ar == NULL) return; /* * XXXAUDIT: The below clears, and then resets the flags for valid * arguments. Ideally, either the new vnode is used, or the old one * would be. */ if (flags & ARG_VNODE1) { ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_VNODE1); vnp = &ar->k_ar.ar_arg_vnode1; } else { ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_VNODE2); vnp = &ar->k_ar.ar_arg_vnode2; } error = VOP_GETATTR(vp, &vattr, curthread->td_ucred, curthread); if (error) { /* XXX: How to handle this case? */ return; } vnp->vn_mode = vattr.va_mode; vnp->vn_uid = vattr.va_uid; vnp->vn_gid = vattr.va_gid; vnp->vn_dev = vattr.va_rdev; vnp->vn_fsid = vattr.va_fsid; vnp->vn_fileid = vattr.va_fileid; vnp->vn_gen = vattr.va_gen; if (flags & ARG_VNODE1) ARG_SET_VALID(ar, ARG_VNODE1); else ARG_SET_VALID(ar, ARG_VNODE2); } /* * Audit the argument strings passed to exec. */ void audit_arg_argv(char *argv, int argc, int length) { struct kaudit_record *ar; if (audit_argv == 0) return; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_argv = malloc(length, M_AUDITTEXT, M_WAITOK); bcopy(argv, ar->k_ar.ar_arg_argv, length); ar->k_ar.ar_arg_argc = argc; ARG_SET_VALID(ar, ARG_ARGV); } /* * Audit the environment strings passed to exec. */ void audit_arg_envv(char *envv, int envc, int length) { struct kaudit_record *ar; if (audit_arge == 0) return; ar = currecord(); if (ar == NULL) return; ar->k_ar.ar_arg_envv = malloc(length, M_AUDITTEXT, M_WAITOK); bcopy(envv, ar->k_ar.ar_arg_envv, length); ar->k_ar.ar_arg_envc = envc; ARG_SET_VALID(ar, ARG_ENVV); } /* * The close() system call uses it's own audit call to capture the path/vnode * information because those pieces are not easily obtained within the system * call itself. */ void audit_sysclose(struct thread *td, int fd) { struct kaudit_record *ar; struct vnode *vp; struct file *fp; int vfslocked; KASSERT(td != NULL, ("audit_sysclose: td == NULL")); ar = currecord(); if (ar == NULL) return; audit_arg_fd(fd); if (getvnode(td->td_proc->p_fd, fd, &fp) != 0) return; vp = fp->f_vnode; vfslocked = VFS_LOCK_GIANT(vp->v_mount); vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td); audit_arg_vnode(vp, ARG_VNODE1); VOP_UNLOCK(vp, 0, td); VFS_UNLOCK_GIANT(vfslocked); fdrop(fp, td); } Index: stable/7/sys/security/audit/audit_bsm.c =================================================================== --- stable/7/sys/security/audit/audit_bsm.c (revision 178335) +++ stable/7/sys/security/audit/audit_bsm.c (revision 178336) @@ -1,1448 +1,1449 @@ /* * Copyright (c) 1999-2005 Apple Computer, 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 Computer, 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. - * - * $FreeBSD$ */ + +#include +__FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MALLOC_DEFINE(M_AUDITBSM, "audit_bsm", "Audit BSM data"); static void audit_sys_auditon(struct audit_record *ar, struct au_record *rec); /* * Initialize the BSM auditing subsystem. */ void kau_init(void) { au_evclassmap_init(); } /* * This call reserves memory for the audit record. Memory must be guaranteed * before any auditable event can be generated. The au_record structure * maintains a reference to the memory allocated above and also the list of * tokens associated with this record */ static struct au_record * kau_open(void) { struct au_record *rec; rec = malloc(sizeof(*rec), M_AUDITBSM, M_WAITOK); rec->data = NULL; TAILQ_INIT(&rec->token_q); rec->len = 0; rec->used = 1; return (rec); } /* * Store the token with the record descriptor. */ static void kau_write(struct au_record *rec, struct au_token *tok) { KASSERT(tok != NULL, ("kau_write: tok == NULL")); TAILQ_INSERT_TAIL(&rec->token_q, tok, tokens); rec->len += tok->len; } /* * Close out the audit record by adding the header token, identifying any * missing tokens. Write out the tokens to the record memory. */ static void kau_close(struct au_record *rec, struct timespec *ctime, short event) { u_char *dptr; size_t tot_rec_size; token_t *cur, *hdr, *trail; struct timeval tm; tot_rec_size = rec->len + AUDIT_HEADER_SIZE + AUDIT_TRAILER_SIZE; rec->data = malloc(tot_rec_size, M_AUDITBSM, M_WAITOK | M_ZERO); tm.tv_usec = ctime->tv_nsec / 1000; tm.tv_sec = ctime->tv_sec; hdr = au_to_header32_tm(tot_rec_size, event, 0, tm); TAILQ_INSERT_HEAD(&rec->token_q, hdr, tokens); trail = au_to_trailer(tot_rec_size); TAILQ_INSERT_TAIL(&rec->token_q, trail, tokens); rec->len = tot_rec_size; dptr = rec->data; TAILQ_FOREACH(cur, &rec->token_q, tokens) { memcpy(dptr, cur->t_data, cur->len); dptr += cur->len; } } /* * Free a BSM audit record by releasing all the tokens and clearing the audit * record information. */ void kau_free(struct au_record *rec) { struct au_token *tok; /* Free the token list. */ while ((tok = TAILQ_FIRST(&rec->token_q))) { TAILQ_REMOVE(&rec->token_q, tok, tokens); free(tok->t_data, M_AUDITBSM); free(tok, M_AUDITBSM); } rec->used = 0; rec->len = 0; free(rec->data, M_AUDITBSM); free(rec, M_AUDITBSM); } /* * XXX: May want turn some (or all) of these macros into functions in order * to reduce the generated code sized. * * XXXAUDIT: These macros assume that 'kar', 'ar', 'rec', and 'tok' in the * caller are OK with this. */ #define UPATH1_TOKENS do { \ if (ARG_IS_VALID(kar, ARG_UPATH1)) { \ tok = au_to_path(ar->ar_arg_upath1); \ kau_write(rec, tok); \ } \ } while (0) #define UPATH2_TOKENS do { \ if (ARG_IS_VALID(kar, ARG_UPATH2)) { \ tok = au_to_path(ar->ar_arg_upath2); \ kau_write(rec, tok); \ } \ } while (0) #define VNODE1_TOKENS do { \ if (ARG_IS_VALID(kar, ARG_VNODE1)) { \ tok = au_to_attr32(&ar->ar_arg_vnode1); \ kau_write(rec, tok); \ } \ } while (0) #define UPATH1_VNODE1_TOKENS do { \ if (ARG_IS_VALID(kar, ARG_UPATH1)) { \ UPATH1_TOKENS; \ } \ if (ARG_IS_VALID(kar, ARG_VNODE1)) { \ tok = au_to_attr32(&ar->ar_arg_vnode1); \ kau_write(rec, tok); \ } \ } while (0) #define VNODE2_TOKENS do { \ if (ARG_IS_VALID(kar, ARG_VNODE2)) { \ tok = au_to_attr32(&ar->ar_arg_vnode2); \ kau_write(rec, tok); \ } \ } while (0) #define FD_VNODE1_TOKENS do { \ if (ARG_IS_VALID(kar, ARG_VNODE1)) { \ if (ARG_IS_VALID(kar, ARG_FD)) { \ tok = au_to_arg32(1, "fd", ar->ar_arg_fd); \ kau_write(rec, tok); \ } \ tok = au_to_attr32(&ar->ar_arg_vnode1); \ kau_write(rec, tok); \ } else { \ if (ARG_IS_VALID(kar, ARG_FD)) { \ tok = au_to_arg32(1, "non-file: fd", \ ar->ar_arg_fd); \ kau_write(rec, tok); \ } \ } \ } while (0) #define PROCESS_PID_TOKENS(argn) do { \ if ((ar->ar_arg_pid > 0) /* Reference a single process */ \ && (ARG_IS_VALID(kar, ARG_PROCESS))) { \ tok = au_to_process32_ex(ar->ar_arg_auid, \ ar->ar_arg_euid, ar->ar_arg_egid, \ ar->ar_arg_ruid, ar->ar_arg_rgid, \ ar->ar_arg_pid, ar->ar_arg_asid, \ &ar->ar_arg_termid_addr); \ kau_write(rec, tok); \ } else if (ARG_IS_VALID(kar, ARG_PID)) { \ tok = au_to_arg32(argn, "process", ar->ar_arg_pid); \ kau_write(rec, tok); \ } \ } while (0) \ #define EXTATTR_TOKENS do { \ if (ARG_IS_VALID(kar, ARG_VALUE)) { \ switch (ar->ar_arg_value) { \ case EXTATTR_NAMESPACE_USER: \ tok = au_to_text(EXTATTR_NAMESPACE_USER_STRING);\ break; \ case EXTATTR_NAMESPACE_SYSTEM: \ tok = au_to_text(EXTATTR_NAMESPACE_SYSTEM_STRING);\ break; \ default: \ tok = au_to_arg32(3, "attrnamespace", \ ar->ar_arg_value); \ break; \ } \ kau_write(rec, tok); \ } \ /* attrname is in the text field */ \ if (ARG_IS_VALID(kar, ARG_TEXT)) { \ tok = au_to_text(ar->ar_arg_text); \ kau_write(rec, tok); \ } \ } while (0) /* * Implement auditing for the auditon() system call. The audit tokens that * are generated depend on the command that was sent into the auditon() * system call. */ static void audit_sys_auditon(struct audit_record *ar, struct au_record *rec) { struct au_token *tok; switch (ar->ar_arg_cmd) { case A_SETPOLICY: if (sizeof(ar->ar_arg_auditon.au_flags) > 4) tok = au_to_arg64(1, "policy", ar->ar_arg_auditon.au_flags); else tok = au_to_arg32(1, "policy", ar->ar_arg_auditon.au_flags); kau_write(rec, tok); break; case A_SETKMASK: tok = au_to_arg32(2, "setkmask:as_success", ar->ar_arg_auditon.au_mask.am_success); kau_write(rec, tok); tok = au_to_arg32(2, "setkmask:as_failure", ar->ar_arg_auditon.au_mask.am_failure); kau_write(rec, tok); break; case A_SETQCTRL: tok = au_to_arg32(3, "setqctrl:aq_hiwater", ar->ar_arg_auditon.au_qctrl.aq_hiwater); kau_write(rec, tok); tok = au_to_arg32(3, "setqctrl:aq_lowater", ar->ar_arg_auditon.au_qctrl.aq_lowater); kau_write(rec, tok); tok = au_to_arg32(3, "setqctrl:aq_bufsz", ar->ar_arg_auditon.au_qctrl.aq_bufsz); kau_write(rec, tok); tok = au_to_arg32(3, "setqctrl:aq_delay", ar->ar_arg_auditon.au_qctrl.aq_delay); kau_write(rec, tok); tok = au_to_arg32(3, "setqctrl:aq_minfree", ar->ar_arg_auditon.au_qctrl.aq_minfree); kau_write(rec, tok); break; case A_SETUMASK: tok = au_to_arg32(3, "setumask:as_success", ar->ar_arg_auditon.au_auinfo.ai_mask.am_success); kau_write(rec, tok); tok = au_to_arg32(3, "setumask:as_failure", ar->ar_arg_auditon.au_auinfo.ai_mask.am_failure); kau_write(rec, tok); break; case A_SETSMASK: tok = au_to_arg32(3, "setsmask:as_success", ar->ar_arg_auditon.au_auinfo.ai_mask.am_success); kau_write(rec, tok); tok = au_to_arg32(3, "setsmask:as_failure", ar->ar_arg_auditon.au_auinfo.ai_mask.am_failure); kau_write(rec, tok); break; case A_SETCOND: if (sizeof(ar->ar_arg_auditon.au_cond) > 4) tok = au_to_arg64(3, "setcond", ar->ar_arg_auditon.au_cond); else tok = au_to_arg32(3, "setcond", ar->ar_arg_auditon.au_cond); kau_write(rec, tok); break; case A_SETCLASS: tok = au_to_arg32(2, "setclass:ec_event", ar->ar_arg_auditon.au_evclass.ec_number); kau_write(rec, tok); tok = au_to_arg32(3, "setclass:ec_class", ar->ar_arg_auditon.au_evclass.ec_class); kau_write(rec, tok); break; case A_SETPMASK: tok = au_to_arg32(2, "setpmask:as_success", ar->ar_arg_auditon.au_aupinfo.ap_mask.am_success); kau_write(rec, tok); tok = au_to_arg32(2, "setpmask:as_failure", ar->ar_arg_auditon.au_aupinfo.ap_mask.am_failure); kau_write(rec, tok); break; case A_SETFSIZE: tok = au_to_arg32(2, "setfsize:filesize", ar->ar_arg_auditon.au_fstat.af_filesz); kau_write(rec, tok); break; default: break; } } /* * Convert an internal kernel audit record to a BSM record and return a * success/failure indicator. The BSM record is passed as an out parameter to * this function. * * Return conditions: * BSM_SUCCESS: The BSM record is valid * BSM_FAILURE: Failure; the BSM record is NULL. * BSM_NOAUDIT: The event is not auditable for BSM; the BSM record is NULL. */ int kaudit_to_bsm(struct kaudit_record *kar, struct au_record **pau) { struct au_token *tok, *subj_tok; struct au_record *rec; au_tid_t tid; struct audit_record *ar; int ctr; KASSERT(kar != NULL, ("kaudit_to_bsm: kar == NULL")); *pau = NULL; ar = &kar->k_ar; rec = kau_open(); /* Create the subject token */ switch (ar->ar_subj_term_addr.at_type) { case AU_IPv4: tid.port = ar->ar_subj_term_addr.at_port; tid.machine = ar->ar_subj_term_addr.at_addr[0]; subj_tok = au_to_subject32(ar->ar_subj_auid, /* audit ID */ ar->ar_subj_cred.cr_uid, /* eff uid */ ar->ar_subj_egid, /* eff group id */ ar->ar_subj_ruid, /* real uid */ ar->ar_subj_rgid, /* real group id */ ar->ar_subj_pid, /* process id */ ar->ar_subj_asid, /* session ID */ &tid); break; case AU_IPv6: subj_tok = au_to_subject32_ex(ar->ar_subj_auid, ar->ar_subj_cred.cr_uid, ar->ar_subj_egid, ar->ar_subj_ruid, ar->ar_subj_rgid, ar->ar_subj_pid, ar->ar_subj_asid, &ar->ar_subj_term_addr); break; default: bzero(&tid, sizeof(tid)); subj_tok = au_to_subject32(ar->ar_subj_auid, ar->ar_subj_cred.cr_uid, ar->ar_subj_egid, ar->ar_subj_ruid, ar->ar_subj_rgid, ar->ar_subj_pid, ar->ar_subj_asid, &tid); } /* * The logic inside each case fills in the tokens required for the * event, except for the header, trailer, and return tokens. The * header and trailer tokens are added by the kau_close() function. * The return token is added outside of the switch statement. */ switch(ar->ar_event) { case AUE_ACCEPT: case AUE_BIND: case AUE_LISTEN: case AUE_CONNECT: case AUE_RECV: case AUE_RECVFROM: case AUE_RECVMSG: case AUE_SEND: case AUE_SENDFILE: case AUE_SENDMSG: case AUE_SENDTO: /* * Socket-related events. */ if (ARG_IS_VALID(kar, ARG_FD)) { tok = au_to_arg32(1, "fd", ar->ar_arg_fd); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_SADDRINET)) { tok = au_to_sock_inet((struct sockaddr_in *) &ar->ar_arg_sockaddr); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_SADDRUNIX)) { tok = au_to_sock_unix((struct sockaddr_un *) &ar->ar_arg_sockaddr); kau_write(rec, tok); UPATH1_TOKENS; } /* XXX Need to handle ARG_SADDRINET6 */ break; case AUE_SOCKET: case AUE_SOCKETPAIR: if (ARG_IS_VALID(kar, ARG_SOCKINFO)) { tok = au_to_arg32(1,"domain", ar->ar_arg_sockinfo.so_domain); kau_write(rec, tok); tok = au_to_arg32(2,"type", ar->ar_arg_sockinfo.so_type); kau_write(rec, tok); tok = au_to_arg32(3,"protocol", ar->ar_arg_sockinfo.so_protocol); kau_write(rec, tok); } break; case AUE_SETSOCKOPT: case AUE_SHUTDOWN: if (ARG_IS_VALID(kar, ARG_FD)) { tok = au_to_arg32(1, "fd", ar->ar_arg_fd); kau_write(rec, tok); } break; case AUE_ACCT: if (ARG_IS_VALID(kar, ARG_UPATH1)) { UPATH1_VNODE1_TOKENS; } else { tok = au_to_arg32(1, "accounting off", 0); kau_write(rec, tok); } break; case AUE_SETAUID: if (ARG_IS_VALID(kar, ARG_AUID)) { tok = au_to_arg32(2, "setauid", ar->ar_arg_auid); kau_write(rec, tok); } break; case AUE_SETAUDIT: if (ARG_IS_VALID(kar, ARG_AUID) && ARG_IS_VALID(kar, ARG_ASID) && ARG_IS_VALID(kar, ARG_AMASK) && ARG_IS_VALID(kar, ARG_TERMID)) { tok = au_to_arg32(1, "setaudit:auid", ar->ar_arg_auid); kau_write(rec, tok); tok = au_to_arg32(1, "setaudit:port", ar->ar_arg_termid.port); kau_write(rec, tok); tok = au_to_arg32(1, "setaudit:machine", ar->ar_arg_termid.machine); kau_write(rec, tok); tok = au_to_arg32(1, "setaudit:as_success", ar->ar_arg_amask.am_success); kau_write(rec, tok); tok = au_to_arg32(1, "setaudit:as_failure", ar->ar_arg_amask.am_failure); kau_write(rec, tok); tok = au_to_arg32(1, "setaudit:asid", ar->ar_arg_asid); kau_write(rec, tok); } break; case AUE_SETAUDIT_ADDR: if (ARG_IS_VALID(kar, ARG_AUID) && ARG_IS_VALID(kar, ARG_ASID) && ARG_IS_VALID(kar, ARG_AMASK) && ARG_IS_VALID(kar, ARG_TERMID_ADDR)) { tok = au_to_arg32(1, "setaudit_addr:auid", ar->ar_arg_auid); kau_write(rec, tok); tok = au_to_arg32(1, "setaudit_addr:as_success", ar->ar_arg_amask.am_success); kau_write(rec, tok); tok = au_to_arg32(1, "setaudit_addr:as_failure", ar->ar_arg_amask.am_failure); kau_write(rec, tok); tok = au_to_arg32(1, "setaudit_addr:asid", ar->ar_arg_asid); kau_write(rec, tok); tok = au_to_arg32(1, "setaudit_addr:type", ar->ar_arg_termid_addr.at_type); kau_write(rec, tok); tok = au_to_arg32(1, "setaudit_addr:port", ar->ar_arg_termid_addr.at_port); kau_write(rec, tok); if (ar->ar_arg_termid_addr.at_type == AU_IPv6) tok = au_to_in_addr_ex((struct in6_addr *) &ar->ar_arg_termid_addr.at_addr[0]); if (ar->ar_arg_termid_addr.at_type == AU_IPv4) tok = au_to_in_addr((struct in_addr *) &ar->ar_arg_termid_addr.at_addr[0]); kau_write(rec, tok); } break; case AUE_AUDITON: /* * For AUDITON commands without own event, audit the cmd. */ if (ARG_IS_VALID(kar, ARG_CMD)) { tok = au_to_arg32(1, "cmd", ar->ar_arg_cmd); kau_write(rec, tok); } /* fall thru */ case AUE_AUDITON_GETCAR: case AUE_AUDITON_GETCLASS: case AUE_AUDITON_GETCOND: case AUE_AUDITON_GETCWD: case AUE_AUDITON_GETKMASK: case AUE_AUDITON_GETSTAT: case AUE_AUDITON_GPOLICY: case AUE_AUDITON_GQCTRL: case AUE_AUDITON_SETCLASS: case AUE_AUDITON_SETCOND: case AUE_AUDITON_SETKMASK: case AUE_AUDITON_SETSMASK: case AUE_AUDITON_SETSTAT: case AUE_AUDITON_SETUMASK: case AUE_AUDITON_SPOLICY: case AUE_AUDITON_SQCTRL: if (ARG_IS_VALID(kar, ARG_AUDITON)) audit_sys_auditon(ar, rec); break; case AUE_AUDITCTL: UPATH1_VNODE1_TOKENS; break; case AUE_EXIT: if (ARG_IS_VALID(kar, ARG_EXIT)) { tok = au_to_exit(ar->ar_arg_exitretval, ar->ar_arg_exitstatus); kau_write(rec, tok); } break; case AUE_ADJTIME: case AUE_CLOCK_SETTIME: case AUE_AUDIT: case AUE_DUP2: case AUE_GETAUDIT: case AUE_GETAUDIT_ADDR: case AUE_GETAUID: case AUE_GETCWD: case AUE_GETFSSTAT: case AUE_GETRESUID: case AUE_GETRESGID: case AUE_KQUEUE: case AUE_LSEEK: case AUE_MODLOAD: case AUE_MODUNLOAD: case AUE_MSGSYS: case AUE_NFS_SVC: case AUE_NTP_ADJTIME: case AUE_PIPE: case AUE_PROFILE: case AUE_RTPRIO: case AUE_SEMSYS: case AUE_SHMSYS: case AUE_SETPGRP: case AUE_SETRLIMIT: case AUE_SETSID: case AUE_SETTIMEOFDAY: case AUE_SYSARCH: /* * Header, subject, and return tokens added at end. */ break; case AUE_MKFIFO: if (ARG_IS_VALID(kar, ARG_MODE)) { tok = au_to_arg32(2, "mode", ar->ar_arg_mode); kau_write(rec, tok); } /* fall through */ case AUE_ACCESS: case AUE_CHDIR: case AUE_CHROOT: case AUE_EACCESS: case AUE_GETATTRLIST: case AUE_JAIL: case AUE_LUTIMES: case AUE_NFS_GETFH: case AUE_LSTAT: case AUE_PATHCONF: case AUE_READLINK: case AUE_REVOKE: case AUE_RMDIR: case AUE_SEARCHFS: case AUE_SETATTRLIST: case AUE_STAT: case AUE_STATFS: case AUE_SWAPON: case AUE_SWAPOFF: case AUE_TRUNCATE: case AUE_UNDELETE: case AUE_UNLINK: case AUE_UTIMES: UPATH1_VNODE1_TOKENS; break; case AUE_FHSTATFS: case AUE_FHOPEN: case AUE_FHSTAT: /* XXXRW: Need to audit vnode argument. */ break; case AUE_CHFLAGS: case AUE_LCHFLAGS: if (ARG_IS_VALID(kar, ARG_FFLAGS)) { tok = au_to_arg32(2, "flags", ar->ar_arg_fflags); kau_write(rec, tok); } UPATH1_VNODE1_TOKENS; break; case AUE_CHMOD: case AUE_LCHMOD: if (ARG_IS_VALID(kar, ARG_MODE)) { tok = au_to_arg32(2, "new file mode", ar->ar_arg_mode); kau_write(rec, tok); } UPATH1_VNODE1_TOKENS; break; case AUE_CHOWN: case AUE_LCHOWN: if (ARG_IS_VALID(kar, ARG_UID)) { tok = au_to_arg32(2, "new file uid", ar->ar_arg_uid); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_GID)) { tok = au_to_arg32(3, "new file gid", ar->ar_arg_gid); kau_write(rec, tok); } UPATH1_VNODE1_TOKENS; break; case AUE_EXCHANGEDATA: UPATH1_VNODE1_TOKENS; UPATH2_TOKENS; break; case AUE_CLOSE: if (ARG_IS_VALID(kar, ARG_FD)) { tok = au_to_arg32(2, "fd", ar->ar_arg_fd); kau_write(rec, tok); } UPATH1_VNODE1_TOKENS; break; case AUE_EXTATTRCTL: UPATH1_VNODE1_TOKENS; if (ARG_IS_VALID(kar, ARG_CMD)) { tok = au_to_arg32(2, "cmd", ar->ar_arg_cmd); kau_write(rec, tok); } /* extattrctl(2) filename parameter is in upath2/vnode2 */ UPATH2_TOKENS; VNODE2_TOKENS; EXTATTR_TOKENS; break; case AUE_EXTATTR_GET_FILE: case AUE_EXTATTR_SET_FILE: case AUE_EXTATTR_LIST_FILE: case AUE_EXTATTR_DELETE_FILE: case AUE_EXTATTR_GET_LINK: case AUE_EXTATTR_SET_LINK: case AUE_EXTATTR_LIST_LINK: case AUE_EXTATTR_DELETE_LINK: UPATH1_VNODE1_TOKENS; EXTATTR_TOKENS; break; case AUE_EXTATTR_GET_FD: case AUE_EXTATTR_SET_FD: case AUE_EXTATTR_LIST_FD: case AUE_EXTATTR_DELETE_FD: if (ARG_IS_VALID(kar, ARG_FD)) { tok = au_to_arg32(2, "fd", ar->ar_arg_fd); kau_write(rec, tok); } EXTATTR_TOKENS; break; case AUE_EXECVE: if (ARG_IS_VALID(kar, ARG_ARGV)) { tok = au_to_exec_args(ar->ar_arg_argv, ar->ar_arg_argc); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_ENVV)) { tok = au_to_exec_env(ar->ar_arg_envv, ar->ar_arg_envc); kau_write(rec, tok); } UPATH1_VNODE1_TOKENS; break; case AUE_FCHMOD: if (ARG_IS_VALID(kar, ARG_MODE)) { tok = au_to_arg32(2, "new file mode", ar->ar_arg_mode); kau_write(rec, tok); } FD_VNODE1_TOKENS; break; /* * XXXRW: Some of these need to handle non-vnode cases as well. */ case AUE_FCHDIR: case AUE_FPATHCONF: case AUE_FSTAT: case AUE_FSTATFS: case AUE_FSYNC: case AUE_FTRUNCATE: case AUE_FUTIMES: case AUE_GETDIRENTRIES: case AUE_GETDIRENTRIESATTR: case AUE_POLL: case AUE_READ: case AUE_READV: case AUE_WRITE: case AUE_WRITEV: FD_VNODE1_TOKENS; break; case AUE_FCHOWN: if (ARG_IS_VALID(kar, ARG_UID)) { tok = au_to_arg32(2, "new file uid", ar->ar_arg_uid); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_GID)) { tok = au_to_arg32(3, "new file gid", ar->ar_arg_gid); kau_write(rec, tok); } FD_VNODE1_TOKENS; break; case AUE_FCNTL: if (ar->ar_arg_cmd == F_GETLK || ar->ar_arg_cmd == F_SETLK || ar->ar_arg_cmd == F_SETLKW) { if (ARG_IS_VALID(kar, ARG_CMD)) { tok = au_to_arg32(2, "cmd", ar->ar_arg_cmd); kau_write(rec, tok); } FD_VNODE1_TOKENS; } break; case AUE_FCHFLAGS: if (ARG_IS_VALID(kar, ARG_FFLAGS)) { tok = au_to_arg32(2, "flags", ar->ar_arg_fflags); kau_write(rec, tok); } FD_VNODE1_TOKENS; break; case AUE_FLOCK: if (ARG_IS_VALID(kar, ARG_CMD)) { tok = au_to_arg32(2, "operation", ar->ar_arg_cmd); kau_write(rec, tok); } FD_VNODE1_TOKENS; break; case AUE_RFORK: if (ARG_IS_VALID(kar, ARG_FFLAGS)) { tok = au_to_arg32(1, "flags", ar->ar_arg_fflags); kau_write(rec, tok); } /* fall through */ case AUE_FORK: case AUE_VFORK: if (ARG_IS_VALID(kar, ARG_PID)) { tok = au_to_arg32(0, "child PID", ar->ar_arg_pid); kau_write(rec, tok); } break; case AUE_IOCTL: if (ARG_IS_VALID(kar, ARG_CMD)) { tok = au_to_arg32(2, "cmd", ar->ar_arg_cmd); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_ADDR)) { tok = au_to_arg32(1, "arg", (u_int32_t)(uintptr_t)ar->ar_arg_addr); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_VNODE1)) FD_VNODE1_TOKENS; else { if (ARG_IS_VALID(kar, ARG_SOCKINFO)) { tok = kau_to_socket(&ar->ar_arg_sockinfo); kau_write(rec, tok); } else { if (ARG_IS_VALID(kar, ARG_FD)) { tok = au_to_arg32(1, "fd", ar->ar_arg_fd); kau_write(rec, tok); } } } break; case AUE_KILL: case AUE_KILLPG: if (ARG_IS_VALID(kar, ARG_SIGNUM)) { tok = au_to_arg32(2, "signal", ar->ar_arg_signum); kau_write(rec, tok); } PROCESS_PID_TOKENS(1); break; case AUE_KTRACE: if (ARG_IS_VALID(kar, ARG_CMD)) { tok = au_to_arg32(2, "ops", ar->ar_arg_cmd); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_VALUE)) { tok = au_to_arg32(3, "trpoints", ar->ar_arg_value); kau_write(rec, tok); } PROCESS_PID_TOKENS(4); UPATH1_VNODE1_TOKENS; break; case AUE_LINK: case AUE_RENAME: UPATH1_VNODE1_TOKENS; UPATH2_TOKENS; break; case AUE_LOADSHFILE: if (ARG_IS_VALID(kar, ARG_ADDR)) { tok = au_to_arg32(4, "base addr", (u_int32_t)(uintptr_t)ar->ar_arg_addr); kau_write(rec, tok); } UPATH1_VNODE1_TOKENS; break; case AUE_MKDIR: if (ARG_IS_VALID(kar, ARG_MODE)) { tok = au_to_arg32(2, "mode", ar->ar_arg_mode); kau_write(rec, tok); } UPATH1_VNODE1_TOKENS; break; case AUE_MKNOD: if (ARG_IS_VALID(kar, ARG_MODE)) { tok = au_to_arg32(2, "mode", ar->ar_arg_mode); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_DEV)) { tok = au_to_arg32(3, "dev", ar->ar_arg_dev); kau_write(rec, tok); } UPATH1_VNODE1_TOKENS; break; case AUE_MMAP: case AUE_MUNMAP: case AUE_MPROTECT: case AUE_MLOCK: case AUE_MUNLOCK: case AUE_MINHERIT: if (ARG_IS_VALID(kar, ARG_ADDR)) { tok = au_to_arg32(1, "addr", (u_int32_t)(uintptr_t)ar->ar_arg_addr); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_LEN)) { tok = au_to_arg32(2, "len", ar->ar_arg_len); kau_write(rec, tok); } if (ar->ar_event == AUE_MMAP) FD_VNODE1_TOKENS; if (ar->ar_event == AUE_MPROTECT) { if (ARG_IS_VALID(kar, ARG_VALUE)) { tok = au_to_arg32(3, "protection", ar->ar_arg_value); kau_write(rec, tok); } } if (ar->ar_event == AUE_MINHERIT) { if (ARG_IS_VALID(kar, ARG_VALUE)) { tok = au_to_arg32(3, "inherit", ar->ar_arg_value); kau_write(rec, tok); } } break; case AUE_MOUNT: case AUE_NMOUNT: /* XXX Need to handle NFS mounts */ if (ARG_IS_VALID(kar, ARG_FFLAGS)) { tok = au_to_arg32(3, "flags", ar->ar_arg_fflags); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_TEXT)) { tok = au_to_text(ar->ar_arg_text); kau_write(rec, tok); } /* fall through */ case AUE_UMOUNT: UPATH1_VNODE1_TOKENS; break; case AUE_MSGCTL: ar->ar_event = audit_msgctl_to_event(ar->ar_arg_svipc_cmd); /* Fall through */ case AUE_MSGRCV: case AUE_MSGSND: tok = au_to_arg32(1, "msg ID", ar->ar_arg_svipc_id); kau_write(rec, tok); if (ar->ar_errno != EINVAL) { tok = au_to_ipc(AT_IPC_MSG, ar->ar_arg_svipc_id); kau_write(rec, tok); } break; case AUE_MSGGET: if (ar->ar_errno == 0) { if (ARG_IS_VALID(kar, ARG_SVIPC_ID)) { tok = au_to_ipc(AT_IPC_MSG, ar->ar_arg_svipc_id); kau_write(rec, tok); } } break; case AUE_RESETSHFILE: if (ARG_IS_VALID(kar, ARG_ADDR)) { tok = au_to_arg32(1, "base addr", (u_int32_t)(uintptr_t)ar->ar_arg_addr); kau_write(rec, tok); } break; case AUE_OPEN_RC: case AUE_OPEN_RTC: case AUE_OPEN_RWC: case AUE_OPEN_RWTC: case AUE_OPEN_WC: case AUE_OPEN_WTC: case AUE_CREAT: if (ARG_IS_VALID(kar, ARG_MODE)) { tok = au_to_arg32(3, "mode", ar->ar_arg_mode); kau_write(rec, tok); } /* fall through */ case AUE_OPEN_R: case AUE_OPEN_RT: case AUE_OPEN_RW: case AUE_OPEN_RWT: case AUE_OPEN_W: case AUE_OPEN_WT: if (ARG_IS_VALID(kar, ARG_FFLAGS)) { tok = au_to_arg32(2, "flags", ar->ar_arg_fflags); kau_write(rec, tok); } UPATH1_VNODE1_TOKENS; break; case AUE_PTRACE: if (ARG_IS_VALID(kar, ARG_CMD)) { tok = au_to_arg32(1, "request", ar->ar_arg_cmd); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_ADDR)) { tok = au_to_arg32(3, "addr", (u_int32_t)(uintptr_t)ar->ar_arg_addr); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_VALUE)) { tok = au_to_arg32(4, "data", ar->ar_arg_value); kau_write(rec, tok); } PROCESS_PID_TOKENS(2); break; case AUE_QUOTACTL: if (ARG_IS_VALID(kar, ARG_CMD)) { tok = au_to_arg32(2, "command", ar->ar_arg_cmd); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_UID)) { tok = au_to_arg32(3, "uid", ar->ar_arg_uid); kau_write(rec, tok); } UPATH1_VNODE1_TOKENS; break; case AUE_REBOOT: if (ARG_IS_VALID(kar, ARG_CMD)) { tok = au_to_arg32(1, "howto", ar->ar_arg_cmd); kau_write(rec, tok); } break; case AUE_SEMCTL: ar->ar_event = audit_semctl_to_event(ar->ar_arg_svipc_cmd); /* Fall through */ case AUE_SEMOP: if (ARG_IS_VALID(kar, ARG_SVIPC_ID)) { tok = au_to_arg32(1, "sem ID", ar->ar_arg_svipc_id); kau_write(rec, tok); if (ar->ar_errno != EINVAL) { tok = au_to_ipc(AT_IPC_SEM, ar->ar_arg_svipc_id); kau_write(rec, tok); } } break; case AUE_SEMGET: if (ar->ar_errno == 0) { if (ARG_IS_VALID(kar, ARG_SVIPC_ID)) { tok = au_to_ipc(AT_IPC_SEM, ar->ar_arg_svipc_id); kau_write(rec, tok); } } break; case AUE_SETEGID: if (ARG_IS_VALID(kar, ARG_EGID)) { tok = au_to_arg32(1, "gid", ar->ar_arg_egid); kau_write(rec, tok); } break; case AUE_SETEUID: if (ARG_IS_VALID(kar, ARG_EUID)) { tok = au_to_arg32(1, "uid", ar->ar_arg_euid); kau_write(rec, tok); } break; case AUE_SETREGID: if (ARG_IS_VALID(kar, ARG_RGID)) { tok = au_to_arg32(1, "rgid", ar->ar_arg_rgid); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_EGID)) { tok = au_to_arg32(2, "egid", ar->ar_arg_egid); kau_write(rec, tok); } break; case AUE_SETREUID: if (ARG_IS_VALID(kar, ARG_RUID)) { tok = au_to_arg32(1, "ruid", ar->ar_arg_ruid); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_EUID)) { tok = au_to_arg32(2, "euid", ar->ar_arg_euid); kau_write(rec, tok); } break; case AUE_SETRESGID: if (ARG_IS_VALID(kar, ARG_RGID)) { tok = au_to_arg32(1, "rgid", ar->ar_arg_rgid); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_EGID)) { tok = au_to_arg32(2, "egid", ar->ar_arg_egid); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_SGID)) { tok = au_to_arg32(3, "sgid", ar->ar_arg_sgid); kau_write(rec, tok); } break; case AUE_SETRESUID: if (ARG_IS_VALID(kar, ARG_RUID)) { tok = au_to_arg32(1, "ruid", ar->ar_arg_ruid); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_EUID)) { tok = au_to_arg32(2, "euid", ar->ar_arg_euid); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_SUID)) { tok = au_to_arg32(3, "suid", ar->ar_arg_suid); kau_write(rec, tok); } break; case AUE_SETGID: if (ARG_IS_VALID(kar, ARG_GID)) { tok = au_to_arg32(1, "gid", ar->ar_arg_gid); kau_write(rec, tok); } break; case AUE_SETUID: if (ARG_IS_VALID(kar, ARG_UID)) { tok = au_to_arg32(1, "uid", ar->ar_arg_uid); kau_write(rec, tok); } break; case AUE_SETGROUPS: if (ARG_IS_VALID(kar, ARG_GROUPSET)) { for(ctr = 0; ctr < ar->ar_arg_groups.gidset_size; ctr++) { tok = au_to_arg32(1, "setgroups", ar->ar_arg_groups.gidset[ctr]); kau_write(rec, tok); } } break; case AUE_SETLOGIN: if (ARG_IS_VALID(kar, ARG_TEXT)) { tok = au_to_text(ar->ar_arg_text); kau_write(rec, tok); } break; case AUE_SETPRIORITY: if (ARG_IS_VALID(kar, ARG_CMD)) { tok = au_to_arg32(1, "which", ar->ar_arg_cmd); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_UID)) { tok = au_to_arg32(2, "who", ar->ar_arg_uid); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_VALUE)) { tok = au_to_arg32(2, "priority", ar->ar_arg_value); kau_write(rec, tok); } break; case AUE_SETPRIVEXEC: if (ARG_IS_VALID(kar, ARG_VALUE)) { tok = au_to_arg32(1, "flag", ar->ar_arg_value); kau_write(rec, tok); } break; /* AUE_SHMAT, AUE_SHMCTL, AUE_SHMDT and AUE_SHMGET are SysV IPC */ case AUE_SHMAT: if (ARG_IS_VALID(kar, ARG_SVIPC_ID)) { tok = au_to_arg32(1, "shmid", ar->ar_arg_svipc_id); kau_write(rec, tok); /* XXXAUDIT: Does having the ipc token make sense? */ tok = au_to_ipc(AT_IPC_SHM, ar->ar_arg_svipc_id); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_SVIPC_ADDR)) { tok = au_to_arg32(2, "shmaddr", (int)(uintptr_t)ar->ar_arg_svipc_addr); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_SVIPC_PERM)) { tok = au_to_ipc_perm(&ar->ar_arg_svipc_perm); kau_write(rec, tok); } break; case AUE_SHMCTL: if (ARG_IS_VALID(kar, ARG_SVIPC_ID)) { tok = au_to_arg32(1, "shmid", ar->ar_arg_svipc_id); kau_write(rec, tok); /* XXXAUDIT: Does having the ipc token make sense? */ tok = au_to_ipc(AT_IPC_SHM, ar->ar_arg_svipc_id); kau_write(rec, tok); } switch (ar->ar_arg_svipc_cmd) { case IPC_STAT: ar->ar_event = AUE_SHMCTL_STAT; break; case IPC_RMID: ar->ar_event = AUE_SHMCTL_RMID; break; case IPC_SET: ar->ar_event = AUE_SHMCTL_SET; if (ARG_IS_VALID(kar, ARG_SVIPC_PERM)) { tok = au_to_ipc_perm(&ar->ar_arg_svipc_perm); kau_write(rec, tok); } break; default: break; /* We will audit a bad command */ } break; case AUE_SHMDT: if (ARG_IS_VALID(kar, ARG_SVIPC_ADDR)) { tok = au_to_arg32(1, "shmaddr", (int)(uintptr_t)ar->ar_arg_svipc_addr); kau_write(rec, tok); } break; case AUE_SHMGET: /* This is unusual; the return value is in an argument token */ if (ARG_IS_VALID(kar, ARG_SVIPC_ID)) { tok = au_to_arg32(0, "shmid", ar->ar_arg_svipc_id); kau_write(rec, tok); tok = au_to_ipc(AT_IPC_SHM, ar->ar_arg_svipc_id); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_SVIPC_PERM)) { tok = au_to_ipc_perm(&ar->ar_arg_svipc_perm); kau_write(rec, tok); } break; /* AUE_SHMOPEN, AUE_SHMUNLINK, AUE_SEMOPEN, AUE_SEMCLOSE * and AUE_SEMUNLINK are Posix IPC */ case AUE_SHMOPEN: if (ARG_IS_VALID(kar, ARG_SVIPC_ADDR)) { tok = au_to_arg32(2, "flags", ar->ar_arg_fflags); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_MODE)) { tok = au_to_arg32(3, "mode", ar->ar_arg_mode); kau_write(rec, tok); } case AUE_SHMUNLINK: if (ARG_IS_VALID(kar, ARG_TEXT)) { tok = au_to_text(ar->ar_arg_text); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_POSIX_IPC_PERM)) { /* Create an ipc_perm token */ struct ipc_perm perm; perm.uid = ar->ar_arg_pipc_perm.pipc_uid; perm.gid = ar->ar_arg_pipc_perm.pipc_gid; perm.cuid = ar->ar_arg_pipc_perm.pipc_uid; perm.cgid = ar->ar_arg_pipc_perm.pipc_gid; perm.mode = ar->ar_arg_pipc_perm.pipc_mode; perm.seq = 0; perm.key = 0; tok = au_to_ipc_perm(&perm); kau_write(rec, tok); } break; case AUE_SEMOPEN: if (ARG_IS_VALID(kar, ARG_FFLAGS)) { tok = au_to_arg32(2, "flags", ar->ar_arg_fflags); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_MODE)) { tok = au_to_arg32(3, "mode", ar->ar_arg_mode); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_VALUE)) { tok = au_to_arg32(4, "value", ar->ar_arg_value); kau_write(rec, tok); } /* fall through */ case AUE_SEMUNLINK: if (ARG_IS_VALID(kar, ARG_TEXT)) { tok = au_to_text(ar->ar_arg_text); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_POSIX_IPC_PERM)) { /* Create an ipc_perm token */ struct ipc_perm perm; perm.uid = ar->ar_arg_pipc_perm.pipc_uid; perm.gid = ar->ar_arg_pipc_perm.pipc_gid; perm.cuid = ar->ar_arg_pipc_perm.pipc_uid; perm.cgid = ar->ar_arg_pipc_perm.pipc_gid; perm.mode = ar->ar_arg_pipc_perm.pipc_mode; perm.seq = 0; perm.key = 0; tok = au_to_ipc_perm(&perm); kau_write(rec, tok); } break; case AUE_SEMCLOSE: if (ARG_IS_VALID(kar, ARG_FD)) { tok = au_to_arg32(1, "sem", ar->ar_arg_fd); kau_write(rec, tok); } break; case AUE_SYMLINK: if (ARG_IS_VALID(kar, ARG_TEXT)) { tok = au_to_text(ar->ar_arg_text); kau_write(rec, tok); } UPATH1_VNODE1_TOKENS; break; case AUE_SYSCTL: if (ARG_IS_VALID(kar, ARG_CTLNAME | ARG_LEN)) { for (ctr = 0; ctr < ar->ar_arg_len; ctr++) { tok = au_to_arg32(1, "name", ar->ar_arg_ctlname[ctr]); kau_write(rec, tok); } } if (ARG_IS_VALID(kar, ARG_VALUE)) { tok = au_to_arg32(5, "newval", ar->ar_arg_value); kau_write(rec, tok); } if (ARG_IS_VALID(kar, ARG_TEXT)) { tok = au_to_text(ar->ar_arg_text); kau_write(rec, tok); } break; case AUE_UMASK: if (ARG_IS_VALID(kar, ARG_MASK)) { tok = au_to_arg32(1, "new mask", ar->ar_arg_mask); kau_write(rec, tok); } tok = au_to_arg32(0, "prev mask", ar->ar_retval); kau_write(rec, tok); break; case AUE_WAIT4: if (ARG_IS_VALID(kar, ARG_PID)) { tok = au_to_arg32(0, "pid", ar->ar_arg_pid); kau_write(rec, tok); } break; case AUE_NULL: default: printf("BSM conversion requested for unknown event %d\n", ar->ar_event); /* Write the subject token so it is properly freed here. */ kau_write(rec, subj_tok); kau_free(rec); return (BSM_NOAUDIT); } kau_write(rec, subj_tok); tok = au_to_return32((char)ar->ar_errno, ar->ar_retval); kau_write(rec, tok); /* Every record gets a return token */ kau_close(rec, &ar->ar_endtime, ar->ar_event); *pau = rec; return (BSM_SUCCESS); } /* * Verify that a record is a valid BSM record. This verification is simple * now, but may be expanded on sometime in the future. Return 1 if the * record is good, 0 otherwise. */ int bsm_rec_verify(void *rec) { char c = *(char *)rec; /* * Check the token ID of the first token; it has to be a header * token. * * XXXAUDIT There needs to be a token structure to map a token. * XXXAUDIT 'Shouldn't be simply looking at the first char. */ if ((c != AUT_HEADER32) && (c != AUT_HEADER32_EX) && (c != AUT_HEADER64) && (c != AUT_HEADER64_EX)) return (0); return (1); } Index: stable/7/sys/security/audit/audit_bsm_klib.c =================================================================== --- stable/7/sys/security/audit/audit_bsm_klib.c (revision 178335) +++ stable/7/sys/security/audit/audit_bsm_klib.c (revision 178336) @@ -1,547 +1,548 @@ /* * Copyright (c) 1999-2005 Apple Computer, Inc. * Copyright (c) 2005 Robert N. M. Watson * 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 Computer, 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. - * - * $FreeBSD$ */ + +#include +__FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Hash table functions for the audit event number to event class mask * mapping. */ #define EVCLASSMAP_HASH_TABLE_SIZE 251 struct evclass_elem { au_event_t event; au_class_t class; LIST_ENTRY(evclass_elem) entry; }; struct evclass_list { LIST_HEAD(, evclass_elem) head; }; static MALLOC_DEFINE(M_AUDITEVCLASS, "audit_evclass", "Audit event class"); static struct mtx evclass_mtx; static struct evclass_list evclass_hash[EVCLASSMAP_HASH_TABLE_SIZE]; /* * Look up the class for an audit event in the class mapping table. */ au_class_t au_event_class(au_event_t event) { struct evclass_list *evcl; struct evclass_elem *evc; au_class_t class; mtx_lock(&evclass_mtx); evcl = &evclass_hash[event % EVCLASSMAP_HASH_TABLE_SIZE]; class = 0; LIST_FOREACH(evc, &evcl->head, entry) { if (evc->event == event) { class = evc->class; goto out; } } out: mtx_unlock(&evclass_mtx); return (class); } /* * Insert a event to class mapping. If the event already exists in the * mapping, then replace the mapping with the new one. * * XXX There is currently no constraints placed on the number of mappings. * May want to either limit to a number, or in terms of memory usage. */ void au_evclassmap_insert(au_event_t event, au_class_t class) { struct evclass_list *evcl; struct evclass_elem *evc, *evc_new; /* * Pessimistically, always allocate storage before acquiring mutex. * Free if there is already a mapping for this event. */ evc_new = malloc(sizeof(*evc), M_AUDITEVCLASS, M_WAITOK); mtx_lock(&evclass_mtx); evcl = &evclass_hash[event % EVCLASSMAP_HASH_TABLE_SIZE]; LIST_FOREACH(evc, &evcl->head, entry) { if (evc->event == event) { evc->class = class; mtx_unlock(&evclass_mtx); free(evc_new, M_AUDITEVCLASS); return; } } evc = evc_new; evc->event = event; evc->class = class; LIST_INSERT_HEAD(&evcl->head, evc, entry); mtx_unlock(&evclass_mtx); } void au_evclassmap_init(void) { int i; mtx_init(&evclass_mtx, "evclass_mtx", NULL, MTX_DEF); for (i = 0; i < EVCLASSMAP_HASH_TABLE_SIZE; i++) LIST_INIT(&evclass_hash[i].head); /* * Set up the initial event to class mapping for system calls. * * XXXRW: Really, this should walk all possible audit events, not all * native ABI system calls, as there may be audit events reachable * only through non-native system calls. It also seems a shame to * frob the mutex this early. */ for (i = 0; i < SYS_MAXSYSCALL; i++) { if (sysent[i].sy_auevent != AUE_NULL) au_evclassmap_insert(sysent[i].sy_auevent, 0); } } /* * Check whether an event is aditable by comparing the mask of classes this * event is part of against the given mask. */ int au_preselect(au_event_t event, au_class_t class, au_mask_t *mask_p, int sorf) { au_class_t effmask = 0; if (mask_p == NULL) return (-1); /* * Perform the actual check of the masks against the event. */ if (sorf & AU_PRS_SUCCESS) effmask |= (mask_p->am_success & class); if (sorf & AU_PRS_FAILURE) effmask |= (mask_p->am_failure & class); if (effmask) return (1); else return (0); } /* * Convert sysctl names and present arguments to events. */ au_event_t audit_ctlname_to_sysctlevent(int name[], uint64_t valid_arg) { /* can't parse it - so return the worst case */ if ((valid_arg & (ARG_CTLNAME | ARG_LEN)) != (ARG_CTLNAME | ARG_LEN)) return (AUE_SYSCTL); switch (name[0]) { /* non-admin "lookups" treat them special */ case KERN_OSTYPE: case KERN_OSRELEASE: case KERN_OSREV: case KERN_VERSION: case KERN_ARGMAX: case KERN_CLOCKRATE: case KERN_BOOTTIME: case KERN_POSIX1: case KERN_NGROUPS: case KERN_JOB_CONTROL: case KERN_SAVED_IDS: case KERN_OSRELDATE: case KERN_DUMMY: return (AUE_SYSCTL_NONADMIN); /* only treat the changeable controls as admin */ case KERN_MAXVNODES: case KERN_MAXPROC: case KERN_MAXFILES: case KERN_MAXPROCPERUID: case KERN_MAXFILESPERPROC: case KERN_HOSTID: case KERN_SECURELVL: case KERN_HOSTNAME: case KERN_VNODE: case KERN_PROC: case KERN_FILE: case KERN_PROF: case KERN_NISDOMAINNAME: case KERN_UPDATEINTERVAL: case KERN_NTP_PLL: case KERN_BOOTFILE: case KERN_DUMPDEV: case KERN_IPC: case KERN_PS_STRINGS: case KERN_USRSTACK: case KERN_LOGSIGEXIT: case KERN_IOV_MAX: case KERN_MAXID: return ((valid_arg & ARG_VALUE) ? AUE_SYSCTL : AUE_SYSCTL_NONADMIN); default: return (AUE_SYSCTL); } /* NOTREACHED */ } /* * Convert an open flags specifier into a specific type of open event for * auditing purposes. */ au_event_t audit_flags_and_error_to_openevent(int oflags, int error) { au_event_t aevent; /* * Need to check only those flags we care about. */ oflags = oflags & (O_RDONLY | O_CREAT | O_TRUNC | O_RDWR | O_WRONLY); /* * These checks determine what flags are on with the condition that * ONLY that combination is on, and no other flags are on. */ switch (oflags) { case O_RDONLY: aevent = AUE_OPEN_R; break; case (O_RDONLY | O_CREAT): aevent = AUE_OPEN_RC; break; case (O_RDONLY | O_CREAT | O_TRUNC): aevent = AUE_OPEN_RTC; break; case (O_RDONLY | O_TRUNC): aevent = AUE_OPEN_RT; break; case O_RDWR: aevent = AUE_OPEN_RW; break; case (O_RDWR | O_CREAT): aevent = AUE_OPEN_RWC; break; case (O_RDWR | O_CREAT | O_TRUNC): aevent = AUE_OPEN_RWTC; break; case (O_RDWR | O_TRUNC): aevent = AUE_OPEN_RWT; break; case O_WRONLY: aevent = AUE_OPEN_W; break; case (O_WRONLY | O_CREAT): aevent = AUE_OPEN_WC; break; case (O_WRONLY | O_CREAT | O_TRUNC): aevent = AUE_OPEN_WTC; break; case (O_WRONLY | O_TRUNC): aevent = AUE_OPEN_WT; break; default: aevent = AUE_OPEN; break; } #if 0 /* * Convert chatty errors to better matching events. Failures to * find a file are really just attribute events -- so recast them as * such. * * XXXAUDIT: Solaris defines that AUE_OPEN will never be returned, it * is just a placeholder. However, in Darwin we return that in * preference to other events. For now, comment this out as we don't * have a BSM conversion routine for AUE_OPEN. */ switch (aevent) { case AUE_OPEN_R: case AUE_OPEN_RT: case AUE_OPEN_RW: case AUE_OPEN_RWT: case AUE_OPEN_W: case AUE_OPEN_WT: if (error == ENOENT) aevent = AUE_OPEN; } #endif return (aevent); } /* * Convert a MSGCTL command to a specific event. */ int audit_msgctl_to_event(int cmd) { switch (cmd) { case IPC_RMID: return (AUE_MSGCTL_RMID); case IPC_SET: return (AUE_MSGCTL_SET); case IPC_STAT: return (AUE_MSGCTL_STAT); default: /* We will audit a bad command. */ return (AUE_MSGCTL); } } /* * Convert a SEMCTL command to a specific event. */ int audit_semctl_to_event(int cmd) { switch (cmd) { case GETALL: return (AUE_SEMCTL_GETALL); case GETNCNT: return (AUE_SEMCTL_GETNCNT); case GETPID: return (AUE_SEMCTL_GETPID); case GETVAL: return (AUE_SEMCTL_GETVAL); case GETZCNT: return (AUE_SEMCTL_GETZCNT); case IPC_RMID: return (AUE_SEMCTL_RMID); case IPC_SET: return (AUE_SEMCTL_SET); case SETALL: return (AUE_SEMCTL_SETALL); case SETVAL: return (AUE_SEMCTL_SETVAL); case IPC_STAT: return (AUE_SEMCTL_STAT); default: /* We will audit a bad command */ return (AUE_SEMCTL); } } /* * Convert a command for the auditon() system call to a audit event. */ int auditon_command_event(int cmd) { switch(cmd) { case A_GETPOLICY: return (AUE_AUDITON_GPOLICY); case A_SETPOLICY: return (AUE_AUDITON_SPOLICY); case A_GETKMASK: return (AUE_AUDITON_GETKMASK); case A_SETKMASK: return (AUE_AUDITON_SETKMASK); case A_GETQCTRL: return (AUE_AUDITON_GQCTRL); case A_SETQCTRL: return (AUE_AUDITON_SQCTRL); case A_GETCWD: return (AUE_AUDITON_GETCWD); case A_GETCAR: return (AUE_AUDITON_GETCAR); case A_GETSTAT: return (AUE_AUDITON_GETSTAT); case A_SETSTAT: return (AUE_AUDITON_SETSTAT); case A_SETUMASK: return (AUE_AUDITON_SETUMASK); case A_SETSMASK: return (AUE_AUDITON_SETSMASK); case A_GETCOND: return (AUE_AUDITON_GETCOND); case A_SETCOND: return (AUE_AUDITON_SETCOND); case A_GETCLASS: return (AUE_AUDITON_GETCLASS); case A_SETCLASS: return (AUE_AUDITON_SETCLASS); case A_GETPINFO: case A_SETPMASK: case A_SETFSIZE: case A_GETFSIZE: case A_GETPINFO_ADDR: case A_GETKAUDIT: case A_SETKAUDIT: default: return (AUE_AUDITON); /* No special record */ } } /* * Create a canonical path from given path by prefixing either the root * directory, or the current working directory. If the process working * directory is NULL, we could use 'rootvnode' to obtain the root directory, * but this results in a volfs name written to the audit log. So we will * leave the filename starting with '/' in the audit log in this case. * * XXXRW: Since we combine two paths here, ideally a buffer of size * MAXPATHLEN * 2 would be passed in. */ void audit_canon_path(struct thread *td, char *path, char *cpath) { char *bufp; char *retbuf, *freebuf; struct vnode *vnp; struct filedesc *fdp; int cisr, error, vfslocked; WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, "audit_canon_path() at %s:%d", __FILE__, __LINE__); fdp = td->td_proc->p_fd; bufp = path; cisr = 0; FILEDESC_SLOCK(fdp); if (*(path) == '/') { while (*(bufp) == '/') bufp++; /* Skip leading '/'s. */ /* * If no process root, or it is the same as the system root, * audit the path as passed in with a single '/'. */ if ((fdp->fd_rdir == NULL) || (fdp->fd_rdir == rootvnode)) { vnp = NULL; bufp--; /* Restore one '/'. */ } else { vnp = fdp->fd_rdir; /* Use process root. */ vref(vnp); } } else { vnp = fdp->fd_cdir; /* Prepend the current dir. */ cisr = (fdp->fd_rdir == fdp->fd_cdir); vref(vnp); bufp = path; } FILEDESC_SUNLOCK(fdp); if (vnp != NULL) { /* * XXX: vn_fullpath() on FreeBSD is "less reliable" than * vn_getpath() on Darwin, so this will need more attention * in the future. Also, the question and string bounding * here seems a bit questionable and will also require * attention. */ vfslocked = VFS_LOCK_GIANT(vnp->v_mount); vn_lock(vnp, LK_EXCLUSIVE | LK_RETRY, td); error = vn_fullpath(td, vnp, &retbuf, &freebuf); if (error == 0) { /* Copy and free buffer allocated by vn_fullpath(). * If the current working directory was the same as * the root directory, and the path was a relative * pathname, do not separate the two components with * the '/' character. */ snprintf(cpath, MAXPATHLEN, "%s%s%s", retbuf, cisr ? "" : "/", bufp); free(freebuf, M_TEMP); } else cpath[0] = '\0'; vput(vnp); VFS_UNLOCK_GIANT(vfslocked); } else strlcpy(cpath, bufp, MAXPATHLEN); } Index: stable/7/sys/security/audit/audit_bsm_token.c =================================================================== --- stable/7/sys/security/audit/audit_bsm_token.c (revision 178335) +++ stable/7/sys/security/audit/audit_bsm_token.c (revision 178336) @@ -1,1370 +1,1371 @@ /* * Copyright (c) 2004 Apple Computer, Inc. * Copyright (c) 2005 SPARTA, Inc. * All rights reserved. * * This code was developed in part by Robert N. M. Watson, Senior Principal * Scientist, SPARTA, Inc. * * 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 Computer, 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. - * - * $FreeBSD$ */ + +#include +__FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GET_TOKEN_AREA(t, dptr, length) do { \ t = malloc(sizeof(token_t), M_AUDITBSM, M_WAITOK); \ t->t_data = malloc(length, M_AUDITBSM, M_WAITOK | M_ZERO); \ t->len = length; \ dptr = t->t_data; \ } while (0) /* * token ID 1 byte * argument # 1 byte * argument value 4 bytes/8 bytes (32-bit/64-bit value) * text length 2 bytes * text N bytes + 1 terminating NULL byte */ token_t * au_to_arg32(char n, char *text, u_int32_t v) { token_t *t; u_char *dptr = NULL; u_int16_t textlen; textlen = strlen(text); textlen += 1; GET_TOKEN_AREA(t, dptr, 2 * sizeof(u_char) + sizeof(u_int32_t) + sizeof(u_int16_t) + textlen); ADD_U_CHAR(dptr, AUT_ARG32); ADD_U_CHAR(dptr, n); ADD_U_INT32(dptr, v); ADD_U_INT16(dptr, textlen); ADD_STRING(dptr, text, textlen); return (t); } token_t * au_to_arg64(char n, char *text, u_int64_t v) { token_t *t; u_char *dptr = NULL; u_int16_t textlen; textlen = strlen(text); textlen += 1; GET_TOKEN_AREA(t, dptr, 2 * sizeof(u_char) + sizeof(u_int64_t) + sizeof(u_int16_t) + textlen); ADD_U_CHAR(dptr, AUT_ARG64); ADD_U_CHAR(dptr, n); ADD_U_INT64(dptr, v); ADD_U_INT16(dptr, textlen); ADD_STRING(dptr, text, textlen); return (t); } token_t * au_to_arg(char n, char *text, u_int32_t v) { return (au_to_arg32(n, text, v)); } #if defined(_KERNEL) || defined(KERNEL) /* * token ID 1 byte * file access mode 4 bytes * owner user ID 4 bytes * owner group ID 4 bytes * file system ID 4 bytes * node ID 8 bytes * device 4 bytes/8 bytes (32-bit/64-bit) */ token_t * au_to_attr32(struct vnode_au_info *vni) { token_t *t; u_char *dptr = NULL; u_int16_t pad0_16 = 0; u_int16_t pad0_32 = 0; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 2 * sizeof(u_int16_t) + 3 * sizeof(u_int32_t) + sizeof(u_int64_t) + sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_ATTR32); /* * Darwin defines the size for the file mode * as 2 bytes; BSM defines 4 so pad with 0 */ ADD_U_INT16(dptr, pad0_16); ADD_U_INT16(dptr, vni->vn_mode); ADD_U_INT32(dptr, vni->vn_uid); ADD_U_INT32(dptr, vni->vn_gid); ADD_U_INT32(dptr, vni->vn_fsid); /* * Some systems use 32-bit file ID's, other's use 64-bit file IDs. * Attempt to handle both, and let the compiler sort it out. If we * could pick this out at compile-time, it would be better, so as to * avoid the else case below. */ if (sizeof(vni->vn_fileid) == sizeof(uint32_t)) { ADD_U_INT32(dptr, pad0_32); ADD_U_INT32(dptr, vni->vn_fileid); } else if (sizeof(vni->vn_fileid) == sizeof(uint64_t)) ADD_U_INT64(dptr, vni->vn_fileid); else ADD_U_INT64(dptr, 0LL); ADD_U_INT32(dptr, vni->vn_dev); return (t); } token_t * au_to_attr64(struct vnode_au_info *vni) { token_t *t; u_char *dptr = NULL; u_int16_t pad0_16 = 0; u_int16_t pad0_32 = 0; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 2 * sizeof(u_int16_t) + 3 * sizeof(u_int32_t) + sizeof(u_int64_t) * 2); ADD_U_CHAR(dptr, AUT_ATTR64); /* * Darwin defines the size for the file mode * as 2 bytes; BSM defines 4 so pad with 0 */ ADD_U_INT16(dptr, pad0_16); ADD_U_INT16(dptr, vni->vn_mode); ADD_U_INT32(dptr, vni->vn_uid); ADD_U_INT32(dptr, vni->vn_gid); ADD_U_INT32(dptr, vni->vn_fsid); /* * Some systems use 32-bit file ID's, other's use 64-bit file IDs. * Attempt to handle both, and let the compiler sort it out. If we * could pick this out at compile-time, it would be better, so as to * avoid the else case below. */ if (sizeof(vni->vn_fileid) == sizeof(uint32_t)) { ADD_U_INT32(dptr, pad0_32); ADD_U_INT32(dptr, vni->vn_fileid); } else if (sizeof(vni->vn_fileid) == sizeof(uint64_t)) ADD_U_INT64(dptr, vni->vn_fileid); else ADD_U_INT64(dptr, 0LL); ADD_U_INT64(dptr, vni->vn_dev); return (t); } token_t * au_to_attr(struct vnode_au_info *vni) { return (au_to_attr32(vni)); } #endif /* !(defined(_KERNEL) || defined(KERNEL) */ /* * token ID 1 byte * how to print 1 byte * basic unit 1 byte * unit count 1 byte * data items (depends on basic unit) */ token_t * au_to_data(char unit_print, char unit_type, char unit_count, char *p) { token_t *t; u_char *dptr = NULL; size_t datasize, totdata; /* Determine the size of the basic unit. */ switch (unit_type) { case AUR_BYTE: /* case AUR_CHAR: */ datasize = AUR_BYTE_SIZE; break; case AUR_SHORT: datasize = AUR_SHORT_SIZE; break; case AUR_INT32: /* case AUR_INT: */ datasize = AUR_INT32_SIZE; break; case AUR_INT64: datasize = AUR_INT64_SIZE; break; default: return (NULL); } totdata = datasize * unit_count; GET_TOKEN_AREA(t, dptr, 4 * sizeof(u_char) + totdata); ADD_U_CHAR(dptr, AUT_DATA); ADD_U_CHAR(dptr, unit_print); ADD_U_CHAR(dptr, unit_type); ADD_U_CHAR(dptr, unit_count); ADD_MEM(dptr, p, totdata); return (t); } /* * token ID 1 byte * status 4 bytes * return value 4 bytes */ token_t * au_to_exit(int retval, int err) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 2 * sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_EXIT); ADD_U_INT32(dptr, err); ADD_U_INT32(dptr, retval); return (t); } /* */ token_t * au_to_groups(int *groups) { return (au_to_newgroups(AUDIT_MAX_GROUPS, (gid_t*)groups)); } /* * token ID 1 byte * number groups 2 bytes * group list count * 4 bytes */ token_t * au_to_newgroups(u_int16_t n, gid_t *groups) { token_t *t; u_char *dptr = NULL; int i; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t) + n * sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_NEWGROUPS); ADD_U_INT16(dptr, n); for (i = 0; i < n; i++) ADD_U_INT32(dptr, groups[i]); return (t); } /* * token ID 1 byte * internet address 4 bytes */ token_t * au_to_in_addr(struct in_addr *internet_addr) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(uint32_t)); ADD_U_CHAR(dptr, AUT_IN_ADDR); ADD_MEM(dptr, &internet_addr->s_addr, sizeof(uint32_t)); return (t); } /* * token ID 1 byte * address type/length 4 bytes * Address 16 bytes */ token_t * au_to_in_addr_ex(struct in6_addr *internet_addr) { token_t *t; u_char *dptr = NULL; u_int32_t type = AU_IPv6; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 5 * sizeof(uint32_t)); ADD_U_CHAR(dptr, AUT_IN_ADDR_EX); ADD_U_INT32(dptr, type); ADD_MEM(dptr, internet_addr, 4 * sizeof(uint32_t)); return (t); } /* * token ID 1 byte * ip header 20 bytes * * The IP header should be submitted in network byte order. */ token_t * au_to_ip(struct ip *ip) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(struct ip)); ADD_U_CHAR(dptr, AUT_IP); ADD_MEM(dptr, ip, sizeof(struct ip)); return (t); } /* * token ID 1 byte * object ID type 1 byte * object ID 4 bytes */ token_t * au_to_ipc(char type, int id) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, 2 * sizeof(u_char) + sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_IPC); ADD_U_CHAR(dptr, type); ADD_U_INT32(dptr, id); return (t); } /* * token ID 1 byte * owner user ID 4 bytes * owner group ID 4 bytes * creator user ID 4 bytes * creator group ID 4 bytes * access mode 4 bytes * slot sequence # 4 bytes * key 4 bytes */ token_t * au_to_ipc_perm(struct ipc_perm *perm) { token_t *t; u_char *dptr = NULL; u_int16_t pad0 = 0; GET_TOKEN_AREA(t, dptr, 12 * sizeof(u_int16_t) + sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_IPC_PERM); /* * Darwin defines the sizes for ipc_perm members * as 2 bytes; BSM defines 4 so pad with 0 */ ADD_U_INT16(dptr, pad0); ADD_U_INT16(dptr, perm->uid); ADD_U_INT16(dptr, pad0); ADD_U_INT16(dptr, perm->gid); ADD_U_INT16(dptr, pad0); ADD_U_INT16(dptr, perm->cuid); ADD_U_INT16(dptr, pad0); ADD_U_INT16(dptr, perm->cgid); ADD_U_INT16(dptr, pad0); ADD_U_INT16(dptr, perm->mode); ADD_U_INT16(dptr, pad0); ADD_U_INT16(dptr, perm->seq); ADD_U_INT32(dptr, perm->key); return (t); } /* * token ID 1 byte * port IP address 2 bytes */ token_t * au_to_iport(u_int16_t iport) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t)); ADD_U_CHAR(dptr, AUT_IPORT); ADD_U_INT16(dptr, iport); return (t); } /* * token ID 1 byte * size 2 bytes * data size bytes */ token_t * au_to_opaque(char *data, u_int16_t bytes) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t) + bytes); ADD_U_CHAR(dptr, AUT_OPAQUE); ADD_U_INT16(dptr, bytes); ADD_MEM(dptr, data, bytes); return (t); } /* * token ID 1 byte * seconds of time 4 bytes * milliseconds of time 4 bytes * file name len 2 bytes * file pathname N bytes + 1 terminating NULL byte */ token_t * au_to_file(char *file, struct timeval tm) { token_t *t; u_char *dptr = NULL; u_int16_t filelen; u_int32_t timems; filelen = strlen(file); filelen += 1; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 2 * sizeof(u_int32_t) + sizeof(u_int16_t) + filelen); timems = tm.tv_usec/1000; ADD_U_CHAR(dptr, AUT_OTHER_FILE32); ADD_U_INT32(dptr, tm.tv_sec); ADD_U_INT32(dptr, timems); /* We need time in ms. */ ADD_U_INT16(dptr, filelen); ADD_STRING(dptr, file, filelen); return (t); } /* * token ID 1 byte * text length 2 bytes * text N bytes + 1 terminating NULL byte */ token_t * au_to_text(char *text) { token_t *t; u_char *dptr = NULL; u_int16_t textlen; textlen = strlen(text); textlen += 1; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t) + textlen); ADD_U_CHAR(dptr, AUT_TEXT); ADD_U_INT16(dptr, textlen); ADD_STRING(dptr, text, textlen); return (t); } /* * token ID 1 byte * path length 2 bytes * path N bytes + 1 terminating NULL byte */ token_t * au_to_path(char *text) { token_t *t; u_char *dptr = NULL; u_int16_t textlen; textlen = strlen(text); textlen += 1; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t) + textlen); ADD_U_CHAR(dptr, AUT_PATH); ADD_U_INT16(dptr, textlen); ADD_STRING(dptr, text, textlen); return (t); } /* * token ID 1 byte * audit ID 4 bytes * effective user ID 4 bytes * effective group ID 4 bytes * real user ID 4 bytes * real group ID 4 bytes * process ID 4 bytes * session ID 4 bytes * terminal ID * port ID 4 bytes/8 bytes (32-bit/64-bit value) * machine address 4 bytes */ token_t * au_to_process32(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid, au_tid_t *tid) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 9 * sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_PROCESS32); ADD_U_INT32(dptr, auid); ADD_U_INT32(dptr, euid); ADD_U_INT32(dptr, egid); ADD_U_INT32(dptr, ruid); ADD_U_INT32(dptr, rgid); ADD_U_INT32(dptr, pid); ADD_U_INT32(dptr, sid); ADD_U_INT32(dptr, tid->port); ADD_MEM(dptr, &tid->machine, sizeof(u_int32_t)); return (t); } token_t * au_to_process64(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid, au_tid_t *tid) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 8 * sizeof(u_int32_t) + sizeof(u_int64_t)); ADD_U_CHAR(dptr, AUT_PROCESS64); ADD_U_INT32(dptr, auid); ADD_U_INT32(dptr, euid); ADD_U_INT32(dptr, egid); ADD_U_INT32(dptr, ruid); ADD_U_INT32(dptr, rgid); ADD_U_INT32(dptr, pid); ADD_U_INT32(dptr, sid); ADD_U_INT64(dptr, tid->port); ADD_MEM(dptr, &tid->machine, sizeof(u_int32_t)); return (t); } token_t * au_to_process(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid, au_tid_t *tid) { return (au_to_process32(auid, euid, egid, ruid, rgid, pid, sid, tid)); } /* * token ID 1 byte * audit ID 4 bytes * effective user ID 4 bytes * effective group ID 4 bytes * real user ID 4 bytes * real group ID 4 bytes * process ID 4 bytes * session ID 4 bytes * terminal ID * port ID 4 bytes/8 bytes (32-bit/64-bit value) * address type-len 4 bytes * machine address 4/16 bytes */ token_t * au_to_process32_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid) { token_t *t; u_char *dptr = NULL; KASSERT((tid->at_type == AU_IPv4) || (tid->at_type == AU_IPv6), ("au_to_process32_ex: type %u", (unsigned int)tid->at_type)); if (tid->at_type == AU_IPv6) GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 13 * sizeof(u_int32_t)); else GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 10 * sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_PROCESS32_EX); ADD_U_INT32(dptr, auid); ADD_U_INT32(dptr, euid); ADD_U_INT32(dptr, egid); ADD_U_INT32(dptr, ruid); ADD_U_INT32(dptr, rgid); ADD_U_INT32(dptr, pid); ADD_U_INT32(dptr, sid); ADD_U_INT32(dptr, tid->at_port); ADD_U_INT32(dptr, tid->at_type); if (tid->at_type == AU_IPv6) ADD_MEM(dptr, &tid->at_addr[0], 4 * sizeof(u_int32_t)); else ADD_MEM(dptr, &tid->at_addr[0], sizeof(u_int32_t)); return (t); } token_t * au_to_process64_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid) { token_t *t; u_char *dptr = NULL; if (tid->at_type == AU_IPv4) GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 7 * sizeof(u_int32_t) + sizeof(u_int64_t) + 2 * sizeof(u_int32_t)); else if (tid->at_type == AU_IPv6) GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 7 * sizeof(u_int32_t) + sizeof(u_int64_t) + 5 * sizeof(u_int32_t)); else panic("au_to_process64_ex: invalidate at_type (%d)", tid->at_type); ADD_U_CHAR(dptr, AUT_PROCESS64_EX); ADD_U_INT32(dptr, auid); ADD_U_INT32(dptr, euid); ADD_U_INT32(dptr, egid); ADD_U_INT32(dptr, ruid); ADD_U_INT32(dptr, rgid); ADD_U_INT32(dptr, pid); ADD_U_INT32(dptr, sid); ADD_U_INT64(dptr, tid->at_port); ADD_U_INT32(dptr, tid->at_type); ADD_MEM(dptr, &tid->at_addr[0], sizeof(u_int32_t)); if (tid->at_type == AU_IPv6) { ADD_MEM(dptr, &tid->at_addr[1], sizeof(u_int32_t)); ADD_MEM(dptr, &tid->at_addr[2], sizeof(u_int32_t)); ADD_MEM(dptr, &tid->at_addr[3], sizeof(u_int32_t)); } return (t); } token_t * au_to_process_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid) { return (au_to_process32_ex(auid, euid, egid, ruid, rgid, pid, sid, tid)); } /* * token ID 1 byte * error status 1 byte * return value 4 bytes/8 bytes (32-bit/64-bit value) */ token_t * au_to_return32(char status, u_int32_t ret) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, 2 * sizeof(u_char) + sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_RETURN32); ADD_U_CHAR(dptr, status); ADD_U_INT32(dptr, ret); return (t); } token_t * au_to_return64(char status, u_int64_t ret) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, 2 * sizeof(u_char) + sizeof(u_int64_t)); ADD_U_CHAR(dptr, AUT_RETURN64); ADD_U_CHAR(dptr, status); ADD_U_INT64(dptr, ret); return (t); } token_t * au_to_return(char status, u_int32_t ret) { return (au_to_return32(status, ret)); } /* * token ID 1 byte * sequence number 4 bytes */ token_t * au_to_seq(long audit_count) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_SEQ); ADD_U_INT32(dptr, audit_count); return (t); } /* * token ID 1 byte * socket type 2 bytes * local port 2 bytes * local Internet address 4 bytes * remote port 2 bytes * remote Internet address 4 bytes */ token_t * au_to_socket(struct socket *so) { /* XXXRW ... */ return (NULL); } /* * Kernel-specific version of the above function. */ #ifdef _KERNEL token_t * kau_to_socket(struct socket_au_info *soi) { token_t *t; u_char *dptr; u_int16_t so_type; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 2 * sizeof(u_int16_t) + sizeof(u_int32_t) + sizeof(u_int16_t) + sizeof(u_int32_t)); ADD_U_CHAR(dptr, AU_SOCK_TOKEN); /* Coerce the socket type into a short value */ so_type = soi->so_type; ADD_U_INT16(dptr, so_type); ADD_U_INT16(dptr, soi->so_lport); ADD_U_INT32(dptr, soi->so_laddr); ADD_U_INT16(dptr, soi->so_rport); ADD_U_INT32(dptr, soi->so_raddr); return (t); } #endif /* * token ID 1 byte * socket type 2 bytes * local port 2 bytes * address type/length 4 bytes * local Internet address 4 bytes/16 bytes (IPv4/IPv6 address) * remote port 4 bytes * address type/length 4 bytes * remote Internet address 4 bytes/16 bytes (IPv4/IPv6 address) */ token_t * au_to_socket_ex_32(u_int16_t lp, u_int16_t rp, struct sockaddr *la, struct sockaddr *ra) { return (NULL); } token_t * au_to_socket_ex_128(u_int16_t lp, u_int16_t rp, struct sockaddr *la, struct sockaddr *ra) { return (NULL); } /* * token ID 1 byte * socket family 2 bytes * path 104 bytes */ token_t * au_to_sock_unix(struct sockaddr_un *so) { token_t *t; u_char *dptr; GET_TOKEN_AREA(t, dptr, 3 * sizeof(u_char) + strlen(so->sun_path) + 1); ADD_U_CHAR(dptr, AU_SOCK_UNIX_TOKEN); /* BSM token has two bytes for family */ ADD_U_CHAR(dptr, 0); ADD_U_CHAR(dptr, so->sun_family); ADD_STRING(dptr, so->sun_path, strlen(so->sun_path) + 1); return (t); } /* * token ID 1 byte * socket family 2 bytes * local port 2 bytes * socket address 4 bytes */ token_t * au_to_sock_inet32(struct sockaddr_in *so) { token_t *t; u_char *dptr = NULL; uint16_t family; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 2 * sizeof(uint16_t) + sizeof(uint32_t)); ADD_U_CHAR(dptr, AUT_SOCKINET32); /* * BSM defines the family field as 16 bits, but many operating * systems have an 8-bit sin_family field. Extend to 16 bits before * writing into the token. Assume that both the port and the address * in the sockaddr_in are already in network byte order, but family * is in local byte order. * * XXXRW: Should a name space conversion be taking place on the value * of sin_family? */ family = so->sin_family; ADD_U_INT16(dptr, family); ADD_MEM(dptr, &so->sin_port, sizeof(uint16_t)); ADD_MEM(dptr, &so->sin_addr.s_addr, sizeof(uint32_t)); return (t); } token_t * au_to_sock_inet128(struct sockaddr_in6 *so) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, 3 * sizeof(u_char) + sizeof(u_int16_t) + 4 * sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_SOCKINET128); /* * In Darwin, sin6_family is one octet, but BSM defines the token * to store two. So we copy in a 0 first. */ ADD_U_CHAR(dptr, 0); ADD_U_CHAR(dptr, so->sin6_family); ADD_U_INT16(dptr, so->sin6_port); ADD_MEM(dptr, &so->sin6_addr, 4 * sizeof(uint32_t)); return (t); } token_t * au_to_sock_inet(struct sockaddr_in *so) { return (au_to_sock_inet32(so)); } /* * token ID 1 byte * audit ID 4 bytes * effective user ID 4 bytes * effective group ID 4 bytes * real user ID 4 bytes * real group ID 4 bytes * process ID 4 bytes * session ID 4 bytes * terminal ID * port ID 4 bytes/8 bytes (32-bit/64-bit value) * machine address 4 bytes */ token_t * au_to_subject32(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid, au_tid_t *tid) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 9 * sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_SUBJECT32); ADD_U_INT32(dptr, auid); ADD_U_INT32(dptr, euid); ADD_U_INT32(dptr, egid); ADD_U_INT32(dptr, ruid); ADD_U_INT32(dptr, rgid); ADD_U_INT32(dptr, pid); ADD_U_INT32(dptr, sid); ADD_U_INT32(dptr, tid->port); ADD_MEM(dptr, &tid->machine, sizeof(u_int32_t)); return (t); } token_t * au_to_subject64(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid, au_tid_t *tid) { token_t *t; u_char *dptr = NULL; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 7 * sizeof(u_int32_t) + sizeof(u_int64_t) + sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_SUBJECT64); ADD_U_INT32(dptr, auid); ADD_U_INT32(dptr, euid); ADD_U_INT32(dptr, egid); ADD_U_INT32(dptr, ruid); ADD_U_INT32(dptr, rgid); ADD_U_INT32(dptr, pid); ADD_U_INT32(dptr, sid); ADD_U_INT64(dptr, tid->port); ADD_MEM(dptr, &tid->machine, sizeof(u_int32_t)); return (t); } token_t * au_to_subject(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid, au_tid_t *tid) { return (au_to_subject32(auid, euid, egid, ruid, rgid, pid, sid, tid)); } /* * token ID 1 byte * audit ID 4 bytes * effective user ID 4 bytes * effective group ID 4 bytes * real user ID 4 bytes * real group ID 4 bytes * process ID 4 bytes * session ID 4 bytes * terminal ID * port ID 4 bytes/8 bytes (32-bit/64-bit value) * address type/length 4 bytes * machine address 4/16 bytes */ token_t * au_to_subject32_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid) { token_t *t; u_char *dptr = NULL; KASSERT((tid->at_type == AU_IPv4) || (tid->at_type == AU_IPv6), ("au_to_subject32_ex: type %u", (unsigned int)tid->at_type)); if (tid->at_type == AU_IPv6) GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 13 * sizeof(u_int32_t)); else GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 10 * sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_SUBJECT32_EX); ADD_U_INT32(dptr, auid); ADD_U_INT32(dptr, euid); ADD_U_INT32(dptr, egid); ADD_U_INT32(dptr, ruid); ADD_U_INT32(dptr, rgid); ADD_U_INT32(dptr, pid); ADD_U_INT32(dptr, sid); ADD_U_INT32(dptr, tid->at_port); ADD_U_INT32(dptr, tid->at_type); if (tid->at_type == AU_IPv6) ADD_MEM(dptr, &tid->at_addr[0], 4 * sizeof(u_int32_t)); else ADD_MEM(dptr, &tid->at_addr[0], sizeof(u_int32_t)); return (t); } token_t * au_to_subject64_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid) { token_t *t; u_char *dptr = NULL; if (tid->at_type == AU_IPv4) GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 7 * sizeof(u_int32_t) + sizeof(u_int64_t) + 2 * sizeof(u_int32_t)); else if (tid->at_type == AU_IPv6) GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 7 * sizeof(u_int32_t) + sizeof(u_int64_t) + 5 * sizeof(u_int32_t)); else panic("au_to_subject64_ex: invalid at_type (%d)", tid->at_type); ADD_U_CHAR(dptr, AUT_SUBJECT64_EX); ADD_U_INT32(dptr, auid); ADD_U_INT32(dptr, euid); ADD_U_INT32(dptr, egid); ADD_U_INT32(dptr, ruid); ADD_U_INT32(dptr, rgid); ADD_U_INT32(dptr, pid); ADD_U_INT32(dptr, sid); ADD_U_INT64(dptr, tid->at_port); ADD_U_INT32(dptr, tid->at_type); if (tid->at_type == AU_IPv6) ADD_MEM(dptr, &tid->at_addr[0], 4 * sizeof(u_int32_t)); else ADD_MEM(dptr, &tid->at_addr[0], sizeof(u_int32_t)); return (t); } token_t * au_to_subject_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid) { return (au_to_subject32_ex(auid, euid, egid, ruid, rgid, pid, sid, tid)); } #if !defined(_KERNEL) && !defined(KERNEL) && defined(HAVE_AUDIT_SYSCALLS) /* * Collects audit information for the current process * and creates a subject token from it */ token_t * au_to_me(void) { auditinfo_t auinfo; if (getaudit(&auinfo) != 0) return (NULL); return (au_to_subject32(auinfo.ai_auid, geteuid(), getegid(), getuid(), getgid(), getpid(), auinfo.ai_asid, &auinfo.ai_termid)); } #endif #if defined(_KERNEL) || defined(KERNEL) static token_t * au_to_exec_strings(char *strs, int count, u_char type) { token_t *t; u_char *dptr = NULL; u_int32_t totlen; int ctr; char *p; totlen = 0; ctr = count; p = strs; while (ctr-- > 0) { totlen += strlen(p) + 1; p = strs + totlen; } GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int32_t) + totlen); ADD_U_CHAR(dptr, type); ADD_U_INT32(dptr, count); ADD_STRING(dptr, strs, totlen); return (t); } /* * token ID 1 byte * count 4 bytes * text count null-terminated strings */ token_t * au_to_exec_args(char *args, int argc) { return (au_to_exec_strings(args, argc, AUT_EXEC_ARGS)); } /* * token ID 1 byte * count 4 bytes * text count null-terminated strings */ token_t * au_to_exec_env(char *envs, int envc) { return (au_to_exec_strings(envs, envc, AUT_EXEC_ENV)); } #else /* * token ID 1 byte * count 4 bytes * text count null-terminated strings */ token_t * au_to_exec_args(char **argv) { token_t *t; u_char *dptr = NULL; const char *nextarg; int i, count = 0; size_t totlen = 0; nextarg = *argv; while (nextarg != NULL) { int nextlen; nextlen = strlen(nextarg); totlen += nextlen + 1; count++; nextarg = *(argv + count); } totlen += count * sizeof(char); /* nul terminations. */ GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int32_t) + totlen); ADD_U_CHAR(dptr, AUT_EXEC_ARGS); ADD_U_INT32(dptr, count); for (i = 0; i < count; i++) { nextarg = *(argv + i); ADD_MEM(dptr, nextarg, strlen(nextarg) + 1); } return (t); } /* * token ID 1 byte * zonename length 2 bytes * zonename N bytes + 1 terminating NULL byte */ token_t * au_to_zonename(char *zonename) { u_char *dptr = NULL; u_int16_t textlen; token_t *t; textlen = strlen(zonename); textlen += 1; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t) + textlen); ADD_U_CHAR(dptr, AUT_ZONENAME); ADD_U_INT16(dptr, textlen); ADD_STRING(dptr, zonename, textlen); return (t); } /* * token ID 1 byte * count 4 bytes * text count null-terminated strings */ token_t * au_to_exec_env(char **envp) { token_t *t; u_char *dptr = NULL; int i, count = 0; size_t totlen = 0; const char *nextenv; nextenv = *envp; while (nextenv != NULL) { int nextlen; nextlen = strlen(nextenv); totlen += nextlen + 1; count++; nextenv = *(envp + count); } totlen += sizeof(char) * count; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int32_t) + totlen); ADD_U_CHAR(dptr, AUT_EXEC_ENV); ADD_U_INT32(dptr, count); for (i = 0; i < count; i++) { nextenv = *(envp + i); ADD_MEM(dptr, nextenv, strlen(nextenv) + 1); } return (t); } #endif /* * token ID 1 byte * record byte count 4 bytes * version # 1 byte [2] * event type 2 bytes * event modifier 2 bytes * seconds of time 4 bytes/8 bytes (32-bit/64-bit value) * milliseconds of time 4 bytes/8 bytes (32-bit/64-bit value) */ token_t * au_to_header32_tm(int rec_size, au_event_t e_type, au_emod_t e_mod, struct timeval tm) { token_t *t; u_char *dptr = NULL; u_int32_t timems; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int32_t) + sizeof(u_char) + 2 * sizeof(u_int16_t) + 2 * sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_HEADER32); ADD_U_INT32(dptr, rec_size); ADD_U_CHAR(dptr, AUDIT_HEADER_VERSION_OPENBSM); ADD_U_INT16(dptr, e_type); ADD_U_INT16(dptr, e_mod); timems = tm.tv_usec/1000; /* Add the timestamp */ ADD_U_INT32(dptr, tm.tv_sec); ADD_U_INT32(dptr, timems); /* We need time in ms. */ return (t); } token_t * au_to_header64_tm(int rec_size, au_event_t e_type, au_emod_t e_mod, struct timeval tm) { token_t *t; u_char *dptr = NULL; u_int32_t timems; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int32_t) + sizeof(u_char) + 2 * sizeof(u_int16_t) + 2 * sizeof(u_int64_t)); ADD_U_CHAR(dptr, AUT_HEADER64); ADD_U_INT32(dptr, rec_size); ADD_U_CHAR(dptr, AUDIT_HEADER_VERSION_OPENBSM); ADD_U_INT16(dptr, e_type); ADD_U_INT16(dptr, e_mod); timems = tm.tv_usec/1000; /* Add the timestamp */ ADD_U_INT64(dptr, tm.tv_sec); ADD_U_INT64(dptr, timems); /* We need time in ms. */ return (t); } /* * token ID 1 byte * trailer magic number 2 bytes * record byte count 4 bytes */ token_t * au_to_trailer(int rec_size) { token_t *t; u_char *dptr = NULL; u_int16_t magic = TRAILER_PAD_MAGIC; GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t) + sizeof(u_int32_t)); ADD_U_CHAR(dptr, AUT_TRAILER); ADD_U_INT16(dptr, magic); ADD_U_INT32(dptr, rec_size); return (t); } Index: stable/7/sys/security/audit/audit_pipe.c =================================================================== --- stable/7/sys/security/audit/audit_pipe.c (revision 178335) +++ stable/7/sys/security/audit/audit_pipe.c (revision 178336) @@ -1,1057 +1,1058 @@ /*- * Copyright (c) 2006 Robert N. M. Watson * All rights reserved. * * This software was developed by Robert Watson for the TrustedBSD Project. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND 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 THE AUTHOR OR 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. - * - * $FreeBSD$ */ + +#include +__FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Implementation of a clonable special device providing a live stream of BSM * audit data. This is a "tee" of the data going to the file. It provides * unreliable but timely access to audit events. Consumers of this interface * should be very careful to avoid introducing event cycles. Consumers may * express interest via a set of preselection ioctls. */ /* * Memory types. */ static MALLOC_DEFINE(M_AUDIT_PIPE, "audit_pipe", "Audit pipes"); static MALLOC_DEFINE(M_AUDIT_PIPE_ENTRY, "audit_pipeent", "Audit pipe entries and buffers"); static MALLOC_DEFINE(M_AUDIT_PIPE_PRESELECT, "audit_pipe_preselect", "Audit pipe preselection structure"); /* * Audit pipe buffer parameters. */ #define AUDIT_PIPE_QLIMIT_DEFAULT (128) #define AUDIT_PIPE_QLIMIT_MIN (0) #define AUDIT_PIPE_QLIMIT_MAX (1024) /* * Description of an entry in an audit_pipe. */ struct audit_pipe_entry { void *ape_record; u_int ape_record_len; TAILQ_ENTRY(audit_pipe_entry) ape_queue; }; /* * Audit pipes allow processes to express "interest" in the set of records * that are delivered via the pipe. They do this in a similar manner to the * mechanism for audit trail configuration, by expressing two global masks, * and optionally expressing per-auid masks. The following data structure is * the per-auid mask description. The global state is stored in the audit * pipe data structure. * * We may want to consider a more space/time-efficient data structure once * usage patterns for per-auid specifications are clear. */ struct audit_pipe_preselect { au_id_t app_auid; au_mask_t app_mask; TAILQ_ENTRY(audit_pipe_preselect) app_list; }; /* * Description of an individual audit_pipe. Consists largely of a bounded * length queue. */ #define AUDIT_PIPE_ASYNC 0x00000001 #define AUDIT_PIPE_NBIO 0x00000002 struct audit_pipe { int ap_open; /* Device open? */ u_int ap_flags; struct selinfo ap_selinfo; struct sigio *ap_sigio; u_int ap_qlen; u_int ap_qlimit; u_int64_t ap_inserts; /* Records added. */ u_int64_t ap_reads; /* Records read. */ u_int64_t ap_drops; /* Records dropped. */ u_int64_t ap_truncates; /* Records too long. */ /* * Fields relating to pipe interest: global masks for unmatched * processes (attributable, non-attributable), and a list of specific * interest specifications by auid. */ int ap_preselect_mode; au_mask_t ap_preselect_flags; au_mask_t ap_preselect_naflags; TAILQ_HEAD(, audit_pipe_preselect) ap_preselect_list; /* * Current pending record list. */ TAILQ_HEAD(, audit_pipe_entry) ap_queue; /* * Global pipe list. */ TAILQ_ENTRY(audit_pipe) ap_list; }; /* * Global list of audit pipes, mutex to protect it and the pipes. Finer * grained locking may be desirable at some point. */ static TAILQ_HEAD(, audit_pipe) audit_pipe_list; static struct mtx audit_pipe_mtx; /* * This CV is used to wakeup on an audit record write. Eventually, it might * be per-pipe to avoid unnecessary wakeups when several pipes with different * preselection masks are present. */ static struct cv audit_pipe_cv; /* * Cloning related variables and constants. */ #define AUDIT_PIPE_NAME "auditpipe" static eventhandler_tag audit_pipe_eh_tag; static struct clonedevs *audit_pipe_clones; /* * Special device methods and definition. */ static d_open_t audit_pipe_open; static d_close_t audit_pipe_close; static d_read_t audit_pipe_read; static d_ioctl_t audit_pipe_ioctl; static d_poll_t audit_pipe_poll; static d_kqfilter_t audit_pipe_kqfilter; static struct cdevsw audit_pipe_cdevsw = { .d_version = D_VERSION, .d_flags = D_PSEUDO | D_NEEDGIANT, .d_open = audit_pipe_open, .d_close = audit_pipe_close, .d_read = audit_pipe_read, .d_ioctl = audit_pipe_ioctl, .d_poll = audit_pipe_poll, .d_kqfilter = audit_pipe_kqfilter, .d_name = AUDIT_PIPE_NAME, }; static int audit_pipe_kqread(struct knote *note, long hint); static void audit_pipe_kqdetach(struct knote *note); static struct filterops audit_pipe_read_filterops = { .f_isfd = 1, .f_attach = NULL, .f_detach = audit_pipe_kqdetach, .f_event = audit_pipe_kqread, }; /* * Some global statistics on audit pipes. */ static int audit_pipe_count; /* Current number of pipes. */ static u_int64_t audit_pipe_ever; /* Pipes ever allocated. */ static u_int64_t audit_pipe_records; /* Records seen. */ static u_int64_t audit_pipe_drops; /* Global record drop count. */ /* * Free an audit pipe entry. */ static void audit_pipe_entry_free(struct audit_pipe_entry *ape) { free(ape->ape_record, M_AUDIT_PIPE_ENTRY); free(ape, M_AUDIT_PIPE_ENTRY); } /* * Find an audit pipe preselection specification for an auid, if any. */ static struct audit_pipe_preselect * audit_pipe_preselect_find(struct audit_pipe *ap, au_id_t auid) { struct audit_pipe_preselect *app; mtx_assert(&audit_pipe_mtx, MA_OWNED); TAILQ_FOREACH(app, &ap->ap_preselect_list, app_list) { if (app->app_auid == auid) return (app); } return (NULL); } /* * Query the per-pipe mask for a specific auid. */ static int audit_pipe_preselect_get(struct audit_pipe *ap, au_id_t auid, au_mask_t *maskp) { struct audit_pipe_preselect *app; int error; mtx_lock(&audit_pipe_mtx); app = audit_pipe_preselect_find(ap, auid); if (app != NULL) { *maskp = app->app_mask; error = 0; } else error = ENOENT; mtx_unlock(&audit_pipe_mtx); return (error); } /* * Set the per-pipe mask for a specific auid. Add a new entry if needed; * otherwise, update the current entry. */ static void audit_pipe_preselect_set(struct audit_pipe *ap, au_id_t auid, au_mask_t mask) { struct audit_pipe_preselect *app, *app_new; /* * Pessimistically assume that the auid doesn't already have a mask * set, and allocate. We will free it if it is unneeded. */ app_new = malloc(sizeof(*app_new), M_AUDIT_PIPE_PRESELECT, M_WAITOK); mtx_lock(&audit_pipe_mtx); app = audit_pipe_preselect_find(ap, auid); if (app == NULL) { app = app_new; app_new = NULL; app->app_auid = auid; TAILQ_INSERT_TAIL(&ap->ap_preselect_list, app, app_list); } app->app_mask = mask; mtx_unlock(&audit_pipe_mtx); if (app_new != NULL) free(app_new, M_AUDIT_PIPE_PRESELECT); } /* * Delete a per-auid mask on an audit pipe. */ static int audit_pipe_preselect_delete(struct audit_pipe *ap, au_id_t auid) { struct audit_pipe_preselect *app; int error; mtx_lock(&audit_pipe_mtx); app = audit_pipe_preselect_find(ap, auid); if (app != NULL) { TAILQ_REMOVE(&ap->ap_preselect_list, app, app_list); error = 0; } else error = ENOENT; mtx_unlock(&audit_pipe_mtx); if (app != NULL) free(app, M_AUDIT_PIPE_PRESELECT); return (error); } /* * Delete all per-auid masks on an audit pipe. */ static void audit_pipe_preselect_flush_locked(struct audit_pipe *ap) { struct audit_pipe_preselect *app; mtx_assert(&audit_pipe_mtx, MA_OWNED); while ((app = TAILQ_FIRST(&ap->ap_preselect_list)) != NULL) { TAILQ_REMOVE(&ap->ap_preselect_list, app, app_list); free(app, M_AUDIT_PIPE_PRESELECT); } } static void audit_pipe_preselect_flush(struct audit_pipe *ap) { mtx_lock(&audit_pipe_mtx); audit_pipe_preselect_flush_locked(ap); mtx_unlock(&audit_pipe_mtx); } /*- * Determine whether a specific audit pipe matches a record with these * properties. Algorithm is as follows: * * - If the pipe is configured to track the default trail configuration, then * use the results of global preselection matching. * - If not, search for a specifically configured auid entry matching the * event. If an entry is found, use that. * - Otherwise, use the default flags or naflags configured for the pipe. */ static int audit_pipe_preselect_check(struct audit_pipe *ap, au_id_t auid, au_event_t event, au_class_t class, int sorf, int trail_preselect) { struct audit_pipe_preselect *app; mtx_assert(&audit_pipe_mtx, MA_OWNED); switch (ap->ap_preselect_mode) { case AUDITPIPE_PRESELECT_MODE_TRAIL: return (trail_preselect); case AUDITPIPE_PRESELECT_MODE_LOCAL: app = audit_pipe_preselect_find(ap, auid); if (app == NULL) { if (auid == AU_DEFAUDITID) return (au_preselect(event, class, &ap->ap_preselect_naflags, sorf)); else return (au_preselect(event, class, &ap->ap_preselect_flags, sorf)); } else return (au_preselect(event, class, &app->app_mask, sorf)); default: panic("audit_pipe_preselect_check: mode %d", ap->ap_preselect_mode); } return (0); } /* * Determine whether there exists a pipe interested in a record with specific * properties. */ int audit_pipe_preselect(au_id_t auid, au_event_t event, au_class_t class, int sorf, int trail_preselect) { struct audit_pipe *ap; mtx_lock(&audit_pipe_mtx); TAILQ_FOREACH(ap, &audit_pipe_list, ap_list) { if (audit_pipe_preselect_check(ap, auid, event, class, sorf, trail_preselect)) { mtx_unlock(&audit_pipe_mtx); return (1); } } mtx_unlock(&audit_pipe_mtx); return (0); } /* * Append individual record to a queue -- allocate queue-local buffer, and * add to the queue. We try to drop from the head of the queue so that more * recent events take precedence over older ones, but if allocation fails we * do drop the new event. */ static void audit_pipe_append(struct audit_pipe *ap, void *record, u_int record_len) { struct audit_pipe_entry *ape, *ape_remove; mtx_assert(&audit_pipe_mtx, MA_OWNED); ape = malloc(sizeof(*ape), M_AUDIT_PIPE_ENTRY, M_NOWAIT | M_ZERO); if (ape == NULL) { ap->ap_drops++; audit_pipe_drops++; return; } ape->ape_record = malloc(record_len, M_AUDIT_PIPE_ENTRY, M_NOWAIT); if (ape->ape_record == NULL) { free(ape, M_AUDIT_PIPE_ENTRY); ap->ap_drops++; audit_pipe_drops++; return; } bcopy(record, ape->ape_record, record_len); ape->ape_record_len = record_len; if (ap->ap_qlen >= ap->ap_qlimit) { ape_remove = TAILQ_FIRST(&ap->ap_queue); TAILQ_REMOVE(&ap->ap_queue, ape_remove, ape_queue); audit_pipe_entry_free(ape_remove); ap->ap_qlen--; ap->ap_drops++; audit_pipe_drops++; } TAILQ_INSERT_TAIL(&ap->ap_queue, ape, ape_queue); ap->ap_inserts++; ap->ap_qlen++; selwakeuppri(&ap->ap_selinfo, PSOCK); KNOTE_LOCKED(&ap->ap_selinfo.si_note, 0); if (ap->ap_flags & AUDIT_PIPE_ASYNC) pgsigio(&ap->ap_sigio, SIGIO, 0); } /* * audit_pipe_submit(): audit_worker submits audit records via this * interface, which arranges for them to be delivered to pipe queues. */ void audit_pipe_submit(au_id_t auid, au_event_t event, au_class_t class, int sorf, int trail_select, void *record, u_int record_len) { struct audit_pipe *ap; /* * Lockless read to avoid mutex overhead if pipes are not in use. */ if (TAILQ_FIRST(&audit_pipe_list) == NULL) return; mtx_lock(&audit_pipe_mtx); TAILQ_FOREACH(ap, &audit_pipe_list, ap_list) { if (audit_pipe_preselect_check(ap, auid, event, class, sorf, trail_select)) audit_pipe_append(ap, record, record_len); } audit_pipe_records++; mtx_unlock(&audit_pipe_mtx); cv_broadcastpri(&audit_pipe_cv, PSOCK); } /* * audit_pipe_submit_user(): the same as audit_pipe_submit(), except that * since we don't currently have selection information available, it is * delivered to the pipe unconditionally. * * XXXRW: This is a bug. The BSM check routine for submitting a user record * should parse that information and return it. */ void audit_pipe_submit_user(void *record, u_int record_len) { struct audit_pipe *ap; /* * Lockless read to avoid mutex overhead if pipes are not in use. */ if (TAILQ_FIRST(&audit_pipe_list) == NULL) return; mtx_lock(&audit_pipe_mtx); TAILQ_FOREACH(ap, &audit_pipe_list, ap_list) audit_pipe_append(ap, record, record_len); audit_pipe_records++; mtx_unlock(&audit_pipe_mtx); cv_broadcastpri(&audit_pipe_cv, PSOCK); } /* * Pop the next record off of an audit pipe. */ static struct audit_pipe_entry * audit_pipe_pop(struct audit_pipe *ap) { struct audit_pipe_entry *ape; mtx_assert(&audit_pipe_mtx, MA_OWNED); ape = TAILQ_FIRST(&ap->ap_queue); KASSERT((ape == NULL && ap->ap_qlen == 0) || (ape != NULL && ap->ap_qlen != 0), ("audit_pipe_pop: qlen")); if (ape == NULL) return (NULL); TAILQ_REMOVE(&ap->ap_queue, ape, ape_queue); ap->ap_qlen--; return (ape); } /* * Allocate a new audit pipe. Connects the pipe, on success, to the global * list and updates statistics. */ static struct audit_pipe * audit_pipe_alloc(void) { struct audit_pipe *ap; mtx_assert(&audit_pipe_mtx, MA_OWNED); ap = malloc(sizeof(*ap), M_AUDIT_PIPE, M_NOWAIT | M_ZERO); if (ap == NULL) return (NULL); ap->ap_qlimit = AUDIT_PIPE_QLIMIT_DEFAULT; TAILQ_INIT(&ap->ap_queue); knlist_init(&ap->ap_selinfo.si_note, &audit_pipe_mtx, NULL, NULL, NULL); /* * Default flags, naflags, and auid-specific preselection settings to * 0. Initialize the mode to the global trail so that if praudit(1) * is run on /dev/auditpipe, it sees events associated with the * default trail. Pipe-aware application can clear the flag, set * custom masks, and flush the pipe as needed. */ bzero(&ap->ap_preselect_flags, sizeof(ap->ap_preselect_flags)); bzero(&ap->ap_preselect_naflags, sizeof(ap->ap_preselect_naflags)); TAILQ_INIT(&ap->ap_preselect_list); ap->ap_preselect_mode = AUDITPIPE_PRESELECT_MODE_TRAIL; /* * Add to global list and update global statistics. */ TAILQ_INSERT_HEAD(&audit_pipe_list, ap, ap_list); audit_pipe_count++; audit_pipe_ever++; return (ap); } /* * Flush all records currently present in an audit pipe; assume mutex is held. */ static void audit_pipe_flush(struct audit_pipe *ap) { struct audit_pipe_entry *ape; mtx_assert(&audit_pipe_mtx, MA_OWNED); while ((ape = TAILQ_FIRST(&ap->ap_queue)) != NULL) { TAILQ_REMOVE(&ap->ap_queue, ape, ape_queue); audit_pipe_entry_free(ape); ap->ap_qlen--; } KASSERT(ap->ap_qlen == 0, ("audit_pipe_free: ap_qlen")); } /* * Free an audit pipe; this means freeing all preselection state and all * records in the pipe. Assumes mutex is held to prevent any new records * from being inserted during the free, and that the audit pipe is still on * the global list. */ static void audit_pipe_free(struct audit_pipe *ap) { mtx_assert(&audit_pipe_mtx, MA_OWNED); audit_pipe_preselect_flush_locked(ap); audit_pipe_flush(ap); knlist_destroy(&ap->ap_selinfo.si_note); TAILQ_REMOVE(&audit_pipe_list, ap, ap_list); free(ap, M_AUDIT_PIPE); audit_pipe_count--; } /* * Audit pipe clone routine -- provide specific requested audit pipe, or a * fresh one if a specific one is not requested. */ static void audit_pipe_clone(void *arg, struct ucred *cred, char *name, int namelen, struct cdev **dev) { int i, u; if (*dev != NULL) return; if (strcmp(name, AUDIT_PIPE_NAME) == 0) u = -1; else if (dev_stdclone(name, NULL, AUDIT_PIPE_NAME, &u) != 1) return; i = clone_create(&audit_pipe_clones, &audit_pipe_cdevsw, &u, dev, 0); if (i) { *dev = make_dev(&audit_pipe_cdevsw, unit2minor(u), UID_ROOT, GID_WHEEL, 0600, "%s%d", AUDIT_PIPE_NAME, u); if (*dev != NULL) { dev_ref(*dev); (*dev)->si_flags |= SI_CHEAPCLONE; } } } /* * Audit pipe open method. Explicit privilege check isn't used as this * allows file permissions on the special device to be used to grant audit * review access. Those file permissions should be managed carefully. */ static int audit_pipe_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { struct audit_pipe *ap; mtx_lock(&audit_pipe_mtx); ap = dev->si_drv1; if (ap == NULL) { ap = audit_pipe_alloc(); if (ap == NULL) { mtx_unlock(&audit_pipe_mtx); return (ENOMEM); } dev->si_drv1 = ap; } else { KASSERT(ap->ap_open, ("audit_pipe_open: ap && !ap_open")); mtx_unlock(&audit_pipe_mtx); return (EBUSY); } ap->ap_open = 1; mtx_unlock(&audit_pipe_mtx); fsetown(td->td_proc->p_pid, &ap->ap_sigio); return (0); } /* * Close audit pipe, tear down all records, etc. */ static int audit_pipe_close(struct cdev *dev, int fflag, int devtype, struct thread *td) { struct audit_pipe *ap; ap = dev->si_drv1; KASSERT(ap != NULL, ("audit_pipe_close: ap == NULL")); KASSERT(ap->ap_open, ("audit_pipe_close: !ap_open")); funsetown(&ap->ap_sigio); mtx_lock(&audit_pipe_mtx); ap->ap_open = 0; audit_pipe_free(ap); dev->si_drv1 = NULL; mtx_unlock(&audit_pipe_mtx); return (0); } /* * Audit pipe ioctl() routine. Handle file descriptor and audit pipe layer * commands. * * Would be desirable to support filtering, although perhaps something simple * like an event mask, as opposed to something complicated like BPF. */ static int audit_pipe_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) { struct auditpipe_ioctl_preselect *aip; struct audit_pipe *ap; au_mask_t *maskp; int error, mode; au_id_t auid; ap = dev->si_drv1; KASSERT(ap != NULL, ("audit_pipe_ioctl: ap == NULL")); /* * Audit pipe ioctls: first come standard device node ioctls, then * manipulation of pipe settings, and finally, statistics query * ioctls. */ switch (cmd) { case FIONBIO: mtx_lock(&audit_pipe_mtx); if (*(int *)data) ap->ap_flags |= AUDIT_PIPE_NBIO; else ap->ap_flags &= ~AUDIT_PIPE_NBIO; mtx_unlock(&audit_pipe_mtx); error = 0; break; case FIONREAD: mtx_lock(&audit_pipe_mtx); if (TAILQ_FIRST(&ap->ap_queue) != NULL) *(int *)data = TAILQ_FIRST(&ap->ap_queue)->ape_record_len; else *(int *)data = 0; mtx_unlock(&audit_pipe_mtx); error = 0; break; case FIOASYNC: mtx_lock(&audit_pipe_mtx); if (*(int *)data) ap->ap_flags |= AUDIT_PIPE_ASYNC; else ap->ap_flags &= ~AUDIT_PIPE_ASYNC; mtx_unlock(&audit_pipe_mtx); error = 0; break; case FIOSETOWN: error = fsetown(*(int *)data, &ap->ap_sigio); break; case FIOGETOWN: *(int *)data = fgetown(&ap->ap_sigio); error = 0; break; case AUDITPIPE_GET_QLEN: *(u_int *)data = ap->ap_qlen; error = 0; break; case AUDITPIPE_GET_QLIMIT: *(u_int *)data = ap->ap_qlimit; error = 0; break; case AUDITPIPE_SET_QLIMIT: /* Lockless integer write. */ if (*(u_int *)data >= AUDIT_PIPE_QLIMIT_MIN || *(u_int *)data <= AUDIT_PIPE_QLIMIT_MAX) { ap->ap_qlimit = *(u_int *)data; error = 0; } else error = EINVAL; break; case AUDITPIPE_GET_QLIMIT_MIN: *(u_int *)data = AUDIT_PIPE_QLIMIT_MIN; error = 0; break; case AUDITPIPE_GET_QLIMIT_MAX: *(u_int *)data = AUDIT_PIPE_QLIMIT_MAX; error = 0; break; case AUDITPIPE_GET_PRESELECT_FLAGS: mtx_lock(&audit_pipe_mtx); maskp = (au_mask_t *)data; *maskp = ap->ap_preselect_flags; mtx_unlock(&audit_pipe_mtx); error = 0; break; case AUDITPIPE_SET_PRESELECT_FLAGS: mtx_lock(&audit_pipe_mtx); maskp = (au_mask_t *)data; ap->ap_preselect_flags = *maskp; mtx_unlock(&audit_pipe_mtx); error = 0; break; case AUDITPIPE_GET_PRESELECT_NAFLAGS: mtx_lock(&audit_pipe_mtx); maskp = (au_mask_t *)data; *maskp = ap->ap_preselect_naflags; mtx_unlock(&audit_pipe_mtx); error = 0; break; case AUDITPIPE_SET_PRESELECT_NAFLAGS: mtx_lock(&audit_pipe_mtx); maskp = (au_mask_t *)data; ap->ap_preselect_naflags = *maskp; mtx_unlock(&audit_pipe_mtx); error = 0; break; case AUDITPIPE_GET_PRESELECT_AUID: aip = (struct auditpipe_ioctl_preselect *)data; error = audit_pipe_preselect_get(ap, aip->aip_auid, &aip->aip_mask); break; case AUDITPIPE_SET_PRESELECT_AUID: aip = (struct auditpipe_ioctl_preselect *)data; audit_pipe_preselect_set(ap, aip->aip_auid, aip->aip_mask); error = 0; break; case AUDITPIPE_DELETE_PRESELECT_AUID: auid = *(au_id_t *)data; error = audit_pipe_preselect_delete(ap, auid); break; case AUDITPIPE_FLUSH_PRESELECT_AUID: audit_pipe_preselect_flush(ap); error = 0; break; case AUDITPIPE_GET_PRESELECT_MODE: mtx_lock(&audit_pipe_mtx); *(int *)data = ap->ap_preselect_mode; mtx_unlock(&audit_pipe_mtx); error = 0; break; case AUDITPIPE_SET_PRESELECT_MODE: mode = *(int *)data; switch (mode) { case AUDITPIPE_PRESELECT_MODE_TRAIL: case AUDITPIPE_PRESELECT_MODE_LOCAL: mtx_lock(&audit_pipe_mtx); ap->ap_preselect_mode = mode; mtx_unlock(&audit_pipe_mtx); error = 0; break; default: error = EINVAL; } break; case AUDITPIPE_FLUSH: mtx_lock(&audit_pipe_mtx); audit_pipe_flush(ap); mtx_unlock(&audit_pipe_mtx); error = 0; break; case AUDITPIPE_GET_MAXAUDITDATA: *(u_int *)data = MAXAUDITDATA; error = 0; break; case AUDITPIPE_GET_INSERTS: *(u_int *)data = ap->ap_inserts; error = 0; break; case AUDITPIPE_GET_READS: *(u_int *)data = ap->ap_reads; error = 0; break; case AUDITPIPE_GET_DROPS: *(u_int *)data = ap->ap_drops; error = 0; break; case AUDITPIPE_GET_TRUNCATES: *(u_int *)data = ap->ap_truncates; error = 0; break; default: error = ENOTTY; } return (error); } /* * Audit pipe read. Pull one record off the queue and copy to user space. * On error, the record is dropped. * * Providing more sophisticated behavior, such as partial reads, is tricky * due to the potential for parallel I/O. If partial read support is * required, it will require a per-pipe "current record being read" along * with an offset into that trecord which has already been read. Threads * performing partial reads will need to allocate per-thread copies of the * data so that if another thread completes the read of the record, it can be * freed without adding reference count logic. If this is added, a flag to * indicate that only atomic record reads are desired would be useful, as if * different threads are all waiting for records on the pipe, they will want * independent record reads, which is currently the behavior. */ static int audit_pipe_read(struct cdev *dev, struct uio *uio, int flag) { struct audit_pipe_entry *ape; struct audit_pipe *ap; int error; ap = dev->si_drv1; KASSERT(ap != NULL, ("audit_pipe_read: ap == NULL")); mtx_lock(&audit_pipe_mtx); do { /* * Wait for a record that fits into the read buffer, dropping * records that would be truncated if actually passed to the * process. This helps maintain the discreet record read * interface. */ while ((ape = audit_pipe_pop(ap)) == NULL) { if (ap->ap_flags & AUDIT_PIPE_NBIO) { mtx_unlock(&audit_pipe_mtx); return (EAGAIN); } error = cv_wait_sig(&audit_pipe_cv, &audit_pipe_mtx); if (error) { mtx_unlock(&audit_pipe_mtx); return (error); } } if (ape->ape_record_len <= uio->uio_resid) break; audit_pipe_entry_free(ape); ap->ap_truncates++; } while (1); ap->ap_reads++; mtx_unlock(&audit_pipe_mtx); /* * Now read record to user space memory. Even if the read is short, * we abandon the remainder of the record, supporting only discreet * record reads. */ error = uiomove(ape->ape_record, ape->ape_record_len, uio); audit_pipe_entry_free(ape); return (error); } /* * Audit pipe poll. */ static int audit_pipe_poll(struct cdev *dev, int events, struct thread *td) { struct audit_pipe *ap; int revents; revents = 0; ap = dev->si_drv1; KASSERT(ap != NULL, ("audit_pipe_poll: ap == NULL")); if (events & (POLLIN | POLLRDNORM)) { mtx_lock(&audit_pipe_mtx); if (TAILQ_FIRST(&ap->ap_queue) != NULL) revents |= events & (POLLIN | POLLRDNORM); else selrecord(td, &ap->ap_selinfo); mtx_unlock(&audit_pipe_mtx); } return (revents); } /* * Audit pipe kqfilter. */ static int audit_pipe_kqfilter(struct cdev *dev, struct knote *kn) { struct audit_pipe *ap; ap = dev->si_drv1; KASSERT(ap != NULL, ("audit_pipe_kqfilter: ap == NULL")); if (kn->kn_filter != EVFILT_READ) return (EINVAL); kn->kn_fop = &audit_pipe_read_filterops; kn->kn_hook = ap; mtx_lock(&audit_pipe_mtx); knlist_add(&ap->ap_selinfo.si_note, kn, 1); mtx_unlock(&audit_pipe_mtx); return (0); } /* * Return true if there are records available for reading on the pipe. */ static int audit_pipe_kqread(struct knote *kn, long hint) { struct audit_pipe_entry *ape; struct audit_pipe *ap; mtx_assert(&audit_pipe_mtx, MA_OWNED); ap = (struct audit_pipe *)kn->kn_hook; KASSERT(ap != NULL, ("audit_pipe_kqread: ap == NULL")); if (ap->ap_qlen != 0) { ape = TAILQ_FIRST(&ap->ap_queue); KASSERT(ape != NULL, ("audit_pipe_kqread: ape == NULL")); kn->kn_data = ape->ape_record_len; return (1); } else { kn->kn_data = 0; return (0); } } /* * Detach kqueue state from audit pipe. */ static void audit_pipe_kqdetach(struct knote *kn) { struct audit_pipe *ap; ap = (struct audit_pipe *)kn->kn_hook; KASSERT(ap != NULL, ("audit_pipe_kqdetach: ap == NULL")); mtx_lock(&audit_pipe_mtx); knlist_remove(&ap->ap_selinfo.si_note, kn, 1); mtx_unlock(&audit_pipe_mtx); } /* * Initialize the audit pipe system. */ static void audit_pipe_init(void *unused) { TAILQ_INIT(&audit_pipe_list); mtx_init(&audit_pipe_mtx, "audit_pipe_mtx", NULL, MTX_DEF); cv_init(&audit_pipe_cv, "audit_pipe_cv"); clone_setup(&audit_pipe_clones); audit_pipe_eh_tag = EVENTHANDLER_REGISTER(dev_clone, audit_pipe_clone, 0, 1000); if (audit_pipe_eh_tag == NULL) panic("audit_pipe_init: EVENTHANDLER_REGISTER"); } SYSINIT(audit_pipe_init, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, audit_pipe_init, NULL); Index: stable/7/sys/security/audit/audit_syscalls.c =================================================================== --- stable/7/sys/security/audit/audit_syscalls.c (revision 178335) +++ stable/7/sys/security/audit/audit_syscalls.c (revision 178336) @@ -1,756 +1,757 @@ /* * Copyright (c) 1999-2005 Apple Computer, 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 Computer, 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. - * - * $FreeBSD$ */ + +#include +__FBSDID("$FreeBSD$"); #include "opt_mac.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef AUDIT /* * System call to allow a user space application to submit a BSM audit record * to the kernel for inclusion in the audit log. This function does little * verification on the audit record that is submitted. * * XXXAUDIT: Audit preselection for user records does not currently work, * since we pre-select only based on the AUE_audit event type, not the event * type submitted as part of the user audit data. */ /* ARGSUSED */ int audit(struct thread *td, struct audit_args *uap) { int error; void * rec; struct kaudit_record *ar; if (jailed(td->td_ucred)) return (ENOSYS); error = priv_check(td, PRIV_AUDIT_SUBMIT); if (error) return (error); if ((uap->length <= 0) || (uap->length > audit_qctrl.aq_bufsz)) return (EINVAL); ar = currecord(); /* * If there's no current audit record (audit() itself not audited) * commit the user audit record. */ if (ar == NULL) { /* * This is not very efficient; we're required to allocate a * complete kernel audit record just so the user record can * tag along. * * XXXAUDIT: Maybe AUE_AUDIT in the system call context and * special pre-select handling? */ td->td_ar = audit_new(AUE_NULL, td); if (td->td_ar == NULL) return (ENOTSUP); ar = td->td_ar; } if (uap->length > MAX_AUDIT_RECORD_SIZE) return (EINVAL); rec = malloc(uap->length, M_AUDITDATA, M_WAITOK); error = copyin(uap->record, rec, uap->length); if (error) goto free_out; /* Verify the record. */ if (bsm_rec_verify(rec) == 0) { error = EINVAL; goto free_out; } #ifdef MAC error = mac_check_system_audit(td->td_ucred, rec, uap->length); if (error) goto free_out; #endif /* * Attach the user audit record to the kernel audit record. Because * this system call is an auditable event, we will write the user * record along with the record for this audit event. * * XXXAUDIT: KASSERT appropriate starting values of k_udata, k_ulen, * k_ar_commit & AR_COMMIT_USER? */ ar->k_udata = rec; ar->k_ulen = uap->length; ar->k_ar_commit |= AR_COMMIT_USER; /* * Currently we assume that all preselection has been performed in * userspace. We unconditionally set these masks so that the records * get committed both to the trail and pipe. In the future we will * want to setup kernel based preselection. */ ar->k_ar_commit |= (AR_PRESELECT_USER_TRAIL | AR_PRESELECT_USER_PIPE); return (0); free_out: /* * audit_syscall_exit() will free the audit record on the thread even * if we allocated it above. */ free(rec, M_AUDITDATA); return (error); } /* * System call to manipulate auditing. */ /* ARGSUSED */ int auditon(struct thread *td, struct auditon_args *uap) { struct ucred *newcred, *oldcred; int error; union auditon_udata udata; struct proc *tp; if (jailed(td->td_ucred)) return (ENOSYS); AUDIT_ARG(cmd, uap->cmd); #ifdef MAC error = mac_check_system_auditon(td->td_ucred, uap->cmd); if (error) return (error); #endif error = priv_check(td, PRIV_AUDIT_CONTROL); if (error) return (error); if ((uap->length <= 0) || (uap->length > sizeof(union auditon_udata))) return (EINVAL); memset((void *)&udata, 0, sizeof(udata)); /* * Some of the GET commands use the arguments too. */ switch (uap->cmd) { case A_SETPOLICY: case A_SETKMASK: case A_SETQCTRL: case A_SETSTAT: case A_SETUMASK: case A_SETSMASK: case A_SETCOND: case A_SETCLASS: case A_SETPMASK: case A_SETFSIZE: case A_SETKAUDIT: case A_GETCLASS: case A_GETPINFO: case A_GETPINFO_ADDR: case A_SENDTRIGGER: error = copyin(uap->data, (void *)&udata, uap->length); if (error) return (error); AUDIT_ARG(auditon, &udata); break; } /* * XXXAUDIT: Locking? */ switch (uap->cmd) { case A_GETPOLICY: if (!audit_fail_stop) udata.au_policy |= AUDIT_CNT; if (audit_panic_on_write_fail) udata.au_policy |= AUDIT_AHLT; if (audit_argv) udata.au_policy |= AUDIT_ARGV; if (audit_arge) udata.au_policy |= AUDIT_ARGE; break; case A_SETPOLICY: if (udata.au_policy & ~(AUDIT_CNT|AUDIT_AHLT|AUDIT_ARGV| AUDIT_ARGE)) return (EINVAL); /* * XXX - Need to wake up waiters if the policy relaxes? */ audit_fail_stop = ((udata.au_policy & AUDIT_CNT) == 0); audit_panic_on_write_fail = (udata.au_policy & AUDIT_AHLT); audit_argv = (udata.au_policy & AUDIT_ARGV); audit_arge = (udata.au_policy & AUDIT_ARGE); break; case A_GETKMASK: udata.au_mask = audit_nae_mask; break; case A_SETKMASK: audit_nae_mask = udata.au_mask; break; case A_GETQCTRL: udata.au_qctrl = audit_qctrl; break; case A_SETQCTRL: if ((udata.au_qctrl.aq_hiwater > AQ_MAXHIGH) || (udata.au_qctrl.aq_lowater >= udata.au_qctrl.aq_hiwater) || (udata.au_qctrl.aq_bufsz > AQ_MAXBUFSZ) || (udata.au_qctrl.aq_minfree < 0) || (udata.au_qctrl.aq_minfree > 100)) return (EINVAL); audit_qctrl = udata.au_qctrl; /* XXX The queue delay value isn't used with the kernel. */ audit_qctrl.aq_delay = -1; break; case A_GETCWD: return (ENOSYS); break; case A_GETCAR: return (ENOSYS); break; case A_GETSTAT: return (ENOSYS); break; case A_SETSTAT: return (ENOSYS); break; case A_SETUMASK: return (ENOSYS); break; case A_SETSMASK: return (ENOSYS); break; case A_GETCOND: if (audit_enabled && !audit_suspended) udata.au_cond = AUC_AUDITING; else udata.au_cond = AUC_NOAUDIT; break; case A_SETCOND: if (udata.au_cond == AUC_NOAUDIT) audit_suspended = 1; if (udata.au_cond == AUC_AUDITING) audit_suspended = 0; if (udata.au_cond == AUC_DISABLED) { audit_suspended = 1; audit_shutdown(NULL, 0); } break; case A_GETCLASS: udata.au_evclass.ec_class = au_event_class( udata.au_evclass.ec_number); break; case A_SETCLASS: au_evclassmap_insert(udata.au_evclass.ec_number, udata.au_evclass.ec_class); break; case A_GETPINFO: if (udata.au_aupinfo.ap_pid < 1) return (EINVAL); if ((tp = pfind(udata.au_aupinfo.ap_pid)) == NULL) return (EINVAL); if (p_cansee(td, tp) != 0) { PROC_UNLOCK(tp); return (EINVAL); } if (tp->p_ucred->cr_audit.ai_termid.at_type == AU_IPv6) { PROC_UNLOCK(tp); return (EINVAL); } udata.au_aupinfo.ap_auid = tp->p_ucred->cr_audit.ai_auid; udata.au_aupinfo.ap_mask.am_success = tp->p_ucred->cr_audit.ai_mask.am_success; udata.au_aupinfo.ap_mask.am_failure = tp->p_ucred->cr_audit.ai_mask.am_failure; udata.au_aupinfo.ap_termid.machine = tp->p_ucred->cr_audit.ai_termid.at_addr[0]; udata.au_aupinfo.ap_termid.port = (dev_t)tp->p_ucred->cr_audit.ai_termid.at_port; udata.au_aupinfo.ap_asid = tp->p_ucred->cr_audit.ai_asid; PROC_UNLOCK(tp); break; case A_SETPMASK: if (udata.au_aupinfo.ap_pid < 1) return (EINVAL); newcred = crget(); if ((tp = pfind(udata.au_aupinfo.ap_pid)) == NULL) { crfree(newcred); return (EINVAL); } if (p_cansee(td, tp) != 0) { PROC_UNLOCK(tp); crfree(newcred); return (EINVAL); } oldcred = tp->p_ucred; crcopy(newcred, oldcred); newcred->cr_audit.ai_mask.am_success = udata.au_aupinfo.ap_mask.am_success; newcred->cr_audit.ai_mask.am_failure = udata.au_aupinfo.ap_mask.am_failure; td->td_proc->p_ucred = newcred; PROC_UNLOCK(tp); crfree(oldcred); break; case A_SETFSIZE: if ((udata.au_fstat.af_filesz != 0) && (udata.au_fstat.af_filesz < MIN_AUDIT_FILE_SIZE)) return (EINVAL); audit_fstat.af_filesz = udata.au_fstat.af_filesz; break; case A_GETFSIZE: udata.au_fstat.af_filesz = audit_fstat.af_filesz; udata.au_fstat.af_currsz = audit_fstat.af_currsz; break; case A_GETPINFO_ADDR: if (udata.au_aupinfo_addr.ap_pid < 1) return (EINVAL); if ((tp = pfind(udata.au_aupinfo_addr.ap_pid)) == NULL) return (EINVAL); udata.au_aupinfo_addr.ap_auid = tp->p_ucred->cr_audit.ai_auid; udata.au_aupinfo_addr.ap_mask.am_success = tp->p_ucred->cr_audit.ai_mask.am_success; udata.au_aupinfo_addr.ap_mask.am_failure = tp->p_ucred->cr_audit.ai_mask.am_failure; udata.au_aupinfo_addr.ap_termid = tp->p_ucred->cr_audit.ai_termid; udata.au_aupinfo_addr.ap_asid = tp->p_ucred->cr_audit.ai_asid; PROC_UNLOCK(tp); break; case A_GETKAUDIT: return (ENOSYS); break; case A_SETKAUDIT: return (ENOSYS); break; case A_SENDTRIGGER: if ((udata.au_trigger < AUDIT_TRIGGER_MIN) || (udata.au_trigger > AUDIT_TRIGGER_MAX)) return (EINVAL); return (audit_send_trigger(udata.au_trigger)); default: return (EINVAL); } /* * Copy data back to userspace for the GET comands. */ switch (uap->cmd) { case A_GETPOLICY: case A_GETKMASK: case A_GETQCTRL: case A_GETCWD: case A_GETCAR: case A_GETSTAT: case A_GETCOND: case A_GETCLASS: case A_GETPINFO: case A_GETFSIZE: case A_GETPINFO_ADDR: case A_GETKAUDIT: error = copyout((void *)&udata, uap->data, uap->length); if (error) return (error); break; } return (0); } /* * System calls to manage the user audit information. */ /* ARGSUSED */ int getauid(struct thread *td, struct getauid_args *uap) { int error; if (jailed(td->td_ucred)) return (ENOSYS); error = priv_check(td, PRIV_AUDIT_GETAUDIT); if (error) return (error); return (copyout(&td->td_ucred->cr_audit.ai_auid, uap->auid, sizeof(td->td_ucred->cr_audit.ai_auid))); } /* ARGSUSED */ int setauid(struct thread *td, struct setauid_args *uap) { struct ucred *newcred, *oldcred; au_id_t id; int error; if (jailed(td->td_ucred)) return (ENOSYS); error = copyin(uap->auid, &id, sizeof(id)); if (error) return (error); audit_arg_auid(id); newcred = crget(); PROC_LOCK(td->td_proc); oldcred = td->td_proc->p_ucred; crcopy(newcred, oldcred); #ifdef MAC error = mac_check_proc_setauid(oldcred, id); if (error) goto fail; #endif error = priv_check_cred(oldcred, PRIV_AUDIT_SETAUDIT, 0); if (error) goto fail; newcred->cr_audit.ai_auid = id; td->td_proc->p_ucred = newcred; PROC_UNLOCK(td->td_proc); crfree(oldcred); return (0); fail: PROC_UNLOCK(td->td_proc); crfree(newcred); return (error); } /* * System calls to get and set process audit information. */ /* ARGSUSED */ int getaudit(struct thread *td, struct getaudit_args *uap) { struct auditinfo ai; int error; if (jailed(td->td_ucred)) return (ENOSYS); error = priv_check(td, PRIV_AUDIT_GETAUDIT); if (error) return (error); if (td->td_ucred->cr_audit.ai_termid.at_type == AU_IPv6) return (E2BIG); bzero(&ai, sizeof(ai)); ai.ai_auid = td->td_ucred->cr_audit.ai_auid; ai.ai_mask = td->td_ucred->cr_audit.ai_mask; ai.ai_asid = td->td_ucred->cr_audit.ai_asid; ai.ai_termid.machine = td->td_ucred->cr_audit.ai_termid.at_addr[0]; ai.ai_termid.port = td->td_ucred->cr_audit.ai_termid.at_port; return (copyout(&ai, uap->auditinfo, sizeof(ai))); } /* ARGSUSED */ int setaudit(struct thread *td, struct setaudit_args *uap) { struct ucred *newcred, *oldcred; struct auditinfo ai; int error; if (jailed(td->td_ucred)) return (ENOSYS); error = copyin(uap->auditinfo, &ai, sizeof(ai)); if (error) return (error); audit_arg_auditinfo(&ai); newcred = crget(); PROC_LOCK(td->td_proc); oldcred = td->td_proc->p_ucred; crcopy(newcred, oldcred); #ifdef MAC error = mac_check_proc_setaudit(oldcred, &ai); if (error) goto fail; #endif error = priv_check_cred(oldcred, PRIV_AUDIT_SETAUDIT, 0); if (error) goto fail; bzero(&newcred->cr_audit, sizeof(newcred->cr_audit)); newcred->cr_audit.ai_auid = ai.ai_auid; newcred->cr_audit.ai_mask = ai.ai_mask; newcred->cr_audit.ai_asid = ai.ai_asid; newcred->cr_audit.ai_termid.at_addr[0] = ai.ai_termid.machine; newcred->cr_audit.ai_termid.at_port = ai.ai_termid.port; newcred->cr_audit.ai_termid.at_type = AU_IPv4; td->td_proc->p_ucred = newcred; PROC_UNLOCK(td->td_proc); crfree(oldcred); return (0); fail: PROC_UNLOCK(td->td_proc); crfree(newcred); return (error); } /* ARGSUSED */ int getaudit_addr(struct thread *td, struct getaudit_addr_args *uap) { int error; if (jailed(td->td_ucred)) return (ENOSYS); if (uap->length < sizeof(*uap->auditinfo_addr)) return (EOVERFLOW); error = priv_check(td, PRIV_AUDIT_GETAUDIT); if (error) return (error); return (copyout(&td->td_ucred->cr_audit, uap->auditinfo_addr, sizeof(*uap->auditinfo_addr))); } /* ARGSUSED */ int setaudit_addr(struct thread *td, struct setaudit_addr_args *uap) { struct ucred *newcred, *oldcred; struct auditinfo_addr aia; int error; if (jailed(td->td_ucred)) return (ENOSYS); error = copyin(uap->auditinfo_addr, &aia, sizeof(aia)); if (error) return (error); audit_arg_auditinfo_addr(&aia); if (aia.ai_termid.at_type != AU_IPv6 && aia.ai_termid.at_type != AU_IPv4) return (EINVAL); newcred = crget(); PROC_LOCK(td->td_proc); oldcred = td->td_proc->p_ucred; crcopy(newcred, oldcred); #ifdef MAC error = mac_check_proc_setaudit_addr(oldcred, &aia); if (error) goto fail; #endif error = priv_check_cred(oldcred, PRIV_AUDIT_SETAUDIT, 0); if (error) goto fail; newcred->cr_audit = aia; td->td_proc->p_ucred = newcred; PROC_UNLOCK(td->td_proc); crfree(oldcred); return (0); fail: PROC_UNLOCK(td->td_proc); crfree(newcred); return (error); } /* * Syscall to manage audit files. */ /* ARGSUSED */ int auditctl(struct thread *td, struct auditctl_args *uap) { struct nameidata nd; struct ucred *cred; struct vnode *vp; int error = 0; int flags, vfslocked; if (jailed(td->td_ucred)) return (ENOSYS); error = priv_check(td, PRIV_AUDIT_CONTROL); if (error) return (error); vp = NULL; cred = NULL; /* * If a path is specified, open the replacement vnode, perform * validity checks, and grab another reference to the current * credential. * * On Darwin, a NULL path argument is also used to disable audit. */ if (uap->path == NULL) return (EINVAL); NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | MPSAFE | AUDITVNODE1, UIO_USERSPACE, uap->path, td); flags = AUDIT_OPEN_FLAGS; error = vn_open(&nd, &flags, 0, NULL); if (error) return (error); vfslocked = NDHASGIANT(&nd); vp = nd.ni_vp; #ifdef MAC error = mac_check_system_auditctl(td->td_ucred, vp); VOP_UNLOCK(vp, 0, td); if (error) { vn_close(vp, AUDIT_CLOSE_FLAGS, td->td_ucred, td); VFS_UNLOCK_GIANT(vfslocked); return (error); } #else VOP_UNLOCK(vp, 0, td); #endif NDFREE(&nd, NDF_ONLY_PNBUF); if (vp->v_type != VREG) { vn_close(vp, AUDIT_CLOSE_FLAGS, td->td_ucred, td); VFS_UNLOCK_GIANT(vfslocked); return (EINVAL); } VFS_UNLOCK_GIANT(vfslocked); cred = td->td_ucred; crhold(cred); /* * XXXAUDIT: Should audit_suspended actually be cleared by * audit_worker? */ audit_suspended = 0; audit_rotate_vnode(cred, vp); return (error); } #else /* !AUDIT */ int audit(struct thread *td, struct audit_args *uap) { return (ENOSYS); } int auditon(struct thread *td, struct auditon_args *uap) { return (ENOSYS); } int getauid(struct thread *td, struct getauid_args *uap) { return (ENOSYS); } int setauid(struct thread *td, struct setauid_args *uap) { return (ENOSYS); } int getaudit(struct thread *td, struct getaudit_args *uap) { return (ENOSYS); } int setaudit(struct thread *td, struct setaudit_args *uap) { return (ENOSYS); } int getaudit_addr(struct thread *td, struct getaudit_addr_args *uap) { return (ENOSYS); } int setaudit_addr(struct thread *td, struct setaudit_addr_args *uap) { return (ENOSYS); } int auditctl(struct thread *td, struct auditctl_args *uap) { return (ENOSYS); } #endif /* AUDIT */ Index: stable/7/sys/security/audit/audit_trigger.c =================================================================== --- stable/7/sys/security/audit/audit_trigger.c (revision 178335) +++ stable/7/sys/security/audit/audit_trigger.c (revision 178336) @@ -1,177 +1,178 @@ /*- * Copyright (c) 2005 Wayne J. Salamon * All rights reserved. * * This software was developed by Wayne Salamon for the TrustedBSD Project. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND 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 THE AUTHOR OR 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. - * - * $FreeBSD$ */ + +#include +__FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include /* * Structures and operations to support the basic character special device * used to communicate with userland. /dev/audit reliably delivers one-byte * messages to a listening application (or discards them if there is no * listening application). * * Currently, select/poll are not supported on the trigger device. */ struct trigger_info { unsigned int trigger; TAILQ_ENTRY(trigger_info) list; }; static MALLOC_DEFINE(M_AUDITTRIGGER, "audit_trigger", "Audit trigger events"); static struct cdev *audit_dev; static int audit_isopen = 0; static TAILQ_HEAD(, trigger_info) trigger_list; static struct mtx audit_trigger_mtx; static int audit_open(struct cdev *dev, int oflags, int devtype, struct thread *td) { int error; /* Only one process may open the device at a time. */ mtx_lock(&audit_trigger_mtx); if (!audit_isopen) { error = 0; audit_isopen = 1; } else error = EBUSY; mtx_unlock(&audit_trigger_mtx); return (error); } static int audit_close(struct cdev *dev, int fflag, int devtype, struct thread *td) { struct trigger_info *ti; /* Flush the queue of pending trigger events. */ mtx_lock(&audit_trigger_mtx); audit_isopen = 0; while (!TAILQ_EMPTY(&trigger_list)) { ti = TAILQ_FIRST(&trigger_list); TAILQ_REMOVE(&trigger_list, ti, list); free(ti, M_AUDITTRIGGER); } mtx_unlock(&audit_trigger_mtx); return (0); } static int audit_read(struct cdev *dev, struct uio *uio, int ioflag) { int error = 0; struct trigger_info *ti = NULL; mtx_lock(&audit_trigger_mtx); while (TAILQ_EMPTY(&trigger_list)) { error = msleep(&trigger_list, &audit_trigger_mtx, PSOCK | PCATCH, "auditd", 0); if (error) break; } if (!error) { ti = TAILQ_FIRST(&trigger_list); TAILQ_REMOVE(&trigger_list, ti, list); } mtx_unlock(&audit_trigger_mtx); if (!error) { error = uiomove(&ti->trigger, sizeof(ti->trigger), uio); free(ti, M_AUDITTRIGGER); } return (error); } static int audit_write(struct cdev *dev, struct uio *uio, int ioflag) { /* Communication is kernel->userspace only. */ return (EOPNOTSUPP); } int audit_send_trigger(unsigned int trigger) { struct trigger_info *ti; ti = malloc(sizeof *ti, M_AUDITTRIGGER, M_WAITOK); mtx_lock(&audit_trigger_mtx); if (!audit_isopen) { /* If nobody's listening, we ain't talking. */ mtx_unlock(&audit_trigger_mtx); free(ti, M_AUDITTRIGGER); return (ENODEV); } ti->trigger = trigger; TAILQ_INSERT_TAIL(&trigger_list, ti, list); wakeup(&trigger_list); mtx_unlock(&audit_trigger_mtx); return (0); } static struct cdevsw audit_cdevsw = { .d_version = D_VERSION, .d_open = audit_open, .d_close = audit_close, .d_read = audit_read, .d_write = audit_write, .d_name = "audit" }; void audit_trigger_init(void) { TAILQ_INIT(&trigger_list); mtx_init(&audit_trigger_mtx, "audit_trigger_mtx", NULL, MTX_DEF); } static void audit_trigger_cdev_init(void *unused) { /* Create the special device file. */ audit_dev = make_dev(&audit_cdevsw, 0, UID_ROOT, GID_KMEM, 0600, AUDITDEV_FILENAME); } SYSINIT(audit_trigger_cdev_init, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, audit_trigger_cdev_init, NULL); Index: stable/7/sys/security/audit/audit_worker.c =================================================================== --- stable/7/sys/security/audit/audit_worker.c (revision 178335) +++ stable/7/sys/security/audit/audit_worker.c (revision 178336) @@ -1,484 +1,485 @@ /* * Copyright (c) 1999-2005 Apple Computer, Inc. * Copyright (c) 2006-2008 Robert N. M. Watson * 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 Computer, 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. - * - * $FreeBSD$ */ + +#include +__FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Worker thread that will schedule disk I/O, etc. */ static struct proc *audit_thread; /* * audit_cred and audit_vp are the stored credential and vnode to use for * active audit trail. They are protected by audit_worker_sx, which will be * held across all I/O and all rotation to prevent them from being replaced * (rotated) while in use. The audit_file_rotate_wait flag is set when the * kernel has delivered a trigger to auditd to rotate the trail, and is * cleared when the next rotation takes place. It is also protected by * audit_worker_sx. */ static int audit_file_rotate_wait; static struct sx audit_worker_sx; static struct ucred *audit_cred; static struct vnode *audit_vp; /* * Write an audit record to a file, performed as the last stage after both * preselection and BSM conversion. Both space management and write failures * are handled in this function. * * No attempt is made to deal with possible failure to deliver a trigger to * the audit daemon, since the message is asynchronous anyway. */ static void audit_record_write(struct vnode *vp, struct ucred *cred, void *data, size_t len) { static struct timeval last_lowspace_trigger; static struct timeval last_fail; static int cur_lowspace_trigger; struct statfs *mnt_stat; int error, vfslocked; static int cur_fail; struct vattr vattr; long temp; sx_assert(&audit_worker_sx, SA_LOCKED); /* audit_file_rotate_wait. */ if (vp == NULL) return; mnt_stat = &vp->v_mount->mnt_stat; vfslocked = VFS_LOCK_GIANT(vp->v_mount); /* * First, gather statistics on the audit log file and file system so * that we know how we're doing on space. Consider failure of these * operations to indicate a future inability to write to the file. */ error = VFS_STATFS(vp->v_mount, mnt_stat, curthread); if (error) goto fail; vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, curthread); error = VOP_GETATTR(vp, &vattr, cred, curthread); VOP_UNLOCK(vp, 0, curthread); if (error) goto fail; audit_fstat.af_currsz = vattr.va_size; /* * We handle four different space-related limits: * * - A fixed (hard) limit on the minimum free blocks we require on * the file system, and results in record loss, a trigger, and * possible fail stop due to violating invariants. * * - An administrative (soft) limit, which when fallen below, results * in the kernel notifying the audit daemon of low space. * * - An audit trail size limit, which when gone above, results in the * kernel notifying the audit daemon that rotation is desired. * * - The total depth of the kernel audit record exceeding free space, * which can lead to possible fail stop (with drain), in order to * prevent violating invariants. Failure here doesn't halt * immediately, but prevents new records from being generated. * * Possibly, the last of these should be handled differently, always * allowing a full queue to be lost, rather than trying to prevent * loss. * * First, handle the hard limit, which generates a trigger and may * fail stop. This is handled in the same manner as ENOSPC from * VOP_WRITE, and results in record loss. */ if (mnt_stat->f_bfree < AUDIT_HARD_LIMIT_FREE_BLOCKS) { error = ENOSPC; goto fail_enospc; } /* * Second, handle falling below the soft limit, if defined; we send * the daemon a trigger and continue processing the record. Triggers * are limited to 1/sec. */ if (audit_qctrl.aq_minfree != 0) { temp = mnt_stat->f_blocks / (100 / audit_qctrl.aq_minfree); if (mnt_stat->f_bfree < temp) { if (ppsratecheck(&last_lowspace_trigger, &cur_lowspace_trigger, 1)) { (void)audit_send_trigger( AUDIT_TRIGGER_LOW_SPACE); printf("Warning: audit space low\n"); } } } /* * If the current file is getting full, generate a rotation trigger * to the daemon. This is only approximate, which is fine as more * records may be generated before the daemon rotates the file. */ if ((audit_fstat.af_filesz != 0) && (audit_file_rotate_wait == 0) && (vattr.va_size >= audit_fstat.af_filesz)) { sx_assert(&audit_worker_sx, SA_XLOCKED); audit_file_rotate_wait = 1; (void)audit_send_trigger(AUDIT_TRIGGER_ROTATE_KERNEL); } /* * If the estimated amount of audit data in the audit event queue * (plus records allocated but not yet queued) has reached the amount * of free space on the disk, then we need to go into an audit fail * stop state, in which we do not permit the allocation/committing of * any new audit records. We continue to process records but don't * allow any activities that might generate new records. In the * future, we might want to detect when space is available again and * allow operation to continue, but this behavior is sufficient to * meet fail stop requirements in CAPP. */ if (audit_fail_stop) { if ((unsigned long)((audit_q_len + audit_pre_q_len + 1) * MAX_AUDIT_RECORD_SIZE) / mnt_stat->f_bsize >= (unsigned long)(mnt_stat->f_bfree)) { if (ppsratecheck(&last_fail, &cur_fail, 1)) printf("audit_record_write: free space " "below size of audit queue, failing " "stop\n"); audit_in_failure = 1; } else if (audit_in_failure) { /* * Note: if we want to handle recovery, this is the * spot to do it: unset audit_in_failure, and issue a * wakeup on the cv. */ } } error = vn_rdwr(UIO_WRITE, vp, data, len, (off_t)0, UIO_SYSSPACE, IO_APPEND|IO_UNIT, cred, NULL, NULL, curthread); if (error == ENOSPC) goto fail_enospc; else if (error) goto fail; /* * Catch completion of a queue drain here; if we're draining and the * queue is now empty, fail stop. That audit_fail_stop is implicitly * true, since audit_in_failure can only be set of audit_fail_stop is * set. * * Note: if we handle recovery from audit_in_failure, then we need to * make panic here conditional. */ if (audit_in_failure) { if (audit_q_len == 0 && audit_pre_q_len == 0) { VOP_LOCK(vp, LK_DRAIN | LK_INTERLOCK, curthread); (void)VOP_FSYNC(vp, MNT_WAIT, curthread); VOP_UNLOCK(vp, 0, curthread); panic("Audit store overflow; record queue drained."); } } VFS_UNLOCK_GIANT(vfslocked); return; fail_enospc: /* * ENOSPC is considered a special case with respect to failures, as * this can reflect either our preemptive detection of insufficient * space, or ENOSPC returned by the vnode write call. */ if (audit_fail_stop) { VOP_LOCK(vp, LK_DRAIN | LK_INTERLOCK, curthread); (void)VOP_FSYNC(vp, MNT_WAIT, curthread); VOP_UNLOCK(vp, 0, curthread); panic("Audit log space exhausted and fail-stop set."); } (void)audit_send_trigger(AUDIT_TRIGGER_NO_SPACE); audit_suspended = 1; /* FALLTHROUGH */ fail: /* * We have failed to write to the file, so the current record is * lost, which may require an immediate system halt. */ if (audit_panic_on_write_fail) { VOP_LOCK(vp, LK_DRAIN | LK_INTERLOCK, curthread); (void)VOP_FSYNC(vp, MNT_WAIT, curthread); VOP_UNLOCK(vp, 0, curthread); panic("audit_worker: write error %d\n", error); } else if (ppsratecheck(&last_fail, &cur_fail, 1)) printf("audit_worker: write error %d\n", error); VFS_UNLOCK_GIANT(vfslocked); } /* * Given a kernel audit record, process as required. Kernel audit records * are converted to one, or possibly two, BSM records, depending on whether * there is a user audit record present also. Kernel records need be * converted to BSM before they can be written out. Both types will be * written to disk, and audit pipes. */ static void audit_worker_process_record(struct kaudit_record *ar) { struct au_record *bsm; au_class_t class; au_event_t event; au_id_t auid; int error, sorf; int trail_locked; /* * We hold the audit_worker_sx lock over both writes, if there are * two, so that the two records won't be split across a rotation and * end up in two different trail files. */ if (((ar->k_ar_commit & AR_COMMIT_USER) && (ar->k_ar_commit & AR_PRESELECT_USER_TRAIL)) || (ar->k_ar_commit & AR_PRESELECT_TRAIL)) { sx_xlock(&audit_worker_sx); trail_locked = 1; } else trail_locked = 0; /* * First, handle the user record, if any: commit to the system trail * and audit pipes as selected. */ if ((ar->k_ar_commit & AR_COMMIT_USER) && (ar->k_ar_commit & AR_PRESELECT_USER_TRAIL)) { sx_assert(&audit_worker_sx, SA_XLOCKED); audit_record_write(audit_vp, audit_cred, ar->k_udata, ar->k_ulen); } if ((ar->k_ar_commit & AR_COMMIT_USER) && (ar->k_ar_commit & AR_PRESELECT_USER_PIPE)) audit_pipe_submit_user(ar->k_udata, ar->k_ulen); if (!(ar->k_ar_commit & AR_COMMIT_KERNEL) || ((ar->k_ar_commit & AR_PRESELECT_PIPE) == 0 && (ar->k_ar_commit & AR_PRESELECT_TRAIL) == 0)) goto out; auid = ar->k_ar.ar_subj_auid; event = ar->k_ar.ar_event; class = au_event_class(event); if (ar->k_ar.ar_errno == 0) sorf = AU_PRS_SUCCESS; else sorf = AU_PRS_FAILURE; error = kaudit_to_bsm(ar, &bsm); switch (error) { case BSM_NOAUDIT: goto out; case BSM_FAILURE: printf("audit_worker_process_record: BSM_FAILURE\n"); goto out; case BSM_SUCCESS: break; default: panic("kaudit_to_bsm returned %d", error); } if (ar->k_ar_commit & AR_PRESELECT_TRAIL) { sx_assert(&audit_worker_sx, SA_XLOCKED); audit_record_write(audit_vp, audit_cred, bsm->data, bsm->len); } if (ar->k_ar_commit & AR_PRESELECT_PIPE) audit_pipe_submit(auid, event, class, sorf, ar->k_ar_commit & AR_PRESELECT_TRAIL, bsm->data, bsm->len); kau_free(bsm); out: if (trail_locked) sx_xunlock(&audit_worker_sx); } /* * The audit_worker thread is responsible for watching the event queue, * dequeueing records, converting them to BSM format, and committing them to * disk. In order to minimize lock thrashing, records are dequeued in sets * to a thread-local work queue. * * Note: this means that the effect bound on the size of the pending record * queue is 2x the length of the global queue. */ static void audit_worker(void *arg) { struct kaudit_queue ar_worklist; struct kaudit_record *ar; int lowater_signal; TAILQ_INIT(&ar_worklist); mtx_lock(&audit_mtx); while (1) { mtx_assert(&audit_mtx, MA_OWNED); /* * Wait for a record. */ while (TAILQ_EMPTY(&audit_q)) cv_wait(&audit_worker_cv, &audit_mtx); /* * If there are records in the global audit record queue, * transfer them to a thread-local queue and process them * one by one. If we cross the low watermark threshold, * signal any waiting processes that they may wake up and * continue generating records. */ lowater_signal = 0; while ((ar = TAILQ_FIRST(&audit_q))) { TAILQ_REMOVE(&audit_q, ar, k_q); audit_q_len--; if (audit_q_len == audit_qctrl.aq_lowater) lowater_signal++; TAILQ_INSERT_TAIL(&ar_worklist, ar, k_q); } if (lowater_signal) cv_broadcast(&audit_watermark_cv); mtx_unlock(&audit_mtx); while ((ar = TAILQ_FIRST(&ar_worklist))) { TAILQ_REMOVE(&ar_worklist, ar, k_q); audit_worker_process_record(ar); audit_free(ar); } mtx_lock(&audit_mtx); } } /* * audit_rotate_vnode() is called by a user or kernel thread to configure or * de-configure auditing on a vnode. The arguments are the replacement * credential (referenced) and vnode (referenced and opened) to substitute * for the current credential and vnode, if any. If either is set to NULL, * both should be NULL, and this is used to indicate that audit is being * disabled. Any previous cred/vnode will be closed and freed. We re-enable * generating rotation requests to auditd. */ void audit_rotate_vnode(struct ucred *cred, struct vnode *vp) { struct ucred *old_audit_cred; struct vnode *old_audit_vp; int vfslocked; KASSERT((cred != NULL && vp != NULL) || (cred == NULL && vp == NULL), ("audit_rotate_vnode: cred %p vp %p", cred, vp)); /* * Rotate the vnode/cred, and clear the rotate flag so that we will * send a rotate trigger if the new file fills. */ sx_xlock(&audit_worker_sx); old_audit_cred = audit_cred; old_audit_vp = audit_vp; audit_cred = cred; audit_vp = vp; audit_file_rotate_wait = 0; audit_enabled = (audit_vp != NULL); sx_xunlock(&audit_worker_sx); /* * If there was an old vnode/credential, close and free. */ if (old_audit_vp != NULL) { vfslocked = VFS_LOCK_GIANT(old_audit_vp->v_mount); vn_close(old_audit_vp, AUDIT_CLOSE_FLAGS, old_audit_cred, curthread); VFS_UNLOCK_GIANT(vfslocked); crfree(old_audit_cred); } } void audit_worker_init(void) { int error; sx_init(&audit_worker_sx, "audit_worker_sx"); error = kthread_create(audit_worker, NULL, &audit_thread, RFHIGHPID, 0, "audit"); if (error) panic("audit_worker_init: kthread_create returned %d", error); }