diff options
author | rwatson <rwatson@FreeBSD.org> | 2006-02-01 20:01:18 +0000 |
---|---|---|
committer | rwatson <rwatson@FreeBSD.org> | 2006-02-01 20:01:18 +0000 |
commit | e100506eafc12e959fd8a34a3d8e4cdc9da2ff9f (patch) | |
tree | 4cdf557824d88e0f2c4a5193059f38fedaeeb2bd /sys/security/audit | |
parent | 57bf2d086c80402caafcfbaf6bc910bbbb8b35bc (diff) | |
download | FreeBSD-src-e100506eafc12e959fd8a34a3d8e4cdc9da2ff9f.zip FreeBSD-src-e100506eafc12e959fd8a34a3d8e4cdc9da2ff9f.tar.gz |
Import kernel audit framework:
- Management of audit state on processes.
- Audit system calls to configure process and system audit state.
- Reliable audit record queue implementation, audit_worker kernel
thread to asynchronously store records on disk.
- Audit event argument.
- Internal audit data structure -> BSM audit trail conversion library.
- Audit event pre-selection.
- Audit pseudo-device permitting kernel->user upcalls to notify auditd
of kernel audit events.
Much work by: wsalamon
Obtained from: TrustedBSD Project, Apple Computer, Inc.
Diffstat (limited to 'sys/security/audit')
-rw-r--r-- | sys/security/audit/audit.c | 1083 | ||||
-rw-r--r-- | sys/security/audit/audit.h | 238 | ||||
-rw-r--r-- | sys/security/audit/audit_arg.c | 803 | ||||
-rw-r--r-- | sys/security/audit/audit_bsm.c | 1261 | ||||
-rw-r--r-- | sys/security/audit/audit_bsm_klib.c | 538 | ||||
-rw-r--r-- | sys/security/audit/audit_bsm_token.c | 1181 | ||||
-rw-r--r-- | sys/security/audit/audit_private.h | 300 | ||||
-rw-r--r-- | sys/security/audit/audit_syscalls.c | 652 | ||||
-rw-r--r-- | sys/security/audit/audit_trigger.c | 172 |
9 files changed, 6228 insertions, 0 deletions
diff --git a/sys/security/audit/audit.c b/sys/security/audit/audit.c new file mode 100644 index 0000000..b2f1143 --- /dev/null +++ b/sys/security/audit/audit.c @@ -0,0 +1,1083 @@ +/* + * Copyright (c) 1999-2005 Apple Computer, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/condvar.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/filedesc.h> +#include <sys/fcntl.h> +#include <sys/ipc.h> +#include <sys/kernel.h> +#include <sys/kthread.h> +#include <sys/malloc.h> +#include <sys/mount.h> +#include <sys/namei.h> +#include <sys/proc.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/protosw.h> +#include <sys/domain.h> +#include <sys/sysproto.h> +#include <sys/sysent.h> +#include <sys/systm.h> +#include <sys/ucred.h> +#include <sys/uio.h> +#include <sys/un.h> +#include <sys/unistd.h> +#include <sys/vnode.h> + +#include <netinet/in.h> +#include <netinet/in_pcb.h> + +#include <bsm/audit.h> +#include <bsm/audit_kevents.h> +#include <security/audit/audit.h> +#include <security/audit/audit_private.h> + +/* + * The AUDIT_EXCESSIVELY_VERBOSE define enables a number of + * gratuitously noisy printf's to the console. Due to the + * volume, it should be left off unless you want your system + * to churn a lot whenever the audit record flow gets high. + */ +//#define AUDIT_EXCESSIVELY_VERBOSE +#ifdef AUDIT_EXCESSIVELY_VERBOSE +#define AUDIT_PRINTF(x) printf x +#else +#define AUDIT_PRINTF(X) +#endif + +static MALLOC_DEFINE(M_AUDITPROC, "audit_proc", "Audit process storage"); +static MALLOC_DEFINE(M_AUDITREC, "audit_rec", "Audit event records"); +MALLOC_DEFINE(M_AUDITDATA, "audit_data", "Audit data storage"); +MALLOC_DEFINE(M_AUDITPATH, "audit_path", "Audit path storage"); +MALLOC_DEFINE(M_AUDITTEXT, "audit_text", "Audit text storage"); + +/* + * Audit control settings that are set/read by system calls and are + * hence non-static. + */ +/* + * Define the audit control flags. + */ +int audit_enabled; +int audit_suspended; + +/* + * Flags controlling behavior in low storage situations. + * Should we panic if a write fails? Should we fail stop + * if we're out of disk space? + */ +int audit_panic_on_write_fail; +int audit_fail_stop; + +/* + * Are we currently "failing stop" due to out of disk space? + */ +static int audit_in_failure; + +/* + * Global audit statistiscs. + */ +struct audit_fstat audit_fstat; + +/* + * Preselection mask for non-attributable events. + */ +struct au_mask audit_nae_mask; + +/* + * Mutex to protect global variables shared between various threads and + * processes. + */ +static 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. + */ +static TAILQ_HEAD(, kaudit_record) audit_q; +static int audit_q_len; +static 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. + */ +static struct cv audit_cv; + +/* + * Worker thread that will schedule disk I/O, etc. + */ +static struct proc *audit_thread; + +/* + * When an audit log is rotated, the actual rotation must be performed + * by the audit worker thread, as it may have outstanding writes on the + * current audit log. audit_replacement_vp holds the vnode replacing + * the current vnode. We can't let more than one replacement occur + * at a time, so if more than one thread requests a replacement, only + * one can have the replacement "in progress" at any given moment. If + * a thread tries to replace the audit vnode and discovers a replacement + * is already in progress (i.e., audit_replacement_flag != 0), then it + * will sleep on audit_replacement_cv waiting its turn to perform a + * replacement. When a replacement is completed, this cv is signalled + * by the worker thread so a waiting thread can start another replacement. + * We also store a credential to perform audit log write operations with. + * + * The current credential and vnode are thread-local to audit_worker. + */ +static struct cv audit_replacement_cv; + +static int audit_replacement_flag; +static struct vnode *audit_replacement_vp; +static struct ucred *audit_replacement_cred; + +/* + * 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. + */ +static struct cv audit_commit_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; + +/* + * Flags related to Kernel->user-space communication. + */ +static int audit_file_rotate_wait; + +/* + * Perform a deep free of an audit record (core record and referenced objects) + */ +static void +audit_record_free(struct kaudit_record *ar) +{ + + 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); + } + free(ar, M_AUDITREC); +} + +/* + * XXXAUDIT: Should adjust comments below to make it clear that we get to + * this point only if we believe we have storage, so not having space here + * is a violation of invariants derived from administrative procedures. + * I.e., someone else has written to the audit partition, leaving less space + * than we accounted for. + */ +static int +audit_record_write(struct vnode *vp, struct kaudit_record *ar, + struct ucred *cred, struct thread *td) +{ + int ret; + long temp; + struct au_record *bsm; + struct vattr vattr; + struct statfs *mnt_stat = &vp->v_mount->mnt_stat; + int vfslocked; + + vfslocked = VFS_LOCK_GIANT(vp->v_mount); + + /* + * First, gather statistics on the audit log file and file system + * so that we know how we're doing on space. In both cases, + * if we're unable to perform the operation, we drop the record + * and return. However, this is arguably an assertion failure. + * XXX Need a FreeBSD equivalent. + */ + ret = VFS_STATFS(vp->v_mount, mnt_stat, td); + if (ret) + goto out; + + ret = VOP_GETATTR(vp, &vattr, cred, td); + if (ret) + goto out; + + /* update the global stats struct */ + audit_fstat.af_currsz = vattr.va_size; + + /* + * XXX Need to decide what to do if the trigger to the audit daemon + * fails. + */ + + /* + * If we fall below minimum free blocks (hard limit), tell the audit + * daemon to force a rotation off of the file system. We also stop + * writing, which means this audit record is probably lost. + * If we fall below the minimum percent free blocks (soft limit), + * then kindly suggest to the audit daemon to do something. + */ + if (mnt_stat->f_bfree < AUDIT_HARD_LIMIT_FREE_BLOCKS) { + send_trigger(AUDIT_TRIGGER_NO_SPACE); + /* Hopefully userspace did something about all the previous + * triggers that were sent prior to this critical condition. + * If fail-stop is set, then we're done; goodnight Gracie. + */ + if (audit_fail_stop) + panic("Audit log space exhausted and fail-stop set."); + else { + audit_suspended = 1; + ret = ENOSPC; + goto out; + } + } else + /* + * Send a message to the audit daemon that disk space + * is getting low. + * + * XXXAUDIT: Check math and block size calculation here. + */ + if (audit_qctrl.aq_minfree != 0) { + temp = mnt_stat->f_blocks / (100 / + audit_qctrl.aq_minfree); + if (mnt_stat->f_bfree < temp) + send_trigger(AUDIT_TRIGGER_LOW_SPACE); + } + + /* Check if the current log file is full; if so, call for + * a log rotate. This is not an exact comparison; we may + * write some records over the limit. If that's not + * acceptable, then add a fudge factor here. + */ + if ((audit_fstat.af_filesz != 0) && + (audit_file_rotate_wait == 0) && + (vattr.va_size >= audit_fstat.af_filesz)) { + audit_file_rotate_wait = 1; + send_trigger(AUDIT_TRIGGER_OPEN_NEW); + } + + /* + * If the estimated amount of audit data in the audit event queue + * (plus records allocated but not yet queued) has reached the + * amount of free space on the disk, then we need to go into an + * audit fail stop state, in which we do not permit the + * allocation/committing of any new audit records. We continue to + * process packets but don't allow any activities that might + * generate new records. In the future, we might want to detect + * when space is available again and allow operation to continue, + * but this behavior is sufficient to meet fail stop requirements + * in CAPP. + */ + if (audit_fail_stop && + (unsigned long) + ((audit_q_len + audit_pre_q_len + 1) * MAX_AUDIT_RECORD_SIZE) / + mnt_stat->f_bsize >= (unsigned long)(mnt_stat->f_bfree)) { + printf( + "audit_worker: free space below size of audit queue, failing stop\n"); + audit_in_failure = 1; + } + + /* + * If there is a user audit record attached to the kernel record, + * then write the user record. + */ + /* XXX Need to decide a few things here: IF the user audit + * record is written, but the write of the kernel record fails, + * what to do? Should the kernel record come before or after the + * user record? For now, we write the user record first, and + * we ignore errors. + */ + if (ar->k_ar_commit & AR_COMMIT_USER) { + ret = vn_rdwr(UIO_WRITE, vp, (void *)ar->k_udata, ar->k_ulen, + (off_t)0, UIO_SYSSPACE, IO_APPEND|IO_UNIT, cred, NULL, + NULL, td); + if (ret) + goto out; + } + + /* + * Convert the internal kernel record to BSM format and write it + * out if everything's OK. + */ + if (!(ar->k_ar_commit & AR_COMMIT_KERNEL)) { + ret = 0; + goto out; + } + + /* + * XXXAUDIT: Should we actually allow this conversion to fail? With + * sleeping memory allocation and invariants checks, perhaps not. + */ + ret = kaudit_to_bsm(ar, &bsm); + if (ret == BSM_NOAUDIT) { + ret = 0; + goto out; + } + + /* + * XXX: We drop the record on BSM conversion failure, but really + * this is an assertion failure. + */ + if (ret == BSM_FAILURE) { + AUDIT_PRINTF(("BSM conversion failure\n")); + ret = EINVAL; + goto out; + } + + /* + * XXX + * We should break the write functionality away from the BSM record + * generation and have the BSM generation done before this function + * is called. This function will then take the BSM record as a + * parameter. + */ + ret = (vn_rdwr(UIO_WRITE, vp, (void *)bsm->data, bsm->len, + (off_t)0, UIO_SYSSPACE, IO_APPEND|IO_UNIT, cred, NULL, NULL, td)); + + kau_free(bsm); + +out: + /* + * When we're done processing the current record, we have to + * check to see if we're in a failure mode, and if so, whether + * this was the last record left to be drained. If we're done + * draining, then we fsync the vnode and panic. + */ + if (audit_in_failure && + audit_q_len == 0 && audit_pre_q_len == 0) { + VOP_LOCK(vp, LK_DRAIN | LK_INTERLOCK, td); + (void)VOP_FSYNC(vp, MNT_WAIT, td); + VOP_UNLOCK(vp, 0, td); + panic("Audit store overflow; record queue drained."); + } + + VFS_UNLOCK_GIANT(vfslocked); + + return (ret); +} + +/* + * The audit_worker thread is responsible for watching the event queue, + * dequeueing records, converting them to BSM format, and committing them to + * disk. In order to minimize lock thrashing, records are dequeued in sets + * to a thread-local work queue. In addition, the audit_work performs the + * actual exchange of audit log vnode pointer, as audit_vp is a thread-local + * variable. + */ +static void +audit_worker(void *arg) +{ + int do_replacement_signal, error; + TAILQ_HEAD(, kaudit_record) ar_worklist; + struct kaudit_record *ar; + struct vnode *audit_vp, *old_vp; + int vfslocked; + + struct ucred *audit_cred, *old_cred; + struct thread *audit_td; + + AUDIT_PRINTF(("audit_worker starting\n")); + + /* + * These are thread-local variables requiring no synchronization. + */ + TAILQ_INIT(&ar_worklist); + audit_cred = NULL; + audit_td = curthread; + audit_vp = NULL; + + mtx_lock(&audit_mtx); + while (1) { + /* + * First priority: replace the audit log target if requested. + * Accessing the vnode here requires dropping the audit_mtx; + * in case another replacement was scheduled while the mutex + * was released, we loop. + * + * XXX It could well be we should drain existing records + * first to ensure that the timestamps and ordering + * are right. + */ + do_replacement_signal = 0; + while (audit_replacement_flag != 0) { + old_cred = audit_cred; + old_vp = audit_vp; + audit_cred = audit_replacement_cred; + audit_vp = audit_replacement_vp; + audit_replacement_cred = NULL; + audit_replacement_vp = NULL; + audit_replacement_flag = 0; + + audit_enabled = (audit_vp != NULL); + + /* + * XXX: What to do about write failures here? + */ + if (old_vp != NULL) { + AUDIT_PRINTF(("Closing old audit file\n")); + mtx_unlock(&audit_mtx); + vfslocked = VFS_LOCK_GIANT(old_vp->v_mount); + vn_close(old_vp, AUDIT_CLOSE_FLAGS, old_cred, + audit_td); + VFS_UNLOCK_GIANT(vfslocked); + crfree(old_cred); + mtx_lock(&audit_mtx); + old_cred = NULL; + old_vp = NULL; + AUDIT_PRINTF(("Audit file closed\n")); + } + if (audit_vp != NULL) { + AUDIT_PRINTF(("Opening new audit file\n")); + } + do_replacement_signal = 1; + } + /* + * Signal that replacement have occurred to wake up and + * start any other replacements started in parallel. We can + * continue about our business in the mean time. We + * broadcast so that both new replacements can be inserted, + * but also so that the source(s) of replacement can return + * successfully. + */ + if (do_replacement_signal) + cv_broadcast(&audit_replacement_cv); + + /* + * Next, check to see if we have any records to drain into + * the vnode. If not, go back to waiting for an event. + */ + if (TAILQ_EMPTY(&audit_q)) { + AUDIT_PRINTF(("audit_worker waiting\n")); + cv_wait(&audit_cv, &audit_mtx); + AUDIT_PRINTF(("audit_worker woken up\n")); + AUDIT_PRINTF(("audit_worker: new vp = %p; value of flag %d\n", + audit_replacement_vp, audit_replacement_flag)); + continue; + } + + /* + * If we have records, but there's no active vnode to + * write to, drain the record queue. Generally, we + * prevent the unnecessary allocation of records + * elsewhere, but we need to allow for races between + * conditional allocation and queueing. Go back to + * waiting when we're done. + * + * XXX: We go out of our way to avoid calling + * audit_record_free(). + * with the audit_mtx held, to avoid a lock order reversal + * as free() may grab Giant. This should be fixed at + * some point. + */ + if (audit_vp == NULL) { + while ((ar = TAILQ_FIRST(&audit_q))) { + TAILQ_REMOVE(&audit_q, ar, k_q); + audit_q_len--; + if (audit_q_len <= audit_qctrl.aq_lowater) + cv_broadcast(&audit_commit_cv); + + TAILQ_INSERT_TAIL(&ar_worklist, ar, k_q); + } + mtx_unlock(&audit_mtx); + while ((ar = TAILQ_FIRST(&ar_worklist))) { + TAILQ_REMOVE(&ar_worklist, ar, k_q); + audit_record_free(ar); + } + mtx_lock(&audit_mtx); + continue; + } + + /* + * We have both records to write and an active vnode + * to write to. Dequeue a record, and start the write. + * Eventually, it might make sense to dequeue several + * records and perform our own clustering, if the lower + * layers aren't doing it automatically enough. + * + * XXX: We go out of our way to avoid calling + * audit_record_free() + * with the audit_mtx held, to avoid a lock order reversal + * as free() may grab Giant. This should be fixed at + * some point. + * + * XXXAUDIT: free() no longer grabs Giant. + */ + while ((ar = TAILQ_FIRST(&audit_q))) { + TAILQ_REMOVE(&audit_q, ar, k_q); + audit_q_len--; + if (audit_q_len <= audit_qctrl.aq_lowater) + cv_broadcast(&audit_commit_cv); + + TAILQ_INSERT_TAIL(&ar_worklist, ar, k_q); + } + + mtx_unlock(&audit_mtx); + while ((ar = TAILQ_FIRST(&ar_worklist))) { + TAILQ_REMOVE(&ar_worklist, ar, k_q); + if (audit_vp != NULL) { + error = audit_record_write(audit_vp, ar, + audit_cred, audit_td); + if (error && audit_panic_on_write_fail) + panic("audit_worker: write error %d\n", + error); + else if (error) + printf("audit_worker: write error %d\n", + error); + } + audit_record_free(ar); + } + mtx_lock(&audit_mtx); + } +} + +/* + * 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) +{ + int error; + + printf("Security auditing service present\n"); + audit_enabled = 0; + audit_suspended = 0; + audit_panic_on_write_fail = 0; + audit_fail_stop = 0; + audit_in_failure = 0; + + audit_replacement_vp = NULL; + audit_replacement_cred = NULL; + audit_replacement_flag = 0; + + audit_fstat.af_filesz = 0; /* '0' means unset, unbounded */ + audit_fstat.af_currsz = 0; + audit_nae_mask.am_success = AU_NULL; + audit_nae_mask.am_failure = AU_NULL; + + TAILQ_INIT(&audit_q); + audit_q_len = 0; + audit_pre_q_len = 0; + audit_qctrl.aq_hiwater = AQ_HIWATER; + audit_qctrl.aq_lowater = AQ_LOWATER; + audit_qctrl.aq_bufsz = AQ_BUFSZ; + audit_qctrl.aq_minfree = AU_FS_MINFREE; + + mtx_init(&audit_mtx, "audit_mtx", NULL, MTX_DEF); + cv_init(&audit_cv, "audit_cv"); + cv_init(&audit_replacement_cv, "audit_replacement_cv"); + cv_init(&audit_commit_cv, "audit_commit_cv"); + cv_init(&audit_fail_cv, "audit_fail_cv"); + + /* Initialize the BSM audit subsystem. */ + kau_init(); + + audit_file_rotate_wait = 0; + audit_trigger_init(); + + /* Register shutdown handler. */ + EVENTHANDLER_REGISTER(shutdown_pre_sync, audit_shutdown, NULL, + SHUTDOWN_PRI_FIRST); + + error = kthread_create(audit_worker, NULL, &audit_thread, RFHIGHPID, + 0, "audit_worker"); + if (error != 0) + panic("audit_init: kthread_create returned %d", error); +} + +SYSINIT(audit_init, SI_SUB_AUDIT, SI_ORDER_FIRST, audit_init, NULL) + +/* + * audit_rotate_vnode() is called by a user or kernel thread to configure or + * de-configure auditing on a vnode. The arguments are the replacement + * credential and vnode to substitute for the current credential and vnode, + * if any. If either is set to NULL, both should be NULL, and this is used + * to indicate that audit is being disabled. The real work is done in the + * audit_worker thread, but audit_rotate_vnode() waits synchronously for that + * to complete. + * + * The vnode should be referenced and opened by the caller. The credential + * should be referenced. audit_rotate_vnode() will own both references as of + * this call, so the caller should not release either. + * + * XXXAUDIT: Review synchronize communication logic. Really, this is a + * message queue of depth 1. + * + * XXXAUDIT: Enhance the comments below to indicate that we are basically + * acquiring ownership of the communications queue, inserting our message, + * and waiting for an acknowledgement. + */ +void +audit_rotate_vnode(struct ucred *cred, struct vnode *vp) +{ + + /* + * If other parallel log replacements have been requested, we wait + * until they've finished before continuing. + */ + mtx_lock(&audit_mtx); + while (audit_replacement_flag != 0) { + AUDIT_PRINTF(("audit_rotate_vnode: sleeping to wait for " + "flag\n")); + cv_wait(&audit_replacement_cv, &audit_mtx); + AUDIT_PRINTF(("audit_rotate_vnode: woken up (flag %d)\n", + audit_replacement_flag)); + } + audit_replacement_cred = cred; + audit_replacement_flag = 1; + audit_replacement_vp = vp; + + /* + * Wake up the audit worker to perform the exchange once we + * release the mutex. + */ + cv_signal(&audit_cv); + + /* + * Wait for the audit_worker to broadcast that a replacement has + * taken place; we know that once this has happened, our vnode + * has been replaced in, so we can return successfully. + */ + AUDIT_PRINTF(("audit_rotate_vnode: waiting for news of " + "replacement\n")); + cv_wait(&audit_replacement_cv, &audit_mtx); + AUDIT_PRINTF(("audit_rotate_vnode: change acknowledged by " + "audit_worker (flag " "now %d)\n", audit_replacement_flag)); + mtx_unlock(&audit_mtx); + + audit_file_rotate_wait = 0; /* We can now request another rotation */ +} + +/* + * Drain the audit queue and close the log at shutdown. Note that this can + * be called both from the system shutdown path and also from audit + * configuration syscalls, so 'arg' and 'howto' are ignored. + */ +void +audit_shutdown(void *arg, int howto) +{ + + audit_rotate_vnode(NULL, NULL); +} + +/* + * Return the current thread's audit record, if any. + */ +__inline__ struct kaudit_record * +currecord(void) +{ + + return (curthread->td_ar); +} + +/* + * MPSAFE + * + * XXXAUDIT: There are a number of races present in the code below due to + * release and re-grab of the mutex. The code should be revised to become + * slightly less racy. + * + * XXXAUDIT: Shouldn't there be logic here to sleep waiting on available + * pre_q space, suspending the system call until there is room? + */ +struct kaudit_record * +audit_new(int event, struct thread *td) +{ + struct kaudit_record *ar; + int no_record; + + /* + * Eventually, there may be certain classes of events that + * we will audit regardless of the audit state at the time + * the record is created. These events will generally + * correspond to changes in the audit state. The dummy + * code below is from our first prototype, but may also + * be used in the final version (with modified event numbers). + */ +#if 0 + if (event != AUDIT_EVENT_FILESTOP && event != AUDIT_EVENT_FILESTART) { +#endif + mtx_lock(&audit_mtx); + no_record = (audit_suspended || !audit_enabled); + mtx_unlock(&audit_mtx); + if (no_record) + return (NULL); +#if 0 + } +#endif + + /* + * Initialize the audit record header. + * XXX: We may want to fail-stop if allocation fails. + * XXX: The number of outstanding uncommitted audit records is + * limited by the number of concurrent threads servicing system + * calls in the kernel. + */ + + ar = malloc(sizeof(*ar), M_AUDITREC, M_WAITOK); + if (ar == NULL) + return NULL; + + mtx_lock(&audit_mtx); + audit_pre_q_len++; + mtx_unlock(&audit_mtx); + + bzero(ar, sizeof(*ar)); + ar->k_ar.ar_magic = AUDIT_RECORD_MAGIC; + ar->k_ar.ar_event = event; + nanotime(&ar->k_ar.ar_starttime); + + /* + * Export the subject credential. + * + * XXXAUDIT: td_ucred access is OK without proc lock, but some other + * fields here may require the proc lock. + */ + cru2x(td->td_ucred, &ar->k_ar.ar_subj_cred); + ar->k_ar.ar_subj_ruid = td->td_ucred->cr_ruid; + ar->k_ar.ar_subj_rgid = td->td_ucred->cr_rgid; + ar->k_ar.ar_subj_egid = td->td_ucred->cr_groups[0]; + ar->k_ar.ar_subj_auid = td->td_proc->p_au->ai_auid; + ar->k_ar.ar_subj_asid = td->td_proc->p_au->ai_asid; + ar->k_ar.ar_subj_pid = td->td_proc->p_pid; + ar->k_ar.ar_subj_amask = td->td_proc->p_au->ai_mask; + ar->k_ar.ar_subj_term = td->td_proc->p_au->ai_termid; + + bcopy(td->td_proc->p_comm, ar->k_ar.ar_subj_comm, MAXCOMLEN); + + return (ar); +} + +/* + * MPSAFE + */ +void +audit_commit(struct kaudit_record *ar, int error, int retval) +{ + int sorf; + struct au_mask *aumask; + + if (ar == NULL) + return; + + /* + * Decide whether to commit the audit record by checking the + * error value from the system call and using the appropriate + * audit mask. + * + * XXXAUDIT: Synchronize access to audit_nae_mask? + */ + if (ar->k_ar.ar_subj_auid == AU_DEFAUDITID) + aumask = &audit_nae_mask; + else + aumask = &ar->k_ar.ar_subj_amask; + + if (error) + sorf = AU_PRS_FAILURE; + else + sorf = AU_PRS_SUCCESS; + + switch(ar->k_ar.ar_event) { + + case AUE_OPEN_RWTC: + /* The open syscall always writes a AUE_OPEN_RWTC event; change + * it to the proper type of event based on the flags and the + * error value. + */ + ar->k_ar.ar_event = flags_and_error_to_openevent( + ar->k_ar.ar_arg_fflags, error); + break; + + case AUE_SYSCTL: + ar->k_ar.ar_event = ctlname_to_sysctlevent( + ar->k_ar.ar_arg_ctlname, ar->k_ar.ar_valid_arg); + break; + + case AUE_AUDITON: + /* Convert the auditon() command to an event */ + ar->k_ar.ar_event = auditon_command_event(ar->k_ar.ar_arg_cmd); + break; + } + + if (au_preselect(ar->k_ar.ar_event, aumask, sorf) != 0) + ar->k_ar_commit |= AR_COMMIT_KERNEL; + + if ((ar->k_ar_commit & (AR_COMMIT_USER | AR_COMMIT_KERNEL)) == 0) { + mtx_lock(&audit_mtx); + audit_pre_q_len--; + mtx_unlock(&audit_mtx); + audit_record_free(ar); + return; + } + + ar->k_ar.ar_errno = error; + ar->k_ar.ar_retval = retval; + + /* + * We might want to do some system-wide post-filtering + * here at some point. + */ + + /* + * Timestamp system call end. + */ + nanotime(&ar->k_ar.ar_endtime); + + mtx_lock(&audit_mtx); + + /* + * Note: it could be that some records initiated while audit was + * enabled should still be committed? + */ + if (audit_suspended || !audit_enabled) { + audit_pre_q_len--; + mtx_unlock(&audit_mtx); + audit_record_free(ar); + return; + } + + /* + * Constrain the number of committed audit records based on + * the configurable parameter. + */ + while (audit_q_len >= audit_qctrl.aq_hiwater) { + AUDIT_PRINTF(("audit_commit: sleeping to wait for " + "audit queue to drain below high water mark\n")); + cv_wait(&audit_commit_cv, &audit_mtx); + AUDIT_PRINTF(("audit_commit: woke up waiting for " + "audit queue draining\n")); + } + + TAILQ_INSERT_TAIL(&audit_q, ar, k_q); + audit_q_len++; + audit_pre_q_len--; + cv_signal(&audit_cv); + mtx_unlock(&audit_mtx); +} + +/* + * audit_syscall_enter() is called on entry to each system call. It is + * responsible for deciding whether or not to audit the call (preselection), + * and if so, allocating a per-thread audit record. audit_new() will fill in + * basic thread/credential properties. + */ +void +audit_syscall_enter(unsigned short code, struct thread *td) +{ + int audit_event; + struct au_mask *aumask; + + KASSERT(td->td_ar == NULL, ("audit_syscall_enter: td->td_ar != NULL")); + + /* + * In FreeBSD, each ABI has its own system call table, and hence + * mapping of system call codes to audit events. Convert the code to + * an audit event identifier using the process system call table + * reference. In Darwin, there's only one, so we use the global + * symbol for the system call table. + * + * XXXAUDIT: Should we audit that a bad system call was made, and if + * so, how? + */ + if (code >= td->td_proc->p_sysent->sv_size) + return; + + audit_event = td->td_proc->p_sysent->sv_table[code].sy_auevent; + if (audit_event == AUE_NULL) + return; + + /* + * Check which audit mask to use; either the kernel non-attributable + * event mask or the process audit mask. + */ + if (td->td_proc->p_au->ai_auid == AU_DEFAUDITID) + aumask = &audit_nae_mask; + else + aumask = &td->td_proc->p_au->ai_mask; + + /* + * Allocate an audit record, if preselection allows it, and store + * in the thread for later use. + */ + if (au_preselect(audit_event, aumask, + AU_PRS_FAILURE | AU_PRS_SUCCESS)) { + /* + * If we're out of space and need to suspend unprivileged + * processes, do that here rather than trying to allocate + * another audit record. + * + * XXXRW: We might wish to be able to continue here in the + * future, if the system recovers. That should be possible + * by means of checking the condition in a loop around + * cv_wait(). It might be desirable to reevaluate whether an + * audit record is still required for this event by + * re-calling au_preselect(). + */ + if (audit_in_failure && suser(td) != 0) { + cv_wait(&audit_fail_cv, &audit_mtx); + panic("audit_failing_stop: thread continued"); + } + td->td_ar = audit_new(audit_event, td); + } else + td->td_ar = NULL; +} + +/* + * audit_syscall_exit() is called from the return of every system call, or in + * the event of exit1(), during the execution of exit1(). It is responsible + * for committing the audit record, if any, along with return condition. + */ +void +audit_syscall_exit(int error, struct thread *td) +{ + int retval; + + /* + * Commit the audit record as desired; once we pass the record + * into audit_commit(), the memory is owned by the audit + * subsystem. + * The return value from the system call is stored on the user + * thread. If there was an error, the return value is set to -1, + * imitating the behavior of the cerror routine. + */ + if (error) + retval = -1; + else + retval = td->td_retval[0]; + + audit_commit(td->td_ar, error, retval); + if (td->td_ar != NULL) + AUDIT_PRINTF(("audit record committed by pid %d\n", + td->td_proc->p_pid)); + td->td_ar = NULL; + +} + +/* + * Allocate storage for a new process (init, or otherwise). + */ +void +audit_proc_alloc(struct proc *p) +{ + + KASSERT(p->p_au == NULL, ("audit_proc_alloc: p->p_au != NULL (%d)", + p->p_pid)); + p->p_au = malloc(sizeof(*(p->p_au)), M_AUDITPROC, M_WAITOK); + /* XXXAUDIT: Zero? Slab allocate? */ + //printf("audit_proc_alloc: pid %d p_au %p\n", p->p_pid, p->p_au); +} + +/* + * Initialize the audit information for the a process, presumably the first + * process in the system. + * XXX It is not clear what the initial values should be for audit ID, + * session ID, etc. + */ +void +audit_proc_kproc0(struct proc *p) +{ + + KASSERT(p->p_au != NULL, ("audit_proc_kproc0: p->p_au == NULL (%d)", + p->p_pid)); + //printf("audit_proc_kproc0: pid %d p_au %p\n", p->p_pid, p->p_au); + bzero(p->p_au, sizeof(*(p)->p_au)); +} + +void +audit_proc_init(struct proc *p) +{ + + KASSERT(p->p_au != NULL, ("audit_proc_init: p->p_au == NULL (%d)", + p->p_pid)); + //printf("audit_proc_init: pid %d p_au %p\n", p->p_pid, p->p_au); + bzero(p->p_au, sizeof(*(p)->p_au)); +} + +/* + * Copy the audit info from the parent process to the child process when + * a fork takes place. + */ +void +audit_proc_fork(struct proc *parent, struct proc *child) +{ + + PROC_LOCK_ASSERT(parent, MA_OWNED); + PROC_LOCK_ASSERT(child, MA_OWNED); + KASSERT(parent->p_au != NULL, + ("audit_proc_fork: parent->p_au == NULL (%d)", parent->p_pid)); + KASSERT(child->p_au != NULL, + ("audit_proc_fork: child->p_au == NULL (%d)", child->p_pid)); + //printf("audit_proc_fork: parent pid %d p_au %p\n", parent->p_pid, + // parent->p_au); + //printf("audit_proc_fork: child pid %d p_au %p\n", child->p_pid, + // child->p_au); + bcopy(parent->p_au, child->p_au, sizeof(*child->p_au)); + /* + * XXXAUDIT: Zero pointers to external memory, or assert they are + * zero? + */ +} + +/* + * Free the auditing structure for the process. + */ +void +audit_proc_free(struct proc *p) +{ + + KASSERT(p->p_au != NULL, ("p->p_au == NULL (%d)", p->p_pid)); + //printf("audit_proc_free: pid %d p_au %p\n", p->p_pid, p->p_au); + /* + * XXXAUDIT: Assert that external memory pointers are NULL? + */ + free(p->p_au, M_AUDITPROC); + p->p_au = NULL; +} diff --git a/sys/security/audit/audit.h b/sys/security/audit/audit.h new file mode 100644 index 0000000..3dbabcd --- /dev/null +++ b/sys/security/audit/audit.h @@ -0,0 +1,238 @@ +/* + * Copyright (c) 1999-2005 Apple Computer, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * This header includes function prototypes and type definitions that are + * necessary for the kernel as a whole to interact with the audit subsystem. + */ + +#ifndef _BSM_AUDIT_KERNEL_H +#define _BSM_AUDIT_KERNEL_H + +#ifndef _KERNEL +#error "no user-serviceable parts inside" +#endif + +#include <bsm/audit.h> + +#include <sys/file.h> +#include <sys/sysctl.h> +#include <sys/user.h> + +/* + * Audit subsystem condition flags. The audit_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. + */ +extern int audit_enabled; +extern int audit_suspended; + +/* + * Define the masks for the audited arguments. + * + * XXXRW: These need to remain in audit.h for now because our vnode and name + * lookup audit calls rely on passing in flags to indicate which name or + * vnode is being logged. These should move to audit_private.h when that is + * fixed. + */ +#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_UNUSED1 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_NONE 0x0000000000000000ULL +#define ARG_ALL 0xFFFFFFFFFFFFFFFFULL + +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_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, + struct sockaddr *so); +void audit_arg_auid(uid_t auid); +void audit_arg_auditinfo(struct auditinfo *au_info); +void audit_arg_upath(struct thread *td, char *upath, + u_int64_t flags); +void audit_arg_vnode(struct vnode *vp, u_int64_t flags); +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_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_sysclose(struct thread *td, int fd); + +void audit_proc_alloc(struct proc *p); +void audit_proc_kproc0(struct proc *p); +void audit_proc_init(struct proc *p); +void audit_proc_fork(struct proc *parent, + struct proc *child); +void audit_proc_free(struct proc *p); + +/* + * Define a macro to wrap the audit_arg_* calls by checking the global + * audit_enabled flag before performing the actual call. + */ +#define AUDIT_ARG(op, args...) do { \ + if (audit_enabled) \ + audit_arg_ ## op (args); \ + } while (0) + +#define AUDIT_SYSCALL_ENTER(code, td) do { \ + if (audit_enabled) { \ + audit_syscall_enter(code, td); \ + } \ + } while (0) + +/* + * Wrap the audit_syscall_exit() function so that it is called only when + * auditing is enabled, or we have a audit record on the thread. It is + * possible that an audit record was begun before auditing was turned off. + */ +#define AUDIT_SYSCALL_EXIT(error, td) do { \ + if (audit_enabled | (td->td_ar != NULL)) \ + audit_syscall_exit(error, td); \ + } while (0) + +/* + * A Macro to wrap the audit_sysclose() function. + */ +#define AUDIT_SYSCLOSE(td, fd) do { \ + if (audit_enabled) \ + audit_sysclose(td, fd); \ + } while (0) + +#else /* !AUDIT */ + +void audit_proc_init(struct proc *p); +void audit_proc_fork(struct proc *parent, + struct proc *child); +void audit_proc_free(struct proc *p); + +#define AUDIT_ARG(op, args...) do { \ + } while (0) + +#define AUDIT_SYSCALL_ENTER(code, td) do { \ + } while (0) + +#define AUDIT_SYSCALL_EXIT(error, td) do { \ + } while (0) + +#define AUDIT_SYSCLOSE(p, fd) do { \ + } while (0) + +#endif /* AUDIT */ + +#endif /* !_BSM_AUDIT_KERNEL_H */ diff --git a/sys/security/audit/audit_arg.c b/sys/security/audit/audit_arg.c new file mode 100644 index 0000000..8aaab69 --- /dev/null +++ b/sys/security/audit/audit_arg.c @@ -0,0 +1,803 @@ +/* + * Copyright (c) 1999-2005 Apple Computer, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/filedesc.h> +#include <sys/ipc.h> +#include <sys/mount.h> +#include <sys/proc.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/protosw.h> +#include <sys/domain.h> +#include <sys/systm.h> +#include <sys/un.h> +#include <sys/vnode.h> + +#include <netinet/in.h> +#include <netinet/in_pcb.h> + +#include <security/audit/audit.h> +#include <security/audit/audit_private.h> + +/* + * Calls to manipulate elements of the audit record structure from system + * call code. Macro wrappers will prevent this functions from being + * entered if auditing is disabled, avoiding the function call cost. We + * check the thread audit record pointer anyway, as the audit condition + * could change, and pre-selection may not have allocated an audit + * record for this event. + * + * XXXAUDIT: Should we assert, in each case, that this field of the record + * hasn't already been filled in? + */ +void +audit_arg_addr(void * addr) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_addr = addr; + ARG_SET_VALID(ar, ARG_ADDR); +} + +void +audit_arg_exit(int status, int retval) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_exitstatus = status; + ar->k_ar.ar_arg_exitretval = retval; + ARG_SET_VALID(ar, ARG_EXIT); +} + +void +audit_arg_len(int len) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_len = len; + ARG_SET_VALID(ar, ARG_LEN); +} + +void +audit_arg_fd(int fd) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_fd = fd; + ARG_SET_VALID(ar, ARG_FD); +} + +void +audit_arg_fflags(int fflags) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_fflags = fflags; + ARG_SET_VALID(ar, ARG_FFLAGS); +} + +void +audit_arg_gid(gid_t gid) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_gid = gid; + ARG_SET_VALID(ar, ARG_GID); +} + +void +audit_arg_uid(uid_t uid) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_uid = uid; + ARG_SET_VALID(ar, ARG_UID); +} + +void +audit_arg_egid(gid_t egid) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_egid = egid; + ARG_SET_VALID(ar, ARG_EGID); +} + +void +audit_arg_euid(uid_t euid) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_euid = euid; + ARG_SET_VALID(ar, ARG_EUID); +} + +void +audit_arg_rgid(gid_t rgid) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_rgid = rgid; + ARG_SET_VALID(ar, ARG_RGID); +} + +void +audit_arg_ruid(uid_t ruid) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_ruid = ruid; + ARG_SET_VALID(ar, ARG_RUID); +} + +void +audit_arg_sgid(gid_t sgid) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_sgid = sgid; + ARG_SET_VALID(ar, ARG_SGID); +} + +void +audit_arg_suid(uid_t suid) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_suid = suid; + ARG_SET_VALID(ar, ARG_SUID); +} + +void +audit_arg_groupset(gid_t *gidset, u_int gidset_size) +{ + int i; + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + for (i = 0; i < gidset_size; i++) + ar->k_ar.ar_arg_groups.gidset[i] = gidset[i]; + ar->k_ar.ar_arg_groups.gidset_size = gidset_size; + ARG_SET_VALID(ar, ARG_GROUPSET); +} + +void +audit_arg_login(char *login) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + strlcpy(ar->k_ar.ar_arg_login, login, MAXLOGNAME); + ARG_SET_VALID(ar, ARG_LOGIN); +} + +void +audit_arg_ctlname(int *name, int namelen) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + bcopy(name, &ar->k_ar.ar_arg_ctlname, namelen * sizeof(int)); + ar->k_ar.ar_arg_len = namelen; + ARG_SET_VALID(ar, ARG_CTLNAME | ARG_LEN); +} + +void +audit_arg_mask(int mask) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_mask = mask; + ARG_SET_VALID(ar, ARG_MASK); +} + +void +audit_arg_mode(mode_t mode) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_mode = mode; + ARG_SET_VALID(ar, ARG_MODE); +} + +void +audit_arg_dev(int dev) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_dev = dev; + ARG_SET_VALID(ar, ARG_DEV); +} + +void +audit_arg_value(long value) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_value = value; + ARG_SET_VALID(ar, ARG_VALUE); +} + +void +audit_arg_owner(uid_t uid, gid_t gid) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_uid = uid; + ar->k_ar.ar_arg_gid = gid; + ARG_SET_VALID(ar, ARG_UID | ARG_GID); +} + +void +audit_arg_pid(pid_t pid) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_pid = pid; + ARG_SET_VALID(ar, ARG_PID); +} + +void +audit_arg_process(struct proc *p) +{ + struct kaudit_record *ar; + + ar = currecord(); + if ((ar == NULL) || (p == NULL)) + return; + + /* + * XXXAUDIT: PROC_LOCK_ASSERT(p); + */ + ar->k_ar.ar_arg_auid = p->p_au->ai_auid; + ar->k_ar.ar_arg_euid = p->p_ucred->cr_uid; + ar->k_ar.ar_arg_egid = p->p_ucred->cr_groups[0]; + ar->k_ar.ar_arg_ruid = p->p_ucred->cr_ruid; + ar->k_ar.ar_arg_rgid = p->p_ucred->cr_rgid; + ar->k_ar.ar_arg_asid = p->p_au->ai_asid; + ar->k_ar.ar_arg_termid = p->p_au->ai_termid; + ARG_SET_VALID(ar, ARG_AUID | ARG_EUID | ARG_EGID | ARG_RUID | + ARG_RGID | ARG_ASID | ARG_TERMID | ARG_PROCESS); +} + +void +audit_arg_signum(u_int signum) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_signum = signum; + ARG_SET_VALID(ar, ARG_SIGNUM); +} + +void +audit_arg_socket(int sodomain, int sotype, int soprotocol) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_sockinfo.so_domain = sodomain; + ar->k_ar.ar_arg_sockinfo.so_type = sotype; + ar->k_ar.ar_arg_sockinfo.so_protocol = soprotocol; + ARG_SET_VALID(ar, ARG_SOCKINFO); +} + +/* + * XXXAUDIT: Argument here should be 'sa' not 'so'. Caller is responsible + * for synchronizing access to the source of the address. + */ +void +audit_arg_sockaddr(struct thread *td, struct sockaddr *so) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL || td == NULL || so == NULL) + return; + + bcopy(so, &ar->k_ar.ar_arg_sockaddr, sizeof(ar->k_ar.ar_arg_sockaddr)); + switch (so->sa_family) { + case AF_INET: + ARG_SET_VALID(ar, ARG_SADDRINET); + break; + + case AF_INET6: + ARG_SET_VALID(ar, ARG_SADDRINET6); + break; + + case AF_UNIX: + audit_arg_upath(td, ((struct sockaddr_un *)so)->sun_path, + ARG_UPATH1); + ARG_SET_VALID(ar, ARG_SADDRUNIX); + break; + /* XXXAUDIT: default:? */ + } +} + +void +audit_arg_auid(uid_t auid) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_auid = auid; + ARG_SET_VALID(ar, ARG_AUID); +} + +void +audit_arg_auditinfo(struct auditinfo *au_info) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_auid = au_info->ai_auid; + ar->k_ar.ar_arg_asid = au_info->ai_asid; + ar->k_ar.ar_arg_amask.am_success = au_info->ai_mask.am_success; + ar->k_ar.ar_arg_amask.am_failure = au_info->ai_mask.am_failure; + ar->k_ar.ar_arg_termid.port = au_info->ai_termid.port; + ar->k_ar.ar_arg_termid.machine = au_info->ai_termid.machine; + ARG_SET_VALID(ar, ARG_AUID | ARG_ASID | ARG_AMASK | ARG_TERMID); +} + +void +audit_arg_text(char *text) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + /* + * XXXAUDIT: Why do we accept a possibly NULL string here? + */ + /* Invalidate the text string */ + ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_TEXT); + if (text == NULL) + return; + + if (ar->k_ar.ar_arg_text == NULL) + ar->k_ar.ar_arg_text = malloc(MAXPATHLEN, M_AUDITTEXT, + M_WAITOK); + + strncpy(ar->k_ar.ar_arg_text, text, MAXPATHLEN); + ARG_SET_VALID(ar, ARG_TEXT); +} + +void +audit_arg_cmd(int cmd) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_cmd = cmd; + ARG_SET_VALID(ar, ARG_CMD); +} + +void +audit_arg_svipc_cmd(int cmd) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_svipc_cmd = cmd; + ARG_SET_VALID(ar, ARG_SVIPC_CMD); +} + +void +audit_arg_svipc_perm(struct ipc_perm *perm) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + bcopy(perm, &ar->k_ar.ar_arg_svipc_perm, + sizeof(ar->k_ar.ar_arg_svipc_perm)); + ARG_SET_VALID(ar, ARG_SVIPC_PERM); +} + +void +audit_arg_svipc_id(int id) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_svipc_id = id; + ARG_SET_VALID(ar, ARG_SVIPC_ID); +} + +void +audit_arg_svipc_addr(void * addr) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_svipc_addr = addr; + ARG_SET_VALID(ar, ARG_SVIPC_ADDR); +} + +void +audit_arg_posix_ipc_perm(uid_t uid, gid_t gid, mode_t mode) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + ar->k_ar.ar_arg_pipc_perm.pipc_uid = uid; + ar->k_ar.ar_arg_pipc_perm.pipc_gid = gid; + ar->k_ar.ar_arg_pipc_perm.pipc_mode = mode; + ARG_SET_VALID(ar, ARG_POSIX_IPC_PERM); +} + +void +audit_arg_auditon(union auditon_udata *udata) +{ + struct kaudit_record *ar; + + ar = currecord(); + if (ar == NULL) + return; + + bcopy((void *)udata, &ar->k_ar.ar_arg_auditon, + sizeof(ar->k_ar.ar_arg_auditon)); + ARG_SET_VALID(ar, ARG_AUDITON); +} + +/* + * Audit information about a file, either the file's vnode info, or its + * socket address info. + */ +void +audit_arg_file(struct proc *p, struct file *fp) +{ + struct kaudit_record *ar; + struct socket *so; + struct inpcb *pcb; + struct vnode *vp; + int vfslocked; + + /* + * XXXAUDIT: Why is the (ar == NULL) test only in the socket case? + */ + switch (fp->f_type) { + case DTYPE_VNODE: + case DTYPE_FIFO: + /* + * XXXAUDIT: Only possibly to record as first vnode? + */ + vp = fp->f_vnode; + vfslocked = VFS_LOCK_GIANT(vp->v_mount); + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, curthread); + audit_arg_vnode(vp, ARG_VNODE1); + VOP_UNLOCK(vp, 0, curthread); + VFS_UNLOCK_GIANT(vfslocked); + break; + + case DTYPE_SOCKET: + ar = currecord(); + if (ar == NULL) + return; + + /* + * XXXAUDIT: Socket locking? Inpcb locking? + */ + so = (struct socket *)fp->f_data; + if (INP_CHECK_SOCKAF(so, PF_INET)) { + if (so->so_pcb == NULL) + return; + ar->k_ar.ar_arg_sockinfo.so_type = + so->so_type; + ar->k_ar.ar_arg_sockinfo.so_domain = + INP_SOCKAF(so); + ar->k_ar.ar_arg_sockinfo.so_protocol = + so->so_proto->pr_protocol; + pcb = (struct inpcb *)so->so_pcb; + ar->k_ar.ar_arg_sockinfo.so_raddr = + pcb->inp_faddr.s_addr; + ar->k_ar.ar_arg_sockinfo.so_laddr = + pcb->inp_laddr.s_addr; + ar->k_ar.ar_arg_sockinfo.so_rport = + pcb->inp_fport; + ar->k_ar.ar_arg_sockinfo.so_lport = + pcb->inp_lport; + ARG_SET_VALID(ar, ARG_SOCKINFO); + } + break; + + default: + /* XXXAUDIT: else? */ + break; + } + +} + +/* + * Store a path as given by the user process for auditing into the audit + * record stored on the user thread. This function will allocate the memory to + * store the path info if not already available. This memory will be + * freed when the audit record is freed. + * + * XXXAUDIT: Possibly assert that the memory isn't already allocated? + */ +void +audit_arg_upath(struct thread *td, char *upath, u_int64_t flag) +{ + struct kaudit_record *ar; + char **pathp; + + if (td == NULL || upath == NULL) + return; /* nothing to do! */ + + /* + * XXXAUDIT: Witness warning for possible sleep here? + */ + KASSERT((flag == ARG_UPATH1) || (flag == ARG_UPATH2), + ("audit_arg_upath: flag %llu", flag)); + KASSERT((flag != ARG_UPATH1) || (flag != ARG_UPATH2), + ("audit_arg_upath: flag %llu", flag)); + + ar = currecord(); + if (ar == NULL) + return; + + if (flag == ARG_UPATH1) + pathp = &ar->k_ar.ar_arg_upath1; + else + pathp = &ar->k_ar.ar_arg_upath2; + + if (*pathp == NULL) + *pathp = malloc(MAXPATHLEN, M_AUDITPATH, M_WAITOK); + + canon_path(td, upath, *pathp); + + ARG_SET_VALID(ar, flag); +} + +/* + * Function to save the path and vnode attr information into the audit + * record. + * + * It is assumed that the caller will hold any vnode locks necessary to + * perform a VOP_GETATTR() on the passed vnode. + * + * XXX: The attr code is very similar to vfs_vnops.c:vn_stat(), but + * always provides access to the generation number as we need that + * to construct the BSM file ID. + * XXX: We should accept the process argument from the caller, since + * it's very likely they already have a reference. + * XXX: Error handling in this function is poor. + * + * XXXAUDIT: Possibly KASSERT the path pointer is NULL? + */ +void +audit_arg_vnode(struct vnode *vp, u_int64_t flags) +{ + struct kaudit_record *ar; + struct vattr vattr; + int error; + struct vnode_au_info *vnp; + struct thread *td; + + /* + * XXXAUDIT: Why is vp possibly NULL here? + */ + if (vp == NULL) + return; + + /* + * Assume that if the caller is calling audit_arg_vnode() on a + * non-MPSAFE vnode, then it will have acquired Giant. + */ + VFS_ASSERT_GIANT(vp->v_mount); + ASSERT_VOP_LOCKED(vp, "audit_arg_vnode"); + + ar = currecord(); + if (ar == NULL) /* This will be the case for unaudited system calls */ + return; + + /* + * XXXAUDIT: KASSERT argument validity instead? + * + * XXXAUDIT: The below clears, and then resets the flags for valid + * arguments. Ideally, either the new vnode is used, or the old one + * would be. + */ + if ((flags & (ARG_VNODE1 | ARG_VNODE2)) == 0) + return; + + td = curthread; + + if (flags & ARG_VNODE1) { + ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_VNODE1); + vnp = &ar->k_ar.ar_arg_vnode1; + } else { + ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_VNODE2); + vnp = &ar->k_ar.ar_arg_vnode2; + } + + error = VOP_GETATTR(vp, &vattr, td->td_ucred, td); + if (error) { + /* XXX: How to handle this case? */ + return; + } + + vnp->vn_mode = vattr.va_mode; + vnp->vn_uid = vattr.va_uid; + vnp->vn_gid = vattr.va_gid; + vnp->vn_dev = vattr.va_rdev; + vnp->vn_fsid = vattr.va_fsid; + vnp->vn_fileid = vattr.va_fileid; + vnp->vn_gen = vattr.va_gen; + if (flags & ARG_VNODE1) + ARG_SET_VALID(ar, ARG_VNODE1); + else + ARG_SET_VALID(ar, ARG_VNODE2); +} + +/* + * The close() system call uses it's own audit call to capture the + * path/vnode information because those pieces are not easily obtained + * within the system call itself. + */ +void +audit_sysclose(struct thread *td, int fd) +{ + struct vnode *vp; + struct file *fp; + int vfslocked; + + audit_arg_fd(fd); + + if (getvnode(td->td_proc->p_fd, fd, &fp) != 0) + return; + + vp = fp->f_vnode; + vfslocked = VFS_LOCK_GIANT(vp->v_mount); + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY, td); + audit_arg_vnode(vp, ARG_VNODE1); + VOP_UNLOCK(vp, 0, td); + VFS_UNLOCK_GIANT(vfslocked); + fdrop(fp, td); +} diff --git a/sys/security/audit/audit_bsm.c b/sys/security/audit/audit_bsm.c new file mode 100644 index 0000000..1a2c5e5 --- /dev/null +++ b/sys/security/audit/audit_bsm.c @@ -0,0 +1,1261 @@ +/* + * Copyright (c) 1999-2005 Apple Computer, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/vnode.h> +#include <sys/ipc.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/mutex.h> +#include <sys/socket.h> +#include <sys/fcntl.h> +#include <sys/user.h> +#include <sys/systm.h> + +#include <bsm/audit.h> +#include <bsm/audit_internal.h> +#include <bsm/audit_record.h> +#include <bsm/audit_kevents.h> + +#include <security/audit/audit.h> +#include <security/audit/audit_private.h> + +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> + +MALLOC_DEFINE(M_AUDITBSM, "audit_bsm", "Audit BSM data"); + +/* + * Forward declares. + */ +static void audit_sys_auditon(struct audit_record *ar, + struct au_record *rec); + +/* + * Initialize the BSM auditing subsystem. + */ +void +kau_init(void) +{ + + printf("BSM auditing present\n"); + au_evclassmap_init(); +} + +/* + * This call reserves memory for the audit record. + * Memory must be guaranteed before any auditable event can be + * generated. + * The au_record structure maintains a reference to the + * memory allocated above and also the list of tokens associated + * with this record + */ +static struct au_record * +kau_open(void) +{ + struct au_record *rec; + + rec = malloc(sizeof(*rec), M_AUDITBSM, M_WAITOK); + rec->data = malloc(MAX_AUDIT_RECORD_SIZE * sizeof(u_char), + M_AUDITBSM, M_WAITOK | M_ZERO); + TAILQ_INIT(&rec->token_q); + rec->len = 0; + rec->used = 1; + + return (rec); +} + +/* + * Store the token with the record descriptor. + */ +static void +kau_write(struct au_record *rec, struct au_token *tok) +{ + + KASSERT(tok != NULL, ("kau_write: tok == NULL")); + + TAILQ_INSERT_TAIL(&rec->token_q, tok, tokens); + rec->len += tok->len; +} + +/* + * Close out the audit record by adding the header token, identifying any + * missing tokens. Write out the tokens to the record memory. + */ +static void +kau_close(struct au_record *rec, struct timespec *ctime, short event) +{ + u_char *dptr; + size_t tot_rec_size; + token_t *cur, *hdr, *trail; + struct timeval tm; + + tot_rec_size = rec->len + BSM_HEADER_SIZE + BSM_TRAILER_SIZE; + if (tot_rec_size <= MAX_AUDIT_RECORD_SIZE) { + /* Create the header token */ + tm.tv_usec = ctime->tv_nsec / 1000; + tm.tv_sec = ctime->tv_sec; + hdr = au_to_header32(tot_rec_size, event, 0, tm); + TAILQ_INSERT_HEAD(&rec->token_q, hdr, tokens); + + trail = au_to_trailer(tot_rec_size); + TAILQ_INSERT_TAIL(&rec->token_q, trail, tokens); + + /* Serialize token data to the record. */ + + rec->len = tot_rec_size; + dptr = rec->data; + TAILQ_FOREACH(cur, &rec->token_q, tokens) { + memcpy(dptr, cur->t_data, cur->len); + dptr += cur->len; + } + } +} + +/* + * Free a BSM audit record by releasing all the tokens and clearing the + * audit record information. + */ +void +kau_free(struct au_record *rec) +{ + struct au_token *tok; + + /* Free the token list */ + while ((tok = TAILQ_FIRST(&rec->token_q))) { + TAILQ_REMOVE(&rec->token_q, tok, tokens); + free(tok->t_data, M_AUDITBSM); + free(tok, M_AUDITBSM); + } + + rec->used = 0; + rec->len = 0; + free(rec->data, M_AUDITBSM); + free(rec, M_AUDITBSM); +} + +/* + * XXX May want turn some (or all) of these macros into functions in order + * to reduce the generated code sized. + * + * XXXAUDIT: These macros assume that 'kar', 'ar', 'rec', and 'tok' in the + * caller are OK with this. + */ +#define UPATH1_TOKENS do { \ + if (ARG_IS_VALID(kar, ARG_UPATH1)) { \ + tok = au_to_path(ar->ar_arg_upath1); \ + kau_write(rec, tok); \ + } \ +} while (0) + +#define UPATH2_TOKENS do { \ + if (ARG_IS_VALID(kar, ARG_UPATH2)) { \ + tok = au_to_path(ar->ar_arg_upath2); \ + kau_write(rec, tok); \ + } \ +} while (0) + +#define VNODE1_TOKENS do { \ + if (ARG_IS_VALID(kar, ARG_VNODE1)) { \ + tok = au_to_attr32(&ar->ar_arg_vnode1); \ + kau_write(rec, tok); \ + } \ +} while (0) + +#define UPATH1_VNODE1_TOKENS do { \ + if (ARG_IS_VALID(kar, ARG_UPATH1)) { \ + UPATH1_TOKENS; \ + } \ + if (ARG_IS_VALID(kar, ARG_VNODE1)) { \ + tok = au_to_attr32(&ar->ar_arg_vnode1); \ + kau_write(rec, tok); \ + } \ +} while (0) + +#define VNODE2_TOKENS do { \ + if (ARG_IS_VALID(kar, ARG_VNODE2)) { \ + tok = au_to_attr32(&ar->ar_arg_vnode2); \ + kau_write(rec, tok); \ + } \ +} while (0) + +#define FD_VNODE1_TOKENS do { \ + if (ARG_IS_VALID(kar, ARG_VNODE1)) { \ + if (ARG_IS_VALID(kar, ARG_FD)) { \ + tok = au_to_arg32(1, "fd", ar->ar_arg_fd); \ + kau_write(rec, tok); \ + } \ + tok = au_to_attr32(&ar->ar_arg_vnode1); \ + kau_write(rec, tok); \ + } else { \ + if (ARG_IS_VALID(kar, ARG_FD)) { \ + tok = au_to_arg32(1, "non-file: fd", ar->ar_arg_fd);\ + kau_write(rec, tok); \ + } \ + } \ +} while (0) + +#define PROCESS_PID_TOKENS(argn) do { \ + if (ARG_IS_VALID(kar, ARG_PID)) { \ + if ((ar->ar_arg_pid > 0) /* Kill a single process */ \ + && (ARG_IS_VALID(kar, ARG_PROCESS))) { \ + tok = au_to_process(ar->ar_arg_auid, \ + ar->ar_arg_euid, ar->ar_arg_egid, \ + ar->ar_arg_ruid, ar->ar_arg_rgid, \ + ar->ar_arg_pid, ar->ar_arg_asid, \ + &ar->ar_arg_termid); \ + kau_write(rec, tok); \ + } else { \ + tok = au_to_arg32(argn, "process", \ + ar->ar_arg_pid); \ + kau_write(rec, tok); \ + } \ + } \ +} while (0) \ + +/* + * Implement auditing for the auditon() system call. The audit tokens that + * are generated depend on the command that was sent into the auditon() + * system call. + */ +static void +audit_sys_auditon(struct audit_record *ar, struct au_record *rec) +{ + struct au_token *tok; + + switch (ar->ar_arg_cmd) { + case A_SETPOLICY: + if (sizeof(ar->ar_arg_auditon.au_flags) > 4) + tok = au_to_arg64(1, "policy", + ar->ar_arg_auditon.au_flags); + else + tok = au_to_arg32(1, "policy", + ar->ar_arg_auditon.au_flags); + kau_write(rec, tok); + break; + + case A_SETKMASK: + tok = au_to_arg32(2, "setkmask:as_success", + ar->ar_arg_auditon.au_mask.am_success); + kau_write(rec, tok); + tok = au_to_arg32(2, "setkmask:as_failure", + ar->ar_arg_auditon.au_mask.am_failure); + kau_write(rec, tok); + break; + + case A_SETQCTRL: + tok = au_to_arg32(3, "setqctrl:aq_hiwater", + ar->ar_arg_auditon.au_qctrl.aq_hiwater); + kau_write(rec, tok); + tok = au_to_arg32(3, "setqctrl:aq_lowater", + ar->ar_arg_auditon.au_qctrl.aq_lowater); + kau_write(rec, tok); + tok = au_to_arg32(3, "setqctrl:aq_bufsz", + ar->ar_arg_auditon.au_qctrl.aq_bufsz); + kau_write(rec, tok); + tok = au_to_arg32(3, "setqctrl:aq_delay", + ar->ar_arg_auditon.au_qctrl.aq_delay); + kau_write(rec, tok); + tok = au_to_arg32(3, "setqctrl:aq_minfree", + ar->ar_arg_auditon.au_qctrl.aq_minfree); + kau_write(rec, tok); + break; + + case A_SETUMASK: + tok = au_to_arg32(3, "setumask:as_success", + ar->ar_arg_auditon.au_auinfo.ai_mask.am_success); + kau_write(rec, tok); + tok = au_to_arg32(3, "setumask:as_failure", + ar->ar_arg_auditon.au_auinfo.ai_mask.am_failure); + kau_write(rec, tok); + break; + + case A_SETSMASK: + tok = au_to_arg32(3, "setsmask:as_success", + ar->ar_arg_auditon.au_auinfo.ai_mask.am_success); + kau_write(rec, tok); + tok = au_to_arg32(3, "setsmask:as_failure", + ar->ar_arg_auditon.au_auinfo.ai_mask.am_failure); + kau_write(rec, tok); + break; + + case A_SETCOND: + if (sizeof(ar->ar_arg_auditon.au_cond) > 4) + tok = au_to_arg64(3, "setcond", + ar->ar_arg_auditon.au_cond); + else + tok = au_to_arg32(3, "setcond", + ar->ar_arg_auditon.au_cond); + kau_write(rec, tok); + break; + + case A_SETCLASS: + tok = au_to_arg32(2, "setclass:ec_event", + ar->ar_arg_auditon.au_evclass.ec_number); + kau_write(rec, tok); + tok = au_to_arg32(3, "setclass:ec_class", + ar->ar_arg_auditon.au_evclass.ec_class); + kau_write(rec, tok); + break; + + case A_SETPMASK: + tok = au_to_arg32(2, "setpmask:as_success", + ar->ar_arg_auditon.au_aupinfo.ap_mask.am_success); + kau_write(rec, tok); + tok = au_to_arg32(2, "setpmask:as_failure", + ar->ar_arg_auditon.au_aupinfo.ap_mask.am_failure); + kau_write(rec, tok); + break; + + case A_SETFSIZE: + tok = au_to_arg32(2, "setfsize:filesize", + ar->ar_arg_auditon.au_fstat.af_filesz); + kau_write(rec, tok); + break; + + default: + break; + } +} + +/* + * Convert an internal kernel audit record to a BSM record and return + * a success/failure indicator. The BSM record is passed as an out + * parameter to this function. + * Return conditions: + * BSM_SUCCESS: The BSM record is valid + * BSM_FAILURE: Failure; the BSM record is NULL. + * BSM_NOAUDIT: The event is not auditable for BSM; the BSM record is NULL. + */ +int +kaudit_to_bsm(struct kaudit_record *kar, struct au_record **pau) +{ + struct au_token *tok, *subj_tok; + struct au_record *rec; + au_tid_t tid; + struct audit_record *ar; + int ctr; + + KASSERT(kar != NULL, ("kaudit_to_bsm: kar == NULL")); + + *pau = NULL; + ar = &kar->k_ar; + rec = kau_open(); + + /* Create the subject token */ + tid.port = ar->ar_subj_term.port; + tid.machine = ar->ar_subj_term.machine; + subj_tok = au_to_subject32(ar->ar_subj_auid, /* audit ID */ + ar->ar_subj_cred.cr_uid, /* eff uid */ + ar->ar_subj_egid, /* eff group id */ + ar->ar_subj_ruid, /* real uid */ + ar->ar_subj_rgid, /* real group id */ + ar->ar_subj_pid, /* process id */ + ar->ar_subj_asid, /* session ID */ + &tid); + + /* The logic inside each case fills in the tokens required for the + * event, except for the header, trailer, and return tokens. The + * header and trailer tokens are added by the kau_close() function. + * The return token is added outside of the switch statement. + */ + switch(ar->ar_event) { + + /* + * Socket-related events. + */ + case AUE_ACCEPT: + case AUE_BIND: + case AUE_CONNECT: + case AUE_RECVFROM: + case AUE_RECVMSG: + case AUE_SENDMSG: + case AUE_SENDTO: + if (ARG_IS_VALID(kar, ARG_FD)) { + tok = au_to_arg32(1, "fd", ar->ar_arg_fd); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_SADDRINET)) { + tok = au_to_sock_inet( + (struct sockaddr_in *)&ar->ar_arg_sockaddr); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_SADDRUNIX)) { + tok = au_to_sock_unix( + (struct sockaddr_un *)&ar->ar_arg_sockaddr); + kau_write(rec, tok); + UPATH1_TOKENS; + } + /* XXX Need to handle ARG_SADDRINET6 */ + break; + + case AUE_SOCKET: + case AUE_SOCKETPAIR: + if (ARG_IS_VALID(kar, ARG_SOCKINFO)) { + tok = au_to_arg32(1,"domain", + ar->ar_arg_sockinfo.so_domain); + kau_write(rec, tok); + tok = au_to_arg32(2,"type", + ar->ar_arg_sockinfo.so_type); + kau_write(rec, tok); + tok = au_to_arg32(3,"protocol", + ar->ar_arg_sockinfo.so_protocol); + kau_write(rec, tok); + } + break; + + case AUE_SETSOCKOPT: + case AUE_SHUTDOWN: + if (ARG_IS_VALID(kar, ARG_FD)) { + tok = au_to_arg32(1, "fd", ar->ar_arg_fd); + kau_write(rec, tok); + } + break; + + case AUE_ACCT: + if (ARG_IS_VALID(kar, ARG_UPATH1)) { + UPATH1_VNODE1_TOKENS; + } else { + tok = au_to_arg32(1, "accounting off", 0); + kau_write(rec, tok); + } + break; + + case AUE_SETAUID: + if (ARG_IS_VALID(kar, ARG_AUID)) { + tok = au_to_arg32(2, "setauid", ar->ar_arg_auid); + kau_write(rec, tok); + } + break; + + case AUE_SETAUDIT: + if (ARG_IS_VALID(kar, ARG_AUID)) { + tok = au_to_arg32(1, "setaudit:auid", ar->ar_arg_auid); + kau_write(rec, tok); + tok = au_to_arg32(1, "setaudit:port", + ar->ar_arg_termid.port); + kau_write(rec, tok); + tok = au_to_arg32(1, "setaudit:machine", + ar->ar_arg_termid.machine); + kau_write(rec, tok); + tok = au_to_arg32(1, "setaudit:as_success", + ar->ar_arg_amask.am_success); + kau_write(rec, tok); + tok = au_to_arg32(1, "setaudit:as_failure", + ar->ar_arg_amask.am_failure); + kau_write(rec, tok); + tok = au_to_arg32(1, "setaudit:asid", ar->ar_arg_asid); + kau_write(rec, tok); + } + break; + + case AUE_SETAUDIT_ADDR: + break; /* XXX need to add arguments */ + + case AUE_AUDITON: + /* For AUDITON commands without own event, audit the cmd */ + if (ARG_IS_VALID(kar, ARG_CMD)) { + tok = au_to_arg32(1, "cmd", ar->ar_arg_cmd); + kau_write(rec, tok); + } + /* fall thru */ + + case AUE_AUDITON_GETCAR: + case AUE_AUDITON_GETCLASS: + case AUE_AUDITON_GETCOND: + case AUE_AUDITON_GETCWD: + case AUE_AUDITON_GETKMASK: + case AUE_AUDITON_GETSTAT: + case AUE_AUDITON_GPOLICY: + case AUE_AUDITON_GQCTRL: + case AUE_AUDITON_SETCLASS: + case AUE_AUDITON_SETCOND: + case AUE_AUDITON_SETKMASK: + case AUE_AUDITON_SETSMASK: + case AUE_AUDITON_SETSTAT: + case AUE_AUDITON_SETUMASK: + case AUE_AUDITON_SPOLICY: + case AUE_AUDITON_SQCTRL: + if (ARG_IS_VALID(kar, ARG_AUDITON)) { + audit_sys_auditon(ar, rec); + } + break; + + case AUE_AUDITCTL: + UPATH1_VNODE1_TOKENS; + break; + + case AUE_EXIT: + if (ARG_IS_VALID(kar, ARG_EXIT)) { + tok = au_to_exit(ar->ar_arg_exitretval, + ar->ar_arg_exitstatus); + kau_write(rec, tok); + } + break; + + case AUE_ADJTIME: + case AUE_AUDIT: + case AUE_GETAUDIT: + case AUE_GETAUDIT_ADDR: + case AUE_GETAUID: + case AUE_GETFSSTAT: + case AUE_PIPE: + case AUE_SETPGRP: + case AUE_SETRLIMIT: + case AUE_SETSID: + case AUE_SETTIMEOFDAY: + case AUE_NEWSYSTEMSHREG: + /* Header, subject, and return tokens added at end */ + break; + + case AUE_ACCESS: + case AUE_CHDIR: + case AUE_CHROOT: + case AUE_EXECVE: + case AUE_GETATTRLIST: + case AUE_NFS_GETFH: + case AUE_LSTAT: + case AUE_MKFIFO: + case AUE_PATHCONF: + case AUE_READLINK: + case AUE_REVOKE: + case AUE_RMDIR: + case AUE_SEARCHFS: + case AUE_SETATTRLIST: + case AUE_STAT: + case AUE_STATFS: + case AUE_TRUNCATE: + case AUE_UNDELETE: + case AUE_UNLINK: + case AUE_UTIMES: + UPATH1_VNODE1_TOKENS; + break; + + case AUE_CHFLAGS: + case AUE_LCHFLAGS: + if (ARG_IS_VALID(kar, ARG_FFLAGS)) { + tok = au_to_arg32(2, "flags", ar->ar_arg_fflags); + kau_write(rec, tok); + } + UPATH1_VNODE1_TOKENS; + break; + + case AUE_CHMOD: + case AUE_LCHMOD: + if (ARG_IS_VALID(kar, ARG_MODE)) { + tok = au_to_arg32(2, "new file mode", ar->ar_arg_mode); + kau_write(rec, tok); + } + UPATH1_VNODE1_TOKENS; + break; + + case AUE_CHOWN: + case AUE_LCHOWN: + if (ARG_IS_VALID(kar, ARG_UID)) { + tok = au_to_arg32(2, "new file uid", ar->ar_arg_uid); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_GID)) { + tok = au_to_arg32(3, "new file gid", ar->ar_arg_gid); + kau_write(rec, tok); + } + UPATH1_VNODE1_TOKENS; + break; + + case AUE_EXCHANGEDATA: + UPATH1_VNODE1_TOKENS; + UPATH2_TOKENS; + break; + + case AUE_CLOSE: + if (ARG_IS_VALID(kar, ARG_FD)) { + tok = au_to_arg32(2, "fd", ar->ar_arg_fd); + kau_write(rec, tok); + } + UPATH1_VNODE1_TOKENS; + break; + + case AUE_FCHMOD: + if (ARG_IS_VALID(kar, ARG_MODE)) { + tok = au_to_arg32(2, "new file mode", ar->ar_arg_mode); + kau_write(rec, tok); + } + FD_VNODE1_TOKENS; + break; + + case AUE_FCHDIR: + case AUE_FPATHCONF: + case AUE_FSTAT: /* XXX Need to handle sockets and shm */ + case AUE_FSTATFS: + case AUE_FSYNC: + case AUE_FTRUNCATE: + case AUE_FUTIMES: + case AUE_GETDIRENTRIES: + case AUE_GETDIRENTRIESATTR: + FD_VNODE1_TOKENS; + break; + + case AUE_FCHOWN: + if (ARG_IS_VALID(kar, ARG_UID)) { + tok = au_to_arg32(2, "new file uid", ar->ar_arg_uid); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_GID)) { + tok = au_to_arg32(3, "new file gid", ar->ar_arg_gid); + kau_write(rec, tok); + } + FD_VNODE1_TOKENS; + break; + + case AUE_FCNTL: + if (ar->ar_arg_cmd == F_GETLK || ar->ar_arg_cmd == F_SETLK || + ar->ar_arg_cmd == F_SETLKW) { + if (ARG_IS_VALID(kar, ARG_CMD)) { + tok = au_to_arg32(2, "cmd", ar->ar_arg_cmd); + kau_write(rec, tok); + } + FD_VNODE1_TOKENS; + } + break; + + case AUE_FCHFLAGS: + if (ARG_IS_VALID(kar, ARG_FFLAGS)) { + tok = au_to_arg32(2, "flags", ar->ar_arg_fflags); + kau_write(rec, tok); + } + FD_VNODE1_TOKENS; + break; + + case AUE_FLOCK: + if (ARG_IS_VALID(kar, ARG_CMD)) { + tok = au_to_arg32(2, "operation", ar->ar_arg_cmd); + kau_write(rec, tok); + } + FD_VNODE1_TOKENS; + break; + + case AUE_RFORK: + if (ARG_IS_VALID(kar, ARG_FFLAGS)) { + tok = au_to_arg32(1, "flags", ar->ar_arg_fflags); + kau_write(rec, tok); + } + /* fall through */ + case AUE_FORK: + case AUE_VFORK: + if (ARG_IS_VALID(kar, ARG_PID)) { + tok = au_to_arg32(0, "child PID", ar->ar_arg_pid); + kau_write(rec, tok); + } + break; + + case AUE_IOCTL: + if (ARG_IS_VALID(kar, ARG_CMD)) { + tok = au_to_arg32(2, "cmd", ar->ar_arg_cmd); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_ADDR)) { + tok = au_to_arg32(1, "arg", (u_int32_t)ar->ar_arg_addr); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_VNODE1)) { + FD_VNODE1_TOKENS; + } else { + if (ARG_IS_VALID(kar, ARG_SOCKINFO)) { + tok = kau_to_socket(&ar->ar_arg_sockinfo); + kau_write(rec, tok); + } else { + if (ARG_IS_VALID(kar, ARG_FD)) { + tok = au_to_arg32(1, "fd", + ar->ar_arg_fd); + kau_write(rec, tok); + } + } + } + break; + + case AUE_KILL: + if (ARG_IS_VALID(kar, ARG_SIGNUM)) { + tok = au_to_arg32(2, "signal", ar->ar_arg_signum); + kau_write(rec, tok); + } + PROCESS_PID_TOKENS(1); + break; + + case AUE_KTRACE: + if (ARG_IS_VALID(kar, ARG_CMD)) { + tok = au_to_arg32(2, "ops", ar->ar_arg_cmd); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_VALUE)) { + tok = au_to_arg32(3, "trpoints", ar->ar_arg_value); + kau_write(rec, tok); + } + PROCESS_PID_TOKENS(4); + UPATH1_VNODE1_TOKENS; + break; + + case AUE_LINK: + case AUE_RENAME: + UPATH1_VNODE1_TOKENS; + UPATH2_TOKENS; + break; + + case AUE_LOADSHFILE: + if (ARG_IS_VALID(kar, ARG_ADDR)) { + tok = au_to_arg32(4, "base addr", + (u_int32_t)ar->ar_arg_addr); + kau_write(rec, tok); + } + UPATH1_VNODE1_TOKENS; + break; + + case AUE_MKDIR: + if (ARG_IS_VALID(kar, ARG_MODE)) { + tok = au_to_arg32(2, "mode", ar->ar_arg_mode); + kau_write(rec, tok); + } + UPATH1_VNODE1_TOKENS; + break; + + case AUE_MKNOD: + if (ARG_IS_VALID(kar, ARG_MODE)) { + tok = au_to_arg32(2, "mode", ar->ar_arg_mode); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_DEV)) { + tok = au_to_arg32(3, "dev", ar->ar_arg_dev); + kau_write(rec, tok); + } + UPATH1_VNODE1_TOKENS; + break; + + case AUE_MMAP: + case AUE_MUNMAP: + case AUE_MPROTECT: + case AUE_MLOCK: + case AUE_MUNLOCK: + case AUE_MINHERIT: + if (ARG_IS_VALID(kar, ARG_ADDR)) { + tok = au_to_arg32(1, "addr", + (u_int32_t)ar->ar_arg_addr); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_LEN)) { + tok = au_to_arg32(2, "len", ar->ar_arg_len); + kau_write(rec, tok); + } + if (ar->ar_event == AUE_MMAP) + FD_VNODE1_TOKENS; + if (ar->ar_event == AUE_MPROTECT) { + if (ARG_IS_VALID(kar, ARG_VALUE)) { + tok = au_to_arg32(3, "protection", + ar->ar_arg_value); + kau_write(rec, tok); + } + } + if (ar->ar_event == AUE_MINHERIT) { + if (ARG_IS_VALID(kar, ARG_VALUE)) { + tok = au_to_arg32(3, "inherit", + ar->ar_arg_value); + kau_write(rec, tok); + } + } + break; + + case AUE_MOUNT: + /* XXX Need to handle NFS mounts */ + if (ARG_IS_VALID(kar, ARG_FFLAGS)) { + tok = au_to_arg32(3, "flags", ar->ar_arg_fflags); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_TEXT)) { + tok = au_to_text(ar->ar_arg_text); + kau_write(rec, tok); + } + /* fall through */ + case AUE_UMOUNT: + UPATH1_VNODE1_TOKENS; + break; + + case AUE_MSGCTL: + ar->ar_event = msgctl_to_event(ar->ar_arg_svipc_cmd); + /* Fall through */ + case AUE_MSGRCV: + case AUE_MSGSND: + tok = au_to_arg32(1, "msg ID", ar->ar_arg_svipc_id); + kau_write(rec, tok); + if (ar->ar_errno != EINVAL) { + tok = au_to_ipc(AT_IPC_MSG, ar->ar_arg_svipc_id); + kau_write(rec, tok); + } + break; + + case AUE_MSGGET: + if (ar->ar_errno == 0) { + if (ARG_IS_VALID(kar, ARG_SVIPC_ID)) { + tok = au_to_ipc(AT_IPC_MSG, + ar->ar_arg_svipc_id); + kau_write(rec, tok); + } + } + break; + + case AUE_RESETSHFILE: + if (ARG_IS_VALID(kar, ARG_ADDR)) { + tok = au_to_arg32(1, "base addr", + (u_int32_t)ar->ar_arg_addr); + kau_write(rec, tok); + } + break; + + case AUE_OPEN_RC: + case AUE_OPEN_RTC: + case AUE_OPEN_RWC: + case AUE_OPEN_RWTC: + case AUE_OPEN_WC: + case AUE_OPEN_WTC: + /* case AUE_O_CREAT: */ /* AUE_O_CREAT == AUE_OPEN_RWTC */ + if (ARG_IS_VALID(kar, ARG_MODE)) { + tok = au_to_arg32(3, "mode", ar->ar_arg_mode); + kau_write(rec, tok); + } + /* fall through */ + + case AUE_OPEN_R: + case AUE_OPEN_RT: + case AUE_OPEN_RW: + case AUE_OPEN_RWT: + case AUE_OPEN_W: + case AUE_OPEN_WT: + if (ARG_IS_VALID(kar, ARG_FFLAGS)) { + tok = au_to_arg32(2, "flags", ar->ar_arg_fflags); + kau_write(rec, tok); + } + UPATH1_VNODE1_TOKENS; + break; + + case AUE_PTRACE: + if (ARG_IS_VALID(kar, ARG_CMD)) { + tok = au_to_arg32(1, "request", ar->ar_arg_cmd); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_ADDR)) { + tok = au_to_arg32(3, "addr", + (u_int32_t)ar->ar_arg_addr); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_VALUE)) { + tok = au_to_arg32(4, "data", ar->ar_arg_value); + kau_write(rec, tok); + } + PROCESS_PID_TOKENS(2); + break; + + case AUE_QUOTACTL: + if (ARG_IS_VALID(kar, ARG_CMD)) { + tok = au_to_arg32(2, "command", ar->ar_arg_cmd); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_UID)) { + tok = au_to_arg32(3, "uid", ar->ar_arg_uid); + kau_write(rec, tok); + } + UPATH1_VNODE1_TOKENS; + break; + + case AUE_REBOOT: + if (ARG_IS_VALID(kar, ARG_CMD)) { + tok = au_to_arg32(1, "howto", ar->ar_arg_cmd); + kau_write(rec, tok); + } + break; + + case AUE_SEMCTL: + ar->ar_event = semctl_to_event(ar->ar_arg_svipc_cmd); + /* Fall through */ + case AUE_SEMOP: + if (ARG_IS_VALID(kar, ARG_SVIPC_ID)) { + tok = au_to_arg32(1, "sem ID", ar->ar_arg_svipc_id); + kau_write(rec, tok); + if (ar->ar_errno != EINVAL) { + tok = au_to_ipc(AT_IPC_SEM, + ar->ar_arg_svipc_id); + kau_write(rec, tok); + } + } + break; + case AUE_SEMGET: + if (ar->ar_errno == 0) { + if (ARG_IS_VALID(kar, ARG_SVIPC_ID)) { + tok = au_to_ipc(AT_IPC_SEM, + ar->ar_arg_svipc_id); + kau_write(rec, tok); + } + } + break; + case AUE_SETEGID: + if (ARG_IS_VALID(kar, ARG_EGID)) { + tok = au_to_arg32(1, "gid", ar->ar_arg_egid); + kau_write(rec, tok); + } + break; + case AUE_SETEUID: + if (ARG_IS_VALID(kar, ARG_EUID)) { + tok = au_to_arg32(1, "uid", ar->ar_arg_euid); + kau_write(rec, tok); + } + break; + case AUE_SETREGID: + if (ARG_IS_VALID(kar, ARG_RGID)) { + tok = au_to_arg32(1, "rgid", ar->ar_arg_rgid); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_EGID)) { + tok = au_to_arg32(2, "egid", ar->ar_arg_egid); + kau_write(rec, tok); + } + break; + case AUE_SETREUID: + if (ARG_IS_VALID(kar, ARG_RUID)) { + tok = au_to_arg32(1, "ruid", ar->ar_arg_ruid); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_EUID)) { + tok = au_to_arg32(2, "euid", ar->ar_arg_euid); + kau_write(rec, tok); + } + break; + case AUE_SETRESGID: + if (ARG_IS_VALID(kar, ARG_RGID)) { + tok = au_to_arg32(1, "rgid", ar->ar_arg_rgid); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_EGID)) { + tok = au_to_arg32(2, "egid", ar->ar_arg_egid); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_SGID)) { + tok = au_to_arg32(3, "sgid", ar->ar_arg_sgid); + kau_write(rec, tok); + } + break; + case AUE_SETRESUID: + if (ARG_IS_VALID(kar, ARG_RUID)) { + tok = au_to_arg32(1, "ruid", ar->ar_arg_ruid); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_EUID)) { + tok = au_to_arg32(2, "euid", ar->ar_arg_euid); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_SUID)) { + tok = au_to_arg32(3, "suid", ar->ar_arg_suid); + kau_write(rec, tok); + } + break; + case AUE_SETGID: + if (ARG_IS_VALID(kar, ARG_GID)) { + tok = au_to_arg32(1, "gid", ar->ar_arg_gid); + kau_write(rec, tok); + } + break; + case AUE_SETUID: + if (ARG_IS_VALID(kar, ARG_UID)) { + tok = au_to_arg32(1, "uid", ar->ar_arg_uid); + kau_write(rec, tok); + } + break; + case AUE_SETGROUPS: + if (ARG_IS_VALID(kar, ARG_GROUPSET)) { + for(ctr = 0; ctr < ar->ar_arg_groups.gidset_size; ctr++) + { + tok = au_to_arg32(1, "setgroups", ar->ar_arg_groups.gidset[ctr]); + kau_write(rec, tok); + } + } + break; + + case AUE_SETLOGIN: + if (ARG_IS_VALID(kar, ARG_TEXT)) { + tok = au_to_text(ar->ar_arg_text); + kau_write(rec, tok); + } + break; + + case AUE_SETPRIORITY: + if (ARG_IS_VALID(kar, ARG_CMD)) { + tok = au_to_arg32(1, "which", ar->ar_arg_cmd); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_UID)) { + tok = au_to_arg32(2, "who", ar->ar_arg_uid); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_VALUE)) { + tok = au_to_arg32(2, "priority", ar->ar_arg_value); + kau_write(rec, tok); + } + break; + + case AUE_SETPRIVEXEC: + if (ARG_IS_VALID(kar, ARG_VALUE)) { + tok = au_to_arg32(1, "flag", ar->ar_arg_value); + kau_write(rec, tok); + } + break; + + /* AUE_SHMAT, AUE_SHMCTL, AUE_SHMDT and AUE_SHMGET are SysV IPC */ + case AUE_SHMAT: + if (ARG_IS_VALID(kar, ARG_SVIPC_ID)) { + tok = au_to_arg32(1, "shmid", ar->ar_arg_svipc_id); + kau_write(rec, tok); + /* XXXAUDIT: Does having the ipc token make sense? */ + tok = au_to_ipc(AT_IPC_SHM, ar->ar_arg_svipc_id); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_SVIPC_ADDR)) { + tok = au_to_arg32(2, "shmaddr", + (int)ar->ar_arg_svipc_addr); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_SVIPC_PERM)) { + tok = au_to_ipc_perm(&ar->ar_arg_svipc_perm); + kau_write(rec, tok); + } + break; + + case AUE_SHMCTL: + if (ARG_IS_VALID(kar, ARG_SVIPC_ID)) { + tok = au_to_arg32(1, "shmid", ar->ar_arg_svipc_id); + kau_write(rec, tok); + /* XXXAUDIT: Does having the ipc token make sense? */ + tok = au_to_ipc(AT_IPC_SHM, ar->ar_arg_svipc_id); + kau_write(rec, tok); + } + switch (ar->ar_arg_svipc_cmd) { + case IPC_STAT: + ar->ar_event = AUE_SHMCTL_STAT; + break; + case IPC_RMID: + ar->ar_event = AUE_SHMCTL_RMID; + break; + case IPC_SET: + ar->ar_event = AUE_SHMCTL_SET; + if (ARG_IS_VALID(kar, ARG_SVIPC_PERM)) { + tok = au_to_ipc_perm(&ar->ar_arg_svipc_perm); + kau_write(rec, tok); + } + break; + default: + break; /* We will audit a bad command */ + } + break; + + case AUE_SHMDT: + if (ARG_IS_VALID(kar, ARG_SVIPC_ADDR)) { + tok = au_to_arg32(1, "shmaddr", + (int)ar->ar_arg_svipc_addr); + kau_write(rec, tok); + } + break; + + case AUE_SHMGET: + /* This is unusual; the return value is in an argument token */ + if (ARG_IS_VALID(kar, ARG_SVIPC_ID)) { + tok = au_to_arg32(0, "shmid", ar->ar_arg_svipc_id); + kau_write(rec, tok); + tok = au_to_ipc(AT_IPC_SHM, ar->ar_arg_svipc_id); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_SVIPC_PERM)) { + tok = au_to_ipc_perm(&ar->ar_arg_svipc_perm); + kau_write(rec, tok); + } + break; + + /* AUE_SHMOPEN, AUE_SHMUNLINK, AUE_SEMOPEN, AUE_SEMCLOSE + * and AUE_SEMUNLINK are Posix IPC */ + case AUE_SHMOPEN: + if (ARG_IS_VALID(kar, ARG_SVIPC_ADDR)) { + tok = au_to_arg32(2, "flags", ar->ar_arg_fflags); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_MODE)) { + tok = au_to_arg32(3, "mode", ar->ar_arg_mode); + kau_write(rec, tok); + } + case AUE_SHMUNLINK: + if (ARG_IS_VALID(kar, ARG_TEXT)) { + tok = au_to_text(ar->ar_arg_text); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_POSIX_IPC_PERM)) { + /* Create an ipc_perm token */ + struct ipc_perm perm; + perm.uid = ar->ar_arg_pipc_perm.pipc_uid; + perm.gid = ar->ar_arg_pipc_perm.pipc_gid; + perm.cuid = ar->ar_arg_pipc_perm.pipc_uid; + perm.cgid = ar->ar_arg_pipc_perm.pipc_gid; + perm.mode = ar->ar_arg_pipc_perm.pipc_mode; + perm.seq = 0; + perm.key = 0; + tok = au_to_ipc_perm(&perm); + kau_write(rec, tok); + } + break; + + case AUE_SEMOPEN: + if (ARG_IS_VALID(kar, ARG_FFLAGS)) { + tok = au_to_arg32(2, "flags", ar->ar_arg_fflags); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_MODE)) { + tok = au_to_arg32(3, "mode", ar->ar_arg_mode); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_VALUE)) { + tok = au_to_arg32(4, "value", ar->ar_arg_value); + kau_write(rec, tok); + } + /* fall through */ + case AUE_SEMUNLINK: + if (ARG_IS_VALID(kar, ARG_TEXT)) { + tok = au_to_text(ar->ar_arg_text); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_POSIX_IPC_PERM)) { + /* Create an ipc_perm token */ + struct ipc_perm perm; + perm.uid = ar->ar_arg_pipc_perm.pipc_uid; + perm.gid = ar->ar_arg_pipc_perm.pipc_gid; + perm.cuid = ar->ar_arg_pipc_perm.pipc_uid; + perm.cgid = ar->ar_arg_pipc_perm.pipc_gid; + perm.mode = ar->ar_arg_pipc_perm.pipc_mode; + perm.seq = 0; + perm.key = 0; + tok = au_to_ipc_perm(&perm); + kau_write(rec, tok); + } + break; + + case AUE_SEMCLOSE: + if (ARG_IS_VALID(kar, ARG_FD)) { + tok = au_to_arg32(1, "sem", ar->ar_arg_fd); + kau_write(rec, tok); + } + break; + + case AUE_SYMLINK: + if (ARG_IS_VALID(kar, ARG_TEXT)) { + tok = au_to_text(ar->ar_arg_text); + kau_write(rec, tok); + } + UPATH1_VNODE1_TOKENS; + break; + + case AUE_SYSCTL: + if (ARG_IS_VALID(kar, ARG_CTLNAME | ARG_LEN)) { + for (ctr = 0; ctr < ar->ar_arg_len; ctr++) { + tok = au_to_arg32(1, "name", ar->ar_arg_ctlname[ctr]); + kau_write(rec, tok); + } + } + if (ARG_IS_VALID(kar, ARG_VALUE)) { + tok = au_to_arg32(5, "newval", ar->ar_arg_value); + kau_write(rec, tok); + } + if (ARG_IS_VALID(kar, ARG_TEXT)) { + tok = au_to_text(ar->ar_arg_text); + kau_write(rec, tok); + } + break; + + case AUE_UMASK: + if (ARG_IS_VALID(kar, ARG_MASK)) { + tok = au_to_arg32(1, "new mask", ar->ar_arg_mask); + kau_write(rec, tok); + } + tok = au_to_arg32(0, "prev mask", ar->ar_retval); + kau_write(rec, tok); + break; + + case AUE_WAIT4: + if (ARG_IS_VALID(kar, ARG_PID)) { + tok = au_to_arg32(0, "pid", ar->ar_arg_pid); + kau_write(rec, tok); + } + break; + + default: /* We shouldn't fall through to here. */ + printf("BSM conversion requested for unknown event %d\n", + ar->ar_event); + /* Write the subject token so it is properly freed here. */ + kau_write(rec, subj_tok); + kau_free(rec); + return (BSM_NOAUDIT); + } + + kau_write(rec, subj_tok); + tok = au_to_return32((char)ar->ar_errno, ar->ar_retval); + kau_write(rec, tok); /* Every record gets a return token */ + + kau_close(rec, &ar->ar_endtime, ar->ar_event); + + *pau = rec; + return (BSM_SUCCESS); +} + +/* + * Verify that a record is a valid BSM record. This verification is + * simple now, but may be expanded on sometime in the future. + * Return 1 if the record is good, 0 otherwise. + * + */ +int +bsm_rec_verify(void *rec) +{ + char c = *(char *)rec; + /* + * Check the token ID of the first token; it has to be a header + * token. + */ + /* XXXAUDIT There needs to be a token structure to map a token. + * XXXAUDIT 'Shouldn't be simply looking at the first char. + */ + if ( (c != AUT_HEADER32) && + (c != AUT_HEADER32_EX) && + (c != AUT_HEADER64) && + (c != AUT_HEADER64_EX) ) { + return (0); + } + return (1); +} diff --git a/sys/security/audit/audit_bsm_klib.c b/sys/security/audit/audit_bsm_klib.c new file mode 100644 index 0000000..abd78d3 --- /dev/null +++ b/sys/security/audit/audit_bsm_klib.c @@ -0,0 +1,538 @@ +/* + * Copyright (c) 1999-2005 Apple Computer, Inc. + * Copyright (c) 2005 Robert N. M. Watson + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/fcntl.h> +#include <sys/filedesc.h> +#include <sys/libkern.h> +#include <sys/malloc.h> +#include <sys/mount.h> +#include <sys/proc.h> +#include <sys/sem.h> +#include <sys/syscall.h> +#include <sys/sysctl.h> +#include <sys/sysent.h> +#include <sys/vnode.h> + +#include <bsm/audit.h> +#include <bsm/audit_kevents.h> +#include <security/audit/audit.h> +#include <security/audit/audit_private.h> + +/* + * Hash table functions for the audit event number to event class mask + * mapping. + */ +#define EVCLASSMAP_HASH_TABLE_SIZE 251 +struct evclass_elem { + au_event_t event; + au_class_t class; + LIST_ENTRY(evclass_elem) entry; +}; +struct evclass_list { + LIST_HEAD(, evclass_elem) head; +}; + +static MALLOC_DEFINE(M_AUDITEVCLASS, "audit_evclass", "Audit event class"); +static struct mtx evclass_mtx; +static struct evclass_list evclass_hash[EVCLASSMAP_HASH_TABLE_SIZE]; + +/* + * Look up the class for an audit event in the class mapping table. + */ +au_class_t +au_event_class(au_event_t event) +{ + struct evclass_list *evcl; + struct evclass_elem *evc; + au_class_t class; + + mtx_lock(&evclass_mtx); + evcl = &evclass_hash[event % EVCLASSMAP_HASH_TABLE_SIZE]; + class = AU_NULL; + LIST_FOREACH(evc, &evcl->head, entry) { + if (evc->event == event) { + class = evc->class; + goto out; + } + } +out: + mtx_unlock(&evclass_mtx); + return (class); +} + +/* + * Insert a event to class mapping. If the event already exists in the + * mapping, then replace the mapping with the new one. + * XXX There is currently no constraints placed on the number of mappings. + * May want to either limit to a number, or in terms of memory usage. + */ +void +au_evclassmap_insert(au_event_t event, au_class_t class) +{ + struct evclass_list *evcl; + struct evclass_elem *evc, *evc_new; + + /* + * Pessimistically, always allocate storage before acquiring mutex. + * Free if there is already a mapping for this event. + */ + evc_new = malloc(sizeof(*evc), M_AUDITEVCLASS, M_WAITOK); + + mtx_lock(&evclass_mtx); + evcl = &evclass_hash[event % EVCLASSMAP_HASH_TABLE_SIZE]; + LIST_FOREACH(evc, &evcl->head, entry) { + if (evc->event == event) { + evc->class = class; + mtx_unlock(&evclass_mtx); + free(evc_new, M_AUDITEVCLASS); + return; + } + } + evc = evc_new; + evc->event = event; + evc->class = class; + LIST_INSERT_HEAD(&evcl->head, evc, entry); + mtx_unlock(&evclass_mtx); +} + +void +au_evclassmap_init(void) +{ + int i; + + mtx_init(&evclass_mtx, "evclass_mtx", NULL, MTX_DEF); + for (i = 0; i < EVCLASSMAP_HASH_TABLE_SIZE; i++) + LIST_INIT(&evclass_hash[i].head); + + /* + * Set up the initial event to class mapping for system calls. + * + * XXXRW: Really, this should walk all possible audit events, not all + * native ABI system calls, as there may be audit events reachable + * only through non-native system calls. It also seems a shame to + * frob the mutex this early. + */ + for (i = 0; i < SYS_MAXSYSCALL; i++) { + if (sysent[i].sy_auevent != AUE_NULL) + au_evclassmap_insert(sysent[i].sy_auevent, AU_NULL); + } +} + +/* + * Check whether an event is aditable by comparing the mask of classes this + * event is part of against the given mask. + */ +int +au_preselect(au_event_t event, au_mask_t *mask_p, int sorf) +{ + au_class_t effmask = 0; + au_class_t ae_class; + + if (mask_p == NULL) + return (-1); + + ae_class = au_event_class(event); + + /* + * Perform the actual check of the masks against the event. + */ + if (sorf & AU_PRS_SUCCESS) + effmask |= (mask_p->am_success & ae_class); + + if (sorf & AU_PRS_FAILURE) + effmask |= (mask_p->am_failure & ae_class); + + if (effmask) + return (1); + else + return (0); +} + +/* + * Convert sysctl names and present arguments to events + */ +au_event_t +ctlname_to_sysctlevent(int name[], uint64_t valid_arg) +{ + + /* can't parse it - so return the worst case */ + if ((valid_arg & (ARG_CTLNAME | ARG_LEN)) != + (ARG_CTLNAME | ARG_LEN)) + return (AUE_SYSCTL); + + switch (name[0]) { + /* non-admin "lookups" treat them special */ + case KERN_OSTYPE: + case KERN_OSRELEASE: + case KERN_OSREV: + case KERN_VERSION: + case KERN_ARGMAX: + case KERN_CLOCKRATE: + case KERN_BOOTTIME: + case KERN_POSIX1: + case KERN_NGROUPS: + case KERN_JOB_CONTROL: + case KERN_SAVED_IDS: + case KERN_OSRELDATE: + case KERN_DUMMY: + return (AUE_SYSCTL_NONADMIN); + + /* only treat the changeable controls as admin */ + case KERN_MAXVNODES: + case KERN_MAXPROC: + case KERN_MAXFILES: + case KERN_MAXPROCPERUID: + case KERN_MAXFILESPERPROC: + case KERN_HOSTID: + case KERN_SECURELVL: + case KERN_HOSTNAME: + case KERN_VNODE: + case KERN_PROC: + case KERN_FILE: + case KERN_PROF: + case KERN_NISDOMAINNAME: + case KERN_UPDATEINTERVAL: + case KERN_NTP_PLL: + case KERN_BOOTFILE: + case KERN_DUMPDEV: + case KERN_IPC: + case KERN_PS_STRINGS: + case KERN_USRSTACK: + case KERN_LOGSIGEXIT: + case KERN_IOV_MAX: + case KERN_MAXID: + return ((valid_arg & ARG_VALUE) ? + AUE_SYSCTL : AUE_SYSCTL_NONADMIN); + + default: + return (AUE_SYSCTL); + } + /* NOTREACHED */ +} + +/* + * Convert an open flags specifier into a specific type of open event for + * auditing purposes. + */ +au_event_t +flags_and_error_to_openevent(int oflags, int error) { + au_event_t aevent; + + /* Need to check only those flags we care about. */ + oflags = oflags & (O_RDONLY | O_CREAT | O_TRUNC | O_RDWR | O_WRONLY); + + /* + * These checks determine what flags are on with the condition that + * ONLY that combination is on, and no other flags are on. + */ + switch (oflags) { + case O_RDONLY: + aevent = AUE_OPEN_R; + break; + + case (O_RDONLY | O_CREAT): + aevent = AUE_OPEN_RC; + break; + + case (O_RDONLY | O_CREAT | O_TRUNC): + aevent = AUE_OPEN_RTC; + break; + + case (O_RDONLY | O_TRUNC): + aevent = AUE_OPEN_RT; + break; + + case O_RDWR: + aevent = AUE_OPEN_RW; + break; + + case (O_RDWR | O_CREAT): + aevent = AUE_OPEN_RWC; + break; + + case (O_RDWR | O_CREAT | O_TRUNC): + aevent = AUE_OPEN_RWTC; + break; + + case (O_RDWR | O_TRUNC): + aevent = AUE_OPEN_RWT; + break; + + case O_WRONLY: + aevent = AUE_OPEN_W; + break; + + case (O_WRONLY | O_CREAT): + aevent = AUE_OPEN_WC; + break; + + case (O_WRONLY | O_CREAT | O_TRUNC): + aevent = AUE_OPEN_WTC; + break; + + case (O_WRONLY | O_TRUNC): + aevent = AUE_OPEN_WT; + break; + + default: + aevent = AUE_OPEN; + break; + } + +#if 0 + /* + * Convert chatty errors to better matching events. + * Failures to find a file are really just attribute + * events - so recast them as such. + * + * XXXAUDIT: Solaris defines that AUE_OPEN will never be returned, it + * is just a placeholder. However, in Darwin we return that in + * preference to other events. For now, comment this out as we don't + * have a BSM conversion routine for AUE_OPEN. + */ + switch (aevent) { + case AUE_OPEN_R: + case AUE_OPEN_RT: + case AUE_OPEN_RW: + case AUE_OPEN_RWT: + case AUE_OPEN_W: + case AUE_OPEN_WT: + if (error == ENOENT) + aevent = AUE_OPEN; + } +#endif + return (aevent); +} + +/* + * Convert a MSGCTL command to a specific event. + */ +int +msgctl_to_event(int cmd) +{ + + switch (cmd) { + case IPC_RMID: + return (AUE_MSGCTL_RMID); + + case IPC_SET: + return (AUE_MSGCTL_SET); + + case IPC_STAT: + return (AUE_MSGCTL_STAT); + + default: + /* We will audit a bad command */ + return (AUE_MSGCTL); + } +} + +/* + * Convert a SEMCTL command to a specific event. + */ +int +semctl_to_event(int cmd) +{ + + switch (cmd) { + case GETALL: + return (AUE_SEMCTL_GETALL); + + case GETNCNT: + return (AUE_SEMCTL_GETNCNT); + + case GETPID: + return (AUE_SEMCTL_GETPID); + + case GETVAL: + return (AUE_SEMCTL_GETVAL); + + case GETZCNT: + return (AUE_SEMCTL_GETZCNT); + + case IPC_RMID: + return (AUE_SEMCTL_RMID); + + case IPC_SET: + return (AUE_SEMCTL_SET); + + case SETALL: + return (AUE_SEMCTL_SETALL); + + case SETVAL: + return (AUE_SEMCTL_SETVAL); + + case IPC_STAT: + return (AUE_SEMCTL_STAT); + + default: + /* We will audit a bad command */ + return (AUE_SEMCTL); + } +} + +/* + * Convert a command for the auditon() system call to a audit event. + */ +int +auditon_command_event(int cmd) +{ + + switch(cmd) { + case A_GETPOLICY: + return (AUE_AUDITON_GPOLICY); + + case A_SETPOLICY: + return (AUE_AUDITON_SPOLICY); + + case A_GETKMASK: + return (AUE_AUDITON_GETKMASK); + + case A_SETKMASK: + return (AUE_AUDITON_SETKMASK); + + case A_GETQCTRL: + return (AUE_AUDITON_GQCTRL); + + case A_SETQCTRL: + return (AUE_AUDITON_SQCTRL); + + case A_GETCWD: + return (AUE_AUDITON_GETCWD); + + case A_GETCAR: + return (AUE_AUDITON_GETCAR); + + case A_GETSTAT: + return (AUE_AUDITON_GETSTAT); + + case A_SETSTAT: + return (AUE_AUDITON_SETSTAT); + + case A_SETUMASK: + return (AUE_AUDITON_SETUMASK); + + case A_SETSMASK: + return (AUE_AUDITON_SETSMASK); + + case A_GETCOND: + return (AUE_AUDITON_GETCOND); + + case A_SETCOND: + return (AUE_AUDITON_SETCOND); + + case A_GETCLASS: + return (AUE_AUDITON_GETCLASS); + + case A_SETCLASS: + return (AUE_AUDITON_SETCLASS); + + case A_GETPINFO: + case A_SETPMASK: + case A_SETFSIZE: + case A_GETFSIZE: + case A_GETPINFO_ADDR: + case A_GETKAUDIT: + case A_SETKAUDIT: + default: + return (AUE_AUDITON); /* No special record */ + } +} + +/* + * Create a canonical path from given path by prefixing either the + * root directory, or the current working directory. + * If the process working directory is NULL, we could use 'rootvnode' + * to obtain the root directoty, but this results in a volfs name + * written to the audit log. So we will leave the filename starting + * with '/' in the audit log in this case. + * + * XXXRW: Since we combine two paths here, ideally a buffer of size + * MAXPATHLEN * 2 would be passed in. + */ +void +canon_path(struct thread *td, char *path, char *cpath) +{ + char *bufp; + char *retbuf, *freebuf; + struct vnode *vnp; + struct filedesc *fdp; + int error, vfslocked; + + fdp = td->td_proc->p_fd; + bufp = path; + FILEDESC_LOCK(fdp); + if (*(path) == '/') { + while (*(bufp) == '/') + bufp++; /* skip leading '/'s */ + /* If no process root, or it is the same as the system root, + * audit the path as passed in with a single '/'. + */ + if ((fdp->fd_rdir == NULL) || + (fdp->fd_rdir == rootvnode)) { + vnp = NULL; + bufp--; /* restore one '/' */ + } else { + vnp = fdp->fd_rdir; /* use process root */ + vref(vnp); + } + } else { + vnp = fdp->fd_cdir; /* prepend the current dir */ + vref(vnp); + bufp = path; + } + FILEDESC_UNLOCK(fdp); + if (vnp != NULL) { + /* + * XXX: vn_fullpath() on FreeBSD is "less reliable" + * than vn_getpath() on Darwin, so this will need more + * attention in the future. Also, the question and + * string bounding here seems a bit questionable and + * will also require attention. + */ + vfslocked = VFS_LOCK_GIANT(vnp->v_mount); + vn_lock(vnp, LK_EXCLUSIVE | LK_RETRY, td); + error = vn_fullpath(td, vnp, &retbuf, &freebuf); + if (error == 0) { + /* Copy and free buffer allocated by vn_fullpath() */ + snprintf(cpath, MAXPATHLEN, "%s/%s", retbuf, bufp); + free(freebuf, M_TEMP); + } else { + cpath[0] = '\0'; + } + vput(vnp); + VFS_UNLOCK_GIANT(vfslocked); + } else { + strlcpy(cpath, bufp, MAXPATHLEN); + } +} diff --git a/sys/security/audit/audit_bsm_token.c b/sys/security/audit/audit_bsm_token.c new file mode 100644 index 0000000..ed7f108 --- /dev/null +++ b/sys/security/audit/audit_bsm_token.c @@ -0,0 +1,1181 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. + * Copyright (c) 2005 SPARTA, Inc. + * All rights reserved. + * + * This code was developed in part by Robert N. M. Watson, Senior Principal + * Scientist, SPARTA, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $P4: //depot/projects/trustedbsd/audit3/sys/security/audit/audit_bsm_token.c#7 $ + * $FreeBSD$ + */ + +#include <sys/types.h> +#ifdef __APPLE__ +#include <compat/endian.h> +#else /* !__APPLE__ */ +#include <sys/endian.h> +#endif /* __APPLE__*/ +#include <sys/socket.h> +#include <sys/time.h> + +#include <sys/ipc.h> +#include <sys/libkern.h> +#include <sys/malloc.h> +#include <sys/un.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> + +#include <sys/socketvar.h> + +#include <bsm/audit.h> +#include <bsm/audit_internal.h> +#include <bsm/audit_record.h> +#include <security/audit/audit.h> +#include <security/audit/audit_private.h> + +#define GET_TOKEN_AREA(t, dptr, length) do { \ + t = malloc(sizeof(token_t), M_AUDITBSM, M_WAITOK); \ + t->t_data = malloc(length, M_AUDITBSM, M_WAITOK | M_ZERO); \ + t->len = length; \ + dptr = t->t_data; \ +} while (0) + +/* + * token ID 1 byte + * argument # 1 byte + * argument value 4 bytes/8 bytes (32-bit/64-bit value) + * text length 2 bytes + * text N bytes + 1 terminating NULL byte + */ +token_t * +au_to_arg32(char n, char *text, u_int32_t v) +{ + token_t *t; + u_char *dptr = NULL; + u_int16_t textlen; + + textlen = strlen(text); + textlen += 1; + + GET_TOKEN_AREA(t, dptr, 2 * sizeof(u_char) + sizeof(u_int32_t) + + sizeof(u_int16_t) + textlen); + + ADD_U_CHAR(dptr, AUT_ARG32); + ADD_U_CHAR(dptr, n); + ADD_U_INT32(dptr, v); + ADD_U_INT16(dptr, textlen); + ADD_STRING(dptr, text, textlen); + + return (t); + +} + +token_t * +au_to_arg64(char n, char *text, u_int64_t v) +{ + token_t *t; + u_char *dptr = NULL; + u_int16_t textlen; + + textlen = strlen(text); + textlen += 1; + + GET_TOKEN_AREA(t, dptr, 2 * sizeof(u_char) + sizeof(u_int64_t) + + sizeof(u_int16_t) + textlen); + + ADD_U_CHAR(dptr, AUT_ARG64); + ADD_U_CHAR(dptr, n); + ADD_U_INT64(dptr, v); + ADD_U_INT16(dptr, textlen); + ADD_STRING(dptr, text, textlen); + + return (t); + +} + +token_t * +au_to_arg(char n, char *text, u_int32_t v) +{ + + return (au_to_arg32(n, text, v)); +} + +#if defined(_KERNEL) || defined(KERNEL) +/* + * token ID 1 byte + * file access mode 4 bytes + * owner user ID 4 bytes + * owner group ID 4 bytes + * file system ID 4 bytes + * node ID 8 bytes + * device 4 bytes/8 bytes (32-bit/64-bit) + */ +token_t * +au_to_attr32(struct vnode_au_info *vni) +{ + token_t *t; + u_char *dptr = NULL; + u_int16_t pad0_16 = 0; + u_int16_t pad0_32 = 0; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 2 * sizeof(u_int16_t) + + 3 * sizeof(u_int32_t) + sizeof(u_int64_t) + sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_ATTR32); + + /* + * Darwin defines the size for the file mode + * as 2 bytes; BSM defines 4 so pad with 0 + */ + ADD_U_INT16(dptr, pad0_16); + ADD_U_INT16(dptr, vni->vn_mode); + + ADD_U_INT32(dptr, vni->vn_uid); + ADD_U_INT32(dptr, vni->vn_gid); + ADD_U_INT32(dptr, vni->vn_fsid); + + /* + * Some systems use 32-bit file ID's, other's use 64-bit file IDs. + * Attempt to handle both, and let the compiler sort it out. If we + * could pick this out at compile-time, it would be better, so as to + * avoid the else case below. + */ + if (sizeof(vni->vn_fileid) == sizeof(uint32_t)) { + ADD_U_INT32(dptr, pad0_32); + ADD_U_INT32(dptr, vni->vn_fileid); + } else if (sizeof(vni->vn_fileid) == sizeof(uint64_t)) + ADD_U_INT64(dptr, vni->vn_fileid); + else + ADD_U_INT64(dptr, 0LL); + + ADD_U_INT32(dptr, vni->vn_dev); + + return (t); +} + +token_t * +au_to_attr64(struct vnode_au_info *vni) +{ + + return (NULL); +} + +token_t * +au_to_attr(struct vnode_au_info *vni) +{ + + return (au_to_attr32(vni)); +} +#endif /* !(defined(_KERNEL) || defined(KERNEL) */ + +/* + * token ID 1 byte + * how to print 1 byte + * basic unit 1 byte + * unit count 1 byte + * data items (depends on basic unit) + */ +token_t * +au_to_data(char unit_print, char unit_type, char unit_count, char *p) +{ + token_t *t; + u_char *dptr = NULL; + size_t datasize, totdata; + + /* Determine the size of the basic unit. */ + switch (unit_type) { + case AUR_BYTE: + datasize = AUR_BYTE_SIZE; + break; + + case AUR_SHORT: + datasize = AUR_SHORT_SIZE; + break; + + case AUR_LONG: + datasize = AUR_LONG_SIZE; + break; + + default: + return (NULL); + } + + totdata = datasize * unit_count; + + GET_TOKEN_AREA(t, dptr, totdata + 4 * sizeof(u_char)); + + ADD_U_CHAR(dptr, AUT_DATA); + ADD_U_CHAR(dptr, unit_print); + ADD_U_CHAR(dptr, unit_type); + ADD_U_CHAR(dptr, unit_count); + ADD_MEM(dptr, p, totdata); + + return (t); +} + + +/* + * token ID 1 byte + * status 4 bytes + * return value 4 bytes + */ +token_t * +au_to_exit(int retval, int err) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 2 * sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_EXIT); + ADD_U_INT32(dptr, err); + ADD_U_INT32(dptr, retval); + + return (t); +} + +/* + */ +token_t * +au_to_groups(int *groups) +{ + + return (au_to_newgroups(BSM_MAX_GROUPS, groups)); +} + +/* + * token ID 1 byte + * number groups 2 bytes + * group list count * 4 bytes + */ +token_t * +au_to_newgroups(u_int16_t n, gid_t *groups) +{ + token_t *t; + u_char *dptr = NULL; + int i; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t) + + n * sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_NEWGROUPS); + ADD_U_INT16(dptr, n); + for (i = 0; i < n; i++) + ADD_U_INT32(dptr, groups[i]); + + return (t); +} + +/* + * token ID 1 byte + * internet address 4 bytes + */ +token_t * +au_to_in_addr(struct in_addr *internet_addr) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_IN_ADDR); + ADD_U_INT32(dptr, internet_addr->s_addr); + + return (t); +} + +/* + * token ID 1 byte + * address type/length 4 bytes + * Address 16 bytes + */ +token_t * +au_to_in_addr_ex(struct in6_addr *internet_addr) +{ + token_t *t; + u_char *dptr = NULL; + u_int32_t type = AF_INET6; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 5 * sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_IN_ADDR_EX); + ADD_U_INT32(dptr, type); + ADD_U_INT32(dptr, internet_addr->__u6_addr.__u6_addr32[0]); + ADD_U_INT32(dptr, internet_addr->__u6_addr.__u6_addr32[1]); + ADD_U_INT32(dptr, internet_addr->__u6_addr.__u6_addr32[2]); + ADD_U_INT32(dptr, internet_addr->__u6_addr.__u6_addr32[3]); + + return (t); +} + +/* + * token ID 1 byte + * ip header 20 bytes + */ +token_t * +au_to_ip(struct ip *ip) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(struct ip)); + + ADD_U_CHAR(dptr, AUT_IP); + /* + * XXXRW: Any byte order work needed on the IP header before writing? + */ + ADD_MEM(dptr, ip, sizeof(struct ip)); + + return (t); +} + +/* + * token ID 1 byte + * object ID type 1 byte + * object ID 4 bytes + */ +token_t * +au_to_ipc(char type, int id) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, 2 * sizeof(u_char) + sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_IPC); + ADD_U_CHAR(dptr, type); + ADD_U_INT32(dptr, id); + + return (t); +} + +/* + * token ID 1 byte + * owner user ID 4 bytes + * owner group ID 4 bytes + * creator user ID 4 bytes + * creator group ID 4 bytes + * access mode 4 bytes + * slot sequence # 4 bytes + * key 4 bytes + */ +token_t * +au_to_ipc_perm(struct ipc_perm *perm) +{ + token_t *t; + u_char *dptr = NULL; + u_int16_t pad0 = 0; + + GET_TOKEN_AREA(t, dptr, 12 * sizeof(u_int16_t) + sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_IPC_PERM); + + /* + * Darwin defines the sizes for ipc_perm members + * as 2 bytes; BSM defines 4 so pad with 0 + */ + ADD_U_INT16(dptr, pad0); + ADD_U_INT16(dptr, perm->uid); + + ADD_U_INT16(dptr, pad0); + ADD_U_INT16(dptr, perm->gid); + + ADD_U_INT16(dptr, pad0); + ADD_U_INT16(dptr, perm->cuid); + + ADD_U_INT16(dptr, pad0); + ADD_U_INT16(dptr, perm->cgid); + + ADD_U_INT16(dptr, pad0); + ADD_U_INT16(dptr, perm->mode); + + ADD_U_INT16(dptr, pad0); + ADD_U_INT16(dptr, perm->seq); + + ADD_U_INT32(dptr, perm->key); + + return (t); +} + +/* + * token ID 1 byte + * port IP address 2 bytes + */ +token_t * +au_to_iport(u_int16_t iport) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t)); + + ADD_U_CHAR(dptr, AUT_IPORT); + ADD_U_INT16(dptr, iport); + + return (t); +} + +/* + * token ID 1 byte + * size 2 bytes + * data size bytes + */ +token_t * +au_to_opaque(char *data, u_int16_t bytes) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t) + bytes); + + ADD_U_CHAR(dptr, AUT_OPAQUE); + ADD_U_INT16(dptr, bytes); + ADD_MEM(dptr, data, bytes); + + return (t); +} + +/* + * token ID 1 byte + * seconds of time 4 bytes + * milliseconds of time 4 bytes + * file name len 2 bytes + * file pathname N bytes + 1 terminating NULL byte + */ +token_t * +#if defined(KERNEL) || defined(_KERNEL) +au_to_file(char *file, struct timeval tm) +#else +au_to_file(char *file) +#endif +{ + token_t *t; + u_char *dptr = NULL; + u_int16_t filelen; + u_int32_t timems; +#if !defined(KERNEL) && !defined(_KERNEL) + struct timeval tm; + struct timezone tzp; + + if (gettimeofday(&tm, &tzp) == -1) + return (NULL); +#endif + /* XXXRW: else ...? */ + + filelen = strlen(file); + filelen += 1; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 2 * sizeof(u_int32_t) + + sizeof(u_int16_t) + filelen); + + timems = tm.tv_usec/1000; + + ADD_U_CHAR(dptr, AUT_OTHER_FILE32); + ADD_U_INT32(dptr, tm.tv_sec); + ADD_U_INT32(dptr, timems); /* We need time in ms. */ + ADD_U_INT16(dptr, filelen); + ADD_STRING(dptr, file, filelen); + + return (t); +} + +/* + * token ID 1 byte + * text length 2 bytes + * text N bytes + 1 terminating NULL byte + */ +token_t * +au_to_text(char *text) +{ + token_t *t; + u_char *dptr = NULL; + u_int16_t textlen; + + textlen = strlen(text); + textlen += 1; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t) + textlen); + + ADD_U_CHAR(dptr, AUT_TEXT); + ADD_U_INT16(dptr, textlen); + ADD_STRING(dptr, text, textlen); + + return (t); +} + +/* + * token ID 1 byte + * path length 2 bytes + * path N bytes + 1 terminating NULL byte + */ +token_t * +au_to_path(char *text) +{ + token_t *t; + u_char *dptr = NULL; + u_int16_t textlen; + + textlen = strlen(text); + textlen += 1; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t) + textlen); + + ADD_U_CHAR(dptr, AUT_PATH); + ADD_U_INT16(dptr, textlen); + ADD_STRING(dptr, text, textlen); + + return (t); +} + +/* + * token ID 1 byte + * audit ID 4 bytes + * effective user ID 4 bytes + * effective group ID 4 bytes + * real user ID 4 bytes + * real group ID 4 bytes + * process ID 4 bytes + * session ID 4 bytes + * terminal ID + * port ID 4 bytes/8 bytes (32-bit/64-bit value) + * machine address 4 bytes + */ +token_t * +au_to_process32(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, + pid_t pid, au_asid_t sid, au_tid_t *tid) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 9 * sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_PROCESS32); + ADD_U_INT32(dptr, auid); + ADD_U_INT32(dptr, euid); + ADD_U_INT32(dptr, egid); + ADD_U_INT32(dptr, ruid); + ADD_U_INT32(dptr, rgid); + ADD_U_INT32(dptr, pid); + ADD_U_INT32(dptr, sid); + ADD_U_INT32(dptr, tid->port); + ADD_U_INT32(dptr, tid->machine); + + return (t); +} + +token_t * +au_to_process64(__unused au_id_t auid, __unused uid_t euid, + __unused gid_t egid, __unused uid_t ruid, __unused gid_t rgid, + __unused pid_t pid, __unused au_asid_t sid, __unused au_tid_t *tid) +{ + + return (NULL); +} + +token_t * +au_to_process(__unused au_id_t auid, __unused uid_t euid, + __unused gid_t egid, __unused uid_t ruid, __unused gid_t rgid, + __unused pid_t pid, __unused au_asid_t sid, __unused au_tid_t *tid) +{ + + return (au_to_process32(auid, euid, egid, ruid, rgid, pid, sid, + tid)); +} + +/* + * token ID 1 byte + * audit ID 4 bytes + * effective user ID 4 bytes + * effective group ID 4 bytes + * real user ID 4 bytes + * real group ID 4 bytes + * process ID 4 bytes + * session ID 4 bytes + * terminal ID + * port ID 4 bytes/8 bytes (32-bit/64-bit value) + * address type-len 4 bytes + * machine address 16 bytes + */ +token_t * +au_to_process32_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, + gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 13 * sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_PROCESS32_EX); + ADD_U_INT32(dptr, auid); + ADD_U_INT32(dptr, euid); + ADD_U_INT32(dptr, egid); + ADD_U_INT32(dptr, ruid); + ADD_U_INT32(dptr, rgid); + ADD_U_INT32(dptr, pid); + ADD_U_INT32(dptr, sid); + ADD_U_INT32(dptr, tid->at_port); + ADD_U_INT32(dptr, tid->at_type); + ADD_U_INT32(dptr, tid->at_addr[0]); + ADD_U_INT32(dptr, tid->at_addr[1]); + ADD_U_INT32(dptr, tid->at_addr[2]); + ADD_U_INT32(dptr, tid->at_addr[3]); + + return (t); +} + +token_t * +au_to_process64_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, + gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid) +{ + + return (NULL); +} + +token_t * +au_to_process_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, + gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid) +{ + + return (au_to_process32_ex(auid, euid, egid, ruid, rgid, pid, sid, + tid)); +} + +/* + * token ID 1 byte + * error status 1 byte + * return value 4 bytes/8 bytes (32-bit/64-bit value) + */ +token_t * +au_to_return32(char status, u_int32_t ret) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, 2 * sizeof(u_char) + sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_RETURN32); + ADD_U_CHAR(dptr, status); + ADD_U_INT32(dptr, ret); + + return (t); +} + +token_t * +au_to_return64(char status, u_int64_t ret) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, 2 * sizeof(u_char) + sizeof(u_int64_t)); + + ADD_U_CHAR(dptr, AUT_RETURN64); + ADD_U_CHAR(dptr, status); + ADD_U_INT64(dptr, ret); + + return (t); +} + +token_t * +au_to_return(char status, u_int32_t ret) +{ + + return (au_to_return32(status, ret)); +} + +/* + * token ID 1 byte + * sequence number 4 bytes + */ +token_t * +au_to_seq(long audit_count) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_SEQ); + ADD_U_INT32(dptr, audit_count); + + return (t); +} + +/* + * token ID 1 byte + * socket type 2 bytes + * local port 2 bytes + * local Internet address 4 bytes + * remote port 2 bytes + * remote Internet address 4 bytes + */ +token_t * +au_to_socket(struct socket *so) +{ + + /* XXXRW ... */ + return (NULL); +} + +/* + * Kernel-specific version of the above function. + */ +#ifdef _KERNEL +token_t * +kau_to_socket(struct socket_au_info *soi) +{ + token_t *t; + u_char *dptr; + u_int16_t so_type; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 2 * sizeof(u_int16_t) + + sizeof(u_int32_t) + sizeof(u_int16_t) + sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AU_SOCK_TOKEN); + /* Coerce the socket type into a short value */ + so_type = soi->so_type; + ADD_U_INT16(dptr, so_type); + ADD_U_INT16(dptr, soi->so_lport); + ADD_U_INT32(dptr, soi->so_laddr); + ADD_U_INT16(dptr, soi->so_rport); + ADD_U_INT32(dptr, soi->so_raddr); + + return (t); +} +#endif + +/* + * token ID 1 byte + * socket type 2 bytes + * local port 2 bytes + * address type/length 4 bytes + * local Internet address 4 bytes/16 bytes (IPv4/IPv6 address) + * remote port 4 bytes + * address type/length 4 bytes + * remote Internet address 4 bytes/16 bytes (IPv4/IPv6 address) + */ +token_t * +au_to_socket_ex_32(u_int16_t lp, u_int16_t rp, struct sockaddr *la, + struct sockaddr *ra) +{ + + return (NULL); +} + +token_t * +au_to_socket_ex_128(u_int16_t lp, u_int16_t rp, struct sockaddr *la, + struct sockaddr *ra) +{ + + return (NULL); +} + +/* + * token ID 1 byte + * socket family 2 bytes + * path 104 bytes + */ +token_t * +au_to_sock_unix(struct sockaddr_un *so) +{ + token_t *t; + u_char *dptr; + + GET_TOKEN_AREA(t, dptr, 3 * sizeof(u_char) + strlen(so->sun_path) + 1); + + ADD_U_CHAR(dptr, AU_SOCK_UNIX_TOKEN); + /* BSM token has two bytes for family */ + ADD_U_CHAR(dptr, 0); + ADD_U_CHAR(dptr, so->sun_family); + ADD_STRING(dptr, so->sun_path, strlen(so->sun_path) + 1); + + return (t); +} + +/* + * token ID 1 byte + * socket family 2 bytes + * local port 2 bytes + * socket address 4 bytes + */ +token_t * +au_to_sock_inet32(struct sockaddr_in *so) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, 3 * sizeof(u_char) + sizeof(u_int16_t) + + sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_SOCKINET32); + /* + * In Darwin, sin_family is one octet, but BSM defines the token + * to store two. So we copy in a 0 first. + */ + ADD_U_CHAR(dptr, 0); + ADD_U_CHAR(dptr, so->sin_family); + ADD_U_INT16(dptr, so->sin_port); + ADD_U_INT32(dptr, so->sin_addr.s_addr); + + return (t); + +} + +token_t * +au_to_sock_inet128(struct sockaddr_in6 *so) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, 3 * sizeof(u_char) + sizeof(u_int16_t) + + 4 * sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_SOCKINET128); + /* + * In Darwin, sin6_family is one octet, but BSM defines the token + * to store two. So we copy in a 0 first. + */ + ADD_U_CHAR(dptr, 0); + ADD_U_CHAR(dptr, so->sin6_family); + + ADD_U_INT16(dptr, so->sin6_port); + ADD_U_INT32(dptr, so->sin6_addr.__u6_addr.__u6_addr32[0]); + ADD_U_INT32(dptr, so->sin6_addr.__u6_addr.__u6_addr32[1]); + ADD_U_INT32(dptr, so->sin6_addr.__u6_addr.__u6_addr32[2]); + ADD_U_INT32(dptr, so->sin6_addr.__u6_addr.__u6_addr32[3]); + + return (t); + +} + +token_t * +au_to_sock_inet(struct sockaddr_in *so) +{ + + return (au_to_sock_inet32(so)); +} + +/* + * token ID 1 byte + * audit ID 4 bytes + * effective user ID 4 bytes + * effective group ID 4 bytes + * real user ID 4 bytes + * real group ID 4 bytes + * process ID 4 bytes + * session ID 4 bytes + * terminal ID + * port ID 4 bytes/8 bytes (32-bit/64-bit value) + * machine address 4 bytes + */ +token_t * +au_to_subject32(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, + pid_t pid, au_asid_t sid, au_tid_t *tid) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 9 * sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_SUBJECT32); + ADD_U_INT32(dptr, auid); + ADD_U_INT32(dptr, euid); + ADD_U_INT32(dptr, egid); + ADD_U_INT32(dptr, ruid); + ADD_U_INT32(dptr, rgid); + ADD_U_INT32(dptr, pid); + ADD_U_INT32(dptr, sid); + ADD_U_INT32(dptr, tid->port); + ADD_U_INT32(dptr, tid->machine); + + return (t); +} + +token_t * +au_to_subject64(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, + pid_t pid, au_asid_t sid, au_tid_t *tid) +{ + + return (NULL); +} + +token_t * +au_to_subject(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, gid_t rgid, + pid_t pid, au_asid_t sid, au_tid_t *tid) +{ + + return (au_to_subject32(auid, euid, egid, ruid, rgid, pid, sid, + tid)); +} + +/* + * token ID 1 byte + * audit ID 4 bytes + * effective user ID 4 bytes + * effective group ID 4 bytes + * real user ID 4 bytes + * real group ID 4 bytes + * process ID 4 bytes + * session ID 4 bytes + * terminal ID + * port ID 4 bytes/8 bytes (32-bit/64-bit value) + * address type/length 4 bytes + * machine address 16 bytes + */ +token_t * +au_to_subject32_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, + gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid) +{ + token_t *t; + u_char *dptr = NULL; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + 13 * sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_SUBJECT32_EX); + ADD_U_INT32(dptr, auid); + ADD_U_INT32(dptr, euid); + ADD_U_INT32(dptr, egid); + ADD_U_INT32(dptr, ruid); + ADD_U_INT32(dptr, rgid); + ADD_U_INT32(dptr, pid); + ADD_U_INT32(dptr, sid); + ADD_U_INT32(dptr, tid->at_port); + ADD_U_INT32(dptr, tid->at_type); + ADD_U_INT32(dptr, tid->at_addr[0]); + ADD_U_INT32(dptr, tid->at_addr[1]); + ADD_U_INT32(dptr, tid->at_addr[2]); + ADD_U_INT32(dptr, tid->at_addr[3]); + + return (t); +} + +token_t * +au_to_subject64_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, + gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid) +{ + + return (NULL); +} + +token_t * +au_to_subject_ex(au_id_t auid, uid_t euid, gid_t egid, uid_t ruid, + gid_t rgid, pid_t pid, au_asid_t sid, au_tid_addr_t *tid) +{ + + return (au_to_subject32_ex(auid, euid, egid, ruid, rgid, pid, sid, + tid)); +} + +#if !defined(_KERNEL) && !defined(KERNEL) +/* + * Collects audit information for the current process + * and creates a subject token from it + */ +token_t * +au_to_me(void) +{ + auditinfo_t auinfo; + + if (getaudit(&auinfo) != 0) + return (NULL); + + return (au_to_subject32(auinfo.ai_auid, geteuid(), getegid(), + getuid(), getgid(), getpid(), auinfo.ai_asid, &auinfo.ai_termid)); +} +#endif + +/* + * token ID 1 byte + * count 4 bytes + * text count null-terminated strings + */ +token_t * +au_to_exec_args(const char **args) +{ + token_t *t; + u_char *dptr = NULL; + const char *nextarg; + int i, count = 0; + size_t totlen = 0; + + nextarg = *args; + + while (nextarg != NULL) { + int nextlen; + + nextlen = strlen(nextarg); + totlen += nextlen + 1; + count++; + nextarg = *(args + count); + } + + totlen += count * sizeof(char); /* nul terminations. */ + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int32_t) + totlen); + + ADD_U_CHAR(dptr, AUT_EXEC_ARGS); + ADD_U_INT32(dptr, count); + + for (i = 0; i < count; i++) { + nextarg = *(args + i); + ADD_MEM(dptr, nextarg, strlen(nextarg) + 1); + } + + return (t); +} + +/* + * token ID 1 byte + * count 4 bytes + * text count null-terminated strings + */ +token_t * +au_to_exec_env(const char **env) +{ + token_t *t; + u_char *dptr = NULL; + int i, count = 0; + size_t totlen = 0; + const char *nextenv; + + nextenv = *env; + + while (nextenv != NULL) { + int nextlen; + + nextlen = strlen(nextenv); + totlen += nextlen + 1; + count++; + nextenv = *(env + count); + } + + totlen += sizeof(char) * count; + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int32_t) + totlen); + + ADD_U_CHAR(dptr, AUT_EXEC_ENV); + ADD_U_INT32(dptr, count); + + for (i = 0; i < count; i++) { + nextenv = *(env + i); + ADD_MEM(dptr, nextenv, strlen(nextenv) + 1); + } + + return (t); +} + +/* + * token ID 1 byte + * record byte count 4 bytes + * version # 1 byte [2] + * event type 2 bytes + * event modifier 2 bytes + * seconds of time 4 bytes/8 bytes (32-bit/64-bit value) + * milliseconds of time 4 bytes/8 bytes (32-bit/64-bit value) + */ +token_t * +#if defined(KERNEL) || defined(_KERNEL) +au_to_header32(int rec_size, au_event_t e_type, au_emod_t e_mod, + struct timeval tm) +#else +au_to_header32(int rec_size, au_event_t e_type, au_emod_t e_mod) +#endif +{ + token_t *t; + u_char *dptr = NULL; + u_int32_t timems; +#if !defined(KERNEL) && !defined(_KERNEL) + struct timeval tm; + struct timezone tzp; + + if (gettimeofday(&tm, &tzp) == -1) + return (NULL); +#endif + /* XXXRW: else ...? */ + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int32_t) + + sizeof(u_char) + 2 * sizeof(u_int16_t) + 2 * sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_HEADER32); + ADD_U_INT32(dptr, rec_size); + ADD_U_CHAR(dptr, HEADER_VERSION); + ADD_U_INT16(dptr, e_type); + ADD_U_INT16(dptr, e_mod); + + timems = tm.tv_usec/1000; + /* Add the timestamp */ + ADD_U_INT32(dptr, tm.tv_sec); + ADD_U_INT32(dptr, timems); /* We need time in ms. */ + + return (t); +} + +token_t * +au_to_header64(__unused int rec_size, __unused au_event_t e_type, + __unused au_emod_t e_mod) +{ + + return (NULL); +} + +token_t * +#if defined(KERNEL) || defined(_KERNEL) +au_to_header(int rec_size, au_event_t e_type, au_emod_t e_mod, + struct timeval tm) +{ + + return (au_to_header32(rec_size, e_type, e_mod, tm)); +} +#else +au_to_header(int rec_size, au_event_t e_type, au_emod_t e_mod) +{ + + return (au_to_header32(rec_size, e_type, e_mod)); +} +#endif + +/* + * token ID 1 byte + * trailer magic number 2 bytes + * record byte count 4 bytes + */ +token_t * +au_to_trailer(int rec_size) +{ + token_t *t; + u_char *dptr = NULL; + u_int16_t magic = TRAILER_PAD_MAGIC; + + GET_TOKEN_AREA(t, dptr, sizeof(u_char) + sizeof(u_int16_t) + + sizeof(u_int32_t)); + + ADD_U_CHAR(dptr, AUT_TRAILER); + ADD_U_INT16(dptr, magic); + ADD_U_INT32(dptr, rec_size); + + return (t); +} diff --git a/sys/security/audit/audit_private.h b/sys/security/audit/audit_private.h new file mode 100644 index 0000000..4d6d4b4 --- /dev/null +++ b/sys/security/audit/audit_private.h @@ -0,0 +1,300 @@ +/* + * Copyright (c) 1999-2005 Apple Computer, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * This include file contains function prototypes and type definitions used + * within the audit implementation. + */ + +#ifndef _BSM_AUDIT_PRIVATE_H +#define _BSM_AUDIT_PRIVATE_H + +#ifndef _KERNEL +#error "no user-serviceable parts inside" +#endif + +#include <sys/ipc.h> +#include <sys/socket.h> +#include <sys/ucred.h> + +#ifdef MALLOC_DECLARE +MALLOC_DECLARE(M_AUDITBSM); +MALLOC_DECLARE(M_AUDITDATA); +MALLOC_DECLARE(M_AUDITPATH); +MALLOC_DECLARE(M_AUDITTEXT); +#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; + +/* + * 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. + */ +#define AR_COMMIT_KERNEL 0x00000001U +#define AR_COMMIT_USER 0x00000010U + +/* + * Audit data is generated as a stream of struct audit_record structures, + * linked by struct kaudit_record, and contain storage for possible audit so + * that it will not need to be allocated during the processing of a system + * call, both improving efficiency and avoiding sleeping at untimely moments. + * This structure is converted to BSM format before being written to disk. + */ +struct vnode_au_info { + mode_t vn_mode; + uid_t vn_uid; + gid_t vn_gid; + dev_t vn_dev; + long vn_fsid; + long vn_fileid; + long vn_gen; +}; + +struct groupset { + gid_t gidset[NGROUPS]; + u_int gidset_size; +}; + +struct socket_au_info { + int so_domain; + int so_type; + int so_protocol; + in_addr_t so_raddr; /* remote address if INET socket */ + in_addr_t so_laddr; /* local address if INET socket */ + u_short so_rport; /* remote port */ + u_short so_lport; /* local port */ +}; + +union auditon_udata { + char *au_path; + long au_cond; + long au_flags; + long au_policy; + int au_trigger; + au_evclass_map_t au_evclass; + au_mask_t au_mask; + auditinfo_t au_auinfo; + auditpinfo_t au_aupinfo; + auditpinfo_addr_t au_aupinfo_addr; + au_qctrl_t au_qctrl; + au_stat_t au_stat; + au_fstat_t au_fstat; +}; + +struct posix_ipc_perm { + uid_t pipc_uid; + gid_t pipc_gid; + mode_t pipc_mode; +}; + +struct audit_record { + /* Audit record header. */ + u_int32_t ar_magic; + int ar_event; + int ar_retval; /* value returned to the process */ + int ar_errno; /* return status of system call */ + struct timespec ar_starttime; + struct timespec ar_endtime; + u_int64_t ar_valid_arg; /* Bitmask of valid arguments */ + + /* Audit subject information. */ + struct xucred ar_subj_cred; + uid_t ar_subj_ruid; + gid_t ar_subj_rgid; + gid_t ar_subj_egid; + uid_t ar_subj_auid; /* Audit user ID */ + pid_t ar_subj_asid; /* Audit session ID */ + pid_t ar_subj_pid; + struct au_tid ar_subj_term; + char ar_subj_comm[MAXCOMLEN + 1]; + struct au_mask ar_subj_amask; + + /* Operation arguments. */ + uid_t ar_arg_euid; + uid_t ar_arg_ruid; + uid_t ar_arg_suid; + gid_t ar_arg_egid; + gid_t ar_arg_rgid; + gid_t ar_arg_sgid; + pid_t ar_arg_pid; + pid_t ar_arg_asid; + struct au_tid ar_arg_termid; + uid_t ar_arg_uid; + uid_t ar_arg_auid; + gid_t ar_arg_gid; + struct groupset ar_arg_groups; + int ar_arg_fd; + int ar_arg_fflags; + mode_t ar_arg_mode; + int ar_arg_dev; + long ar_arg_value; + void * ar_arg_addr; + int ar_arg_len; + int ar_arg_mask; + u_int ar_arg_signum; + char ar_arg_login[MAXLOGNAME]; + int ar_arg_ctlname[CTL_MAXNAME]; + struct sockaddr ar_arg_sockaddr; + struct socket_au_info ar_arg_sockinfo; + char *ar_arg_upath1; + char *ar_arg_upath2; + char *ar_arg_text; + struct au_mask ar_arg_amask; + struct vnode_au_info ar_arg_vnode1; + struct vnode_au_info ar_arg_vnode2; + int ar_arg_cmd; + int ar_arg_svipc_cmd; + struct ipc_perm ar_arg_svipc_perm; + int ar_arg_svipc_id; + void * ar_arg_svipc_addr; + struct posix_ipc_perm ar_arg_pipc_perm; + union auditon_udata ar_arg_auditon; + int ar_arg_exitstatus; + int ar_arg_exitretval; +}; + +/* + * Arguments in the audit record are initially not defined; flags are set to + * indicate if they are present so they can be included in the audit log + * stream only if defined. + */ +#define ARG_IS_VALID(kar, arg) ((kar)->k_ar.ar_valid_arg & (arg)) +#define ARG_SET_VALID(kar, arg) do { \ + (kar)->k_ar.ar_valid_arg |= (arg); \ +} while (0) + +/* + * In-kernel version of audit record; the basic record plus queue meta-data. + * This record can also have a pointer set to some opaque data that will + * be passed through to the audit writing mechanism. + */ +struct kaudit_record { + struct audit_record k_ar; + u_int32_t k_ar_commit; + void *k_udata; /* user data */ + u_int k_ulen; /* user data length */ + struct uthread *k_uthread; /* thread we are auditing */ + TAILQ_ENTRY(kaudit_record) k_q; +}; + +/* + * Functions to manage the allocation, release, and commit of kernel audit + * records. + */ +void audit_abort(struct kaudit_record *ar); +void audit_commit(struct kaudit_record *ar, int error, + int retval); +struct kaudit_record *audit_new(int event, struct thread *td); + +/* + * Functions relating to the conversion of internal kernel audit records to + * the BSM file format. + */ +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) + +/* + * Flags to use on audit files when opening and closing. + */ +#define AUDIT_OPEN_FLAGS (FWRITE | O_APPEND) +#define AUDIT_CLOSE_FLAGS (FWRITE | O_APPEND) + +#include <sys/fcntl.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <security/audit/audit.h> + +/* + * 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_mask_t *mask_p, int sorf); +au_event_t flags_and_error_to_openevent(int oflags, int error); +void au_evclassmap_init(void); +void au_evclassmap_insert(au_event_t event, au_class_t class); +au_class_t au_event_class(au_event_t event); +au_event_t ctlname_to_sysctlevent(int name[], uint64_t valid_arg); +int auditon_command_event(int cmd); +int msgctl_to_event(int cmd); +int semctl_to_event(int cmr); +void canon_path(struct thread *td, char *path, char *cpath); + +/* + * Audit trigger events notify user space of kernel audit conditions + * asynchronously. + */ +void audit_trigger_init(void); +void send_trigger(unsigned int trigger); + +/* + * General audit related functions. + */ +struct kaudit_record *currecord(void); +void audit_shutdown(void *arg, int howto); +void audit_rotate_vnode(struct ucred *cred, + struct vnode *vp); + +#endif /* ! _BSM_AUDIT_PRIVATE_H */ diff --git a/sys/security/audit/audit_syscalls.c b/sys/security/audit/audit_syscalls.c new file mode 100644 index 0000000..19f1d30 --- /dev/null +++ b/sys/security/audit/audit_syscalls.c @@ -0,0 +1,652 @@ +/* + * Copyright (c) 1999-2005 Apple Computer, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/namei.h> +#include <sys/proc.h> +#include <sys/sysproto.h> +#include <sys/systm.h> +#include <sys/vnode.h> + +#include <bsm/audit.h> +#include <bsm/audit_kevents.h> +#include <security/audit/audit.h> +#include <security/audit/audit_private.h> + +#ifdef AUDIT + +/* + * MPSAFE + * + * System call to allow a user space application to submit a BSM audit + * record to the kernel for inclusion in the audit log. This function + * does little verification on the audit record that is submitted. + * + * XXXAUDIT: Audit preselection for user records does not currently + * work, since we pre-select only based on the AUE_audit event type, + * not the event type submitted as part of the user audit data. + */ +/* ARGSUSED */ +int +audit(struct thread *td, struct audit_args *uap) +{ + int error; + void * rec; + struct kaudit_record *ar; + + error = suser(td); + if (error) + return (error); + + if ((uap->length <= 0) || (uap->length > audit_qctrl.aq_bufsz)) + return (EINVAL); + + ar = currecord(); + + /* If there's no current audit record (audit() itself not audited) + * commit the user audit record. + */ + if (ar == NULL) { + + /* This is not very efficient; we're required to allocate + * a complete kernel audit record just so the user record + * can tag along. + * + * XXXAUDIT: Maybe AUE_AUDIT in the system call context and + * special pre-select handling? + */ + td->td_ar = audit_new(AUE_NULL, td); + if (td->td_ar == NULL) + return (ENOTSUP); + ar = td->td_ar; + } + + if (uap->length > MAX_AUDIT_RECORD_SIZE) + return (EINVAL); + + rec = malloc(uap->length, M_AUDITDATA, M_WAITOK); + + error = copyin(uap->record, rec, uap->length); + if (error) + goto free_out; + + /* Verify the record */ + if (bsm_rec_verify(rec) == 0) { + error = EINVAL; + goto free_out; + } + + /* Attach the user audit record to the kernel audit record. Because + * this system call is an auditable event, we will write the user + * record along with the record for this audit event. + * + * XXXAUDIT: KASSERT appropriate starting values of k_udata, k_ulen, + * k_ar_commit & AR_COMMIT_USER? + */ + ar->k_udata = rec; + ar->k_ulen = uap->length; + ar->k_ar_commit |= AR_COMMIT_USER; + return (0); + +free_out: + /* audit_syscall_exit() will free the audit record on the thread + * even if we allocated it above. + */ + free(rec, M_AUDITDATA); + return (error); +} + +/* + * MPSAFE + * + * System call to manipulate auditing. + */ +/* ARGSUSED */ +int +auditon(struct thread *td, struct auditon_args *uap) +{ + int error; + union auditon_udata udata; + struct proc *tp; + + AUDIT_ARG(cmd, uap->cmd); + error = suser(td); + if (error) + return (error); + + if ((uap->length <= 0) || (uap->length > sizeof(union auditon_udata))) + return (EINVAL); + + memset((void *)&udata, 0, sizeof(udata)); + + switch (uap->cmd) { + /* Some of the GET commands use the arguments too */ + case A_SETPOLICY: + case A_SETKMASK: + case A_SETQCTRL: + case A_SETSTAT: + case A_SETUMASK: + case A_SETSMASK: + case A_SETCOND: + case A_SETCLASS: + case A_SETPMASK: + case A_SETFSIZE: + case A_SETKAUDIT: + case A_GETCLASS: + case A_GETPINFO: + case A_GETPINFO_ADDR: + case A_SENDTRIGGER: + error = copyin(uap->data, (void *)&udata, uap->length); + if (error) + return (error); + AUDIT_ARG(auditon, &udata); + break; + } + + /* XXX Need to implement these commands by accessing the global + * values associated with the commands. + * + * XXXAUDIT: Locking? + */ + switch (uap->cmd) { + case A_GETPOLICY: + if (!audit_fail_stop) + udata.au_policy |= AUDIT_CNT; + if (audit_panic_on_write_fail) + udata.au_policy |= AUDIT_AHLT; + break; + + case A_SETPOLICY: + if (udata.au_policy & ~(AUDIT_CNT|AUDIT_AHLT)) + 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); + break; + + case A_GETKMASK: + udata.au_mask = audit_nae_mask; + break; + + case A_SETKMASK: + audit_nae_mask = udata.au_mask; + break; + + case A_GETQCTRL: + udata.au_qctrl = audit_qctrl; + break; + + case A_SETQCTRL: + if ((udata.au_qctrl.aq_hiwater > AQ_MAXHIGH) || + (udata.au_qctrl.aq_lowater >= udata.au_qctrl.aq_hiwater) || + (udata.au_qctrl.aq_bufsz > AQ_MAXBUFSZ) || + (udata.au_qctrl.aq_minfree < 0) || + (udata.au_qctrl.aq_minfree > 100)) + return (EINVAL); + + audit_qctrl = udata.au_qctrl; + /* XXX The queue delay value isn't used with the kernel. */ + audit_qctrl.aq_delay = -1; + break; + + case A_GETCWD: + return (ENOSYS); + break; + + case A_GETCAR: + return (ENOSYS); + break; + + case A_GETSTAT: + return (ENOSYS); + break; + + case A_SETSTAT: + return (ENOSYS); + break; + + case A_SETUMASK: + return (ENOSYS); + break; + + case A_SETSMASK: + return (ENOSYS); + break; + + case A_GETCOND: + if (audit_enabled && !audit_suspended) + udata.au_cond = AUC_AUDITING; + else + udata.au_cond = AUC_NOAUDIT; + break; + + case A_SETCOND: + if (udata.au_cond == AUC_NOAUDIT) + audit_suspended = 1; + if (udata.au_cond == AUC_AUDITING) + audit_suspended = 0; + if (udata.au_cond == AUC_DISABLED) { + audit_suspended = 1; + audit_shutdown(NULL, 0); + } + break; + + case A_GETCLASS: + udata.au_evclass.ec_class = + au_event_class(udata.au_evclass.ec_number); + break; + + case A_SETCLASS: + au_evclassmap_insert(udata.au_evclass.ec_number, + udata.au_evclass.ec_class); + break; + + case A_GETPINFO: + if (udata.au_aupinfo.ap_pid < 1) + return (EINVAL); + + /* XXXAUDIT: p_cansee()? */ + if ((tp = pfind(udata.au_aupinfo.ap_pid)) == NULL) + return (EINVAL); + + udata.au_aupinfo.ap_auid = tp->p_au->ai_auid; + udata.au_aupinfo.ap_mask.am_success = + tp->p_au->ai_mask.am_success; + udata.au_aupinfo.ap_mask.am_failure = + tp->p_au->ai_mask.am_failure; + udata.au_aupinfo.ap_termid.machine = + tp->p_au->ai_termid.machine; + udata.au_aupinfo.ap_termid.port = + tp->p_au->ai_termid.port; + udata.au_aupinfo.ap_asid = tp->p_au->ai_asid; + PROC_UNLOCK(tp); + break; + + case A_SETPMASK: + if (udata.au_aupinfo.ap_pid < 1) + return (EINVAL); + + /* XXXAUDIT: p_cansee()? */ + if ((tp = pfind(udata.au_aupinfo.ap_pid)) == NULL) + return (EINVAL); + + tp->p_au->ai_mask.am_success = + udata.au_aupinfo.ap_mask.am_success; + tp->p_au->ai_mask.am_failure = + udata.au_aupinfo.ap_mask.am_failure; + PROC_UNLOCK(tp); + break; + + case A_SETFSIZE: + if ((udata.au_fstat.af_filesz != 0) && + (udata.au_fstat.af_filesz < MIN_AUDIT_FILE_SIZE)) + return (EINVAL); + audit_fstat.af_filesz = udata.au_fstat.af_filesz; + break; + + case A_GETFSIZE: + udata.au_fstat.af_filesz = audit_fstat.af_filesz; + udata.au_fstat.af_currsz = audit_fstat.af_currsz; + break; + + case A_GETPINFO_ADDR: + return (ENOSYS); + break; + + case A_GETKAUDIT: + return (ENOSYS); + break; + + case A_SETKAUDIT: + return (ENOSYS); + break; + + case A_SENDTRIGGER: + if ((udata.au_trigger < AUDIT_TRIGGER_MIN) || + (udata.au_trigger > AUDIT_TRIGGER_MAX)) + return (EINVAL); + send_trigger(udata.au_trigger); + break; + } + /* Copy data back to userspace for the GET comands */ + switch (uap->cmd) { + case A_GETPOLICY: + case A_GETKMASK: + case A_GETQCTRL: + case A_GETCWD: + case A_GETCAR: + case A_GETSTAT: + case A_GETCOND: + case A_GETCLASS: + case A_GETPINFO: + case A_GETFSIZE: + case A_GETPINFO_ADDR: + case A_GETKAUDIT: + error = copyout((void *)&udata, uap->data, uap->length); + if (error) + return (error); + break; + } + + return (0); +} + +/* + * MPSAFE + * + * System calls to manage the user audit information. + */ +/* ARGSUSED */ +int +getauid(struct thread *td, struct getauid_args *uap) +{ + int error; + au_id_t id; + + error = suser(td); + if (error) + return (error); + + /* + * XXX: + * Integer read on static pointer dereference: doesn't need locking? + */ + PROC_LOCK(td->td_proc); + id = td->td_proc->p_au->ai_auid; + PROC_UNLOCK(td->td_proc); + return copyout(&id, uap->auid, sizeof(id)); +} + +/* MPSAFE */ +/* ARGSUSED */ +int +setauid(struct thread *td, struct setauid_args *uap) +{ + int error; + au_id_t id; + + error = suser(td); + if (error) + return (error); + + error = copyin(uap->auid, &id, sizeof(id)); + if (error) + return (error); + + audit_arg_auid(id); + + /* + * XXX: + * Integer write on static pointer dereference: doesn't need locking? + * + * XXXAUDIT: Might need locking to serialize audit events in the same + * order as change events? Or maybe that's an under-solveable + * problem. + * + * XXXRW: Test privilege while holding the proc lock? + */ + PROC_LOCK(td->td_proc); + td->td_proc->p_au->ai_auid = id; + PROC_UNLOCK(td->td_proc); + + return (0); +} + +/* + * MPSAFE + * System calls to get and set process audit information. + */ +/* ARGSUSED */ +int +getaudit(struct thread *td, struct getaudit_args *uap) +{ + struct auditinfo ai; + int error; + + error = suser(td); + if (error) + return (error); + + PROC_LOCK(td->td_proc); + ai = *td->td_proc->p_au; + PROC_UNLOCK(td->td_proc); + + return (copyout(&ai, uap->auditinfo, sizeof(ai))); +} + +/* MPSAFE */ +/* ARGSUSED */ +int +setaudit(struct thread *td, struct setaudit_args *uap) +{ + struct auditinfo ai; + int error; + + error = suser(td); + if (error) + return (error); + + error = copyin(uap->auditinfo, &ai, sizeof(ai)); + if (error) + return (error); + + audit_arg_auditinfo(&ai); + + /* + * XXXRW: Test privilege while holding the proc lock? + */ + PROC_LOCK(td->td_proc); + *td->td_proc->p_au = ai; + PROC_UNLOCK(td->td_proc); + + return (0); +} + +/* MPSAFE */ +/* ARGSUSED */ +int +getaudit_addr(struct thread *td, struct getaudit_addr_args *uap) +{ + int error; + + error = suser(td); + if (error) + return (error); + return (ENOSYS); +} + +/* MPSAFE */ +/* ARGSUSED */ +int +setaudit_addr(struct thread *td, struct setaudit_addr_args *uap) +{ + int error; + + error = suser(td); + if (error) + return (error); + return (ENOSYS); +} + +/* + * MPSAFE + * Syscall to manage audit files. + * + * XXX: Should generate an audit event. + */ +/* ARGSUSED */ +int +auditctl(struct thread *td, struct auditctl_args *uap) +{ + struct nameidata nd; + struct ucred *cred; + struct vnode *vp; + int error = 0; + int flags; + + error = suser(td); + if (error) + return (error); + + vp = NULL; + cred = NULL; + + /* + * If a path is specified, open the replacement vnode, perform + * validity checks, and grab another reference to the current + * credential. + * + * XXXAUDIT: On Darwin, a NULL path is used to disable audit. + */ + if (uap->path == NULL) + return (EINVAL); + + /* + * XXXAUDIT: Giant may no longer be required here. + */ + mtx_lock(&Giant); + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_USERSPACE, uap->path, td); + flags = AUDIT_OPEN_FLAGS; + error = vn_open(&nd, &flags, 0, -1); + if (error) { + mtx_unlock(&Giant); + goto err_out; + } + VOP_UNLOCK(nd.ni_vp, 0, td); + vp = nd.ni_vp; + if (vp->v_type != VREG) { + vn_close(vp, AUDIT_CLOSE_FLAGS, td->td_ucred, td); + mtx_unlock(&Giant); + error = EINVAL; + goto err_out; + } + cred = td->td_ucred; + crhold(cred); + + /* + * XXXAUDIT: Should audit_suspended actually be cleared by + * audit_worker? + */ + audit_suspended = 0; + + mtx_unlock(&Giant); + audit_rotate_vnode(cred, vp); + +err_out: + return (error); +} + +#else /* !AUDIT */ + +int +audit(struct thread *td, struct audit_args *uap) +{ + + return (ENOSYS); +} + +int +auditon(struct thread *td, struct auditon_args *uap) +{ + + return (ENOSYS); +} + +int +getauid(struct thread *td, struct getauid_args *uap) +{ + + return (ENOSYS); +} + +int +setauid(struct thread *td, struct setauid_args *uap) +{ + + return (ENOSYS); +} + +int +getaudit(struct thread *td, struct getaudit_args *uap) +{ + + return (ENOSYS); +} + +int +setaudit(struct thread *td, struct setaudit_args *uap) +{ + + return (ENOSYS); +} + +int +getaudit_addr(struct thread *td, struct getaudit_addr_args *uap) +{ + + return (ENOSYS); +} + +int +setaudit_addr(struct thread *td, struct setaudit_addr_args *uap) +{ + + return (ENOSYS); +} + +int +auditctl(struct thread *td, struct auditctl_args *uap) +{ + + return (ENOSYS); +} + +void +audit_proc_init(struct proc *p) +{ + +} + +void +audit_proc_fork(struct proc *parent, struct proc *child) +{ + +} + +void +audit_proc_free(struct proc *p) +{ + +} + +#endif /* AUDIT */ diff --git a/sys/security/audit/audit_trigger.c b/sys/security/audit/audit_trigger.c new file mode 100644 index 0000000..081858b --- /dev/null +++ b/sys/security/audit/audit_trigger.c @@ -0,0 +1,172 @@ +/*- + * Copyright (c) 2005 Wayne J. Salamon + * All rights reserved. + * + * This software was developed by Wayne Salamon for the TrustedBSD Project. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/proc.h> +#include <sys/queue.h> +#include <sys/systm.h> +#include <sys/uio.h> + +#include <security/audit/audit.h> +#include <security/audit/audit_private.h> + +/* + * Structures and operations to support the basic character special device + * used to communicate with userland. + */ +struct trigger_info { + unsigned int trigger; + TAILQ_ENTRY(trigger_info) list; +}; +static MALLOC_DEFINE(M_AUDITTRIGGER, "audit_trigger", "Audit trigger events"); +static struct cdev *audit_dev; +static int audit_isopen = 0; +static TAILQ_HEAD(, trigger_info) trigger_list; +static struct mtx audit_trigger_mtx; + +static int +audit_open(struct cdev *dev, int oflags, int devtype, struct thread *td) +{ + int error; + + // Only one process may open the device at a time + mtx_lock(&audit_trigger_mtx); + if (!audit_isopen) { + error = 0; + audit_isopen = 1; + } else + error = EBUSY; + mtx_unlock(&audit_trigger_mtx); + + return (error); +} + +static int +audit_close(struct cdev *dev, int fflag, int devtype, struct thread *td) +{ + struct trigger_info *ti; + + /* Flush the queue of pending trigger events. */ + mtx_lock(&audit_trigger_mtx); + audit_isopen = 0; + while (!TAILQ_EMPTY(&trigger_list)) { + ti = TAILQ_FIRST(&trigger_list); + TAILQ_REMOVE(&trigger_list, ti, list); + free(ti, M_AUDITTRIGGER); + } + mtx_unlock(&audit_trigger_mtx); + + return (0); +} + +static int +audit_read(struct cdev *dev, struct uio *uio, int ioflag) +{ + int error = 0; + struct trigger_info *ti = NULL; + + mtx_lock(&audit_trigger_mtx); + while (TAILQ_EMPTY(&trigger_list)) { + error = msleep(&trigger_list, &audit_trigger_mtx, + PSOCK | PCATCH, "auditd", 0); + if (error) + break; + } + if (!error) { + ti = TAILQ_FIRST(&trigger_list); + TAILQ_REMOVE(&trigger_list, ti, list); + } + mtx_unlock(&audit_trigger_mtx); + if (!error) { + error = uiomove(ti, sizeof *ti, uio); + free(ti, M_AUDITTRIGGER); + } + return (error); +} + +static int +audit_write(struct cdev *dev, struct uio *uio, int ioflag) +{ + + /* Communication is kernel->userspace only. */ + return (EOPNOTSUPP); +} + +void +send_trigger(unsigned int trigger) +{ + struct trigger_info *ti; + + /* If nobody's listening, we ain't talking. */ + if (!audit_isopen) + return; + + /* + * XXXAUDIT: Use a condition variable instead of msleep/wakeup? + */ + ti = malloc(sizeof *ti, M_AUDITTRIGGER, M_WAITOK); + mtx_lock(&audit_trigger_mtx); + ti->trigger = trigger; + TAILQ_INSERT_TAIL(&trigger_list, ti, list); + wakeup(&trigger_list); + mtx_unlock(&audit_trigger_mtx); +} + +static struct cdevsw audit_cdevsw = { + .d_version = D_VERSION, + .d_open = audit_open, + .d_close = audit_close, + .d_read = audit_read, + .d_write = audit_write, + .d_name = "audit" +}; + +void +audit_trigger_init(void) +{ + + TAILQ_INIT(&trigger_list); + mtx_init(&audit_trigger_mtx, "audit_trigger_mtx", NULL, MTX_DEF); +} + +static void +audit_trigger_cdev_init(void *unused) +{ + + /* Create the special device file. */ + audit_dev = make_dev(&audit_cdevsw, 0, UID_ROOT, GID_KMEM, 0600, + AUDITDEV_FILENAME); +} + +SYSINIT(audit_trigger_cdev_init, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, + audit_trigger_cdev_init, NULL); |