diff options
Diffstat (limited to 'sys/security/audit')
-rw-r--r-- | sys/security/audit/audit.c | 13 | ||||
-rw-r--r-- | sys/security/audit/audit_pipe.c | 532 | ||||
-rw-r--r-- | sys/security/audit/audit_private.h | 5 |
3 files changed, 550 insertions, 0 deletions
diff --git a/sys/security/audit/audit.c b/sys/security/audit/audit.c index 96da28c..3ddad11 100644 --- a/sys/security/audit/audit.c +++ b/sys/security/audit/audit.c @@ -372,6 +372,14 @@ audit_record_write(struct vnode *vp, struct kaudit_record *ar, * we ignore errors. */ if (ar->k_ar_commit & AR_COMMIT_USER) { + /* + * Try submitting the record to any active audit pipes. + */ + audit_pipe_submit((void *)ar->k_udata, ar->k_ulen); + + /* + * And to disk. + */ 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); @@ -407,6 +415,11 @@ audit_record_write(struct vnode *vp, struct kaudit_record *ar, ret = EINVAL; goto out; } + + /* + * Try submitting the record to any active audit pipes. + */ + audit_pipe_submit((void *)bsm->data, bsm->len); /* * XXX diff --git a/sys/security/audit/audit_pipe.c b/sys/security/audit/audit_pipe.c new file mode 100644 index 0000000..25c6a7e --- /dev/null +++ b/sys/security/audit/audit_pipe.c @@ -0,0 +1,532 @@ +/*- + * Copyright (c) 2006 Robert N. M. Watson + * All rights reserved. + * + * This software was developed by Robert Watson for the TrustedBSD Project. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/condvar.h> +#include <sys/conf.h> +#include <sys/eventhandler.h> +#include <sys/filio.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/mutex.h> +#include <sys/poll.h> +#include <sys/proc.h> +#include <sys/queue.h> +#include <sys/selinfo.h> +#include <sys/sigio.h> +#include <sys/signal.h> +#include <sys/signalvar.h> +#include <sys/systm.h> +#include <sys/uio.h> + +#include <security/audit/audit.h> +#include <security/audit/audit_private.h> + +/* + * Implementation of a clonable special device providing a live stream of BSM + * audit data. This is a "tee" of the data going to the file. It provides + * unreliable but timely access to audit events. Consumers of this interface + * should be very careful to avoid introducing event cycles. + */ + +/* + * Memory types. + */ +static MALLOC_DEFINE(M_AUDIT_PIPE, "audit_pipe", "Audit pipes"); +static MALLOC_DEFINE(M_AUDIT_PIPE_ENTRY, "audit_pipeent", + "Audit pipe entries and buffers"); + +/* + * Audit pipe buffer parameters. + */ +#define AUDIT_PIPE_QLIMIT_DEFAULT (32) +#define AUDIT_PIPE_QLIMIT_MAX (1024) + +/* + * Description of an entry in an audit_pipe. + */ +struct audit_pipe_entry { + void *ape_record; + u_int ape_record_len; + TAILQ_ENTRY(audit_pipe_entry) ape_queue; +}; + +/* + * Description of an individual audit_pipe. Consists largely of a bounded + * length queue. + */ +#define AUDIT_PIPE_ASYNC 0x00000001 +#define AUDIT_PIPE_NBIO 0x00000002 +struct audit_pipe { + int ap_open; /* Device open? */ + u_int ap_flags; + + struct selinfo ap_selinfo; + struct sigio *ap_sigio; + + u_int ap_qlen; + u_int ap_qlimit; + + u_int64_t ap_inserts; /* Records added. */ + u_int64_t ap_reads; /* Records read. */ + u_int64_t ap_drops; /* Records dropped. */ + u_int64_t ap_truncates; /* Records too long. */ + + TAILQ_HEAD(, audit_pipe_entry) ap_queue; + + TAILQ_ENTRY(audit_pipe) ap_list; +}; + +/* + * Global list of audit pipes, mutex to protect it and the pipes. Finder + * grained locking may be desirable at some point. + */ +static TAILQ_HEAD(, audit_pipe) audit_pipe_list; +static struct mtx audit_pipe_mtx; + +/* + * This CV is used to wakeup on an audit record write. Eventually, it should + * probably be per-pipe. + */ +static struct cv audit_pipe_cv; + +/* + * Cloning related variables and constants. + */ +#define AUDIT_PIPE_NAME "auditpipe" +static eventhandler_tag audit_pipe_eh_tag; +static struct clonedevs *audit_pipe_clones; + +/* + * Special device methods and definition. + */ +static d_open_t audit_pipe_open; +static d_close_t audit_pipe_close; +static d_read_t audit_pipe_read; +static d_ioctl_t audit_pipe_ioctl; +static d_poll_t audit_pipe_poll; + +static struct cdevsw audit_pipe_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_PSEUDO, + .d_open = audit_pipe_open, + .d_close = audit_pipe_close, + .d_read = audit_pipe_read, + .d_ioctl = audit_pipe_ioctl, + .d_poll = audit_pipe_poll, + .d_name = AUDIT_PIPE_NAME, +}; + +/* + * Some global statistics on audit pipes. + */ +static int audit_pipe_count; /* Current number of pipes. */ +static u_int64_t audit_pipe_ever; /* Pipes ever allocated. */ +static u_int64_t audit_pipe_records; /* Records seen. */ +static u_int64_t audit_pipe_drops; /* Global record drop count. */ + +/* + * Free an audit pipe entry. + */ +static void +audit_pipe_entry_free(struct audit_pipe_entry *ape) +{ + + free(ape->ape_record, M_AUDIT_PIPE_ENTRY); + free(ape, M_AUDIT_PIPE_ENTRY); +} + +/* + * Apparent individual record to a queue -- allocate queue-local buffer, and + * add to the queue. We try to drop from the head of the queue so that more + * recent events take precedence over older ones, but if allocation fails we + * do drop the new event. + */ +static void +audit_pipe_append(struct audit_pipe *ap, void *record, u_int record_len) +{ + struct audit_pipe_entry *ape, *ape_remove; + + mtx_assert(&audit_pipe_mtx, MA_OWNED); + + ape = malloc(sizeof(*ape), M_AUDIT_PIPE_ENTRY, M_NOWAIT | M_ZERO); + if (ape == NULL) { + ap->ap_drops++; + return; + } + + ape->ape_record = malloc(record_len, M_AUDIT_PIPE_ENTRY, M_NOWAIT); + if (ape->ape_record == NULL) { + free(ape, M_AUDIT_PIPE_ENTRY); + ap->ap_drops++; + audit_pipe_drops++; + return; + } + + bcopy(record, ape->ape_record, record_len); + ape->ape_record_len = record_len; + + if (ap->ap_qlen >= ap->ap_qlimit) { + ape_remove = TAILQ_FIRST(&ap->ap_queue); + TAILQ_REMOVE(&ap->ap_queue, ape_remove, ape_queue); + audit_pipe_entry_free(ape_remove); + ap->ap_drops++; + audit_pipe_drops++; + } + + TAILQ_INSERT_TAIL(&ap->ap_queue, ape, ape_queue); + ap->ap_inserts++; + ap->ap_qlen++; + selwakeuppri(&ap->ap_selinfo, PSOCK); + if (ap->ap_flags & AUDIT_PIPE_ASYNC) + pgsigio(&ap->ap_sigio, SIGIO, 0); +} + +/* + * audit_pipe_submit(): audit_worker submits audit records via this + * interface, which arranges for them to be delivered to pipe queues. + */ +void +audit_pipe_submit(void *record, u_int record_len) +{ + struct audit_pipe *ap; + + /* + * Lockless read to avoid mutex overhead if pipes are not in use. + */ + if (TAILQ_FIRST(&audit_pipe_list) == NULL) + return; + + mtx_lock(&audit_pipe_mtx); + TAILQ_FOREACH(ap, &audit_pipe_list, ap_list) + audit_pipe_append(ap, record, record_len); + audit_pipe_records++; + mtx_unlock(&audit_pipe_mtx); + cv_signal(&audit_pipe_cv); +} + +/* + * Read the next record off of an audit pipe. + */ +static struct audit_pipe_entry * +audit_pipe_pop(struct audit_pipe *ap) +{ + struct audit_pipe_entry *ape; + + mtx_assert(&audit_pipe_mtx, MA_OWNED); + + ape = TAILQ_FIRST(&ap->ap_queue); + KASSERT((ape == NULL && ap->ap_qlen == 0) || + (ape != NULL && ap->ap_qlen != 0), ("audit_pipe_pop: qlen")); + if (ape == NULL) + return (NULL); + TAILQ_REMOVE(&ap->ap_queue, ape, ape_queue); + ap->ap_qlen--; + return (ape); +} + +/* + * Allocate a new audit pipe. Connects the pipe, on success, to the global + * list and updates statistics. + */ +static struct audit_pipe * +audit_pipe_alloc(void) +{ + struct audit_pipe *ap; + + mtx_assert(&audit_pipe_mtx, MA_OWNED); + + ap = malloc(sizeof(*ap), M_AUDIT_PIPE, M_NOWAIT | M_ZERO); + if (ap == NULL) + return (NULL); + ap->ap_qlimit = AUDIT_PIPE_QLIMIT_DEFAULT; + TAILQ_INIT(&ap->ap_queue); + TAILQ_INSERT_HEAD(&audit_pipe_list, ap, ap_list); + audit_pipe_count++; + audit_pipe_ever++; + return (ap); +} + +/* + * Free an audit pipe. Assumes mutex is held, audit_pipe is still on the + * global list. Frees any audit pipe entries in the queue. + */ +static void +audit_pipe_free(struct audit_pipe *ap) +{ + struct audit_pipe_entry *ape; + + mtx_assert(&audit_pipe_mtx, MA_OWNED); + + TAILQ_REMOVE(&audit_pipe_list, ap, ap_list); + while ((ape = TAILQ_FIRST(&ap->ap_queue)) != NULL) { + TAILQ_REMOVE(&ap->ap_queue, ape, ape_queue); + audit_pipe_entry_free(ape); + ap->ap_qlen--; + } + KASSERT(ap->ap_qlen == 0, ("audit_pipe_free: ap_qlen")); + free(ap, M_AUDIT_PIPE); + audit_pipe_count--; +} + +/* + * Audit pipe clone routine -- provide specific requested audit pipe, or a + * fresh one if a specific one is not requested. + */ +static void +audit_pipe_clone(void *arg, struct ucred *cred, char *name, int namelen, + struct cdev **dev) +{ + int i, u; + + if (*dev != NULL) + return; + + if (strcmp(name, AUDIT_PIPE_NAME) == 0) + u = -1; + else if (dev_stdclone(name, NULL, AUDIT_PIPE_NAME, &u) != 1) + return; + + i = clone_create(&audit_pipe_clones, &audit_pipe_cdevsw, &u, dev, 0); + if (i) { + *dev = make_dev(&audit_pipe_cdevsw, unit2minor(u), UID_ROOT, + GID_WHEEL, 0600, "%s%d", AUDIT_PIPE_NAME, u); + if (*dev != NULL) { + dev_ref(*dev); + (*dev)->si_flags |= SI_CHEAPCLONE; + } + } +} + +/* + * Audit pipe open method. Explicit suser check isn't used as this allows + * file permissions on the special device to be used to grant audit review + * access. + */ +static int +audit_pipe_open(struct cdev *dev, int oflags, int devtype, struct thread *td) +{ + struct audit_pipe *ap; + + mtx_lock(&audit_pipe_mtx); + ap = dev->si_drv1; + if (ap == NULL) { + ap = audit_pipe_alloc(); + if (ap == NULL) { + mtx_unlock(&audit_pipe_mtx); + return (ENOMEM); + } + dev->si_drv1 = ap; + } else { + KASSERT(ap->ap_open, ("audit_pipe_open: ap && !ap_open")); + mtx_unlock(&audit_pipe_mtx); + return (EBUSY); + } + ap->ap_open = 1; + mtx_unlock(&audit_pipe_mtx); + fsetown(td->td_proc->p_pid, &ap->ap_sigio); + return (0); +} + +/* + * Close audit pipe, tear down all records, etc. + */ +static int +audit_pipe_close(struct cdev *dev, int fflag, int devtype, struct thread *td) +{ + struct audit_pipe *ap; + + ap = dev->si_drv1; + KASSERT(ap != NULL, ("audit_pipe_close: ap == NULL")); + KASSERT(ap->ap_open, ("audit_pipe_close: !ap_open")); + funsetown(&ap->ap_sigio); + mtx_lock(&audit_pipe_mtx); + ap->ap_open = 0; + audit_pipe_free(ap); + dev->si_drv1 = NULL; + mtx_unlock(&audit_pipe_mtx); + return (0); +} + +/* + * Audit pipe ioctl() routine. Nothing for now, but eventually will allow + * setting and retrieval of current queue depth, queue limit, flush, etc. + * + * Would be desirable to support filtering, although perhaps something simple + * like an event mask, as opposed to something complicated like BPF. + */ +static int +audit_pipe_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, + struct thread *td) +{ + struct audit_pipe *ap; + int error; + + ap = dev->si_drv1; + KASSERT(ap != NULL, ("audit_pipe_ioctl: ap == NULL")); + switch (cmd) { + case FIONBIO: + mtx_lock(&audit_pipe_mtx); + if (*(int *)data) + ap->ap_flags |= AUDIT_PIPE_NBIO; + else + ap->ap_flags &= ~AUDIT_PIPE_NBIO; + mtx_unlock(&audit_pipe_mtx); + error = 0; + break; + + case FIONREAD: + mtx_lock(&audit_pipe_mtx); + if (TAILQ_FIRST(&ap->ap_queue) != NULL) + *(int *)data = + TAILQ_FIRST(&ap->ap_queue)->ape_record_len; + else + *(int *)data = 0; + mtx_unlock(&audit_pipe_mtx); + error = 0; + break; + + case FIOASYNC: + mtx_lock(&audit_pipe_mtx); + if (*(int *)data) + ap->ap_flags |= AUDIT_PIPE_ASYNC; + else + ap->ap_flags &= ~AUDIT_PIPE_ASYNC; + mtx_unlock(&audit_pipe_mtx); + error = 0; + break; + + case FIOSETOWN: + error = fsetown(*(int *)data, &ap->ap_sigio); + break; + + case FIOGETOWN: + *(int *)data = fgetown(&ap->ap_sigio); + error = 0; + + default: + error = ENOTTY; + } + return (error); +} + +/* + * Audit pipe read. Pull one record off the queue and copy to user space. + * On error, the record is dropped. + */ +static int +audit_pipe_read(struct cdev *dev, struct uio *uio, int flag) +{ + struct audit_pipe_entry *ape; + struct audit_pipe *ap; + int error; + + ap = dev->si_drv1; + KASSERT(ap != NULL, ("audit_pipe_read: ap == NULL")); + mtx_lock(&audit_pipe_mtx); + do { + /* + * Wait for a record that fits into the read buffer, dropping + * records that would be truncated if actually passed to the + * process. This helps maintain the discreet record read + * interface. + */ + while ((ape = audit_pipe_pop(ap)) == NULL) { + if (ap->ap_flags & AUDIT_PIPE_NBIO) { + mtx_unlock(&audit_pipe_mtx); + return (EAGAIN); + } + error = cv_wait_sig(&audit_pipe_cv, &audit_pipe_mtx); + if (error) { + mtx_unlock(&audit_pipe_mtx); + return (error); + } + } + if (ape->ape_record_len <= uio->uio_resid) + break; + audit_pipe_entry_free(ape); + ap->ap_truncates++; + } while (1); + mtx_unlock(&audit_pipe_mtx); + + /* + * Now read record to user space memory. Even if the read is short, + * we abandon the remainder of the record, supporting only discreet + * record reads. + */ + error = uiomove(ape->ape_record, ape->ape_record_len, uio); + audit_pipe_entry_free(ape); + return (error); +} + +/* + * Audit pipe poll. + */ +static int +audit_pipe_poll(struct cdev *dev, int events, struct thread *td) +{ + struct audit_pipe *ap; + int revents; + + revents = 0; + ap = dev->si_drv1; + KASSERT(ap != NULL, ("audit_pipe_poll: ap == NULL")); + if (events & (POLLIN | POLLRDNORM)) { + mtx_lock(&audit_pipe_mtx); + if (TAILQ_FIRST(&ap->ap_queue) != NULL) + revents |= events & (POLLIN | POLLRDNORM); + else + selrecord(td, &ap->ap_selinfo); + mtx_unlock(&audit_pipe_mtx); + } + return (revents); +} + +/* + * Initialize the audit pipe system. + */ +static void +audit_pipe_init(void *unused) +{ + + TAILQ_INIT(&audit_pipe_list); + mtx_init(&audit_pipe_mtx, "audit_pipe_mtx", NULL, MTX_DEF); + cv_init(&audit_pipe_cv, "audit_pipe_cv"); + + clone_setup(&audit_pipe_clones); + audit_pipe_eh_tag = EVENTHANDLER_REGISTER(dev_clone, + audit_pipe_clone, 0, 1000); + if (audit_pipe_eh_tag == NULL) + panic("audit_pipe_init: EVENTHANDLER_REGISTER"); +} + +SYSINIT(audit_pipe_init, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, audit_pipe_init, + NULL); diff --git a/sys/security/audit/audit_private.h b/sys/security/audit/audit_private.h index 4d6d4b4..c23289f 100644 --- a/sys/security/audit/audit_private.h +++ b/sys/security/audit/audit_private.h @@ -297,4 +297,9 @@ void audit_shutdown(void *arg, int howto); void audit_rotate_vnode(struct ucred *cred, struct vnode *vp); +/* + * Audit pipe functions. + */ +void audit_pipe_submit(void *record, u_int record_len); + #endif /* ! _BSM_AUDIT_PRIVATE_H */ |