/*
 * DF-4971 harness: userspace mirror of the mfip_attach / mfip_cam_action /
 * mfip_start / mfip_detach choreography against a faithful model of the
 * DragonFly CAM object lifecycle (cam_sim.c refcounting, xpt_bus_register /
 * xpt_bus_deregister side effects transcribed from sys/bus/cam/).
 *
 * It demonstrates, with the stock mfip_detach():
 *   V1  ccbs still queued in sc->mfi_cam_ccbq at mfip_detach time are
 *       leaked (never xpt_done()'d) -> issuing periph hangs forever
 *       (mfip_cam_action has no XPT_ABORT support: default case replies
 *       CAM_REQ_INVALID, so CAM cannot reclaim them either).
 *   V2  mfi_softc.mfi_cam_start still points at mfip_start after the sim is
 *       freed; the parent mfi controller is still fully alive until
 *       mfi_pci_detach reaches mfi_free() (children are deleted first,
 *       mfi_pci.c:328), so mfi_intr()->mfi_startio() (mfi.c:1245) pops the
 *       leaked ccb and dispatches it through the stale callback.
 *   V3  the late mfip_done() calls xpt_done() on a ccb whose bus was
 *       deregistered (path->bus->sim == &cam_dead_sim per cam_xpt.c:4456)
 *       and whose periph was destroyed by the deregistration asyncs --
 *       CAM-state use-after-free shape.  On kldunload(8) of mfi/mfip the
 *       callback target itself is unmapped module text.
 *
 * And that the fixed mfip_detach() (drain + clear mfi_cam_start) removes
 * all three.
 *
 * Build:  cc -O -o mfip_detach_mirror mfip_detach_mirror.c
 * Run:    ./mfip_detach_mirror buggy|fixed    (exit 0 = clean, 1 = violated)
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>

static int violations;

/* ---------------- CAM model (transcribed semantics) -------------- */
struct cam_sim {
	const char	*name;
	int		 refcount;	/* cam_sim_alloc: 1 */
	int		 freed;		/* model: kfree happened */
	int		 flags;
#define CAM_SIM_DEREGISTERED	0x100
	struct cam_devq	*devq;
};
struct cam_devq {
	int refcount;
	int freed;
};
static struct cam_sim cam_dead_sim = { "dead_sim", 1, 0, 0, NULL };

static struct cam_devq *
cam_simq_alloc(void)					/* cam_sim.c:143 */
{
	struct cam_devq *q = calloc(1, sizeof(*q));
	q->refcount = 1;
	return q;
}
static void
cam_simq_release(struct cam_devq *q)			/* cam_sim.c:149 */
{
	if (--q->refcount == 0)
		q->freed = 1;			/* cam_devq_free */
}
static struct cam_sim *
cam_sim_alloc(struct cam_devq *queue)			/* cam_sim.c:159 (M_INTWAIT) */
{
	struct cam_sim *sim = calloc(1, sizeof(*sim));
	if (queue != NULL)
		queue->refcount++;		/* cam_sim.c:185 */
	sim->refcount = 1;
	sim->devq = queue;
	return sim;
}
static void
cam_sim_release(struct cam_sim *sim)			/* cam_sim.c:242 */
{
	if (sim->refcount == 1) {
		if (sim->devq) {
			cam_simq_release(sim->devq);
			sim->devq = NULL;
		}
		sim->refcount = 0;
		sim->freed = 1;			/* kfree(sim, M_CAMSIM) */
	} else {
		--sim->refcount;
	}
}
static void
cam_sim_free(struct cam_sim *sim)			/* cam_sim.c:229 */
{
	cam_sim_release(sim);
}
struct cam_bus {
	struct cam_sim *sim;				/* cam_xpt.c:4456 retarget */
	struct cam_sim *registered_sim;			/* ref holder */
	int	deregistered;
};
static void
xpt_bus_register(struct cam_bus *bus, struct cam_sim *sim)	/* cam_xpt.c:4298 */
{
	bus->sim = sim;
	bus->registered_sim = sim;
	sim->refcount++;			/* cam_xpt.c:4322 */
}
static void
xpt_bus_deregister(struct cam_bus *bus)			/* cam_xpt.c:4361 */
{
	/*
	 * Model of what CAM does *not* recover for us: the XXX at
	 * cam_xpt.c:4383 -- AC_LOST_DEVICE "does not precisely abort the
	 * pending requests".  SIM-private queue entries are invisible to
	 * the devq/ccbq walk at cam_xpt.c:4402-4421.
	 */
	bus->registered_sim->flags |= CAM_SIM_DEREGISTERED;
	bus->sim = &cam_dead_sim;		/* cam_xpt.c:4456 */
	bus->deregistered = 1;
	cam_sim_release(bus->registered_sim);	/* cam_xpt.c:4484 */
}

