โฌข DragonFlyBSD Kernel Audit
DF-5071 / df5071.c
โ† back to finding โ†“ download raw
/*
 * DF-5071 sink demo โ€” amr/amrd: bios submitted while the controller is
 * in AMR_STATE_SHUTDOWN (or wedged) are parked on sc->amr_bioq forever;
 * nothing ever drains or fails them
 *
 * Root cause chain replicated verbatim:
 *
 *   sys/dev/raid/amr/amr_disk.c:166-194 amrd_strategy():
 *         if (sc == NULL) { ...biodone... }
 *         bio->bio_driver_info = sc;
 *         devstat_start_transaction(...);
 *         amr_submit_bio(sc->amrd_controller, bio);   <-- NO GATE on
 *         return (0);                                     AMR_STATE_SHUTDOWN
 *   sys/dev/raid/amr/amr_disk.c:126-127 amrd_open():   <-- the gate EXISTS
 *         if (sc->amrd_controller->amr_state & AMR_STATE_SHUTDOWN)
 *             return(ENXIO);                                in open only
 *   sys/dev/raid/amr/amr.c:407-416 amr_submit_bio():
 *         lockmgr(...); amr_enqueue_bio(sc, bio); amr_startio(sc); ...
 *   sys/dev/raid/amr/amr.c:1155-1188 amr_startio():
 *         ... if (ac == NULL) (void)amr_bio_command(sc, &ac); ...
 *         if (ac == NULL) break;       <-- ENOMEM return IGNORED; bios stay
 *   sys/dev/raid/amr/amr.c:1224-1247 amr_bio_command():
 *         if ((ac = amr_alloccmd(sc)) == NULL) return (ENOMEM);
 *         <-- with every command stuck busy on a dead adapter (there is NO
 *             timeout/watchdog anywhere in amr) every bio parks on amr_bioq
 *   sys/dev/raid/amr/amr_pci.c:387-417 amr_pci_shutdown() and
 *   sys/dev/raid/amr/amr.c:454-485 amr_rescan_drives():
 *         sc->amr_state |= AMR_STATE_SHUTDOWN; ... device_delete_child(...)
 *         <-- amr_bioq is NEVER drained; the comment at amr_pci.c:383-384
 *             ("we can assume that the bioq ... is empty, as we won't allow
 *              shutdown if any device is open") is false: AMR_STATE_OPEN is
 *             set only by the CONTROL device /dev/amr0 (amr.c:430); amrd_open
 *             sets nothing, so open disk fds and queued bios do not block it.
 *   sys/dev/raid/amr/amr.c:371-400 amr_free() + kfree(sc) at detach:
 *         bios still on amr_bioq are never biodone'd (and feed the DF-2082
 *         in-flight UAF window).
 *
 * Consequence: an issuer whose bio parks sleeps in biowait forever โ€”
 * unkillable D-state; queue grows unbounded. Trigger: root kldunload amr /
 * AMR_RESCAN_DRIVES ioctl with I/O queued or in flight, or any adapter
 * wedge (no watchdog exists to recover).
 *
 * Following the DF-4986/DF-5012/DF-5026 house precedent (guest has no
 * MegaRAID adapter), this module wires the exact enqueue/start/swallow
 * topology to a raw D_DISK device (/dev/df50710). amrd_open's verbatim
 * ENXIO gate is kept on the raw device (it is part of the asymmetry being
 * demonstrated); the demo control plane (shutdown/poke/online toggles)
 * lives on a separate ungated char device /dev/df5071ctl so the demo is
 * self-driving and the guest can always be returned to a clean state.
 */
#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/bio.h>
#include <sys/buf2.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/types.h>
#include <sys/device.h>
#include <sys/devfs.h>

#ifdef DF5071_FIXED
#define	PREFIX	"df5071f"
#else
#define	PREFIX	"df5071"
#endif

#define	AMR_STATE_SHUTDOWN	(1<<3)	/* amrvar.h:222 */

/* control-plane ioctls on /dev/df5071ctl */
#define	DF5071_CTL_SHUTDOWN	_IOW('D', 71, int)	/* amr_pci_shutdown */
#define	DF5071_CTL_POKE		_IOW('D', 72, int)	/* external drain */
#define	DF5071_CTL_ONLINE	_IOW('D', 73, int)	/* amr_startup */

#define	PARK_OFF	(2UL << 20)	/* data-path read offset (in-media) */

static struct lock		amr_list_lock;	/* sc->amr_list_lock */
static struct bio_queue_head	amr_bioq;	/* sc->amr_bioq */
static int			amr_state;	/* sc->amr_state */
static int			cmd_stuck;	/* adapter stops completing */
static int			parked;		/* observability only */

/*
 * amr.c:1881-1904 amr_alloccmd() analog: returns NULL when no command is
 * free. On a wedged adapter every command sits busy forever (amr has no
 * timeout/watchdog to complete or reclaim them), so every call fails.
 */
