⬢ DragonFlyBSD Kernel Audit
DF-4671 / bktrig2.c
← back to finding ↓ download raw
/*
 * bktrig2.c -- DF-4671/4672/4673 verification rig (DragonFly KLD module)
 *
 * The audit QEMU guest has no Brooktree bt848/878 PCI capture card, so the
 * production bktr driver never attaches and /dev/bktr0 is never created
 * (same starting point as the DF-1215 rig).  This rig makes the PRODUCTION
 * METEORSETGEO grow path and the production (verbatim) bktr_mmap fault path
 * reachable anyway:
 *
 *   /dev/bktrig0 (mode 0666 -- real /dev/bktr0 is 0444, both unpriv-openable)
 *
 *   d_ioctl  - private 'Z' control ioctls (RIG_INIT / RIG_STATUS / RIG_RELEASE)
 *              + EVERYTHING ELSE dispatched to the PRODUCTION video_ioctl()
 *              (sys/dev/video/bktr/bktr_core.c:1202) resolved from the loaded
 *              production bktr.ko module.  METEORSETGEO therefore executes the
 *              real grow block bktr_core.c:1664-1681 (get_bktr_mem ->
 *              kmem_free -> reassign) with the real contigmalloc/kmem_free.
 *
 *   d_mmap   - rig_mmap(): VERBATIM COPY of the production bktr_mmap()
 *              (sys/dev/video/bktr/bktr_os.c:656-685).  bktr_mmap is a local
 *              symbol in bktr.ko (nm: 't bktr_mmap' @0x7840) so it cannot be
 *              linked; the copy below differs ONLY in the softc lookup
 *              (devclass_get_softc() replaced by the direct rig_sc pointer)
 *              and the dropped minor()/FUNCTION() decode (rig node is the
 *              video function by construction).  Every load, check and store
 *              is line-for-line identical, including the unlocked
 *              alloc_pages check (bktr_os.c:680) and unlocked
 *              vtophys(bigbuf) (bktr_os.c:683) that constitute DF-4671.
 *
 *   RIG_INIT mirrors what bktr_attach()/common_bktr_attach() does on real
 *   hardware at boot (bktr_core.c:393-446): allocates the initial frame
 *   buffer with the PRODUCTION get_bktr_mem() (bktr_os.c:398, global symbol),
 *   bzero()s it (bktr_core.c:432), sets alloc_pages and the attach-time
 *   softc flags.  The only deviation is the buffer SIZE (32 pages instead of
 *   BROOKTREE_ALLOC_PAGES=868) to keep the per-grow wired leak measurable
 *   without exhausting the 4 GB guest; the grow code path under test is
 *   completely size-agnostic.
 *
 *   METEORSETGEO's OUTW/OUTL register writes (bktr_core.c:1652-1655) land in
 *   a fake register page (memt=1 -> memory space per
 *   sys/cpu/x86_64/include/bus_dma.h:139-141; memh -> kmalloc'd page).
 *
 * Usage (root):
 *   kldload ./bktrig2.ko          (MODULE_DEPEND pulls in /boot/kernel/bktr.ko)
 *   rig-init /dev/bktrig0 32      (or RIG_INIT from the PoCs)
 * ...then all attacks run as an UNPRIVILEGED user, exactly like on real
 * hardware where /dev/bktr0 is 0444 (bktr_os.c:325).
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/bus.h>
#include <sys/mman.h>
#include <sys/types.h>

#include <vm/vm.h>
#include <vm/vm_kern.h>
#include <vm/pmap.h>
#include <vm/vm_extern.h>

#include <dev/video/meteor/ioctl_meteor.h>
#include <dev/video/bktr/ioctl_bt848.h>
#include <dev/video/bktr/bktr_reg.h>
#include <dev/video/bktr/bktr_core.h>

/* ---- production entry points (global symbols in bktr.ko / built-in) ---- */
/* video_ioctl: declared by bktr_core.h (bktr_core.c:1202) */
extern vm_offset_t get_bktr_mem(int unit, unsigned size);	/* bktr_os.c:398 */

/* ---- rig control ioctls ('Z'; driver uses 'x' only) ---- */
struct rig_ctl {
	int32_t		npages;		/* IN: desired alloc_pages */
	uint32_t	pad;
	uint64_t	bigbuf;		/* OUT: current KVA */
	int32_t		alloc_pages;	/* OUT: current pages */
	int32_t		rc;
	uint64_t	bigbuf_pa;	/* OUT: vtophys(bigbuf) */
	uint64_t	probe_off;	/* IN: offset for RIG_PROBE */
	uint64_t	probe_pa;	/* OUT: vtophys(bigbuf+probe_off) */
};
#define	RIG_INIT	_IOWR('Z', 1, struct rig_ctl)
#define	RIG_STATUS	_IOWR('Z', 2, struct rig_ctl)
#define	RIG_RELEASE	_IOWR('Z', 3, struct rig_ctl)
#define	RIG_PROBE	_IOWR('Z', 4, struct rig_ctl)

