⬢ DragonFlyBSD Kernel Audit
DF-5012 / df5012.c
← back to finding ↓ download raw
/*
 * DF-5012 sink demo — mfi_disk_strategy() bio leak (sys/dev/raid/mfi/mfi_disk.c:266-274)
 *
 * The guest has no LSI MegaRAID controller, so the mfi path cannot be entered
 * at runtime. Following the DF-4986 precedent, this module replicates the
 * *exact completion pattern* of the vulnerable early-return paths on the
 * stock kernel:
 *
 *   mfi_disk.c:266-269 (controller->hw_crit_error):
 *           bp->b_error = EBUSY;
 *           return (0);                 <-- no B_ERROR, no b_resid, no biodone()
 *   mfi_disk.c:271-274 (issuepend_done == 0):
 *           bp->b_error = EBUSY;
 *           return (0);                 <-- same
 *
 * A raw D_DISK device built exactly like mfid (disk_create + d_strategy).
 * Reads at offset >= LEAK_OFF reproduce the leak verbatim. A read at POKE_OFF
 * ("recovery poke", standing in for what mfi never does) completes the leaked
 * bio so the guest can be returned to a clean state after the demo.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/disk.h>
#include <sys/buf2.h>

#define	LEAK_OFF	(2UL << 20)	/* offset that triggers the leak */
#define	POKE_OFF	(1UL << 20)	/* offset that completes the leaked bio */

static d_open_t	df5012_open;
static d_close_t	df5012_close;
static d_strategy_t	df5012_strategy;

static struct dev_ops df5012_ops = {
	{ "df5012", 0, D_DISK | D_MPSAFE },
	.d_open = df5012_open,
	.d_close = df5012_close,
	.d_strategy = df5012_strategy,
};

static struct disk	df5012_disk;
static struct bio	*df5012_leaked;

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

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

static int
df5012_strategy(struct dev_strategy_args *ap)
{
	struct bio *bio = ap->a_bio;
	struct buf *bp = bio->bio_buf;

	if (bio->bio_offset >= LEAK_OFF) {
		/* ---- verbatim mfi_disk.c:266-274 pattern ---- */
		kprintf("df5012: LEAKING bio %p (off %jd) -- "
		    "b_error=EBUSY, return 0, NO biodone\n",
		    bio, (intmax_t)bio->bio_offset);
		if (df5012_leaked == NULL)
			df5012_leaked = bio;
		bp->b_error = EBUSY;
		return (0);
	}

	if (bio->bio_offset == POKE_OFF && df5012_leaked != NULL) {
		/* what the fixed mfi code would do for the faulted bios */
		struct buf *lb = df5012_leaked->bio_buf;
		kprintf("df5012: poke -- completing leaked bio %p\n",
		    df5012_leaked);
		lb->b_flags |= B_ERROR;
		lb->b_resid = lb->b_bcount;
		biodone(df5012_leaked);
		df5012_leaked = NULL;
	}

	bp->b_resid = 0;
	biodone(bio);
	return (0);
}

static int
df5012_modevent(module_t mod, int type, void *data)
{
	struct disk_info info;
	int error = 0;

	switch (type) {
	case MOD_LOAD:
		bzero(&df5012_disk, sizeof(df5012_disk));
		bzero(&info, sizeof(info));
		info.d_media_blksize = 512;
		info.d_media_blocks = 8192;	/* 4 MiB test media */
		info.d_nheads = 64;
		info.d_secpertrack = 32;
		disk_create(0, &df5012_disk, &df5012_ops);
		disk_setdiskinfo(&df5012_disk, &info);
		kprintf("df5012: raw disk \"%s\" up; read @0x%lx => leak, "
		    "@0x%lx => recovery poke\n",
		    df5012_disk.d_cdev->si_name, LEAK_OFF, POKE_OFF);
		break;
	case MOD_UNLOAD:
		if (df5012_leaked != NULL) {
			struct buf *bp = df5012_leaked->bio_buf;
			kprintf("df5012: unload -- completing leaked bio %p\n",
			    df5012_leaked);
			bp->b_flags |= B_ERROR;
			bp->b_error = EIO;
			bp->b_resid = bp->b_bcount;
			biodone(df5012_leaked);
			df5012_leaked = NULL;
		}
		disk_destroy(&df5012_disk);
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

DEV_MODULE(df5012, df5012_modevent, NULL);