static void *
sim_alloccmd(void)
{
	if (cmd_stuck)
		return (NULL);		/* all commands stuck busy */
	return ((void *)1);
}

/* amr_disk.c:196-214 amrd_intr() โ€” healthy completion path */
static void
sim_amrd_intr(struct bio *bio)
{
	struct buf *bp = bio->bio_buf;

	/* devstat_end_transaction_buf elided */
	if (bp->b_flags & B_ERROR) {
		bp->b_error = EIO;
	} else {
		bp->b_resid = 0;		/* amr_disk.c:210 */
	}
	biodone(bio);				/* amr_disk.c:213 */
	parked--;				/* observability only */
}

/*
 * amr.c:1224-1304 amr_bio_command() โ€” VERBATIM structure. The bio is only
 * dequeued AFTER a command was obtained; on ENOMEM it stays queued.
 * *acp is set ONLY when a command was actually built (bio dequeued) โ€”
 * the caller's `ac` stays NULL otherwise, exactly as in amr.c.
 */
static int
sim_bio_command(int *acp)
{
	struct bio *bio;

	*acp = 0;
	if (sim_alloccmd() == NULL)
		return (ENOMEM);		/* amr.c:1240-1241 */
	if ((bio = bioq_first(&amr_bioq)) == NULL)
		return (0);			/* amr.c:1244-1246: releasecmd,
						 * ac stays NULL */
	bioq_remove(&amr_bioq, bio);
	*acp = 1;				/* command built */
	sim_amrd_intr(bio);			/* command completes (online) */
	return (0);
}

/* amr.c:1155-1188 amr_startio() โ€” VERBATIM structure */
static void
sim_startio(void)
{
	int havecmd;

	for (;;) {
		havecmd = 0;
		(void)sim_bio_command(&havecmd);	/* amr.c:1171: return
							 * IGNORED */
		if (havecmd == 0)
			break;		/* amr.c:1178: give up -- bios STAY
					 * QUEUED on amr_bioq forever */
		/* amr_start() succeeds in the sim; loop for the next bio */
	}
}

/* what amr_pci_shutdown/amr_rescan_drives must do but never do */
static void
sim_drain_bios(int error)
{
	struct bio *bio;

	while ((bio = bioq_first(&amr_bioq)) != NULL) {
		struct buf *bp = bio->bio_buf;
		bioq_remove(&amr_bioq, bio);
		bp->b_flags |= B_ERROR;
		bp->b_error = error;
		bp->b_resid = bp->b_bcount;
		biodone(bio);
		parked--;
	}
}

/* ---- raw disk device (the amrd replica) ---- */

static d_open_t	df5071_open;
static d_close_t df5071_close;
static d_strategy_t df5071_strategy;

static struct dev_ops df5071_ops = {
#ifdef DF5071_FIXED
	{ "df5071f", 0, D_DISK | D_MPSAFE },
#else
	{ "df5071", 0, D_DISK | D_MPSAFE },
#endif
	.d_open = df5071_open,
	.d_close = df5071_close,
	.d_strategy = df5071_strategy,
};

static struct disk	df5071_disk;

/*
 * amrd_open(), amr_disk.c:116-130 โ€” VERBATIM: the SHUTDOWN gate that the
 * real driver HAS in open() and LACKS in strategy().
 */
static int
df5071_open(struct dev_open_args *ap)
{
	/* controller not active? */
	if (amr_state & AMR_STATE_SHUTDOWN)
		return (ENXIO);			/* amr_disk.c:126-127 */
	return (0);
}

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

/*
 * amrd_strategy(), amr_disk.c:166-194 โ€” VERBATIM structure (a parked-bio
 * counter is added for observability).
 */
static int
df5071_strategy(struct dev_strategy_args *ap)
{
	struct bio *bio = ap->a_bio;
#ifdef DF5071_FIXED
	struct buf *bp = bio->bio_buf;

	/* THE FIX (part 1): mirror the amrd_open() gate (:126-127) */
	if (amr_state & AMR_STATE_SHUTDOWN) {
		bp->b_error = ENXIO;
		goto bad;
	}
#endif
	bio->bio_driver_info = NULL;		/* amr_disk.c:179 (sc elided) */
	/* devstat_start_transaction elided (amr_disk.c:181) */
	lockmgr(&amr_list_lock, LK_EXCLUSIVE | LK_RETRY);	/* amr.c:411 */
	bioq_insert_tail(&amr_bioq, bio);	/* amr_enqueue_bio, amrvar.h:309 */
	parked++;				/* observability only */
	sim_startio();				/* amr.c:413 โ€” parks if stuck */
	lockmgr(&amr_list_lock, LK_RELEASE);	/* amr.c:414 */
	return (0);				/* amr_submit_bio returns 0 */

#ifdef DF5071_FIXED
bad:
	bp->b_flags |= B_ERROR;			/* amr_disk.c:186-192 */
	bp->b_resid = bp->b_bcount;
	biodone(bio);
	return (0);
#endif
}