/* ---------------- ccb / periph model ------------------------------ */
struct ccb_hdr {
	TAILQ_ENTRY(ccb_hdr) sim_links;
	int		 id;
	int		 done;			/* xpt_done() called */
	struct cam_bus	*path_bus;		/* ccb->ccb_h.path->bus */
	int		 periph_gone;		/* periph destroyed by dereg asyncs */
};
TAILQ_HEAD(ccb_hdrq, ccb_hdr);

static void
xpt_done(struct ccb_hdr *ccbh)				/* cam_xpt.c:4891 */
{
	struct cam_sim *sim = ccbh->path_bus->sim;	/* cam_xpt.c:4898 */

	/*
	 * cam_xpt.c:4900+ dispatches through path->periph; after the bus was
	 * deregistered the asyncs have destroyed periphs of outstanding
	 * ccbs that CAM believes it aborted (but our ccb was invisible).
	 */
	if (ccbh->periph_gone) {
		printf("    [xpt_done] UAF: ccb %d completed after its periph "
		    "was destroyed (dispatch sim=%p %s)\n", ccbh->id,
		    (void *)sim, sim->name);
		violations++;			/* V3 */
	}
	ccbh->done = 1;
}

/* ---------------- mfi / mfip model --------------------------------- */
struct mfi_softc {
	struct ccb_hdrq	mfi_cam_ccbq;		/* mfivar.h:302 */
	struct mfi_command *(*mfi_cam_start)(void *);	/* mfivar.h:303 */
	int		free_cmds;		/* mfi_free TAILQ depth */
	int		module_text_mapped;	/* kldunload model */
};
struct mfi_command {
	struct ccb_hdr	*ccb;
};
struct mfip_softc {
	struct mfi_softc *mfi_sc;
	struct cam_sim	*sim;
};

static struct mfi_softc *g_mfisc;
static struct mfi_command *mfip_start(void *);

/* mfip_start(), mfi_cam.c:255-316 (data plumbing elided) */
static struct mfi_command *
mfip_start(void *data)
{
	struct ccb_hdr *ccbh = data;
	struct mfi_softc *sc = g_mfisc;	/* via ccb_mfip_ptr, mfi_cam.c:241/266 */
	struct mfi_command *cm;

	if (!sc->module_text_mapped) {
		printf("    [mfip_start] UAF: call into unmapped module text "
		    "after kldunload\n");
		violations++;				/* V2 (unload case) */
		return NULL;
	}
	if (sc->free_cmds == 0)
		return NULL;			/* mfi_dequeue_free empty */
	cm = calloc(1, sizeof(*cm));
	cm->ccb = ccbh;
	sc->free_cmds--;
	TAILQ_REMOVE(&sc->mfi_cam_ccbq, ccbh, sim_links);	/* mfi_cam.c:314 */
	return cm;
}

/* mfip_cam_action() XPT_SCSI_IO, mfi_cam.c:220-245 */
static void
mfip_cam_action_scsiio(struct mfi_softc *sc, struct ccb_hdr *ccbh)
{
	TAILQ_INSERT_TAIL(&sc->mfi_cam_ccbq, ccbh, sim_links); /* :242 */
	/* mfi_startio() may or may not drain it now (free_cmds may be 0) */
}

/* mfip_done(), mfi_cam.c:318-379 (status plumbing elided) */
static void
mfip_done(struct mfi_softc *sc, struct mfi_command *cm)
{
	(void)sc;
	xpt_done(cm->ccb);			/* mfi_cam.c:378 */
	free(cm);
}

/* mfi_startio(), mfi.c:2118-2150 -- only the cam branch matters here */
static void
mfi_startio(struct mfi_softc *sc)
{
	struct ccb_hdr *ccbh;
	struct mfi_command *cm;

	while ((ccbh = TAILQ_FIRST(&sc->mfi_cam_ccbq)) != NULL) {
		cm = sc->mfi_cam_start(ccbh);		/* mfi.c:2133 */
		if (cm == NULL)
			break;
		/* model immediate firmware completion (mfi_intr path) */
		mfip_done(sc, cm);
	}
}

static int
qcount(struct mfi_softc *sc)
{
	struct ccb_hdr *c;
	int n = 0;
	TAILQ_FOREACH(c, &sc->mfi_cam_ccbq, sim_links)
		n++;
	return n;
}

/* ---------------- mfip_attach / mfip_detach ------------------------ */
static int
mfip_attach(struct mfip_softc *sc, struct cam_bus *bus, struct cam_devq **devq_out)
{
	sc->mfi_sc->mfi_cam_start = mfip_start;		/* mfi_cam.c:116 */
	if ((*devq_out = cam_simq_alloc()) == NULL)	/* mfi_cam.c:118 */
		return 1;
	sc->sim = cam_sim_alloc(*devq_out);		/* mfi_cam.c:121 */
	cam_simq_release(*devq_out);			/* mfi_cam.c:124 */
	xpt_bus_register(bus, sc->sim);			/* mfi_cam.c:131 */
	return 0;
}

