+/*
+ * Audit session device.
+ */
+
+/*
+ * Free an audit sdev entry.
+ */
+static void
+audit_sdev_entry_free(struct audit_sdev_entry *ase)
+{
+
+ free(ase->ase_record, M_AUDIT_SDEV_ENTRY);
+ free(ase, M_AUDIT_SDEV_ENTRY);
+}
+
+/*
+ * Append individual record to a queue. Allocate queue-local buffer and
+ * add to the queue. If the queue is full or we can't allocate memory,
+ * drop the newest record.
+ */
+static void
+audit_sdev_append(struct audit_sdev *asdev, void *record, u_int record_len)
+{
+ struct audit_sdev_entry *ase;
+
+ AUDIT_SDEV_LOCK_ASSERT(asdev);
+
+ if (asdev->asdev_qlen >= asdev->asdev_qlimit) {
+ asdev->asdev_drops++;
+ audit_sdev_drops++;
+ return;
+ }
+
+ ase = malloc(sizeof (*ase), M_AUDIT_SDEV_ENTRY, M_NOWAIT | M_ZERO);
+ if (NULL == ase) {
+ asdev->asdev_drops++;
+ audit_sdev_drops++;
+ return;
+ }
+
+ ase->ase_record = malloc(record_len, M_AUDIT_SDEV_ENTRY, M_NOWAIT);
+ if (NULL == ase->ase_record) {
+ free(ase, M_AUDIT_SDEV_ENTRY);
+ asdev->asdev_drops++;
+ audit_sdev_drops++;
+ return;
+ }
+
+ bcopy(record, ase->ase_record, record_len);
+ ase->ase_record_len = record_len;
+
+ TAILQ_INSERT_TAIL(&asdev->asdev_queue, ase, ase_queue);
+ asdev->asdev_inserts++;
+ asdev->asdev_qlen++;
+ asdev->asdev_qbyteslen += ase->ase_record_len;
+ selwakeup(&asdev->asdev_selinfo);
+ if (asdev->asdev_flags & AUDIT_SDEV_ASYNC)
+ pgsigio(asdev->asdev_sigio, SIGIO);
+
+ cv_broadcast(&asdev->asdev_cv);
+}
+
+/*
+ * Submit an audit record to be queued in the audit session device.
+ */
+void
+audit_sdev_submit(__unused au_id_t auid, __unused au_asid_t asid, void *record,
+ u_int record_len)
+{
+ struct audit_sdev *asdev;
+
+ /*
+ * Lockless read to avoid lock overhead if sessio devices are not in
+ * use.
+ */
+ if (NULL == TAILQ_FIRST(&audit_sdev_list))
+ return;
+
+ AUDIT_SDEV_LIST_RLOCK();
+ TAILQ_FOREACH(asdev, &audit_sdev_list, asdev_list) {
+ AUDIT_SDEV_LOCK(asdev);
+
+ /*
+ * Only append to the sdev queue if the AUID and ASID match that
+ * of the process that opened this session device or if the
+ * ALLSESSIONS flag is set.
+ */
+ if ((/* XXXss auid == asdev->asdev_auid && */
+ asid == asdev->asdev_asid) ||
+ (asdev->asdev_flags & AUDIT_SDEV_ALLSESSIONS) != 0)
+ audit_sdev_append(asdev, record, record_len);
+ AUDIT_SDEV_UNLOCK(asdev);
+ }
+ AUDIT_SDEV_LIST_RUNLOCK();
+
+ /* Unlocked increment. */
+ audit_sdev_records++;
+}
+
+/*
+ * Allocate a new audit sdev. Connects the sdev, on succes, to the global
+ * list and updates statistics.
+ */
+static struct audit_sdev *
+audit_sdev_alloc(void)
+{
+ struct audit_sdev *asdev;
+
+ AUDIT_SDEV_LIST_WLOCK_ASSERT();
+
+ asdev = malloc(sizeof (*asdev), M_AUDIT_SDEV, M_WAITOK | M_ZERO);
+ if (NULL == asdev)
+ return (NULL);
+
+ asdev->asdev_qlimit = AUDIT_SDEV_QLIMIT_DEFAULT;
+ TAILQ_INIT(&asdev->asdev_queue);
+ AUDIT_SDEV_LOCK_INIT(asdev);
+ AUDIT_SDEV_SX_LOCK_INIT(asdev);
+ cv_init(&asdev->asdev_cv, "audit_sdev_cv");
+
+ /*
+ * Add to global list and update global statistics.
+ */
+ TAILQ_INSERT_HEAD(&audit_sdev_list, asdev, asdev_list);
+ audit_sdev_count++;
+ audit_sdev_ever++;
+
+ return (asdev);
+}
+
+/*
+ * Flush all records currently present in an audit sdev.
+ */
+static void
+audit_sdev_flush(struct audit_sdev *asdev)
+{
+ struct audit_sdev_entry *ase;
+
+ AUDIT_SDEV_LOCK_ASSERT(asdev);
+
+ while ((ase = TAILQ_FIRST(&asdev->asdev_queue)) != NULL) {
+ TAILQ_REMOVE(&asdev->asdev_queue, ase, ase_queue);
+ asdev->asdev_qbyteslen -= ase->ase_record_len;
+ audit_sdev_entry_free(ase);
+ asdev->asdev_qlen--;
+ }
+ asdev->asdev_qoffset = 0;
+
+ KASSERT(0 == asdev->asdev_qlen, ("audit_sdev_flush: asdev_qlen"));
+ KASSERT(0 == asdev->asdev_qbyteslen,
+ ("audit_sdev_flush: asdev_qbyteslen"));
+}
+
+/*
+ * Free an audit sdev.
+ */
+static void
+audit_sdev_free(struct audit_sdev *asdev)
+{
+
+ AUDIT_SDEV_LIST_WLOCK_ASSERT();
+ AUDIT_SDEV_LOCK_ASSERT(asdev);
+
+ /* XXXss - preselect hook here */
+ audit_sdev_flush(asdev);
+ cv_destroy(&asdev->asdev_cv);
+ AUDIT_SDEV_SX_LOCK_DESTROY(asdev);
+ AUDIT_SDEV_UNLOCK(asdev);
+ AUDIT_SDEV_LOCK_DESTROY(asdev);
+
+ TAILQ_REMOVE(&audit_sdev_list, asdev, asdev_list);
+ free(asdev, M_AUDIT_SDEV);
+ audit_sdev_count--;
+}
+
+/*
+ * Get the auditinfo_addr of the proc and check to see if suser. Will return
+ * non-zero if not suser.
+ */
+static int
+audit_sdev_get_aia(proc_t p, struct auditinfo_addr *aia_p)
+{
+ int error;
+ kauth_cred_t scred;
+
+ scred = kauth_cred_proc_ref(p);
+ error = suser(scred, &p->p_acflag);
+
+ if (NULL != aia_p)
+ bcopy(scred->cr_audit.as_aia_p, aia_p, sizeof (*aia_p));
+ kauth_cred_unref(&scred);
+
+ return (error);
+}
+
+/*
+ * Audit session dev open method.
+ */
+static int
+audit_sdev_open(dev_t dev, __unused int flags, __unused int devtype, proc_t p)
+{
+ struct audit_sdev *asdev;
+ struct auditinfo_addr aia;
+ int u;
+
+ u = minor(dev);
+ if (u < 0 || u > MAX_AUDIT_SDEVS)
+ return (ENXIO);
+
+ (void) audit_sdev_get_aia(p, &aia);
+
+ AUDIT_SDEV_LIST_WLOCK();
+ asdev = audit_sdev_dtab[u];
+ if (NULL == asdev) {
+ asdev = audit_sdev_alloc();
+ if (NULL == asdev) {
+ AUDIT_SDEV_LIST_WUNLOCK();
+ return (ENOMEM);
+ }
+ audit_sdev_dtab[u] = asdev;
+ } else {
+ KASSERT(asdev->asdev_open, ("audit_sdev_open: Already open"));
+ AUDIT_SDEV_LIST_WUNLOCK();
+ return (EBUSY);
+ }
+ asdev->asdev_open = 1;
+ asdev->asdev_auid = aia.ai_auid;
+ asdev->asdev_asid = aia.ai_asid;
+ asdev->asdev_flags = 0;
+
+ AUDIT_SDEV_LIST_WUNLOCK();
+
+ return (0);
+}
+
+/*
+ * Audit session dev close method.
+ */
+static int
+audit_sdev_close(dev_t dev, __unused int flags, __unused int devtype,
+ __unused proc_t p)
+{
+ struct audit_sdev *asdev;
+ int u;
+
+ u = minor(dev);
+ asdev = audit_sdev_dtab[u];
+
+ KASSERT(asdev != NULL, ("audit_sdev_close: asdev == NULL"));
+ KASSERT(asdev->asdev_open, ("audit_sdev_close: !asdev_open"));
+
+ AUDIT_SDEV_LIST_WLOCK();
+ AUDIT_SDEV_LOCK(asdev);
+ asdev->asdev_open = 0;
+ audit_sdev_free(asdev); /* sdev lock unlocked in audit_sdev_free() */
+ audit_sdev_dtab[u] = NULL;
+ AUDIT_SDEV_LIST_WUNLOCK();
+
+ return (0);
+}
+
+/*
+ * Audit session dev ioctl method.
+ */
+static int
+audit_sdev_ioctl(dev_t dev, u_long cmd, caddr_t data,
+ __unused int flag, proc_t p)
+{
+ struct audit_sdev *asdev;
+ int error;
+
+ asdev = audit_sdev_dtab[minor(dev)];
+ KASSERT(asdev != NULL, ("audit_sdev_ioctl: asdev == NULL"));
+
+ error = 0;
+
+ switch (cmd) {
+ case FIONBIO:
+ AUDIT_SDEV_LOCK(asdev);
+ if (*(int *)data)
+ asdev->asdev_flags |= AUDIT_SDEV_NBIO;
+ else
+ asdev->asdev_flags &= ~AUDIT_SDEV_NBIO;
+ AUDIT_SDEV_UNLOCK(asdev);
+ break;
+
+ case FIONREAD:
+ AUDIT_SDEV_LOCK(asdev);
+ *(int *)data = asdev->asdev_qbyteslen - asdev->asdev_qoffset;
+ AUDIT_SDEV_UNLOCK(asdev);
+ break;
+
+ case AUDITSDEV_GET_QLEN:
+ *(u_int *)data = asdev->asdev_qlen;
+ break;
+
+ case AUDITSDEV_GET_QLIMIT:
+ *(u_int *)data = asdev->asdev_qlimit;
+ break;
+
+ case AUDITSDEV_SET_QLIMIT:
+ if (*(u_int *)data >= AUDIT_SDEV_QLIMIT_MIN ||
+ *(u_int *)data <= AUDIT_SDEV_QLIMIT_MAX) {
+ asdev->asdev_qlimit = *(u_int *)data;
+ } else
+ error = EINVAL;
+ break;
+
+ case AUDITSDEV_GET_QLIMIT_MIN:
+ *(u_int *)data = AUDIT_SDEV_QLIMIT_MIN;
+ break;
+
+ case AUDITSDEV_GET_QLIMIT_MAX:
+ *(u_int *)data = AUDIT_SDEV_QLIMIT_MAX;
+ break;
+
+ case AUDITSDEV_FLUSH:
+ if (AUDIT_SDEV_SX_XLOCK_SIG(asdev) != 0)
+ return (EINTR);
+ AUDIT_SDEV_LOCK(asdev);
+ audit_sdev_flush(asdev);
+ AUDIT_SDEV_UNLOCK(asdev);
+ AUDIT_SDEV_SX_XUNLOCK(asdev);
+ break;
+
+ case AUDITSDEV_GET_MAXDATA:
+ *(u_int *)data = MAXAUDITDATA;
+ break;
+
+ /* XXXss these should be 64 bit, maybe. */
+ case AUDITSDEV_GET_INSERTS:
+ *(u_int *)data = asdev->asdev_inserts;
+ break;
+
+ case AUDITSDEV_GET_READS:
+ *(u_int *)data = asdev->asdev_reads;
+ break;
+
+ case AUDITSDEV_GET_DROPS:
+ *(u_int *)data = asdev->asdev_drops;
+ break;
+
+ case AUDITSDEV_GET_ALLSESSIONS:
+ error = audit_sdev_get_aia(p, NULL);
+ if (error)
+ break;
+ *(u_int *)data = (asdev->asdev_flags & AUDIT_SDEV_ALLSESSIONS) ?
+ 1 : 0;
+ break;
+
+ case AUDITSDEV_SET_ALLSESSIONS:
+ error = audit_sdev_get_aia(p, NULL);
+ if (error)
+ break;
+
+ AUDIT_SDEV_LOCK(asdev);
+ if (*(int *)data)
+ asdev->asdev_flags |= AUDIT_SDEV_ALLSESSIONS;
+ else
+ asdev->asdev_flags &= ~AUDIT_SDEV_ALLSESSIONS;
+ AUDIT_SDEV_UNLOCK(asdev);
+ break;
+
+ default:
+ error = ENOTTY;
+ }
+
+ return (error);
+}
+
+/*
+ * Audit session dev read method.
+ */
+static int
+audit_sdev_read(dev_t dev, struct uio *uio, __unused int flag)
+{
+ struct audit_sdev_entry *ase;
+ struct audit_sdev *asdev;
+ u_int toread;
+ int error;
+
+ asdev = audit_sdev_dtab[minor(dev)];
+ KASSERT(NULL != asdev, ("audit_sdev_read: asdev == NULL"));
+
+ /*
+ * We hold a sleep lock over read and flush because we rely on the
+ * stability of a record in the queue during uiomove.
+ */
+ if (0 != AUDIT_SDEV_SX_XLOCK_SIG(asdev))
+ return (EINTR);
+ AUDIT_SDEV_LOCK(asdev);
+ while (TAILQ_EMPTY(&asdev->asdev_queue)) {
+ if (asdev->asdev_flags & AUDIT_SDEV_NBIO) {
+ AUDIT_SDEV_UNLOCK(asdev);
+ AUDIT_SDEV_SX_XUNLOCK(asdev);
+ return (EAGAIN);
+ }
+ error = cv_wait_sig(&asdev->asdev_cv, AUDIT_SDEV_MTX(asdev));
+ if (error) {
+ AUDIT_SDEV_UNLOCK(asdev);
+ AUDIT_SDEV_SX_XUNLOCK(asdev);
+ return (error);
+ }
+ }
+
+ /*
+ * Copy as many remaining bytes from the current record to userspace
+ * as we can. Keep processing records until we run out of records in
+ * the queue or until the user buffer runs out of space.
+ *
+ * We rely on the sleep lock to maintain ase's stability here.
+ */
+ asdev->asdev_reads++;
+ while ((ase = TAILQ_FIRST(&asdev->asdev_queue)) != NULL &&
+ uio_resid(uio) > 0) {
+ AUDIT_SDEV_LOCK_ASSERT(asdev);
+
+ KASSERT(ase->ase_record_len > asdev->asdev_qoffset,
+ ("audit_sdev_read: record_len > qoffset (1)"));
+ toread = MIN((int)(ase->ase_record_len - asdev->asdev_qoffset),
+ uio_resid(uio));
+ AUDIT_SDEV_UNLOCK(asdev);
+ error = uiomove((char *) ase->ase_record + asdev->asdev_qoffset,
+ toread, uio);
+ if (error) {
+ AUDIT_SDEV_SX_XUNLOCK(asdev);
+ return (error);
+ }
+
+ /*
+ * If the copy succeeded then update book-keeping, and if no
+ * bytes remain in the current record then free it.
+ */
+ AUDIT_SDEV_LOCK(asdev);
+ KASSERT(TAILQ_FIRST(&asdev->asdev_queue) == ase,
+ ("audit_sdev_read: queue out of sync after uiomove"));
+ asdev->asdev_qoffset += toread;
+ KASSERT(ase->ase_record_len >= asdev->asdev_qoffset,
+ ("audit_sdev_read: record_len >= qoffset (2)"));
+ if (asdev->asdev_qoffset == ase->ase_record_len) {
+ TAILQ_REMOVE(&asdev->asdev_queue, ase, ase_queue);
+ asdev->asdev_qbyteslen -= ase->ase_record_len;
+ audit_sdev_entry_free(ase);
+ asdev->asdev_qlen--;
+ asdev->asdev_qoffset = 0;
+ }
+ }
+ AUDIT_SDEV_UNLOCK(asdev);
+ AUDIT_SDEV_SX_XUNLOCK(asdev);
+ return (0);
+}
+
+/*
+ * Audit session device poll method.
+ */
+static int
+audit_sdev_poll(dev_t dev, int events, void *wql, struct proc *p)
+{
+ struct audit_sdev *asdev;
+ int revents;
+
+ revents = 0;
+ asdev = audit_sdev_dtab[minor(dev)];
+ KASSERT(NULL != asdev, ("audit_sdev_poll: asdev == NULL"));
+
+ if (events & (POLLIN | POLLRDNORM)) {
+ AUDIT_SDEV_LOCK(asdev);
+ if (NULL != TAILQ_FIRST(&asdev->asdev_queue))
+ revents |= events & (POLLIN | POLLRDNORM);
+ else
+ selrecord(p, &asdev->asdev_selinfo, wql);
+ AUDIT_SDEV_UNLOCK(asdev);
+ }
+ return (revents);
+}
+
+/*
+ * Audit sdev clone routine. Provides a new minor number or returns -1.
+ * This called with DEVFS_LOCK held.
+ */
+static int
+audit_sdev_clone(__unused dev_t dev, int action)
+{
+ int i;
+
+ if (DEVFS_CLONE_ALLOC == action) {
+ for(i = 0; i < MAX_AUDIT_SDEVS; i++)
+ if (NULL == audit_sdev_dtab[i])
+ return (i);
+
+ /*
+ * This really should return -1 here but that seems to
+ * hang things in devfs. We instead return 0 and let
+ * audit_sdev_open tell userland the bad news.
+ */
+ return (0);
+ }
+
+ return (-1);
+}
+
+static int
+audit_sdev_init(void)
+{
+ dev_t dev;
+
+ TAILQ_INIT(&audit_sdev_list);
+ AUDIT_SDEV_LIST_LOCK_INIT();
+
+ audit_sdev_major = cdevsw_add(-1, &audit_sdev_cdevsw);
+ if (audit_sdev_major < 0)
+ return (KERN_FAILURE);
+
+ dev = makedev(audit_sdev_major, 0);
+ devnode = devfs_make_node_clone(dev, DEVFS_CHAR, UID_ROOT, GID_WHEEL,
+ 0644, audit_sdev_clone, AUDIT_SDEV_NAME, 0);
+
+ if (NULL == devnode)
+ return (KERN_FAILURE);
+
+ return (KERN_SUCCESS);
+}
+
+/* XXXss
+static int
+audit_sdev_shutdown(void)
+{
+
+ devfs_remove(devnode);
+ (void) cdevsw_remove(audit_sdev_major, &audit_sdev_cdevsw);
+
+ return (KERN_SUCCESS);
+}
+*/
+