/* ---- control device (demo plane; NOT part of the replicated code) ---- */

static d_open_t	df5071ctl_open;
static d_close_t df5071ctl_close;
static d_ioctl_t df5071ctl_ioctl;

static struct dev_ops df5071ctl_ops = {
	{ "df5071ctl", 0, D_MEM },
	.d_open = df5071ctl_open,
	.d_close = df5071ctl_close,
	.d_ioctl = df5071ctl_ioctl,
};

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

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

static int
df5071ctl_ioctl(struct dev_ioctl_args *ap)
{
	switch (ap->a_cmd) {
	case DF5071_CTL_SHUTDOWN:
		/* amr_pci_shutdown (amr_pci.c:394-411) /
		 * amr_rescan_drives (amr.c:465-481) */
		lockmgr(&amr_list_lock, LK_EXCLUSIVE | LK_RETRY);
		amr_state |= AMR_STATE_SHUTDOWN;	/* amr_pci.c:395 */
		cmd_stuck = 1;		/* adapter wedged / children deleted */
		kprintf(PREFIX ": amr_pci_shutdown/amr_rescan_drives: "
		    "AMR_STATE_SHUTDOWN set; amr_bioq NOT drained "
		    "(%d bio(s) already parked); amrd_strategy has NO "
		    "shutdown gate\n", parked);
#ifdef DF5071_FIXED
		/* THE FIX (part 2): drain queued bios before deleting
		 * children โ€” they can never complete now */
		if (parked) {
			kprintf(PREFIX ": (fixed) draining %d parked bio(s) "
			    "with ENXIO\n", parked);
			sim_drain_bios(ENXIO);
		}
#endif
		lockmgr(&amr_list_lock, LK_RELEASE);
		break;
	case DF5071_CTL_POKE:
		/* what the shutdown path never does */
		lockmgr(&amr_list_lock, LK_EXCLUSIVE | LK_RETRY);
		kprintf(PREFIX ": poke -- draining %d parked bio(s) with EIO "
		    "(what the shutdown path should have done)\n", parked);
		sim_drain_bios(EIO);
		lockmgr(&amr_list_lock, LK_RELEASE);
		break;
	case DF5071_CTL_ONLINE:
		/* amr_startup (amr.c:294-344) */
		lockmgr(&amr_list_lock, LK_EXCLUSIVE | LK_RETRY);
		amr_state &= ~AMR_STATE_SHUTDOWN;	/* amr.c:338 */
		cmd_stuck = 0;
		kprintf(PREFIX ": amr_startup: back online, restarting io\n");
		sim_startio();
		lockmgr(&amr_list_lock, LK_RELEASE);
		break;
	default:
		return (EINVAL);
	}
	return (0);
}

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

	switch (type) {
	case MOD_LOAD:
		lockinit(&amr_list_lock, PREFIX "q", 0, 0);
		bioq_init(&amr_bioq);
		bzero(&df5071_disk, sizeof(df5071_disk));
		bzero(&info, sizeof(info));
		info.d_media_blksize = 512;
		info.d_media_blocks = 16384;	/* 8 MiB test media */
		info.d_nheads = 64;
		info.d_secpertrack = 32;
		disk_create(0, &df5071_disk, &df5071_ops);
		disk_setdiskinfo(&df5071_disk, &info);
#ifdef DF5071_FIXED
		make_dev(&df5071ctl_ops, 0, UID_ROOT, GID_WHEEL, 0666,
		    "df5071fctl");
#else
		make_dev(&df5071ctl_ops, 0, UID_ROOT, GID_WHEEL, 0666,
		    "df5071ctl");
#endif
		amr_state = 0;
		cmd_stuck = 0;
		parked = 0;
		kprintf(PREFIX ": raw disk \"%s\" + ctl up (ONLINE) โ€” ctl "
		    "ioctls 71/72/73 = shutdown/poke/online; open() carries "
		    "the verbatim amrd_open ENXIO gate\n",
		    df5071_disk.d_cdev->si_name);
		break;
	case MOD_UNLOAD:
		/* hygiene: never unload with bios still parked */
		lockmgr(&amr_list_lock, LK_EXCLUSIVE | LK_RETRY);
		if (parked)
			kprintf(PREFIX ": unload -- draining %d parked "
			    "bio(s)\n", parked);
		sim_drain_bios(ENXIO);
		lockmgr(&amr_list_lock, LK_RELEASE);
		disk_destroy(&df5071_disk);
#ifdef DF5071_FIXED
		destroy_dev(devfs_find_device_by_name("df5071fctl"));
#else
		destroy_dev(devfs_find_device_by_name("df5071ctl"));
#endif
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

#ifdef DF5071_FIXED
DEV_MODULE(df5071f, df5071_modevent, NULL);
#else
DEV_MODULE(df5071, df5071_modevent, NULL);
#endif