#define	RIG_NDEV	8
static cdev_t	bktrig_devs[RIG_NDEV];
static cdev_t	bktrig_dev;
static int	rig_unit;
static bktr_ptr_t rig_sc;
static void	*fake_regs;
static struct dev_ops bktrig_ops;

/*
 * VERBATIM copy of bktr_mmap(), sys/dev/video/bktr/bktr_os.c:656-685.
 * Deltas (and only these) are marked [RIG].
 */
static int
rig_mmap(struct dev_mmap_args *ap)
{
	bktr_ptr_t	bktr;

	/* [RIG] production does UNIT()/FUNCTION() minor decode + devclass
	 * lookup; the rig node IS the video function, softc is rig_sc. */
	bktr = rig_sc;
	if (bktr == NULL) {
		/* the device is no longer valid/functioning */
		return (ENXIO);
	}

	if (ap->a_nprot & PROT_EXEC)
		return(EINVAL);

	if (ap->a_offset < 0)
		return(EINVAL);

	if (ap->a_offset >= bktr->alloc_pages * PAGE_SIZE)	/* bktr_os.c:680 */
		return(EINVAL);

	ap->a_result = atop(vtophys(bktr->bigbuf) + ap->a_offset); /* :683 */
	return(0);
}

static int
bktrig_open(struct dev_open_args *ap)
{
	return 0;
}

static int
bktrig_close(struct dev_close_args *ap)
{
	return 0;
}

static int
bktrig_ioctl(struct dev_ioctl_args *ap)
{
	struct rig_ctl *rc;

	if (IOCBASECMD(ap->a_cmd) == IOCBASECMD(RIG_INIT) ||
	    IOCBASECMD(ap->a_cmd) == IOCBASECMD(RIG_STATUS) ||
	    IOCBASECMD(ap->a_cmd) == IOCBASECMD(RIG_RELEASE) ||
	    IOCBASECMD(ap->a_cmd) == IOCBASECMD(RIG_PROBE)) {
		rc = (struct rig_ctl *)ap->a_data;
		if (rc == NULL)
			return (EFAULT);
		switch (IOCBASECMD(ap->a_cmd)) {
		case IOCBASECMD(RIG_INIT):
			if (rig_sc == NULL)
				return (ENXIO);
			/* attach-mirror hygiene: release a previous buffer
			 * with the CORRECT API (contigfree) before re-init */
			if (rig_sc->bigbuf) {
				contigfree((void *)rig_sc->bigbuf,
			    rig_sc->alloc_pages * PAGE_SIZE, M_DEVBUF);
				rig_sc->bigbuf = 0;
				rig_sc->alloc_pages = 0;
			}
			if (rc->npages <= 0 || rc->npages > 4096) {
				rc->rc = EINVAL;
				return (EINVAL);
			}
			/* recycle ALL the cdevs: a fresh cdev_t gives a
			 * fresh device-pager object (si_object is keyed
			 * by the cdev) -> fresh first-fault d_mmap
			 * queries.  The old pagers' cached fake pages
			 * die with the old cdevs (empirically they
			 * survive plain munmap).  No fds are open here
			 * (root runs this between PoC runs). */
			{
				int i;

				for (i = 0; i < RIG_NDEV; i++) {
					if (bktrig_devs[i]) {
						destroy_dev(bktrig_devs[i]);
						bktrig_devs[i] = NULL;
					}
				}
				for (i = 0; i < RIG_NDEV; i++) {
					bktrig_devs[i] = make_dev(&bktrig_ops,
					    rig_unit++, UID_ROOT, GID_WHEEL,
					    0666, "bktrig%d", i);
				}
				bktrig_dev = bktrig_devs[0];
			}
			/* production allocation path, exactly like attach */
			rig_sc->bigbuf = get_bktr_mem(0,
			    rc->npages * PAGE_SIZE);
			if (rig_sc->bigbuf == 0) {
				rc->rc = ENOMEM;
				return (ENOMEM);
			}
			rig_sc->alloc_pages = rc->npages;
			/* attach zeroes the initial buffer: bktr_core.c:432 */
			bzero((caddr_t)rig_sc->bigbuf,
			    rig_sc->alloc_pages * PAGE_SIZE);
			/* common_bktr_attach state, bktr_core.c:438-446 */
			rig_sc->flags = METEOR_INITALIZED | METEOR_AUTOMODE |
					METEOR_DEV0 | METEOR_RGB16;
			rig_sc->dma_prog_loaded = FALSE;
			rig_sc->cols = 640;
			rig_sc->rows = 480;
			rig_sc->frames = 1;
			rig_sc->format = METEOR_GEO_RGB16;
			rig_sc->pixfmt_compat = TRUE;
			rc->bigbuf = (uint64_t)rig_sc->bigbuf;
			rc->alloc_pages = rig_sc->alloc_pages;
			rc->rc = 0;
			return (0);
		case IOCBASECMD(RIG_STATUS):
			if (rig_sc == NULL)
				return (ENXIO);
			rc->bigbuf = (uint64_t)rig_sc->bigbuf;
			rc->alloc_pages = rig_sc->alloc_pages;
			rc->bigbuf_pa = rig_sc->bigbuf ?
			    (uint64_t)vtophys(rig_sc->bigbuf) : 0;
			rc->rc = 0;
			return (0);
		case IOCBASECMD(RIG_PROBE):
			/* what bktr_mmap would compute for this offset */
			if (rig_sc == NULL || rig_sc->bigbuf == 0)
				return (ENXIO);
			rc->probe_pa = (uint64_t)vtophys(rig_sc->bigbuf +
			    (vm_offset_t)rc->probe_off);
			rc->bigbuf = (uint64_t)rig_sc->bigbuf;
			rc->alloc_pages = rig_sc->alloc_pages;
			rc->bigbuf_pa = (uint64_t)vtophys(rig_sc->bigbuf);
			rc->rc = 0;
			return (0);
		default: /* RIG_RELEASE: correct-API release of the current
			  * buffer (rig hygiene; the DRIVER never does this) */
			if (rig_sc && rig_sc->bigbuf) {
				contigfree((void *)rig_sc->bigbuf,
				    rig_sc->alloc_pages * PAGE_SIZE, M_DEVBUF);
				rig_sc->bigbuf = 0;
				rig_sc->alloc_pages = 0;
			}
			rc->bigbuf = 0;
			rc->alloc_pages = 0;
			rc->rc = 0;
			return (0);
		}
	}

	/* dispatch to the PRODUCTION handler on the synthetic softc */
	return video_ioctl(rig_sc, 0, ap->a_cmd, ap->a_data, curthread);
}

