DragonFlyBSD Kernel Audit
DF-0793 / asyncd.c
← back to finding ↓ download raw
/*
 * asyncd -- a deliberately slow ASYNC disk for DF-0793 reproduction.
 *
 * Modeled on sys/dev/disk/md/md.c.  Memory-backed storage so newfs/mount
 * succeed.  READ/WRITE/FLUSH complete SYNCHRONOUSLY (so the FS works
 * normally).  BUF_CMD_FREEBLKS (the TRIM command ffs_blkfree issues) is
 * DEFERRED: completion (biodone) is pushed out by a callout, mimicking a
 * real SSD that takes milliseconds to retire a TRIM/DISCARD.
 *
 * Why this exists: the default QEMU guest exposes no device that completes
 * TRIM asynchronously (vn completes FREEBLKS as a synchronous no-op).  The
 * DF-0793 UAF window only opens when a TRIM bio is still in-flight at
 * ffs_unmount time, so we need an async device to reproduce it
 * deterministically.  The BUG is in sys/vfs/ufs (missing taskqueue_drain in
 * the unmount path); this module only supplies the async-device
 * precondition.  (Root-loaded module, used to CHARACTERIZE the primitive.)
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/devicestat.h>
#include <sys/disk.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/proc.h>
#include <sys/buf2.h>
#include <sys/thread2.h>
#include <sys/queue.h>
#include <sys/callout.h>
#include <sys/module.h>
#include <sys/udev.h>

MALLOC_DEFINE(M_ASYNCD, "asyncd", "async disk for DF-0793 repro");

#define ASYNCD_SIZE_MB	256
#define ASYNCD_DELAY_HZ	(2*hz)		/* 2 s async completion delay */

static int asyncd_trace = 1;	/* kprintf every bio by default */
SYSCTL_INT(_debug, OID_AUTO, asyncd_trace, CTLFLAG_RW, &asyncd_trace, 0,
    "trace every asyncd bio");

static d_strategy_t asyncdstrategy;
static d_open_t asyncdopen;
static d_close_t asyncdclose;
static d_ioctl_t asyncdioctl;

static struct dev_ops asyncd_ops = {
	{ "asyncd", 0, D_DISK | D_CANFREE | D_MPSAFE | D_TRACKCLOSE },
	.d_open		= asyncdopen,
	.d_close	= asyncdclose,
	.d_read		= physread,
	.d_write	= physwrite,
	.d_ioctl	= asyncdioctl,
	.d_strategy	= asyncdstrategy,
};

struct asyncd_softc {
	struct lwkt_token tok;
	struct disk disk;
	struct devstat stats;
	cdev_t dev;
	u_char *image;			/* whole-disk backing memory */
	size_t size;
	TAILQ_HEAD(, bio) trimq;	/* deferred FREEBLKS bios */
	struct callout timer;
	int timer_armed;
} asc;

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

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

static int
asyncdioctl(struct dev_ioctl_args *ap)
{
	return (ENOIOCTL);
}

/* callout: complete all deferred FREEBLKS bios */
static void
asyncd_complete(void *arg)
{
	struct asyncd_softc *sc = arg;
	struct bio *bio;

	lwkt_gettoken(&sc->tok);
	sc->timer_armed = 0;
	while ((bio = TAILQ_FIRST(&sc->trimq)) != NULL) {
		TAILQ_REMOVE(&sc->trimq, bio, bio_act);
		kprintf("asyncd: completing deferred bio %p (biodone)\n", bio);
		bio->bio_buf->b_resid = 0;
		biodone(bio);
	}
	lwkt_reltoken(&sc->tok);
}

