Index: head/sys/security/audit/audit.c =================================================================== --- head/sys/security/audit/audit.c (revision 339084) +++ head/sys/security/audit/audit.c (revision 339085) @@ -1,811 +1,843 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1999-2005 Apple Inc. - * Copyright (c) 2006-2007, 2016-2017 Robert N. M. Watson + * Copyright (c) 2006-2007, 2016-2018 Robert N. M. Watson * All rights reserved. * * Portions of this software were developed by BAE Systems, the University of * Cambridge Computer Laboratory, and Memorial University under DARPA/AFRL * contract FA8650-15-C-7558 ("CADETS"), as part of the DARPA Transparent * Computing (TC) research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __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 #include FEATURE(audit, "BSM audit support"); 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"); MALLOC_DEFINE(M_AUDITGIDSET, "audit_gidset", "Audit GID set storage"); static 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 __read_frequently audit_enabled; -int audit_suspended; +int audit_trail_enabled; +int audit_trail_suspended; +#ifdef KDTRACE_HOOKS +u_int audit_dtrace_enabled; +#endif +int __read_frequently audit_syscalls_enabled; /* * 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; /* * Optional DTrace audit provider support: function pointers for preselection * and commit events. */ #ifdef KDTRACE_HOOKS void *(*dtaudit_hook_preselect)(au_id_t auid, au_event_t event, au_class_t class); int (*dtaudit_hook_commit)(struct kaudit_record *kar, au_id_t auid, au_event_t event, au_class_t class, int sorf); void (*dtaudit_hook_bsm)(struct kaudit_record *kar, au_id_t auid, au_event_t event, au_class_t class, int sorf, void *bsm_data, size_t bsm_lenlen); #endif /* * Kernel audit information. This will store the current audit address * or host information that the kernel will use when it's generating * audit records. This data is modified by the A_GET{SET}KAUDIT auditon(2) * command. */ static struct auditinfo_addr audit_kinfo; static struct rwlock audit_kinfo_lock; #define KINFO_LOCK_INIT() rw_init(&audit_kinfo_lock, \ "audit_kinfo_lock") #define KINFO_RLOCK() rw_rlock(&audit_kinfo_lock) #define KINFO_WLOCK() rw_wlock(&audit_kinfo_lock) #define KINFO_RUNLOCK() rw_runlock(&audit_kinfo_lock) #define KINFO_WUNLOCK() rw_wunlock(&audit_kinfo_lock) +/* + * Check various policies to see if we should enable system-call audit hooks. + * Note that despite the mutex being held, we want to assign a value exactly + * once, as checks of the flag are performed lock-free for performance + * reasons. The mutex is used to get a consistent snapshot of policy state -- + * e.g., safely accessing the two audit_trail flags. + */ void +audit_syscalls_enabled_update(void) +{ + + mtx_lock(&audit_mtx); +#ifdef KDTRACE_HOOKS + if (audit_dtrace_enabled) + audit_syscalls_enabled = 1; + else { +#endif + if (audit_trail_enabled && !audit_trail_suspended) + audit_syscalls_enabled = 1; + else + audit_syscalls_enabled = 0; +#ifdef KDTRACE_HOOKS + } +#endif + mtx_unlock(&audit_mtx); +} + +void audit_set_kinfo(struct auditinfo_addr *ak) { KASSERT(ak->ai_termid.at_type == AU_IPv4 || ak->ai_termid.at_type == AU_IPv6, ("audit_set_kinfo: invalid address type")); KINFO_WLOCK(); audit_kinfo = *ak; KINFO_WUNLOCK(); } void audit_get_kinfo(struct auditinfo_addr *ak) { KASSERT(audit_kinfo.ai_termid.at_type == AU_IPv4 || audit_kinfo.ai_termid.at_type == AU_IPv6, ("audit_set_kinfo: invalid address type")); KINFO_RLOCK(); *ak = audit_kinfo; KINFO_RUNLOCK(); } /* * 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; struct ucred *cred; struct prison *pr; 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. */ cred = td->td_ucred; cru2x(cred, &ar->k_ar.ar_subj_cred); ar->k_ar.ar_subj_ruid = cred->cr_ruid; ar->k_ar.ar_subj_rgid = cred->cr_rgid; ar->k_ar.ar_subj_egid = cred->cr_groups[0]; ar->k_ar.ar_subj_auid = cred->cr_audit.ai_auid; ar->k_ar.ar_subj_asid = cred->cr_audit.ai_asid; ar->k_ar.ar_subj_pid = td->td_proc->p_pid; ar->k_ar.ar_subj_amask = cred->cr_audit.ai_mask; ar->k_ar.ar_subj_term_addr = cred->cr_audit.ai_termid; /* * If this process is jailed, make sure we capture the name of the * jail so we can use it to generate a zonename token when we covert * this record to BSM. */ if (jailed(cred)) { pr = cred->cr_prison; (void) strlcpy(ar->k_ar.ar_jailname, pr->pr_name, sizeof(ar->k_ar.ar_jailname)); } else ar->k_ar.ar_jailname[0] = '\0'; 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); if (ar->k_ar.ar_arg_groups.gidset != NULL) free(ar->k_ar.ar_arg_groups.gidset, M_AUDITGIDSET); } /* * 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_trail_enabled = 0; + audit_trail_suspended = 0; + audit_syscalls_enabled = 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; audit_kinfo.ai_termid.at_type = AU_IPv4; audit_kinfo.ai_termid.at_addr[0] = INADDR_ANY; mtx_init(&audit_mtx, "audit_mtx", NULL, MTX_DEF); KINFO_LOCK_INIT(); 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); + /* First initialisation of audit_syscalls_enabled. */ + audit_syscalls_enabled_update(); + /* 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. * * XXXRW: In FreeBSD 7.x and 8.x, this fails to wait for the record queue to * drain before returning, which could lead to lost records on shutdown. */ 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; ar->k_ar.ar_errno = error; ar->k_ar.ar_retval = retval; nanotime(&ar->k_ar.ar_endtime); /* * 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; /* * syscalls.master sometimes contains a prototype event number, which * we will transform into a more specific event number now that we * have more complete information gathered during the system call. */ switch(ar->k_ar.ar_event) { case AUE_OPEN_RWTC: ar->k_ar.ar_event = audit_flags_and_error_to_openevent( ar->k_ar.ar_arg_fflags, error); break; case AUE_OPENAT_RWTC: ar->k_ar.ar_event = audit_flags_and_error_to_openatevent( 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; case AUE_MSGSYS: if (ARG_IS_VALID(ar, ARG_SVIPC_WHICH)) ar->k_ar.ar_event = audit_msgsys_to_event(ar->k_ar.ar_arg_svipc_which); break; case AUE_SEMSYS: if (ARG_IS_VALID(ar, ARG_SVIPC_WHICH)) ar->k_ar.ar_event = audit_semsys_to_event(ar->k_ar.ar_arg_svipc_which); break; case AUE_SHMSYS: if (ARG_IS_VALID(ar, ARG_SVIPC_WHICH)) ar->k_ar.ar_event = audit_shmsys_to_event(ar->k_ar.ar_arg_svipc_which); 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; #ifdef KDTRACE_HOOKS /* * Expose the audit record to DTrace, both to allow the "commit" probe * to fire if it's desirable, and also to allow a decision to be made * about later firing with BSM in the audit worker. */ if (dtaudit_hook_commit != NULL) { if (dtaudit_hook_commit(ar, auid, event, class, sorf) != 0) ar->k_ar_commit |= AR_PRESELECT_DTRACE; } #endif if ((ar->k_ar_commit & (AR_PRESELECT_TRAIL | AR_PRESELECT_PIPE | AR_PRESELECT_USER_TRAIL | AR_PRESELECT_USER_PIPE | AR_PRESELECT_DTRACE)) == 0) { mtx_lock(&audit_mtx); audit_pre_q_len--; mtx_unlock(&audit_mtx); audit_free(ar); return; } /* * Note: it could be that some records initiated while audit was * enabled should still be committed? + * + * NB: The check here is not for audit_syscalls because any + * DTrace-related obligations have been fulfilled above -- we're just + * down to the trail and pipes now. */ mtx_lock(&audit_mtx); - if (audit_suspended || !audit_enabled) { + if (audit_trail_suspended || !audit_trail_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. + * + * This function will be entered only if audit_syscalls_enabled was set in the + * macro wrapper for this function. It could be cleared by the time this + * function runs, but that is an acceptable race. */ void audit_syscall_enter(unsigned short code, struct thread *td) { struct au_mask *aumask; #ifdef KDTRACE_HOOKS void *dtaudit_state; #endif au_class_t class; au_event_t event; au_id_t auid; int record_needed; KASSERT(td->td_ar == NULL, ("audit_syscall_enter: td->td_ar != NULL")); KASSERT((td->td_pflags & TDP_AUDITREC) == 0, ("audit_syscall_enter: TDP_AUDITREC set")); /* * 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; /* * Determine whether trail or pipe preselection would like an audit * record allocated for this system call. */ 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"); } record_needed = 1; } else if (audit_pipe_preselect(auid, event, class, AU_PRS_BOTH, 0)) { record_needed = 1; } else { record_needed = 0; } /* * After audit trails and pipes have made their policy choices, DTrace * may request that records be generated as well. This is a slightly * complex affair, as the DTrace audit provider needs the audit * framework to maintain some state on the audit record, which has not * been allocated at the point where the decision has to be made. * This hook must run even if we are not changing the decision, as * DTrace may want to stick event state onto a record we were going to * produce due to the trail or pipes. The event state returned by the * DTrace provider must be safe without locks held between here and * below -- i.e., dtaudit_state must must refer to stable memory. */ #ifdef KDTRACE_HOOKS dtaudit_state = NULL; if (dtaudit_hook_preselect != NULL) { dtaudit_state = dtaudit_hook_preselect(auid, event, class); if (dtaudit_state != NULL) record_needed = 1; } #endif /* * If a record is required, allocate it and attach it to the thread * for use throughout the system call. Also attach DTrace state if * required. * * XXXRW: If we decide to reference count the evname_elem underlying * dtaudit_state, we will need to free here if no record is allocated * or allocatable. */ if (record_needed) { td->td_ar = audit_new(event, td); if (td->td_ar != NULL) { td->td_pflags |= TDP_AUDITREC; #ifdef KDTRACE_HOOKS td->td_ar->k_dtaudit_state = dtaudit_state; #endif } } 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; td->td_pflags &= ~TDP_AUDITREC; } 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; cred->cr_audit.ai_termid.at_type = AU_IPv4; } void audit_cred_proc1(struct ucred *cred) { cred->cr_audit.ai_auid = AU_DEFAUDITID; cred->cr_audit.ai_termid.at_type = AU_IPv4; } 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")); KASSERT((td->td_pflags & TDP_AUDITREC) == 0, ("audit_thread_free: TDP_AUDITREC set")); } void audit_proc_coredump(struct thread *td, char *path, int errcode) { struct kaudit_record *ar; struct au_mask *aumask; struct ucred *cred; au_class_t class; int ret, sorf; char **pathp; au_id_t auid; ret = 0; /* * Make sure we are using the correct preselection mask. */ cred = td->td_ucred; auid = cred->cr_audit.ai_auid; if (auid == AU_DEFAUDITID) aumask = &audit_nae_mask; else aumask = &cred->cr_audit.ai_mask; /* * It's possible for coredump(9) generation to fail. Make sure that * we handle this case correctly for preselection. */ if (errcode != 0) sorf = AU_PRS_FAILURE; else sorf = AU_PRS_SUCCESS; class = au_event_class(AUE_CORE); if (au_preselect(AUE_CORE, class, aumask, sorf) == 0 && audit_pipe_preselect(auid, AUE_CORE, class, sorf, 0) == 0) return; /* * If we are interested in seeing this audit record, allocate it. * Where possible coredump records should contain a pathname and arg32 * (signal) tokens. */ ar = audit_new(AUE_CORE, td); if (ar == NULL) return; if (path != NULL) { pathp = &ar->k_ar.ar_arg_upath1; *pathp = malloc(MAXPATHLEN, M_AUDITPATH, M_WAITOK); audit_canon_path(td, AT_FDCWD, path, *pathp); ARG_SET_VALID(ar, ARG_UPATH1); } ar->k_ar.ar_arg_signum = td->td_proc->p_sig; ARG_SET_VALID(ar, ARG_SIGNUM); if (errcode != 0) ret = 1; audit_commit(ar, errcode, ret); } Index: head/sys/security/audit/audit.h =================================================================== --- head/sys/security/audit/audit.h (revision 339084) +++ head/sys/security/audit/audit.h (revision 339085) @@ -1,450 +1,459 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1999-2005 Apple Inc. - * Copyright (c) 2016-2017 Robert N. M. Watson + * Copyright (c) 2016-2018 Robert N. M. Watson * All rights reserved. * * This software was developed by BAE Systems, the University of Cambridge * Computer Laboratory, and Memorial University under DARPA/AFRL contract * FA8650-15-C-7558 ("CADETS"), as part of the DARPA Transparent Computing * (TC) research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ /* * This header includes function prototypes and type definitions that are * necessary for the kernel as a whole to interact with the audit subsystem. */ #ifndef _SECURITY_AUDIT_KERNEL_H_ #define _SECURITY_AUDIT_KERNEL_H_ #ifndef _KERNEL #error "no user-serviceable parts inside" #endif #include #include #include /* - * Audit subsystem condition flags. The audit_enabled flag is set and + * Audit subsystem condition flags. The audit_trail_enabled flag is set and * removed automatically as a result of configuring log files, and can be * observed but should not be directly manipulated. The audit suspension * flag permits audit to be temporarily disabled without reconfiguring the * audit target. + * + * As DTrace can also request system-call auditing, a further + * audit_syscalls_enabled flag tracks whether newly entering system calls + * should be considered for auditing or not. + * + * XXXRW: Move trail flags to audit_private.h, as they no longer need to be + * visible outside the audit code...? */ -extern int audit_enabled; -extern int audit_suspended; +extern u_int audit_dtrace_enabled; +extern int audit_trail_enabled; +extern int audit_trail_suspended; +extern int audit_syscalls_enabled; void audit_syscall_enter(unsigned short code, struct thread *td); void audit_syscall_exit(int error, struct thread *td); /* * The remaining kernel functions are conditionally compiled in as they are * wrapped by a macro, and the macro should be the only place in the source * tree where these functions are referenced. */ #ifdef AUDIT struct ipc_perm; struct sockaddr; union auditon_udata; void audit_arg_addr(void * addr); void audit_arg_exit(int status, int retval); void audit_arg_len(int len); void audit_arg_atfd1(int atfd); void audit_arg_atfd2(int atfd); void audit_arg_fd(int fd); void audit_arg_fflags(int fflags); void audit_arg_gid(gid_t gid); void audit_arg_uid(uid_t uid); void audit_arg_egid(gid_t egid); void audit_arg_euid(uid_t euid); void audit_arg_rgid(gid_t rgid); void audit_arg_ruid(uid_t ruid); void audit_arg_sgid(gid_t sgid); void audit_arg_suid(uid_t suid); void audit_arg_groupset(gid_t *gidset, u_int gidset_size); void audit_arg_login(char *login); void audit_arg_ctlname(int *name, int namelen); void audit_arg_mask(int mask); void audit_arg_mode(mode_t mode); void audit_arg_dev(int dev); void audit_arg_value(long value); void audit_arg_owner(uid_t uid, gid_t gid); void audit_arg_pid(pid_t pid); void audit_arg_process(struct proc *p); void audit_arg_signum(u_int signum); void audit_arg_socket(int sodomain, int sotype, int soprotocol); void audit_arg_sockaddr(struct thread *td, int dirfd, struct sockaddr *sa); void audit_arg_auid(uid_t auid); void audit_arg_auditinfo(struct auditinfo *au_info); void audit_arg_auditinfo_addr(struct auditinfo_addr *au_info); void audit_arg_upath1(struct thread *td, int dirfd, char *upath); void audit_arg_upath1_canon(char *upath); void audit_arg_upath2(struct thread *td, int dirfd, char *upath); void audit_arg_upath2_canon(char *upath); void audit_arg_vnode1(struct vnode *vp); void audit_arg_vnode2(struct vnode *vp); void audit_arg_text(char *text); void audit_arg_cmd(int cmd); void audit_arg_svipc_cmd(int cmd); void audit_arg_svipc_perm(struct ipc_perm *perm); void audit_arg_svipc_id(int id); void audit_arg_svipc_addr(void *addr); void audit_arg_svipc_which(int which); void audit_arg_posix_ipc_perm(uid_t uid, gid_t gid, mode_t mode); void audit_arg_auditon(union auditon_udata *udata); void audit_arg_file(struct proc *p, struct file *fp); void audit_arg_argv(char *argv, int argc, int length); void audit_arg_envv(char *envv, int envc, int length); void audit_arg_rights(cap_rights_t *rightsp); void audit_arg_fcntl_rights(uint32_t fcntlrights); void audit_sysclose(struct thread *td, int fd); void audit_cred_copy(struct ucred *src, struct ucred *dest); void audit_cred_destroy(struct ucred *cred); void audit_cred_init(struct ucred *cred); void audit_cred_kproc0(struct ucred *cred); void audit_cred_proc1(struct ucred *cred); void audit_proc_coredump(struct thread *td, char *path, int errcode); void audit_thread_alloc(struct thread *td); void audit_thread_free(struct thread *td); /* * Define macros to wrap the audit_arg_* calls by checking the global - * audit_enabled flag before performing the actual call. + * audit_syscalls_enabled flag before performing the actual call. */ #define AUDITING_TD(td) ((td)->td_pflags & TDP_AUDITREC) #define AUDIT_ARG_ADDR(addr) do { \ if (AUDITING_TD(curthread)) \ audit_arg_addr((addr)); \ } while (0) #define AUDIT_ARG_ARGV(argv, argc, length) do { \ if (AUDITING_TD(curthread)) \ audit_arg_argv((argv), (argc), (length)); \ } while (0) #define AUDIT_ARG_ATFD1(atfd) do { \ if (AUDITING_TD(curthread)) \ audit_arg_atfd1((atfd)); \ } while (0) #define AUDIT_ARG_ATFD2(atfd) do { \ if (AUDITING_TD(curthread)) \ audit_arg_atfd2((atfd)); \ } while (0) #define AUDIT_ARG_AUDITON(udata) do { \ if (AUDITING_TD(curthread)) \ audit_arg_auditon((udata)); \ } while (0) #define AUDIT_ARG_CMD(cmd) do { \ if (AUDITING_TD(curthread)) \ audit_arg_cmd((cmd)); \ } while (0) #define AUDIT_ARG_DEV(dev) do { \ if (AUDITING_TD(curthread)) \ audit_arg_dev((dev)); \ } while (0) #define AUDIT_ARG_EGID(egid) do { \ if (AUDITING_TD(curthread)) \ audit_arg_egid((egid)); \ } while (0) #define AUDIT_ARG_ENVV(envv, envc, length) do { \ if (AUDITING_TD(curthread)) \ audit_arg_envv((envv), (envc), (length)); \ } while (0) #define AUDIT_ARG_EXIT(status, retval) do { \ if (AUDITING_TD(curthread)) \ audit_arg_exit((status), (retval)); \ } while (0) #define AUDIT_ARG_EUID(euid) do { \ if (AUDITING_TD(curthread)) \ audit_arg_euid((euid)); \ } while (0) #define AUDIT_ARG_FD(fd) do { \ if (AUDITING_TD(curthread)) \ audit_arg_fd((fd)); \ } while (0) #define AUDIT_ARG_FILE(p, fp) do { \ if (AUDITING_TD(curthread)) \ audit_arg_file((p), (fp)); \ } while (0) #define AUDIT_ARG_FFLAGS(fflags) do { \ if (AUDITING_TD(curthread)) \ audit_arg_fflags((fflags)); \ } while (0) #define AUDIT_ARG_GID(gid) do { \ if (AUDITING_TD(curthread)) \ audit_arg_gid((gid)); \ } while (0) #define AUDIT_ARG_GROUPSET(gidset, gidset_size) do { \ if (AUDITING_TD(curthread)) \ audit_arg_groupset((gidset), (gidset_size)); \ } while (0) #define AUDIT_ARG_LOGIN(login) do { \ if (AUDITING_TD(curthread)) \ audit_arg_login((login)); \ } while (0) #define AUDIT_ARG_MODE(mode) do { \ if (AUDITING_TD(curthread)) \ audit_arg_mode((mode)); \ } while (0) #define AUDIT_ARG_OWNER(uid, gid) do { \ if (AUDITING_TD(curthread)) \ audit_arg_owner((uid), (gid)); \ } while (0) #define AUDIT_ARG_PID(pid) do { \ if (AUDITING_TD(curthread)) \ audit_arg_pid((pid)); \ } while (0) #define AUDIT_ARG_POSIX_IPC_PERM(uid, gid, mode) do { \ if (AUDITING_TD(curthread)) \ audit_arg_posix_ipc_perm((uid), (gid), (mod)); \ } while (0) #define AUDIT_ARG_PROCESS(p) do { \ if (AUDITING_TD(curthread)) \ audit_arg_process((p)); \ } while (0) #define AUDIT_ARG_RGID(rgid) do { \ if (AUDITING_TD(curthread)) \ audit_arg_rgid((rgid)); \ } while (0) #define AUDIT_ARG_RIGHTS(rights) do { \ if (AUDITING_TD(curthread)) \ audit_arg_rights((rights)); \ } while (0) #define AUDIT_ARG_FCNTL_RIGHTS(fcntlrights) do { \ if (AUDITING_TD(curthread)) \ audit_arg_fcntl_rights((fcntlrights)); \ } while (0) #define AUDIT_ARG_RUID(ruid) do { \ if (AUDITING_TD(curthread)) \ audit_arg_ruid((ruid)); \ } while (0) #define AUDIT_ARG_SIGNUM(signum) do { \ if (AUDITING_TD(curthread)) \ audit_arg_signum((signum)); \ } while (0) #define AUDIT_ARG_SGID(sgid) do { \ if (AUDITING_TD(curthread)) \ audit_arg_sgid((sgid)); \ } while (0) #define AUDIT_ARG_SOCKET(sodomain, sotype, soprotocol) do { \ if (AUDITING_TD(curthread)) \ audit_arg_socket((sodomain), (sotype), (soprotocol)); \ } while (0) #define AUDIT_ARG_SOCKADDR(td, dirfd, sa) do { \ if (AUDITING_TD(curthread)) \ audit_arg_sockaddr((td), (dirfd), (sa)); \ } while (0) #define AUDIT_ARG_SUID(suid) do { \ if (AUDITING_TD(curthread)) \ audit_arg_suid((suid)); \ } while (0) #define AUDIT_ARG_SVIPC_CMD(cmd) do { \ if (AUDITING_TD(curthread)) \ audit_arg_svipc_cmd((cmd)); \ } while (0) #define AUDIT_ARG_SVIPC_PERM(perm) do { \ if (AUDITING_TD(curthread)) \ audit_arg_svipc_perm((perm)); \ } while (0) #define AUDIT_ARG_SVIPC_ID(id) do { \ if (AUDITING_TD(curthread)) \ audit_arg_svipc_id((id)); \ } while (0) #define AUDIT_ARG_SVIPC_ADDR(addr) do { \ if (AUDITING_TD(curthread)) \ audit_arg_svipc_addr((addr)); \ } while (0) #define AUDIT_ARG_SVIPC_WHICH(which) do { \ if (AUDITING_TD(curthread)) \ audit_arg_svipc_which((which)); \ } while (0) #define AUDIT_ARG_TEXT(text) do { \ if (AUDITING_TD(curthread)) \ audit_arg_text((text)); \ } while (0) #define AUDIT_ARG_UID(uid) do { \ if (AUDITING_TD(curthread)) \ audit_arg_uid((uid)); \ } while (0) #define AUDIT_ARG_UPATH1(td, dirfd, upath) do { \ if (AUDITING_TD(curthread)) \ audit_arg_upath1((td), (dirfd), (upath)); \ } while (0) #define AUDIT_ARG_UPATH1_CANON(upath) do { \ if (AUDITING_TD(curthread)) \ audit_arg_upath1_canon((upath)); \ } while (0) #define AUDIT_ARG_UPATH2(td, dirfd, upath) do { \ if (AUDITING_TD(curthread)) \ audit_arg_upath2((td), (dirfd), (upath)); \ } while (0) #define AUDIT_ARG_UPATH2_CANON(upath) do { \ if (AUDITING_TD(curthread)) \ audit_arg_upath2_canon((upath)); \ } while (0) #define AUDIT_ARG_VALUE(value) do { \ if (AUDITING_TD(curthread)) \ audit_arg_value((value)); \ } while (0) #define AUDIT_ARG_VNODE1(vp) do { \ if (AUDITING_TD(curthread)) \ audit_arg_vnode1((vp)); \ } while (0) #define AUDIT_ARG_VNODE2(vp) do { \ if (AUDITING_TD(curthread)) \ audit_arg_vnode2((vp)); \ } while (0) #define AUDIT_SYSCALL_ENTER(code, td) do { \ - if (audit_enabled) { \ + if (audit_syscalls_enabled) { \ audit_syscall_enter(code, td); \ } \ } while (0) /* * Wrap the audit_syscall_exit() function so that it is called only when * we have a audit record on the thread. Audit records can persist after - * auditing is disabled, so we don't just check audit_enabled here. + * auditing is disabled, so we don't just check audit_syscalls_enabled here. */ #define AUDIT_SYSCALL_EXIT(error, td) do { \ if (td->td_pflags & TDP_AUDITREC) \ audit_syscall_exit(error, td); \ } while (0) /* * A Macro to wrap the audit_sysclose() function. */ #define AUDIT_SYSCLOSE(td, fd) do { \ if (td->td_pflags & TDP_AUDITREC) \ audit_sysclose(td, fd); \ } while (0) #else /* !AUDIT */ #define AUDIT_ARG_ADDR(addr) #define AUDIT_ARG_ARGV(argv, argc, length) #define AUDIT_ARG_ATFD1(atfd) #define AUDIT_ARG_ATFD2(atfd) #define AUDIT_ARG_AUDITON(udata) #define AUDIT_ARG_CMD(cmd) #define AUDIT_ARG_DEV(dev) #define AUDIT_ARG_EGID(egid) #define AUDIT_ARG_ENVV(envv, envc, length) #define AUDIT_ARG_EXIT(status, retval) #define AUDIT_ARG_EUID(euid) #define AUDIT_ARG_FD(fd) #define AUDIT_ARG_FILE(p, fp) #define AUDIT_ARG_FFLAGS(fflags) #define AUDIT_ARG_GID(gid) #define AUDIT_ARG_GROUPSET(gidset, gidset_size) #define AUDIT_ARG_LOGIN(login) #define AUDIT_ARG_MODE(mode) #define AUDIT_ARG_OWNER(uid, gid) #define AUDIT_ARG_PID(pid) #define AUDIT_ARG_POSIX_IPC_PERM(uid, gid, mode) #define AUDIT_ARG_PROCESS(p) #define AUDIT_ARG_RGID(rgid) #define AUDIT_ARG_RIGHTS(rights) #define AUDIT_ARG_FCNTL_RIGHTS(fcntlrights) #define AUDIT_ARG_RUID(ruid) #define AUDIT_ARG_SIGNUM(signum) #define AUDIT_ARG_SGID(sgid) #define AUDIT_ARG_SOCKET(sodomain, sotype, soprotocol) #define AUDIT_ARG_SOCKADDR(td, dirfd, sa) #define AUDIT_ARG_SUID(suid) #define AUDIT_ARG_SVIPC_CMD(cmd) #define AUDIT_ARG_SVIPC_PERM(perm) #define AUDIT_ARG_SVIPC_ID(id) #define AUDIT_ARG_SVIPC_ADDR(addr) #define AUDIT_ARG_SVIPC_WHICH(which) #define AUDIT_ARG_TEXT(text) #define AUDIT_ARG_UID(uid) #define AUDIT_ARG_UPATH1(td, dirfd, upath) #define AUDIT_ARG_UPATH1_CANON(upath) #define AUDIT_ARG_UPATH2(td, dirfd, upath) #define AUDIT_ARG_UPATH2_CANON(upath) #define AUDIT_ARG_VALUE(value) #define AUDIT_ARG_VNODE1(vp) #define AUDIT_ARG_VNODE2(vp) #define AUDIT_SYSCALL_ENTER(code, td) #define AUDIT_SYSCALL_EXIT(error, td) #define AUDIT_SYSCLOSE(p, fd) #endif /* AUDIT */ #endif /* !_SECURITY_AUDIT_KERNEL_H_ */ Index: head/sys/security/audit/audit_dtrace.c =================================================================== --- head/sys/security/audit/audit_dtrace.c (revision 339084) +++ head/sys/security/audit/audit_dtrace.c (revision 339085) @@ -1,532 +1,538 @@ /*- - * Copyright (c) 2016 Robert N. M. Watson + * Copyright (c) 2016, 2018 Robert N. M. Watson * All rights reserved. * * This software was developed by BAE Systems, the University of Cambridge * Computer Laboratory, and Memorial University under DARPA/AFRL contract * FA8650-15-C-7558 ("CADETS"), as part of the DARPA Transparent Computing * (TC) research program. * * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*- * Audit DTrace provider: allow DTrace to request that audit records be * generated for various audit events, and then expose those records (in * various forms) to probes. The model is that each event type has two * probes, which use the event's name to create the probe: * * - "commit" passes the kernel-internal (unserialised) kaudit_record * synchronously (from the originating thread) of the record as we prepare * to "commit" the record to the audit queue. * * - "bsm" also passes generated BSM, and executes asynchronously in the audit * worker thread, once it has been extracted from the audit queue. This is * the point at which an audit record would be enqueued to the trail on * disk, or to pipes. * * These probes support very different goals. The former executes in the * thread originating the record, making it easier to correlate other DTrace * probe activity with the event described in the record. The latter gives * access to BSM-formatted events (at a cost) allowing DTrace to extract BSM * directly an alternative mechanism to the formal audit trail and audit * pipes. * * To generate names for numeric event IDs, userspace will push the contents * of /etc/security/audit_event into the kernel during audit setup, much as it * does /etc/security/audit_class. We then create the probes for each of * those mappings. If one (or both) of the probes are enabled, then we cause * a record to be generated (as both normal audit preselection and audit pipes * do), and catch it on the way out during commit. There are suitable hook * functions in the audit code that this provider can register to catch * various events in the audit-record life cycle. * * Further ponderings: * * - How do we want to handle events for which there are not names -- perhaps * a catch-all probe for those events without mappings? * * - Should the evname code really be present even if DTrace isn't loaded...? * Right now, we arrange that it is so that userspace can usefully maintain * the list in case DTrace is later loaded (and to prevent userspace * confusion). * * - Should we add an additional set of audit:class::commit probes that use * event class names to match broader categories of events as specified in * /etc/security/event_class? * * - If we pursue that last point, we will want to pass the name of the event * into the probe explicitly (e.g., as arg0), since it would no longer be * available as the probe function name. */ static int dtaudit_unload(void); static void dtaudit_getargdesc(void *, dtrace_id_t, void *, dtrace_argdesc_t *); static void dtaudit_provide(void *, dtrace_probedesc_t *); static void dtaudit_destroy(void *, dtrace_id_t, void *); static void dtaudit_enable(void *, dtrace_id_t, void *); static void dtaudit_disable(void *, dtrace_id_t, void *); static void dtaudit_load(void *); static dtrace_pattr_t dtaudit_attr = { { DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_COMMON }, { DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN }, { DTRACE_STABILITY_PRIVATE, DTRACE_STABILITY_PRIVATE, DTRACE_CLASS_UNKNOWN }, { DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_COMMON }, { DTRACE_STABILITY_EVOLVING, DTRACE_STABILITY_EVOLVING, DTRACE_CLASS_COMMON }, }; /* * Strings for the "module" and "name" portions of the probe. The name of the * audit event will be the "function" portion of the probe. All dtaudit * probes therefore take the form audit:event::commit. */ static char *dtaudit_module_str = "event"; static char *dtaudit_name_commit_str = "commit"; static char *dtaudit_name_bsm_str = "bsm"; static dtrace_pops_t dtaudit_pops = { .dtps_provide = dtaudit_provide, .dtps_provide_module = NULL, .dtps_enable = dtaudit_enable, .dtps_disable = dtaudit_disable, .dtps_suspend = NULL, .dtps_resume = NULL, .dtps_getargdesc = dtaudit_getargdesc, .dtps_getargval = NULL, .dtps_usermode = NULL, .dtps_destroy = dtaudit_destroy }; static dtrace_provider_id_t dtaudit_id; /* * Because looking up entries in the event-to-name mapping is quite expensive, * maintain a global flag tracking whether any dtaudit probes are enabled. If * not, don't bother doing all that work whenever potential queries about * events turn up during preselection or commit. + * + * NB: We used to maintain our own variable in dtaudit, but now use the + * centralized audit_dtrace_enabled variable imported from the audit code. + * + * static uint_t dtaudit_probes_enabled; */ -static uint_t dtaudit_probes_enabled; /* * Check dtaudit policy for the event to see whether this is an event we would * like to preselect (i.e., cause an audit record to be generated for). To * minimise probe effect when not used at all, we not only check for the probe * on the individual event, but also a global flag indicating that at least * one probe is enabled, before acquiring locks, searching lists, etc. * * If the event is selected, return an evname_elem reference to be stored in * the audit record, which we can use later to avoid further lookups. The * contents of the evname_elem must be sufficiently stable so as to not risk * race conditions here. * * Currently, we take an interest only in the 'event' argument, but in the * future might want to support other types of record selection tied to * additional probe types (e.g., event clases). * * XXXRW: Should we have a catch-all probe here for events without registered * names? */ static void * dtaudit_preselect(au_id_t auid, au_event_t event, au_class_t class) { struct evname_elem *ene; int probe_enabled; /* * NB: Lockless reads here may return a slightly stale value; this is * considered better than acquiring a lock, however. */ - if (!dtaudit_probes_enabled) + if (!audit_dtrace_enabled) return (NULL); ene = au_evnamemap_lookup(event); if (ene == NULL) return (NULL); /* * See if either of the two probes for the audit event are enabled. * * NB: Lock also not acquired here -- but perhaps it wouldn't matter * given that we've already used the list lock above? * * XXXRW: Alternatively, au_evnamemap_lookup() could return these * values while holding the list lock...? */ probe_enabled = ene->ene_commit_probe_enabled || ene->ene_bsm_probe_enabled; if (!probe_enabled) return (NULL); return ((void *)ene); } /* * Commit probe pre-BSM. Fires the probe but also checks to see if we should * ask the audit framework to call us again with BSM arguments in the audit * worker thread. * * XXXRW: Should we have a catch-all probe here for events without registered * names? */ static int dtaudit_commit(struct kaudit_record *kar, au_id_t auid, au_event_t event, au_class_t class, int sorf) { char ene_name_lower[EVNAMEMAP_NAME_SIZE]; struct evname_elem *ene; int i; ene = (struct evname_elem *)kar->k_dtaudit_state; if (ene == NULL) return (0); /* * Process a possibly registered commit probe. */ if (ene->ene_commit_probe_enabled) { /* * XXXRW: Lock ene to provide stability to the name string. A * bit undesirable! We may want another locking strategy * here. At least we don't run the DTrace probe under the * lock. * * XXXRW: We provide the struct audit_record pointer -- but * perhaps should provide the kaudit_record pointer? */ EVNAME_LOCK(ene); for (i = 0; i < sizeof(ene_name_lower); i++) ene_name_lower[i] = tolower(ene->ene_name[i]); EVNAME_UNLOCK(ene); dtrace_probe(ene->ene_commit_probe_id, (uintptr_t)ene_name_lower, (uintptr_t)&kar->k_ar, 0, 0, 0); } /* * Return the state of the BSM probe to the caller. */ return (ene->ene_bsm_probe_enabled); } /* * Commit probe post-BSM. * * XXXRW: Should we have a catch-all probe here for events without registered * names? */ static void dtaudit_bsm(struct kaudit_record *kar, au_id_t auid, au_event_t event, au_class_t class, int sorf, void *bsm_data, size_t bsm_len) { char ene_name_lower[EVNAMEMAP_NAME_SIZE]; struct evname_elem *ene; int i; ene = (struct evname_elem *)kar->k_dtaudit_state; if (ene == NULL) return; if (!(ene->ene_bsm_probe_enabled)) return; /* * XXXRW: Lock ene to provide stability to the name string. A bit * undesirable! We may want another locking strategy here. At least * we don't run the DTrace probe under the lock. * * XXXRW: We provide the struct audit_record pointer -- but perhaps * should provide the kaudit_record pointer? */ EVNAME_LOCK(ene); for (i = 0; i < sizeof(ene_name_lower); i++) ene_name_lower[i] = tolower(ene->ene_name[i]); EVNAME_UNLOCK(ene); dtrace_probe(ene->ene_bsm_probe_id, (uintptr_t)ene_name_lower, (uintptr_t)&kar->k_ar, (uintptr_t)bsm_data, (uintptr_t)bsm_len, 0); } /* * A very simple provider: argument types are identical across all probes: the * kaudit_record, plus a BSM pointer and length. */ static void dtaudit_getargdesc(void *arg, dtrace_id_t id, void *parg, dtrace_argdesc_t *desc) { struct evname_elem *ene; const char *p; ene = (struct evname_elem *)parg; p = NULL; switch (desc->dtargd_ndx) { case 0: /* Audit event name. */ p = "char *"; break; case 1: /* In-kernel audit record. */ p = "struct audit_record *"; break; case 2: /* BSM data, if present. */ if (id == ene->ene_bsm_probe_id) p = "const void *"; else desc->dtargd_ndx = DTRACE_ARGNONE; break; case 3: /* BSM length, if present. */ if (id == ene->ene_bsm_probe_id) p = "size_t"; else desc->dtargd_ndx = DTRACE_ARGNONE; break; default: desc->dtargd_ndx = DTRACE_ARGNONE; break; } if (p != NULL) strlcpy(desc->dtargd_native, p, sizeof(desc->dtargd_native)); } /* * Callback from the event-to-name mapping code when performing * evname_foreach(). Note that we may update the entry, so the foreach code * must have a write lock. However, as the synchronisation model is private * to the evname code, we cannot easily assert it here. * * XXXRW: How do we want to handle event rename / collision issues here -- * e.g., if userspace was using a name to point to one event number, and then * changes it so that the name points at another? For now, paper over this by * skipping event numbers that are already registered, and likewise skipping * names that are already registered. However, this could lead to confusing * behaviour so possibly needs to be resolved in the longer term. */ static void dtaudit_au_evnamemap_callback(struct evname_elem *ene) { char ene_name_lower[EVNAMEMAP_NAME_SIZE]; int i; /* * DTrace, by convention, has lower-case probe names. However, the * in-kernel event-to-name mapping table must maintain event-name case * as submitted by userspace. Create a temporary lower-case version * here, away from the fast path, to use when exposing the event name * to DTrace as part of the name of a probe. * * NB: Convert the entire array, including the terminating nul, * because these strings are short and it's more work not to. If they * become long, we might feel more guilty about this sloppiness! */ for (i = 0; i < sizeof(ene_name_lower); i++) ene_name_lower[i] = tolower(ene->ene_name[i]); /* * Don't register a new probe if this event number already has an * associated commit probe -- or if another event has already * registered this name. * * XXXRW: There is an argument that if multiple numeric events match * a single name, they should all be exposed to the same named probe. * In particular, we should perhaps use a probe ID returned by this * lookup and just stick that in the saved probe ID? */ if ((ene->ene_commit_probe_id == 0) && (dtrace_probe_lookup(dtaudit_id, dtaudit_module_str, ene_name_lower, dtaudit_name_commit_str) == 0)) { /* * Create the commit probe. * * NB: We don't declare any extra stack frames because stack() * will just return the path to the audit commit code, which * is not really interesting anyway. * * We pass in the pointer to the evnam_elem entry so that we * can easily change its enabled flag in the probe * enable/disable interface. */ ene->ene_commit_probe_id = dtrace_probe_create(dtaudit_id, dtaudit_module_str, ene_name_lower, dtaudit_name_commit_str, 0, ene); } /* * Don't register a new probe if this event number already has an * associated bsm probe -- or if another event has already * registered this name. * * XXXRW: There is an argument that if multiple numeric events match * a single name, they should all be exposed to the same named probe. * In particular, we should perhaps use a probe ID returned by this * lookup and just stick that in the saved probe ID? */ if ((ene->ene_bsm_probe_id == 0) && (dtrace_probe_lookup(dtaudit_id, dtaudit_module_str, ene_name_lower, dtaudit_name_bsm_str) == 0)) { /* * Create the bsm probe. * * NB: We don't declare any extra stack frames because stack() * will just return the path to the audit commit code, which * is not really interesting anyway. * * We pass in the pointer to the evnam_elem entry so that we * can easily change its enabled flag in the probe * enable/disable interface. */ ene->ene_bsm_probe_id = dtrace_probe_create(dtaudit_id, dtaudit_module_str, ene_name_lower, dtaudit_name_bsm_str, 0, ene); } } static void dtaudit_provide(void *arg, dtrace_probedesc_t *desc) { /* * Walk all registered number-to-name mapping entries, and ensure each * is properly registered. */ au_evnamemap_foreach(dtaudit_au_evnamemap_callback); } static void dtaudit_destroy(void *arg, dtrace_id_t id, void *parg) { } static void dtaudit_enable(void *arg, dtrace_id_t id, void *parg) { struct evname_elem *ene; ene = parg; KASSERT(ene->ene_commit_probe_id == id || ene->ene_bsm_probe_id == id, ("%s: probe ID mismatch (%u, %u != %u)", __func__, ene->ene_commit_probe_id, ene->ene_bsm_probe_id, id)); if (id == ene->ene_commit_probe_id) ene->ene_commit_probe_enabled = 1; else ene->ene_bsm_probe_enabled = 1; - refcount_acquire(&dtaudit_probes_enabled); + refcount_acquire(&audit_dtrace_enabled); + audit_syscalls_enabled_update(); } static void dtaudit_disable(void *arg, dtrace_id_t id, void *parg) { struct evname_elem *ene; ene = parg; KASSERT(ene->ene_commit_probe_id == id || ene->ene_bsm_probe_id == id, ("%s: probe ID mismatch (%u, %u != %u)", __func__, ene->ene_commit_probe_id, ene->ene_bsm_probe_id, id)); if (id == ene->ene_commit_probe_id) ene->ene_commit_probe_enabled = 0; else ene->ene_bsm_probe_enabled = 0; - (void)refcount_release(&dtaudit_probes_enabled); + (void)refcount_release(&audit_dtrace_enabled); + audit_syscalls_enabled_update(); } static void dtaudit_load(void *dummy) { if (dtrace_register("audit", &dtaudit_attr, DTRACE_PRIV_USER, NULL, &dtaudit_pops, NULL, &dtaudit_id) != 0) return; dtaudit_hook_preselect = dtaudit_preselect; dtaudit_hook_commit = dtaudit_commit; dtaudit_hook_bsm = dtaudit_bsm; } static int dtaudit_unload(void) { int error; dtaudit_hook_preselect = NULL; dtaudit_hook_commit = NULL; dtaudit_hook_bsm = NULL; if ((error = dtrace_unregister(dtaudit_id)) != 0) return (error); return (0); } static int dtaudit_modevent(module_t mod __unused, int type, void *data __unused) { int error = 0; switch (type) { case MOD_LOAD: case MOD_UNLOAD: case MOD_SHUTDOWN: break; default: error = EOPNOTSUPP; break; } return (error); } SYSINIT(dtaudit_load, SI_SUB_DTRACE_PROVIDER, SI_ORDER_ANY, dtaudit_load, NULL); SYSUNINIT(dtaudit_unload, SI_SUB_DTRACE_PROVIDER, SI_ORDER_ANY, dtaudit_unload, NULL); DEV_MODULE(dtaudit, dtaudit_modevent, NULL); MODULE_VERSION(dtaudit, 1); MODULE_DEPEND(dtaudit, dtrace, 1, 1, 1); MODULE_DEPEND(dtaudit, opensolaris, 1, 1, 1); Index: head/sys/security/audit/audit_private.h =================================================================== --- head/sys/security/audit/audit_private.h (revision 339084) +++ head/sys/security/audit/audit_private.h (revision 339085) @@ -1,502 +1,509 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1999-2009 Apple Inc. - * Copyright (c) 2016-2017 Robert N. M. Watson + * Copyright (c) 2016, 2018 Robert N. M. Watson * All rights reserved. * * Portions of this software were developed by BAE Systems, the University of * Cambridge Computer Laboratory, and Memorial University under DARPA/AFRL * contract FA8650-15-C-7558 ("CADETS"), as part of the DARPA Transparent * Computing (TC) research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * $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 #include #ifdef MALLOC_DECLARE MALLOC_DECLARE(M_AUDITBSM); MALLOC_DECLARE(M_AUDITDATA); MALLOC_DECLARE(M_AUDITPATH); MALLOC_DECLARE(M_AUDITTEXT); MALLOC_DECLARE(M_AUDITGIDSET); #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 #define AR_PRESELECT_DTRACE 0x00010000U /* * 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; u_int32_t vn_dev; /* XXX dev_t compatibility */ long vn_fsid; /* XXX uint64_t compatibility */ long vn_fileid; /* XXX ino_t compatibility */ long vn_gen; }; struct groupset { gid_t *gidset; 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. */ }; /* * The following is used for A_OLDSETQCTRL and AU_OLDGETQCTRL and a 64-bit * userland. */ struct au_qctrl64 { u_int64_t aq64_hiwater; u_int64_t aq64_lowater; u_int64_t aq64_bufsz; u_int64_t aq64_delay; u_int64_t aq64_minfree; }; typedef struct au_qctrl64 au_qctrl64_t; union auditon_udata { char *au_path; int au_cond; int au_flags; int au_policy; int au_trigger; int64_t au_cond64; int64_t au_policy64; 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_qctrl64_t au_qctrl64; au_stat_t au_stat; au_fstat_t au_fstat; auditinfo_addr_t au_kau_info; au_evname_map_t au_evname; }; 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; struct au_tid_addr ar_subj_term_addr; 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; struct au_tid_addr ar_arg_termid_addr; 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_atfd1; int ar_arg_atfd2; int ar_arg_fflags; mode_t ar_arg_mode; int ar_arg_dev; /* XXX dev_t compatibility */ 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 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_which; 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; struct sockaddr_storage ar_arg_sockaddr; cap_rights_t ar_arg_rights; uint32_t ar_arg_fcntl_rights; char ar_jailname[MAXHOSTNAMELEN]; }; /* * 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_EUID 0x0000000000000001ULL #define ARG_RUID 0x0000000000000002ULL #define ARG_SUID 0x0000000000000004ULL #define ARG_EGID 0x0000000000000008ULL #define ARG_RGID 0x0000000000000010ULL #define ARG_SGID 0x0000000000000020ULL #define ARG_PID 0x0000000000000040ULL #define ARG_UID 0x0000000000000080ULL #define ARG_AUID 0x0000000000000100ULL #define ARG_GID 0x0000000000000200ULL #define ARG_FD 0x0000000000000400ULL #define ARG_POSIX_IPC_PERM 0x0000000000000800ULL #define ARG_FFLAGS 0x0000000000001000ULL #define ARG_MODE 0x0000000000002000ULL #define ARG_DEV 0x0000000000004000ULL #define ARG_ADDR 0x0000000000008000ULL #define ARG_LEN 0x0000000000010000ULL #define ARG_MASK 0x0000000000020000ULL #define ARG_SIGNUM 0x0000000000040000ULL #define ARG_LOGIN 0x0000000000080000ULL #define ARG_SADDRINET 0x0000000000100000ULL #define ARG_SADDRINET6 0x0000000000200000ULL #define ARG_SADDRUNIX 0x0000000000400000ULL #define ARG_TERMID_ADDR 0x0000000000800000ULL #define ARG_UNUSED2 0x0000000001000000ULL #define ARG_UPATH1 0x0000000002000000ULL #define ARG_UPATH2 0x0000000004000000ULL #define ARG_TEXT 0x0000000008000000ULL #define ARG_VNODE1 0x0000000010000000ULL #define ARG_VNODE2 0x0000000020000000ULL #define ARG_SVIPC_CMD 0x0000000040000000ULL #define ARG_SVIPC_PERM 0x0000000080000000ULL #define ARG_SVIPC_ID 0x0000000100000000ULL #define ARG_SVIPC_ADDR 0x0000000200000000ULL #define ARG_GROUPSET 0x0000000400000000ULL #define ARG_CMD 0x0000000800000000ULL #define ARG_SOCKINFO 0x0000001000000000ULL #define ARG_ASID 0x0000002000000000ULL #define ARG_TERMID 0x0000004000000000ULL #define ARG_AUDITON 0x0000008000000000ULL #define ARG_VALUE 0x0000010000000000ULL #define ARG_AMASK 0x0000020000000000ULL #define ARG_CTLNAME 0x0000040000000000ULL #define ARG_PROCESS 0x0000080000000000ULL #define ARG_MACHPORT1 0x0000100000000000ULL #define ARG_MACHPORT2 0x0000200000000000ULL #define ARG_EXIT 0x0000400000000000ULL #define ARG_IOVECSTR 0x0000800000000000ULL #define ARG_ARGV 0x0001000000000000ULL #define ARG_ENVV 0x0002000000000000ULL #define ARG_ATFD1 0x0004000000000000ULL #define ARG_ATFD2 0x0008000000000000ULL #define ARG_RIGHTS 0x0010000000000000ULL #define ARG_FCNTL_RIGHTS 0x0020000000000000ULL #define ARG_SVIPC_WHICH 0x0200000000000000ULL #define ARG_NONE 0x0000000000000000ULL #define ARG_ALL 0xFFFFFFFFFFFFFFFFULL #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) #define ARG_CLEAR_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. */ void *k_dtaudit_state; 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); + +/* + * Function to update the audit_syscalls_enabled flag, whose value is affected + * by configuration of the audit trail/pipe mechanism and DTrace. Call this + * function when any of the inputs to that policy change. + */ +void audit_syscalls_enabled_update(void); /* * 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) /* * Audit event-to-name mapping structure, maintained in audit_bsm_klib.c. It * appears in this header so that the DTrace audit provider can dereference * instances passed back in the au_evname_foreach() callbacks. Safe access to * its fields requires holding ene_lock (after it is visible in the global * table). * * Locking: * (c) - Constant after inserted in the global table * (l) - Protected by ene_lock * (m) - Protected by evnamemap_lock (audit_bsm_klib.c) * (M) - Writes protected by evnamemap_lock; reads unprotected. */ struct evname_elem { au_event_t ene_event; /* (c) */ char ene_name[EVNAMEMAP_NAME_SIZE]; /* (l) */ LIST_ENTRY(evname_elem) ene_entry; /* (m) */ struct mtx ene_lock; /* DTrace probe IDs; 0 if not yet registered. */ uint32_t ene_commit_probe_id; /* (M) */ uint32_t ene_bsm_probe_id; /* (M) */ /* Flags indicating if the probes enabled or not. */ int ene_commit_probe_enabled; /* (M) */ int ene_bsm_probe_enabled; /* (M) */ }; #define EVNAME_LOCK(ene) mtx_lock(&(ene)->ene_lock) #define EVNAME_UNLOCK(ene) mtx_unlock(&(ene)->ene_lock) /* * Callback function typedef for the same. */ typedef void (*au_evnamemap_callback_t)(struct evname_elem *ene); /* * DTrace audit provider (dtaudit) hooks -- to be set non-NULL when the audit * provider is loaded and ready to be called into. */ extern void *(*dtaudit_hook_preselect)(au_id_t auid, au_event_t event, au_class_t class); extern int (*dtaudit_hook_commit)(struct kaudit_record *kar, au_id_t auid, au_event_t event, au_class_t class, int sorf); extern void (*dtaudit_hook_bsm)(struct kaudit_record *kar, au_id_t auid, au_event_t event, au_class_t class, int sorf, void *bsm_data, size_t bsm_len); #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); 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); void au_evnamemap_init(void); void au_evnamemap_insert(au_event_t event, const char *name); void au_evnamemap_foreach(au_evnamemap_callback_t callback); struct evname_elem *au_evnamemap_lookup(au_event_t event); int au_event_name(au_event_t event, char *name); au_event_t audit_ctlname_to_sysctlevent(int name[], uint64_t valid_arg); au_event_t audit_flags_and_error_to_openevent(int oflags, int error); au_event_t audit_flags_and_error_to_openatevent(int oflags, int error); au_event_t audit_msgctl_to_event(int cmd); au_event_t audit_msgsys_to_event(int which); au_event_t audit_semctl_to_event(int cmd); au_event_t audit_semsys_to_event(int which); au_event_t audit_shmsys_to_event(int which); void audit_canon_path(struct thread *td, int dirfd, char *path, char *cpath); au_event_t auditon_command_event(int cmd); /* * Audit trigger events notify user space of kernel audit conditions * asynchronously. */ void audit_trigger_init(void); int audit_send_trigger(unsigned int trigger); /* * Accessor functions to manage global audit state. */ void audit_set_kinfo(struct auditinfo_addr *); void audit_get_kinfo(struct auditinfo_addr *); /* * 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 339084) +++ head/sys/security/audit/audit_syscalls.c (revision 339085) @@ -1,903 +1,906 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1999-2009 Apple Inc. - * Copyright (c) 2016 Robert N. M. Watson + * Copyright (c) 2016, 2018 Robert N. M. Watson * All rights reserved. * * Portions of this software were developed by BAE Systems, the University of * Cambridge Computer Laboratory, and Memorial University under DARPA/AFRL * contract FA8650-15-C-7558 ("CADETS"), as part of the DARPA Transparent * Computing (TC) research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #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 sys_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); td->td_pflags |= TDP_AUDITREC; 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_system_check_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 sys_auditon(struct thread *td, struct auditon_args *uap) { struct ucred *cred, *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_system_check_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_OLDSETPOLICY: case A_SETKMASK: case A_SETQCTRL: case A_OLDSETQCTRL: case A_SETSTAT: case A_SETUMASK: case A_SETSMASK: case A_SETCOND: case A_OLDSETCOND: case A_SETCLASS: case A_SETEVENT: case A_SETPMASK: case A_SETFSIZE: case A_SETKAUDIT: case A_GETCLASS: case A_GETEVENT: 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_OLDGETPOLICY: case A_GETPOLICY: if (uap->length == sizeof(udata.au_policy64)) { if (!audit_fail_stop) udata.au_policy64 |= AUDIT_CNT; if (audit_panic_on_write_fail) udata.au_policy64 |= AUDIT_AHLT; if (audit_argv) udata.au_policy64 |= AUDIT_ARGV; if (audit_arge) udata.au_policy64 |= AUDIT_ARGE; break; } if (uap->length != sizeof(udata.au_policy)) return (EINVAL); 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_OLDSETPOLICY: case A_SETPOLICY: if (uap->length == sizeof(udata.au_policy64)) { if (udata.au_policy & ~(AUDIT_CNT|AUDIT_AHLT| AUDIT_ARGV|AUDIT_ARGE)) return (EINVAL); audit_fail_stop = ((udata.au_policy64 & AUDIT_CNT) == 0); audit_panic_on_write_fail = (udata.au_policy64 & AUDIT_AHLT); audit_argv = (udata.au_policy64 & AUDIT_ARGV); audit_arge = (udata.au_policy64 & AUDIT_ARGE); break; } if (uap->length != sizeof(udata.au_policy)) return (EINVAL); 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: if (uap->length != sizeof(udata.au_mask)) return (EINVAL); udata.au_mask = audit_nae_mask; break; case A_SETKMASK: if (uap->length != sizeof(udata.au_mask)) return (EINVAL); audit_nae_mask = udata.au_mask; break; case A_OLDGETQCTRL: case A_GETQCTRL: if (uap->length == sizeof(udata.au_qctrl64)) { udata.au_qctrl64.aq64_hiwater = (u_int64_t)audit_qctrl.aq_hiwater; udata.au_qctrl64.aq64_lowater = (u_int64_t)audit_qctrl.aq_lowater; udata.au_qctrl64.aq64_bufsz = (u_int64_t)audit_qctrl.aq_bufsz; udata.au_qctrl64.aq64_minfree = (u_int64_t)audit_qctrl.aq_minfree; break; } if (uap->length != sizeof(udata.au_qctrl)) return (EINVAL); udata.au_qctrl = audit_qctrl; break; case A_OLDSETQCTRL: case A_SETQCTRL: if (uap->length == sizeof(udata.au_qctrl64)) { /* NB: aq64_minfree is unsigned unlike aq_minfree. */ if ((udata.au_qctrl64.aq64_hiwater > AQ_MAXHIGH) || (udata.au_qctrl64.aq64_lowater >= udata.au_qctrl.aq_hiwater) || (udata.au_qctrl64.aq64_bufsz > AQ_MAXBUFSZ) || (udata.au_qctrl64.aq64_minfree > 100)) return (EINVAL); audit_qctrl.aq_hiwater = (int)udata.au_qctrl64.aq64_hiwater; audit_qctrl.aq_lowater = (int)udata.au_qctrl64.aq64_lowater; audit_qctrl.aq_bufsz = (int)udata.au_qctrl64.aq64_bufsz; audit_qctrl.aq_minfree = (int)udata.au_qctrl64.aq64_minfree; audit_qctrl.aq_delay = -1; /* Not used. */ break; } if (uap->length != sizeof(udata.au_qctrl)) return (EINVAL); 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_OLDGETCOND: case A_GETCOND: if (uap->length == sizeof(udata.au_cond64)) { - if (audit_enabled && !audit_suspended) + if (audit_trail_enabled && !audit_trail_suspended) udata.au_cond64 = AUC_AUDITING; else udata.au_cond64 = AUC_NOAUDIT; break; } if (uap->length != sizeof(udata.au_cond)) return (EINVAL); - if (audit_enabled && !audit_suspended) + if (audit_trail_enabled && !audit_trail_suspended) udata.au_cond = AUC_AUDITING; else udata.au_cond = AUC_NOAUDIT; break; case A_OLDSETCOND: case A_SETCOND: if (uap->length == sizeof(udata.au_cond64)) { if (udata.au_cond64 == AUC_NOAUDIT) - audit_suspended = 1; + audit_trail_suspended = 1; if (udata.au_cond64 == AUC_AUDITING) - audit_suspended = 0; + audit_trail_suspended = 0; if (udata.au_cond64 == AUC_DISABLED) { - audit_suspended = 1; + audit_trail_suspended = 1; audit_shutdown(NULL, 0); } + audit_syscalls_enabled_update(); break; } if (uap->length != sizeof(udata.au_cond)) return (EINVAL); if (udata.au_cond == AUC_NOAUDIT) - audit_suspended = 1; + audit_trail_suspended = 1; if (udata.au_cond == AUC_AUDITING) - audit_suspended = 0; + audit_trail_suspended = 0; if (udata.au_cond == AUC_DISABLED) { - audit_suspended = 1; + audit_trail_suspended = 1; audit_shutdown(NULL, 0); } + audit_syscalls_enabled_update(); break; case A_GETCLASS: if (uap->length != sizeof(udata.au_evclass)) return (EINVAL); udata.au_evclass.ec_class = au_event_class( udata.au_evclass.ec_number); break; case A_GETEVENT: if (uap->length != sizeof(udata.au_evname)) return (EINVAL); error = au_event_name(udata.au_evname.en_number, udata.au_evname.en_name); if (error != 0) return (error); break; case A_SETCLASS: if (uap->length != sizeof(udata.au_evclass)) return (EINVAL); au_evclassmap_insert(udata.au_evclass.ec_number, udata.au_evclass.ec_class); break; case A_SETEVENT: if (uap->length != sizeof(udata.au_evname)) return (EINVAL); /* Ensure nul termination from userspace. */ udata.au_evname.en_name[sizeof(udata.au_evname.en_name) - 1] = 0; au_evnamemap_insert(udata.au_evname.en_number, udata.au_evname.en_name); break; case A_GETPINFO: if (uap->length != sizeof(udata.au_aupinfo)) return (EINVAL); if (udata.au_aupinfo.ap_pid < 1) return (ESRCH); if ((tp = pfind(udata.au_aupinfo.ap_pid)) == NULL) return (ESRCH); if ((error = p_cansee(td, tp)) != 0) { PROC_UNLOCK(tp); return (error); } cred = tp->p_ucred; if (cred->cr_audit.ai_termid.at_type == AU_IPv6) { PROC_UNLOCK(tp); return (EINVAL); } udata.au_aupinfo.ap_auid = cred->cr_audit.ai_auid; udata.au_aupinfo.ap_mask.am_success = cred->cr_audit.ai_mask.am_success; udata.au_aupinfo.ap_mask.am_failure = cred->cr_audit.ai_mask.am_failure; udata.au_aupinfo.ap_termid.machine = cred->cr_audit.ai_termid.at_addr[0]; udata.au_aupinfo.ap_termid.port = (dev_t)cred->cr_audit.ai_termid.at_port; udata.au_aupinfo.ap_asid = cred->cr_audit.ai_asid; PROC_UNLOCK(tp); break; case A_SETPMASK: if (uap->length != sizeof(udata.au_aupinfo)) return (EINVAL); if (udata.au_aupinfo.ap_pid < 1) return (ESRCH); newcred = crget(); if ((tp = pfind(udata.au_aupinfo.ap_pid)) == NULL) { crfree(newcred); return (ESRCH); } if ((error = p_cansee(td, tp)) != 0) { PROC_UNLOCK(tp); crfree(newcred); return (error); } 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; proc_set_cred(tp, newcred); PROC_UNLOCK(tp); crfree(oldcred); break; case A_SETFSIZE: if (uap->length != sizeof(udata.au_fstat)) return (EINVAL); 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: if (uap->length != sizeof(udata.au_fstat)) return (EINVAL); udata.au_fstat.af_filesz = audit_fstat.af_filesz; udata.au_fstat.af_currsz = audit_fstat.af_currsz; break; case A_GETPINFO_ADDR: if (uap->length != sizeof(udata.au_aupinfo_addr)) return (EINVAL); if (udata.au_aupinfo_addr.ap_pid < 1) return (ESRCH); if ((tp = pfind(udata.au_aupinfo_addr.ap_pid)) == NULL) return (ESRCH); cred = tp->p_ucred; udata.au_aupinfo_addr.ap_auid = cred->cr_audit.ai_auid; udata.au_aupinfo_addr.ap_mask.am_success = cred->cr_audit.ai_mask.am_success; udata.au_aupinfo_addr.ap_mask.am_failure = cred->cr_audit.ai_mask.am_failure; udata.au_aupinfo_addr.ap_termid = cred->cr_audit.ai_termid; udata.au_aupinfo_addr.ap_asid = cred->cr_audit.ai_asid; PROC_UNLOCK(tp); break; case A_GETKAUDIT: if (uap->length != sizeof(udata.au_kau_info)) return (EINVAL); audit_get_kinfo(&udata.au_kau_info); break; case A_SETKAUDIT: if (uap->length != sizeof(udata.au_kau_info)) return (EINVAL); if (udata.au_kau_info.ai_termid.at_type != AU_IPv4 && udata.au_kau_info.ai_termid.at_type != AU_IPv6) return (EINVAL); audit_set_kinfo(&udata.au_kau_info); break; case A_SENDTRIGGER: if (uap->length != sizeof(udata.au_trigger)) return (EINVAL); 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_OLDGETPOLICY: case A_GETKMASK: case A_GETQCTRL: case A_OLDGETQCTRL: case A_GETCWD: case A_GETCAR: case A_GETSTAT: case A_GETCOND: case A_OLDGETCOND: 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 sys_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 sys_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_cred_check_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; proc_set_cred(td->td_proc, 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 sys_getaudit(struct thread *td, struct getaudit_args *uap) { struct auditinfo ai; struct ucred *cred; int error; cred = td->td_ucred; if (jailed(cred)) return (ENOSYS); error = priv_check(td, PRIV_AUDIT_GETAUDIT); if (error) return (error); if (cred->cr_audit.ai_termid.at_type == AU_IPv6) return (E2BIG); bzero(&ai, sizeof(ai)); ai.ai_auid = cred->cr_audit.ai_auid; ai.ai_mask = cred->cr_audit.ai_mask; ai.ai_asid = cred->cr_audit.ai_asid; ai.ai_termid.machine = cred->cr_audit.ai_termid.at_addr[0]; ai.ai_termid.port = cred->cr_audit.ai_termid.at_port; return (copyout(&ai, uap->auditinfo, sizeof(ai))); } /* ARGSUSED */ int sys_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_cred_check_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; proc_set_cred(td->td_proc, newcred); PROC_UNLOCK(td->td_proc); crfree(oldcred); return (0); fail: PROC_UNLOCK(td->td_proc); crfree(newcred); return (error); } /* ARGSUSED */ int sys_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 sys_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_cred_check_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; proc_set_cred(td->td_proc, 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 sys_auditctl(struct thread *td, struct auditctl_args *uap) { struct nameidata nd; struct ucred *cred; struct vnode *vp; int error = 0; int flags; 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 | AUDITVNODE1, UIO_USERSPACE, uap->path, td); flags = AUDIT_OPEN_FLAGS; error = vn_open(&nd, &flags, 0, NULL); if (error) return (error); vp = nd.ni_vp; #ifdef MAC error = mac_system_check_auditctl(td->td_ucred, vp); VOP_UNLOCK(vp, 0); if (error) { vn_close(vp, AUDIT_CLOSE_FLAGS, td->td_ucred, td); return (error); } #else VOP_UNLOCK(vp, 0); #endif NDFREE(&nd, NDF_ONLY_PNBUF); if (vp->v_type != VREG) { vn_close(vp, AUDIT_CLOSE_FLAGS, td->td_ucred, td); return (EINVAL); } cred = td->td_ucred; crhold(cred); /* - * XXXAUDIT: Should audit_suspended actually be cleared by + * XXXAUDIT: Should audit_trail_suspended actually be cleared by * audit_worker? */ - audit_suspended = 0; + audit_trail_suspended = 0; + audit_syscalls_enabled_update(); audit_rotate_vnode(cred, vp); return (error); } #else /* !AUDIT */ int sys_audit(struct thread *td, struct audit_args *uap) { return (ENOSYS); } int sys_auditon(struct thread *td, struct auditon_args *uap) { return (ENOSYS); } int sys_getauid(struct thread *td, struct getauid_args *uap) { return (ENOSYS); } int sys_setauid(struct thread *td, struct setauid_args *uap) { return (ENOSYS); } int sys_getaudit(struct thread *td, struct getaudit_args *uap) { return (ENOSYS); } int sys_setaudit(struct thread *td, struct setaudit_args *uap) { return (ENOSYS); } int sys_getaudit_addr(struct thread *td, struct getaudit_addr_args *uap) { return (ENOSYS); } int sys_setaudit_addr(struct thread *td, struct setaudit_addr_args *uap) { return (ENOSYS); } int sys_auditctl(struct thread *td, struct auditctl_args *uap) { return (ENOSYS); } #endif /* AUDIT */ Index: head/sys/security/audit/audit_worker.c =================================================================== --- head/sys/security/audit/audit_worker.c (revision 339084) +++ head/sys/security/audit/audit_worker.c (revision 339085) @@ -1,544 +1,546 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1999-2008 Apple Inc. - * Copyright (c) 2006-2008, 2016 Robert N. M. Watson + * Copyright (c) 2006-2008, 2016, 2018 Robert N. M. Watson * All rights reserved. * * Portions of this software were developed by BAE Systems, the University of * Cambridge Computer Laboratory, and Memorial University under DARPA/AFRL * contract FA8650-15-C-7558 ("CADETS"), as part of the DARPA Transparent * Computing (TC) research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __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 /* * 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 the audit worker lock, 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 * the audit worker lock. */ static int audit_file_rotate_wait; static struct ucred *audit_cred; static struct vnode *audit_vp; static off_t audit_size; static struct sx audit_worker_lock; #define AUDIT_WORKER_LOCK_INIT() sx_init(&audit_worker_lock, \ "audit_worker_lock"); #define AUDIT_WORKER_LOCK_ASSERT() sx_assert(&audit_worker_lock, \ SA_XLOCKED) #define AUDIT_WORKER_LOCK() sx_xlock(&audit_worker_lock) #define AUDIT_WORKER_UNLOCK() sx_xunlock(&audit_worker_lock) static void audit_worker_sync_vp(struct vnode *vp, struct mount *mp, const char *fmt, ...) { struct mount *mp1; int error; va_list va; va_start(va, fmt); error = vn_start_write(vp, &mp1, 0); if (error == 0) { VOP_LOCK(vp, LK_EXCLUSIVE | LK_RETRY); (void)VOP_FSYNC(vp, MNT_WAIT, curthread); VOP_UNLOCK(vp, 0); vn_finished_write(mp1); } vfs_unbusy(mp); vpanic(fmt, va); va_end(va); } /* * 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; struct mount *mp; int error; static int cur_fail; long temp; AUDIT_WORKER_LOCK_ASSERT(); if (vp == NULL) return; mp = vp->v_mount; if (mp == NULL) { error = EINVAL; goto fail; } error = vfs_busy(mp, 0); if (error != 0) { mp = NULL; goto fail; } mnt_stat = &mp->mnt_stat; /* * 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(mp, mnt_stat); if (error != 0) goto fail; /* * 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: disk space low (< %d%% free) " "on audit log file-system\n", audit_qctrl.aq_minfree); } } } /* * 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_size >= audit_fstat.af_filesz * (audit_file_rotate_wait + 1)) { AUDIT_WORKER_LOCK_ASSERT(); audit_file_rotate_wait++; (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; AUDIT_WORKER_LOCK_ASSERT(); audit_size += len; /* * 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) { audit_worker_sync_vp(vp, mp, "Audit store overflow; record queue drained."); } } vfs_unbusy(mp); 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) { audit_worker_sync_vp(vp, mp, "Audit log space exhausted and fail-stop set."); } (void)audit_send_trigger(AUDIT_TRIGGER_NO_SPACE); - audit_suspended = 1; + audit_trail_suspended = 1; + audit_syscalls_enabled_update(); /* 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) { audit_worker_sync_vp(vp, mp, "audit_worker: write error %d\n", error); } else if (ppsratecheck(&last_fail, &cur_fail, 1)) printf("audit_worker: write error %d\n", error); if (mp != NULL) vfs_unbusy(mp); } /* * 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 locked; /* * We hold the audit worker 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)) { AUDIT_WORKER_LOCK(); locked = 1; } else 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)) { AUDIT_WORKER_LOCK_ASSERT(); 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 && (ar->k_ar_commit & AR_PRESELECT_DTRACE) == 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) { AUDIT_WORKER_LOCK_ASSERT(); 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); #ifdef KDTRACE_HOOKS /* * Version of the dtaudit commit hook that accepts BSM. */ if (ar->k_ar_commit & AR_PRESELECT_DTRACE) { if (dtaudit_hook_bsm != NULL) dtaudit_hook_bsm(ar, auid, event, class, sorf, bsm->data, bsm->len); } #endif kau_free(bsm); out: if (locked) AUDIT_WORKER_UNLOCK(); } /* * 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; struct vattr vattr; KASSERT((cred != NULL && vp != NULL) || (cred == NULL && vp == NULL), ("audit_rotate_vnode: cred %p vp %p", cred, vp)); if (vp != NULL) { vn_lock(vp, LK_SHARED | LK_RETRY); if (VOP_GETATTR(vp, &vattr, cred) != 0) vattr.va_size = 0; VOP_UNLOCK(vp, 0); } else { vattr.va_size = 0; } /* * Rotate the vnode/cred, and clear the rotate flag so that we will * send a rotate trigger if the new file fills. */ AUDIT_WORKER_LOCK(); old_audit_cred = audit_cred; old_audit_vp = audit_vp; audit_cred = cred; audit_vp = vp; audit_size = vattr.va_size; audit_file_rotate_wait = 0; - audit_enabled = (audit_vp != NULL); + audit_trail_enabled = (audit_vp != NULL); + audit_syscalls_enabled_update(); AUDIT_WORKER_UNLOCK(); /* * If there was an old vnode/credential, close and free. */ if (old_audit_vp != NULL) { vn_close(old_audit_vp, AUDIT_CLOSE_FLAGS, old_audit_cred, curthread); crfree(old_audit_cred); } } void audit_worker_init(void) { int error; AUDIT_WORKER_LOCK_INIT(); error = kproc_create(audit_worker, NULL, &audit_thread, RFHIGHPID, 0, "audit"); if (error) panic("audit_worker_init: kproc_create returned %d", error); }