static void
mfip_detach_stock(struct mfip_softc *sc, struct cam_bus *bus)
{
	/* mfi_cam.c:142-159, verbatim structure */
	if (sc->sim != NULL) {
		xpt_bus_deregister(bus);		/* mfi_cam.c:153 */
		cam_sim_free(sc->sim);			/* mfi_cam.c:154 */
	}
	/* NB: mfi_cam_ccbq NOT drained, mfi_cam_start NOT cleared. */
}

static void
mfip_detach_fixed(struct mfip_softc *sc, struct cam_bus *bus)
{
	struct ccb_hdr *ccbh;

	if (sc->sim != NULL) {
		/* drain SIM-private queue, fail the ccbs back to CAM */
		while ((ccbh = TAILQ_FIRST(&sc->mfi_sc->mfi_cam_ccbq)) != NULL) {
			TAILQ_REMOVE(&sc->mfi_sc->mfi_cam_ccbq, ccbh, sim_links);
			ccbh->periph_gone = 0;	/* still registered: safe */
			xpt_done(ccbh);
		}
		xpt_bus_deregister(bus);
		cam_sim_free(sc->sim);
		sc->mfi_sc->mfi_cam_start = NULL;	/* kill the stale cb */
	}
}

int
main(int argc, char **argv)
{
	struct mfi_softc sc;
	struct mfip_softc mfip;
	struct cam_bus bus;
	struct cam_devq *devq;
	struct ccb_hdr ccbs[4];
	int fixed, i;

	if (argc != 2 || (strcmp(argv[1], "buggy") && strcmp(argv[1], "fixed"))) {
		fprintf(stderr, "usage: %s buggy|fixed\n", argv[0]);
		return 2;
	}
	fixed = !strcmp(argv[1], "fixed");
	memset(&sc, 0, sizeof(sc));
	memset(&mfip, 0, sizeof(mfip));
	memset(&bus, 0, sizeof(bus));
	TAILQ_INIT(&sc.mfi_cam_ccbq);
	sc.module_text_mapped = 1;
	g_mfisc = &sc;
	mfip.mfi_sc = &sc;

	if (mfip_attach(&mfip, &bus, &devq) != 0)
		return 2;
	printf("attached: sim refcount=%d devq refcount=%d\n",
	    mfip.sim->refcount, devq->refcount);

	/*
	 * CAM boot scan / camcontrol passthrough issues 4 XPT_SCSI_IOs; the
	 * controller is busy (free command pool = 1), so 3 of them sit in
	 * the SIM-private mfi_cam_ccbq when detach begins.
	 */
	sc.free_cmds = 1;
	for (i = 0; i < 4; i++) {
		ccbs[i].id = i;
		ccbs[i].path_bus = &bus;
		ccbs[i].periph_gone = 0;
		mfip_cam_action_scsiio(&sc, &ccbs[i]);
	}
	mfi_startio(&sc);			/* one gets a command */
	printf("pre-detach: queued=%d free_cmds=%d\n", qcount(&sc), sc.free_cmds);

	/* mfi_pci_detach: children first (mfi_pci.c:328) -> mfip_detach */
	if (fixed)
		mfip_detach_fixed(&mfip, &bus);
	else
		mfip_detach_stock(&mfip, &bus);
	printf("post-detach: sim freed=%d bus sim=%s queued-nonempty=%s "
	    "mfi_cam_start=%s\n", mfip.sim->freed, bus.sim->name,
	    TAILQ_FIRST(&sc.mfi_cam_ccbq) ? "YES" : "no",
	    sc.mfi_cam_start ? "STALE (mfip_start)" : "NULL");

	if (TAILQ_FIRST(&sc.mfi_cam_ccbq) != NULL) {
		printf("V1 VIOLATED: %d ccb(s) leaked in mfi_cam_ccbq "
		    "(never xpt_done; no XPT_ABORT support -> unrecoverable "
		    "hang + leak)\n", qcount(&sc));
		violations++;
	}
	if (sc.mfi_cam_start != NULL) {
		printf("V2 VIOLATED: mfi_cam_start still set after sim freed\n");
		violations++;
	}

	/* deregistration destroyed periphs of still-outstanding ccbs */
	for (i = 0; i < 4; i++)
		if (!ccbs[i].done)
			ccbs[i].periph_gone = 1;

	/*
	 * Parent controller still alive: mfi_intr() completes a disk bio
	 * (returning an mfi_command to the free pool) and then calls
	 * mfi_startio() (mfi.c:1245) -- which pops the leaked CAM ccb.
	 */
	sc.free_cmds = 1;		/* disk command completed -> free */
	printf("simulating post-mfip_detach disk completion -> mfi_startio\n");
	mfi_startio(&sc);

	/* kldunload case: mfip module text unmapped while cb still set */
	if (sc.mfi_cam_start != NULL) {
		sc.module_text_mapped = 0;
		printf("simulating kldunload (module text unmapped) + one more "
		    "mfi_startio\n");
		mfi_startio(&sc);
	}

	printf("%s: %d violation(s)\n", fixed ? "FIXED" : "BUGGY", violations);
	return violations ? 1 : 0;
}
