Index: head/sys/security/audit/audit.c =================================================================== --- head/sys/security/audit/audit.c (revision 162379) +++ head/sys/security/audit/audit.c (revision 162380) @@ -1,656 +1,656 @@ /* * Copyright (c) 1999-2005 Apple Computer, Inc. * Copyright (c) 2006 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 #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_AUDITPROC, "audit_proc", "Audit process 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"); /* * 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 statistiscs. */ 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]; PROC_LOCK(td->td_proc); ar->k_ar.ar_subj_auid = td->td_proc->p_au->ai_auid; ar->k_ar.ar_subj_asid = td->td_proc->p_au->ai_asid; ar->k_ar.ar_subj_pid = td->td_proc->p_pid; ar->k_ar.ar_subj_amask = td->td_proc->p_au->ai_mask; ar->k_ar.ar_subj_term = td->td_proc->p_au->ai_termid; bcopy(td->td_proc->p_comm, ar->k_ar.ar_subj_comm, MAXCOMLEN); PROC_UNLOCK(td->td_proc); 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) { printf("Security auditing service present\n"); 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 = AU_NULL; audit_nae_mask.am_failure = AU_NULL; 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. */ __inline__ struct kaudit_record * currecord(void) { return (curthread->td_ar); } /* * MPSAFE * * 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); /* * XXX: 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); } /* * MPSAFE */ 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. * * XXXAUDIT: Synchronize access to audit_nae_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 = flags_and_error_to_openevent( ar->k_ar.ar_arg_fflags, error); break; case AUE_SYSCTL: ar->k_ar.ar_event = 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)) == - 0) { + 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; /* * We might want to do some system-wide post-filtering * here at some point. */ /* * Timestamp system call end. */ nanotime(&ar->k_ar.ar_endtime); mtx_lock(&audit_mtx); /* * Note: it could be that some records initiated while audit was * enabled should still be committed? */ 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) { AUDIT_PRINTF(("audit_commit: sleeping to wait for " "audit queue to drain below high water mark\n")); cv_wait(&audit_watermark_cv, &audit_mtx); AUDIT_PRINTF(("audit_commit: woke up waiting for " "audit queue draining\n")); } 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. * * XXXAUDIT: Should we audit that a bad system call was made, and if * so, how? */ 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_proc->p_au->ai_auid; if (auid == AU_DEFAUDITID) aumask = &audit_nae_mask; else aumask = &td->td_proc->p_au->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. * * XXXRW: 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 && suser(td) != 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); if (td->td_ar != NULL) AUDIT_PRINTF(("audit record committed by pid %d\n", td->td_proc->p_pid)); td->td_ar = NULL; } /* * Allocate storage for a new process (init, or otherwise). */ void audit_proc_alloc(struct proc *p) { KASSERT(p->p_au == NULL, ("audit_proc_alloc: p->p_au != NULL (%d)", p->p_pid)); p->p_au = malloc(sizeof(*(p->p_au)), M_AUDITPROC, M_WAITOK); /* XXXAUDIT: Zero? Slab allocate? */ //printf("audit_proc_alloc: pid %d p_au %p\n", p->p_pid, p->p_au); } /* * Allocate storage for a new thread. */ void audit_thread_alloc(struct thread *td) { td->td_ar = NULL; } /* * Thread destruction. */ void audit_thread_free(struct thread *td) { KASSERT(td->td_ar == NULL, ("audit_thread_free: td_ar != NULL")); } /* * Initialize the audit information for the a process, presumably the first * process in the system. * XXX It is not clear what the initial values should be for audit ID, * session ID, etc. */ void audit_proc_kproc0(struct proc *p) { KASSERT(p->p_au != NULL, ("audit_proc_kproc0: p->p_au == NULL (%d)", p->p_pid)); //printf("audit_proc_kproc0: pid %d p_au %p\n", p->p_pid, p->p_au); bzero(p->p_au, sizeof(*(p)->p_au)); } void audit_proc_init(struct proc *p) { KASSERT(p->p_au != NULL, ("audit_proc_init: p->p_au == NULL (%d)", p->p_pid)); //printf("audit_proc_init: pid %d p_au %p\n", p->p_pid, p->p_au); bzero(p->p_au, sizeof(*(p)->p_au)); p->p_au->ai_auid = AU_DEFAUDITID; } /* * Copy the audit info from the parent process to the child process when * a fork takes place. */ void audit_proc_fork(struct proc *parent, struct proc *child) { PROC_LOCK_ASSERT(parent, MA_OWNED); PROC_LOCK_ASSERT(child, MA_OWNED); KASSERT(parent->p_au != NULL, ("audit_proc_fork: parent->p_au == NULL (%d)", parent->p_pid)); KASSERT(child->p_au != NULL, ("audit_proc_fork: child->p_au == NULL (%d)", child->p_pid)); //printf("audit_proc_fork: parent pid %d p_au %p\n", parent->p_pid, // parent->p_au); //printf("audit_proc_fork: child pid %d p_au %p\n", child->p_pid, // child->p_au); bcopy(parent->p_au, child->p_au, sizeof(*child->p_au)); /* * XXXAUDIT: Zero pointers to external memory, or assert they are * zero? */ } /* * Free the auditing structure for the process. */ void audit_proc_free(struct proc *p) { KASSERT(p->p_au != NULL, ("p->p_au == NULL (%d)", p->p_pid)); //printf("audit_proc_free: pid %d p_au %p\n", p->p_pid, p->p_au); /* * XXXAUDIT: Assert that external memory pointers are NULL? */ free(p->p_au, M_AUDITPROC); p->p_au = NULL; } Index: head/sys/security/audit/audit_private.h =================================================================== --- head/sys/security/audit/audit_private.h (revision 162379) +++ head/sys/security/audit/audit_private.h (revision 162380) @@ -1,348 +1,351 @@ /* * 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$ */ /* * This include file contains function prototypes and type definitions used * within the audit implementation. */ #ifndef _SECURITY_AUDIT_PRIVATE_H_ #define _SECURITY_AUDIT_PRIVATE_H_ #ifndef _KERNEL #error "no user-serviceable parts inside" #endif #include #include #include #ifdef MALLOC_DECLARE MALLOC_DECLARE(M_AUDITBSM); MALLOC_DECLARE(M_AUDITDATA); MALLOC_DECLARE(M_AUDITPATH); MALLOC_DECLARE(M_AUDITTEXT); #endif /* * The AUDIT_EXCESSIVELY_VERBOSE define enables a number of gratuitously * noisy printf's to the console. Due to the volume, it should be left off * unless you want your system to churn a lot whenever the audit record flow * gets high. */ //#define AUDIT_EXCESSIVELY_VERBOSE #ifdef AUDIT_EXCESSIVELY_VERBOSE #define AUDIT_PRINTF(x) printf x #else #define AUDIT_PRINTF(x) #endif /* * Audit control variables that are usually set/read via system calls and * used to control various aspects of auditing. */ extern struct au_qctrl audit_qctrl; extern struct audit_fstat audit_fstat; extern struct au_mask audit_nae_mask; extern int audit_panic_on_write_fail; extern int audit_fail_stop; extern int audit_argv; extern int audit_arge; /* * Success/failure conditions for the conversion of a kernel audit record to * BSM format. */ #define BSM_SUCCESS 0 #define BSM_FAILURE 1 #define BSM_NOAUDIT 2 /* * Defines for the kernel audit record k_ar_commit field. Flags are set to * indicate what sort of record it is, and which preselection mechanism * selected it. */ #define AR_COMMIT_KERNEL 0x00000001U #define AR_COMMIT_USER 0x00000010U #define AR_PRESELECT_TRAIL 0x00001000U #define AR_PRESELECT_PIPE 0x00002000U +#define AR_PRESELECT_USER_TRAIL 0x00004000U +#define AR_PRESELECT_USER_PIPE 0x00008000U + /* * Audit data is generated as a stream of struct audit_record structures, * linked by struct kaudit_record, and contain storage for possible audit so * that it will not need to be allocated during the processing of a system * call, both improving efficiency and avoiding sleeping at untimely moments. * This structure is converted to BSM format before being written to disk. */ struct vnode_au_info { mode_t vn_mode; uid_t vn_uid; gid_t vn_gid; dev_t vn_dev; long vn_fsid; long vn_fileid; long vn_gen; }; struct groupset { gid_t gidset[NGROUPS]; u_int gidset_size; }; struct socket_au_info { int so_domain; int so_type; int so_protocol; in_addr_t so_raddr; /* Remote address if INET socket. */ in_addr_t so_laddr; /* Local address if INET socket. */ u_short so_rport; /* Remote port. */ u_short so_lport; /* Local port. */ }; union auditon_udata { char *au_path; long au_cond; long au_flags; long au_policy; int au_trigger; au_evclass_map_t au_evclass; au_mask_t au_mask; auditinfo_t au_auinfo; auditpinfo_t au_aupinfo; auditpinfo_addr_t au_aupinfo_addr; au_qctrl_t au_qctrl; au_stat_t au_stat; au_fstat_t au_fstat; }; struct posix_ipc_perm { uid_t pipc_uid; gid_t pipc_gid; mode_t pipc_mode; }; struct audit_record { /* Audit record header. */ u_int32_t ar_magic; int ar_event; int ar_retval; /* value returned to the process */ int ar_errno; /* return status of system call */ struct timespec ar_starttime; struct timespec ar_endtime; u_int64_t ar_valid_arg; /* Bitmask of valid arguments */ /* Audit subject information. */ struct xucred ar_subj_cred; uid_t ar_subj_ruid; gid_t ar_subj_rgid; gid_t ar_subj_egid; uid_t ar_subj_auid; /* Audit user ID */ pid_t ar_subj_asid; /* Audit session ID */ pid_t ar_subj_pid; struct au_tid ar_subj_term; char ar_subj_comm[MAXCOMLEN + 1]; struct au_mask ar_subj_amask; /* Operation arguments. */ uid_t ar_arg_euid; uid_t ar_arg_ruid; uid_t ar_arg_suid; gid_t ar_arg_egid; gid_t ar_arg_rgid; gid_t ar_arg_sgid; pid_t ar_arg_pid; pid_t ar_arg_asid; struct au_tid ar_arg_termid; uid_t ar_arg_uid; uid_t ar_arg_auid; gid_t ar_arg_gid; struct groupset ar_arg_groups; int ar_arg_fd; int ar_arg_fflags; mode_t ar_arg_mode; int ar_arg_dev; long ar_arg_value; void * ar_arg_addr; int ar_arg_len; int ar_arg_mask; u_int ar_arg_signum; char ar_arg_login[MAXLOGNAME]; int ar_arg_ctlname[CTL_MAXNAME]; struct sockaddr ar_arg_sockaddr; struct socket_au_info ar_arg_sockinfo; char *ar_arg_upath1; char *ar_arg_upath2; char *ar_arg_text; struct au_mask ar_arg_amask; struct vnode_au_info ar_arg_vnode1; struct vnode_au_info ar_arg_vnode2; int ar_arg_cmd; int ar_arg_svipc_cmd; struct ipc_perm ar_arg_svipc_perm; int ar_arg_svipc_id; void * ar_arg_svipc_addr; struct posix_ipc_perm ar_arg_pipc_perm; union auditon_udata ar_arg_auditon; char *ar_arg_argv; int ar_arg_argc; char *ar_arg_envv; int ar_arg_envc; int ar_arg_exitstatus; int ar_arg_exitretval; }; /* * Arguments in the audit record are initially not defined; flags are set to * indicate if they are present so they can be included in the audit log * stream only if defined. */ #define ARG_IS_VALID(kar, arg) ((kar)->k_ar.ar_valid_arg & (arg)) #define ARG_SET_VALID(kar, arg) do { \ (kar)->k_ar.ar_valid_arg |= (arg); \ } while (0) /* * In-kernel version of audit record; the basic record plus queue meta-data. * This record can also have a pointer set to some opaque data that will be * passed through to the audit writing mechanism. */ struct kaudit_record { struct audit_record k_ar; u_int32_t k_ar_commit; void *k_udata; /* User data. */ u_int k_ulen; /* User data length. */ struct uthread *k_uthread; /* Audited thread. */ TAILQ_ENTRY(kaudit_record) k_q; }; TAILQ_HEAD(kaudit_queue, kaudit_record); /* * Functions to manage the allocation, release, and commit of kernel audit * records. */ void audit_abort(struct kaudit_record *ar); void audit_commit(struct kaudit_record *ar, int error, int retval); struct kaudit_record *audit_new(int event, struct thread *td); /* * Functions relating to the conversion of internal kernel audit records to * the BSM file format. */ struct au_record; int kaudit_to_bsm(struct kaudit_record *kar, struct au_record **pau); int bsm_rec_verify(void *rec); /* * Kernel versions of the libbsm audit record functions. */ void kau_free(struct au_record *rec); void kau_init(void); /* * Return values for pre-selection and post-selection decisions. */ #define AU_PRS_SUCCESS 1 #define AU_PRS_FAILURE 2 #define AU_PRS_BOTH (AU_PRS_SUCCESS|AU_PRS_FAILURE) /* * Data structures relating to the kernel audit queue. Ideally, these might * be abstracted so that only accessor methods are exposed. */ extern struct mtx audit_mtx; extern struct cv audit_watermark_cv; extern struct cv audit_worker_cv; extern struct kaudit_queue audit_q; extern int audit_q_len; extern int audit_pre_q_len; extern int audit_in_failure; /* * Flags to use on audit files when opening and closing. */ #define AUDIT_OPEN_FLAGS (FWRITE | O_APPEND) #define AUDIT_CLOSE_FLAGS (FWRITE | O_APPEND) #include #include #include /* * Some of the BSM tokenizer functions take different parameters in the * kernel implementations in order to save the copying of large kernel data * structures. The prototypes of these functions are declared here. */ token_t *kau_to_socket(struct socket_au_info *soi); /* * audit_klib prototypes */ int au_preselect(au_event_t event, au_class_t class, au_mask_t *mask_p, int sorf); au_event_t flags_and_error_to_openevent(int oflags, int error); void au_evclassmap_init(void); void au_evclassmap_insert(au_event_t event, au_class_t class); au_class_t au_event_class(au_event_t event); au_event_t ctlname_to_sysctlevent(int name[], uint64_t valid_arg); int auditon_command_event(int cmd); int msgctl_to_event(int cmd); int semctl_to_event(int cmr); void canon_path(struct thread *td, char *path, char *cpath); /* * Audit trigger events notify user space of kernel audit conditions * asynchronously. */ void audit_trigger_init(void); int send_trigger(unsigned int trigger); /* * General audit related functions. */ struct kaudit_record *currecord(void); void audit_free(struct kaudit_record *ar); void audit_shutdown(void *arg, int howto); void audit_rotate_vnode(struct ucred *cred, struct vnode *vp); void audit_worker_init(void); /* * Audit pipe functions. */ int audit_pipe_preselect(au_id_t auid, au_event_t event, au_class_t class, int sorf, int trail_select); 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); void audit_pipe_submit_user(void *record, u_int record_len); #endif /* ! _SECURITY_AUDIT_PRIVATE_H_ */ Index: head/sys/security/audit/audit_syscalls.c =================================================================== --- head/sys/security/audit/audit_syscalls.c (revision 162379) +++ head/sys/security/audit/audit_syscalls.c (revision 162380) @@ -1,662 +1,670 @@ /* * 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 #include #include #include #include #include #include #include #include #include #include #ifdef AUDIT /* * MPSAFE * * 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; error = suser(td); 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; } /* * 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); } /* * MPSAFE * * System call to manipulate auditing. */ /* ARGSUSED */ int auditon(struct thread *td, struct auditon_args *uap) { int error; union auditon_udata udata; struct proc *tp; AUDIT_ARG(cmd, uap->cmd); error = suser(td); 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; } /* * XXX Need to implement these commands by accessing the global * values associated with the commands. * * 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); /* XXXAUDIT: p_cansee()? */ if ((tp = pfind(udata.au_aupinfo.ap_pid)) == NULL) return (EINVAL); udata.au_aupinfo.ap_auid = tp->p_au->ai_auid; udata.au_aupinfo.ap_mask.am_success = tp->p_au->ai_mask.am_success; udata.au_aupinfo.ap_mask.am_failure = tp->p_au->ai_mask.am_failure; udata.au_aupinfo.ap_termid.machine = tp->p_au->ai_termid.machine; udata.au_aupinfo.ap_termid.port = tp->p_au->ai_termid.port; udata.au_aupinfo.ap_asid = tp->p_au->ai_asid; PROC_UNLOCK(tp); break; case A_SETPMASK: if (udata.au_aupinfo.ap_pid < 1) return (EINVAL); /* XXXAUDIT: p_cansee()? */ if ((tp = pfind(udata.au_aupinfo.ap_pid)) == NULL) return (EINVAL); tp->p_au->ai_mask.am_success = udata.au_aupinfo.ap_mask.am_success; tp->p_au->ai_mask.am_failure = udata.au_aupinfo.ap_mask.am_failure; PROC_UNLOCK(tp); 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: return (ENOSYS); 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 (send_trigger(udata.au_trigger)); } /* * 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); } /* * MPSAFE * * System calls to manage the user audit information. */ /* ARGSUSED */ int getauid(struct thread *td, struct getauid_args *uap) { int error; au_id_t id; error = suser(td); if (error) return (error); /* * XXX: Integer read on static pointer dereference: doesn't need * locking? */ PROC_LOCK(td->td_proc); id = td->td_proc->p_au->ai_auid; PROC_UNLOCK(td->td_proc); return copyout(&id, uap->auid, sizeof(id)); } /* MPSAFE */ /* ARGSUSED */ int setauid(struct thread *td, struct setauid_args *uap) { int error; au_id_t id; error = suser(td); if (error) return (error); error = copyin(uap->auid, &id, sizeof(id)); if (error) return (error); audit_arg_auid(id); /* * XXX: Integer write on static pointer dereference: doesn't need * locking? * * XXXAUDIT: Might need locking to serialize audit events in the same * order as change events? Or maybe that's an under-solveable * problem. * * XXXRW: Test privilege while holding the proc lock? */ PROC_LOCK(td->td_proc); td->td_proc->p_au->ai_auid = id; PROC_UNLOCK(td->td_proc); return (0); } /* * MPSAFE * System calls to get and set process audit information. */ /* ARGSUSED */ int getaudit(struct thread *td, struct getaudit_args *uap) { struct auditinfo ai; int error; error = suser(td); if (error) return (error); PROC_LOCK(td->td_proc); ai = *td->td_proc->p_au; PROC_UNLOCK(td->td_proc); return (copyout(&ai, uap->auditinfo, sizeof(ai))); } /* MPSAFE */ /* ARGSUSED */ int setaudit(struct thread *td, struct setaudit_args *uap) { struct auditinfo ai; int error; error = suser(td); if (error) return (error); error = copyin(uap->auditinfo, &ai, sizeof(ai)); if (error) return (error); audit_arg_auditinfo(&ai); /* * XXXRW: Test privilege while holding the proc lock? */ PROC_LOCK(td->td_proc); *td->td_proc->p_au = ai; PROC_UNLOCK(td->td_proc); return (0); } /* MPSAFE */ /* ARGSUSED */ int getaudit_addr(struct thread *td, struct getaudit_addr_args *uap) { int error; error = suser(td); if (error) return (error); return (ENOSYS); } /* MPSAFE */ /* ARGSUSED */ int setaudit_addr(struct thread *td, struct setaudit_addr_args *uap) { int error; error = suser(td); if (error) return (error); return (ENOSYS); } /* * MPSAFE * Syscall to manage audit files. * * XXX: Should generate an audit event. */ /* 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; error = suser(td); 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. * * XXXAUDIT: On Darwin, a NULL path is used to disable audit. */ if (uap->path == NULL) return (EINVAL); NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | MPSAFE, UIO_USERSPACE, uap->path, td); flags = AUDIT_OPEN_FLAGS; error = vn_open(&nd, &flags, 0, -1); if (error) return (error); vfslocked = NDHASGIANT(&nd); VOP_UNLOCK(nd.ni_vp, 0, td); vp = nd.ni_vp; 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); } void audit_proc_init(struct proc *p) { } void audit_proc_fork(struct proc *parent, struct proc *child) { } void audit_proc_free(struct proc *p) { } #endif /* AUDIT */ Index: head/sys/security/audit/audit_worker.c =================================================================== --- head/sys/security/audit/audit_worker.c (revision 162379) +++ head/sys/security/audit/audit_worker.c (revision 162380) @@ -1,531 +1,535 @@ /* * Copyright (c) 1999-2005 Apple Computer, Inc. * Copyright (c) 2006 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 #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; /* * When an audit log is rotated, the actual rotation must be performed by the * audit worker thread, as it may have outstanding writes on the current * audit log. audit_replacement_vp holds the vnode replacing the current * vnode. We can't let more than one replacement occur at a time, so if more * than one thread requests a replacement, only one can have the replacement * "in progress" at any given moment. If a thread tries to replace the audit * vnode and discovers a replacement is already in progress (i.e., * audit_replacement_flag != 0), then it will sleep on audit_replacement_cv * waiting its turn to perform a replacement. When a replacement is * completed, this cv is signalled by the worker thread so a waiting thread * can start another replacement. We also store a credential to perform * audit log write operations with. * * The current credential and vnode are thread-local to audit_worker. */ static struct cv audit_replacement_cv; static int audit_replacement_flag; static struct vnode *audit_replacement_vp; static struct ucred *audit_replacement_cred; /* * Flags related to Kernel->user-space communication. */ static int audit_file_rotate_wait; /* * XXXAUDIT: Should adjust comments below to make it clear that we get to * this point only if we believe we have storage, so not having space here is * a violation of invariants derived from administrative procedures. I.e., * someone else has written to the audit partition, leaving less space than * we accounted for. */ static int audit_record_write(struct vnode *vp, struct ucred *cred, struct thread *td, void *data, size_t len) { int ret; long temp; struct vattr vattr; struct statfs *mnt_stat; int vfslocked; if (vp == NULL) return (0); 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. In both cases, if we're * unable to perform the operation, we drop the record and return. * However, this is arguably an assertion failure. * XXX Need a FreeBSD equivalent. */ ret = VFS_STATFS(vp->v_mount, mnt_stat, td); if (ret) goto out; vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td); ret = VOP_GETATTR(vp, &vattr, cred, td); VOP_UNLOCK(vp, 0, td); if (ret) goto out; /* update the global stats struct */ audit_fstat.af_currsz = vattr.va_size; /* * XXX Need to decide what to do if the trigger to the audit daemon * fails. */ /* * If we fall below minimum free blocks (hard limit), tell the audit * daemon to force a rotation off of the file system. We also stop * writing, which means this audit record is probably lost. If we * fall below the minimum percent free blocks (soft limit), then * kindly suggest to the audit daemon to do something. */ if (mnt_stat->f_bfree < AUDIT_HARD_LIMIT_FREE_BLOCKS) { (void)send_trigger(AUDIT_TRIGGER_NO_SPACE); /* * Hopefully userspace did something about all the previous * triggers that were sent prior to this critical condition. * If fail-stop is set, then we're done; goodnight Gracie. */ if (audit_fail_stop) panic("Audit log space exhausted and fail-stop set."); else { audit_suspended = 1; ret = ENOSPC; goto out; } } else /* * Send a message to the audit daemon that disk space is * getting low. * * XXXAUDIT: Check math and block size calculation here. */ if (audit_qctrl.aq_minfree != 0) { temp = mnt_stat->f_blocks / (100 / audit_qctrl.aq_minfree); if (mnt_stat->f_bfree < temp) (void)send_trigger(AUDIT_TRIGGER_LOW_SPACE); } /* * Check if the current log file is full; if so, call for a log * rotate. This is not an exact comparison; we may write some records * over the limit. If that's not acceptable, then add a fudge factor * here. */ if ((audit_fstat.af_filesz != 0) && (audit_file_rotate_wait == 0) && (vattr.va_size >= audit_fstat.af_filesz)) { audit_file_rotate_wait = 1; (void)send_trigger(AUDIT_TRIGGER_OPEN_NEW); } /* * 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 packets 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 && (unsigned long) ((audit_q_len + audit_pre_q_len + 1) * MAX_AUDIT_RECORD_SIZE) / mnt_stat->f_bsize >= (unsigned long)(mnt_stat->f_bfree)) { printf("audit_record_write: free space below size of audit " "queue, failing stop\n"); audit_in_failure = 1; } ret = vn_rdwr(UIO_WRITE, vp, data, len, (off_t)0, UIO_SYSSPACE, IO_APPEND|IO_UNIT, cred, NULL, NULL, td); out: /* * When we're done processing the current record, we have to check to * see if we're in a failure mode, and if so, whether this was the * last record left to be drained. If we're done draining, then we * fsync the vnode and panic. */ if (audit_in_failure && audit_q_len == 0 && audit_pre_q_len == 0) { VOP_LOCK(vp, LK_DRAIN | LK_INTERLOCK, td); (void)VOP_FSYNC(vp, MNT_WAIT, td); VOP_UNLOCK(vp, 0, td); panic("Audit store overflow; record queue drained."); } VFS_UNLOCK_GIANT(vfslocked); return (ret); } /* * If an appropriate signal has been received rotate the audit log based on * the global replacement variables. Signal consumers as needed that the * rotation has taken place. * * XXXRW: The global variables and CVs used to signal the audit_worker to * perform a rotation are essentially a message queue of depth 1. It would * be much nicer to actually use a message queue. */ static void audit_worker_rotate(struct ucred **audit_credp, struct vnode **audit_vpp, struct thread *audit_td) { int do_replacement_signal, vfslocked; struct ucred *old_cred; struct vnode *old_vp; mtx_assert(&audit_mtx, MA_OWNED); do_replacement_signal = 0; while (audit_replacement_flag != 0) { old_cred = *audit_credp; old_vp = *audit_vpp; *audit_credp = audit_replacement_cred; *audit_vpp = audit_replacement_vp; audit_replacement_cred = NULL; audit_replacement_vp = NULL; audit_replacement_flag = 0; audit_enabled = (*audit_vpp != NULL); /* * XXX: What to do about write failures here? */ if (old_vp != NULL) { AUDIT_PRINTF(("Closing old audit file\n")); mtx_unlock(&audit_mtx); vfslocked = VFS_LOCK_GIANT(old_vp->v_mount); vn_close(old_vp, AUDIT_CLOSE_FLAGS, old_cred, audit_td); VFS_UNLOCK_GIANT(vfslocked); crfree(old_cred); mtx_lock(&audit_mtx); old_cred = NULL; old_vp = NULL; AUDIT_PRINTF(("Audit file closed\n")); } if (*audit_vpp != NULL) { AUDIT_PRINTF(("Opening new audit file\n")); } do_replacement_signal = 1; } /* * Signal that replacement have occurred to wake up and * start any other replacements started in parallel. We can * continue about our business in the mean time. We * broadcast so that both new replacements can be inserted, * but also so that the source(s) of replacement can return * successfully. */ if (do_replacement_signal) cv_broadcast(&audit_replacement_cv); } /* * 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 vnode *audit_vp, struct ucred *audit_cred, struct thread *audit_td, struct kaudit_record *ar) { struct au_record *bsm; au_class_t class; au_event_t event; int error, ret; au_id_t auid; int sorf; if ((ar->k_ar_commit & AR_COMMIT_USER) && - (ar->k_ar_commit & AR_PRESELECT_TRAIL)) { + (ar->k_ar_commit & AR_PRESELECT_USER_TRAIL)) { error = audit_record_write(audit_vp, audit_cred, audit_td, ar->k_udata, ar->k_ulen); if (error && audit_panic_on_write_fail) panic("audit_worker: write error %d\n", error); else if (error) printf("audit_worker: write error %d\n", error); } + if ((ar->k_ar_commit & AR_COMMIT_USER) && - (ar->k_ar_commit & AR_PRESELECT_PIPE)) + (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)) + if (!(ar->k_ar_commit & AR_COMMIT_KERNEL) || + ((ar->k_ar_commit & AR_PRESELECT_PIPE) == 0 && + (ar->k_ar_commit & AR_PRESELECT_TRAIL) == 0)) return; 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; ret = kaudit_to_bsm(ar, &bsm); switch (ret) { case BSM_NOAUDIT: return; case BSM_FAILURE: printf("audit_worker_process_record: BSM_FAILURE\n"); return; case BSM_SUCCESS: break; default: panic("kaudit_to_bsm returned %d", ret); } if (ar->k_ar_commit & AR_PRESELECT_TRAIL) { error = audit_record_write(audit_vp, audit_cred, audit_td, bsm->data, bsm->len); if (error && audit_panic_on_write_fail) panic("audit_worker: write error %d\n", error); else if (error) printf("audit_worker: write error %d\n", error); } + 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); } /* * 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. In addition, the audit_work performs the * actual exchange of audit log vnode pointer, as audit_vp is a thread-local * variable. */ static void audit_worker(void *arg) { struct kaudit_queue ar_worklist; struct kaudit_record *ar; struct ucred *audit_cred; struct thread *audit_td; struct vnode *audit_vp; int lowater_signal; AUDIT_PRINTF(("audit_worker starting\n")); /* * These are thread-local variables requiring no synchronization. */ TAILQ_INIT(&ar_worklist); audit_cred = NULL; audit_td = curthread; audit_vp = NULL; mtx_lock(&audit_mtx); while (1) { mtx_assert(&audit_mtx, MA_OWNED); /* * Wait for record or rotation events. */ while (!audit_replacement_flag && TAILQ_EMPTY(&audit_q)) { AUDIT_PRINTF(("audit_worker waiting\n")); cv_wait(&audit_worker_cv, &audit_mtx); AUDIT_PRINTF(("audit_worker woken up\n")); AUDIT_PRINTF(("audit_worker: new vp = %p; value of " "flag %d\n", audit_replacement_vp, audit_replacement_flag)); } /* * First priority: replace the audit log target if requested. */ audit_worker_rotate(&audit_cred, &audit_vp, audit_td); /* * 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(audit_vp, audit_cred, audit_td, 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 and vnode 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. The real work is done in the * audit_worker thread, but audit_rotate_vnode() waits synchronously for that * to complete. * * The vnode should be referenced and opened by the caller. The credential * should be referenced. audit_rotate_vnode() will own both references as of * this call, so the caller should not release either. * * XXXAUDIT: Review synchronize communication logic. Really, this is a * message queue of depth 1. * * XXXAUDIT: Enhance the comments below to indicate that we are basically * acquiring ownership of the communications queue, inserting our message, * and waiting for an acknowledgement. */ void audit_rotate_vnode(struct ucred *cred, struct vnode *vp) { /* * If other parallel log replacements have been requested, we wait * until they've finished before continuing. */ mtx_lock(&audit_mtx); while (audit_replacement_flag != 0) { AUDIT_PRINTF(("audit_rotate_vnode: sleeping to wait for " "flag\n")); cv_wait(&audit_replacement_cv, &audit_mtx); AUDIT_PRINTF(("audit_rotate_vnode: woken up (flag %d)\n", audit_replacement_flag)); } audit_replacement_cred = cred; audit_replacement_flag = 1; audit_replacement_vp = vp; /* * Wake up the audit worker to perform the exchange once we * release the mutex. */ cv_signal(&audit_worker_cv); /* * Wait for the audit_worker to broadcast that a replacement has * taken place; we know that once this has happened, our vnode * has been replaced in, so we can return successfully. */ AUDIT_PRINTF(("audit_rotate_vnode: waiting for news of " "replacement\n")); cv_wait(&audit_replacement_cv, &audit_mtx); AUDIT_PRINTF(("audit_rotate_vnode: change acknowledged by " "audit_worker (flag " "now %d)\n", audit_replacement_flag)); mtx_unlock(&audit_mtx); audit_file_rotate_wait = 0; /* We can now request another rotation */ } void audit_worker_init(void) { int error; cv_init(&audit_replacement_cv, "audit_replacement_cv"); error = kthread_create(audit_worker, NULL, &audit_thread, RFHIGHPID, 0, "audit_worker"); if (error) panic("audit_worker_init: kthread_create returned %d", error); }