static int
asyncdstrategy(struct dev_strategy_args *ap)
{
	struct bio *bio = ap->a_bio;
	struct buf *bp = bio->bio_buf;
	struct asyncd_softc *sc = &asc;

	switch (bp->b_cmd) {
	case BUF_CMD_READ:
		if (asyncd_trace)
			kprintf("asyncd: READ off=%lld len=%ld\n",
			    (long long)bio->bio_offset, (long)bp->b_bcount);
		bcopy(sc->image + bio->bio_offset, bp->b_data, bp->b_bcount);
		bp->b_resid = 0;
		biodone(bio);
		return (0);
	case BUF_CMD_WRITE:
	case BUF_CMD_FLUSH:
		if (asyncd_trace)
			kprintf("asyncd: %s off=%lld len=%ld\n",
			    bp->b_cmd == BUF_CMD_WRITE ? "WRITE" : "FLUSH",
			    (long long)bio->bio_offset, (long)bp->b_bcount);
		bcopy(bp->b_data, sc->image + bio->bio_offset, bp->b_bcount);
		bp->b_resid = 0;
		biodone(bio);
		return (0);
	case BUF_CMD_FREEBLKS:
		/* DEFER completion -> mimics async TRIM/DISCARD */
		kprintf("asyncd: FREEBLKS bio %p offset=%lld len=%ld (deferring %ds)\n",
		    bio, (long long)bio->bio_offset, (long)bp->b_bcount,
		    ASYNCD_DELAY_HZ/hz);
		lwkt_gettoken(&sc->tok);
		TAILQ_INSERT_TAIL(&sc->trimq, bio, bio_act);
		if (!sc->timer_armed) {
			sc->timer_armed = 1;
			callout_reset(&sc->timer, ASYNCD_DELAY_HZ,
			    asyncd_complete, sc);
		}
		lwkt_reltoken(&sc->tok);
		return (0);
	default:
		bp->b_flags |= B_ERROR;
		bp->b_error = EINVAL;
		bp->b_resid = bp->b_bcount;
		biodone(bio);
		return (0);
	}
}

static int
asyncd_modevent(module_t mod, int cmd, void *arg)
{
	struct disk_info info;
	int err = 0;

	switch (cmd) {
	case MOD_LOAD:
		bzero(&asc, sizeof(asc));
		lwkt_token_init(&asc.tok, "asyncd");
		TAILQ_INIT(&asc.trimq);
		callout_init_mp(&asc.timer);
		asc.size = ASYNCD_SIZE_MB * 1024 * 1024;
		asc.image = kmalloc(asc.size, M_ASYNCD, M_WAITOK | M_ZERO);
		if (asc.image == NULL)
			return (ENOMEM);

		devstat_add_entry(&asc.stats, "asyncd", 0, DEV_BSIZE,
		    DEVSTAT_NO_ORDERED_TAGS,
		    DEVSTAT_TYPE_DIRECT | DEVSTAT_TYPE_IF_OTHER,
		    DEVSTAT_PRIORITY_OTHER);
		asc.dev = disk_create(0, &asc.disk, &asyncd_ops);
		asc.dev->si_drv1 = &asc;
		asc.dev->si_iosize_max = MAXPHYS;

		bzero(&info, sizeof(info));
		info.d_media_blksize = DEV_BSIZE;
		info.d_media_blocks = asc.size / DEV_BSIZE;
		info.d_secpertrack = 1024;
		info.d_nheads = 1;
		info.d_secpercyl = info.d_secpertrack * info.d_nheads;
		info.d_ncylinders = info.d_media_blocks / info.d_secpercyl;
		disk_setdiskinfo(&asc.disk, &info);
		/* no disklabel slice required: expose as a raw disk */
		kprintf("asyncd0: %dMB async-TRIM disk (FREEBLKS delayed %dms)\n",
		    ASYNCD_SIZE_MB, 1000 / hz * ASYNCD_DELAY_HZ);
		break;
	case MOD_UNLOAD:
		callout_cancel(&asc.timer);
		/* drain any pending bios */
		lwkt_gettoken(&asc.tok);
		{
			struct bio *bio;
			while ((bio = TAILQ_FIRST(&asc.trimq)) != NULL) {
				TAILQ_REMOVE(&asc.trimq, bio, bio_act);
				bio->bio_buf->b_resid = 0;
				biodone(bio);
			}
		}
		lwkt_reltoken(&asc.tok);
		if (asc.dev)
			disk_destroy(&asc.disk);
		devstat_remove_entry(&asc.stats);
		if (asc.image)
			kfree(asc.image, M_ASYNCD);
		break;
	default:
		err = EOPNOTSUPP;
		break;
	}
	return (err);
}

DEV_MODULE(asyncd, asyncd_modevent, NULL);
MODULE_VERSION(asyncd, 1);