static struct dev_ops bktrig_ops = {
	{ "bktrig", 0, 0 },
	.d_open =	bktrig_open,
	.d_close =	bktrig_close,
	.d_ioctl =	bktrig_ioctl,
	.d_mmap =	rig_mmap,
};

static int
bktrig_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		rig_sc = kmalloc(sizeof(struct bktr_softc), M_DEVBUF,
				 M_WAITOK | M_ZERO);
		if (rig_sc == NULL)
			return (ENOMEM);
		fake_regs = kmalloc(PAGE_SIZE, M_DEVBUF, M_WAITOK | M_ZERO);
		if (fake_regs == NULL) {
			kfree(rig_sc, M_DEVBUF);
			rig_sc = NULL;
			return (ENOMEM);
		}
		/* fake PCI register window: memt != X86_64_BUS_SPACE_IO(0)
		 * selects memory-space access == direct deref of memh+offset
		 * (sys/cpu/x86_64/include/bus_dma.h:139-141) */
		rig_sc->memt = 1;
		rig_sc->memh = (bus_space_handle_t)(uintptr_t)fake_regs;
		ksnprintf(rig_sc->bktr_xname, sizeof(rig_sc->bktr_xname),
		    "bktr0");
		{
			int i;

			for (i = 0; i < RIG_NDEV; i++)
				bktrig_devs[i] = make_dev(&bktrig_ops,
				    rig_unit++, UID_ROOT, GID_WHEEL, 0666,
				    "bktrig%d", i);
			bktrig_dev = bktrig_devs[0];
		}
		kprintf("bktrig2: DF-4671/4672/4673 rig ready /dev/bktrig0 "
			"sc=%p (video_ioctl=%p get_bktr_mem=%p)\n",
			rig_sc, video_ioctl, get_bktr_mem);
		return 0;
	case MOD_UNLOAD:
		{
			int i;

			for (i = 0; i < RIG_NDEV; i++)
				if (bktrig_devs[i])
					destroy_dev(bktrig_devs[i]);
		}
		/* correct-API release of the CURRENT buffer only; every buffer
		 * freed by the buggy grow path stays leaked forever -- that
		 * is the DF-4672 finding and cannot be undone by anyone. */
		if (rig_sc && rig_sc->bigbuf)
			contigfree((void *)rig_sc->bigbuf,
			    rig_sc->alloc_pages * PAGE_SIZE, M_DEVBUF);
		kfree(rig_sc, M_DEVBUF);
		kfree(fake_regs, M_DEVBUF);
		rig_sc = NULL;
		fake_regs = NULL;
		return 0;
	default:
		return 0;
	}
}

static moduledata_t bktrig_mod = {
	"bktrig2",
	bktrig_modevent,
	NULL
};

/* pull in the production (unmodified) bktr.ko for video_ioctl/get_bktr_mem.
 * NB: on a FIX-built kernel (bktr compiled in) use the BKTRIG_NODEP variant. */
#ifndef BKTRIG_NODEP
MODULE_DEPEND(bktrig2, bktr, 1, 1, 1);
#endif
DECLARE_MODULE(bktrig2, bktrig_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);