/*
 * DF-4438 logic mirror: mrsas_cam_attach() error paths vs mrsas_cam_detach()
 * unwind — double cam_sim_free() / use-after-free of struct cam_sim.
 *
 * This is NOT a kernel exploit; it is a faithful userspace mirror of the
 * DragonFlyBSD CAM refcount/cleanup choreography exercised by the bug:
 *
 *   sys/bus/cam/cam_sim.c:cam_sim_alloc()      -> cam_devq_reference(queue);
 *                                                  sim->refcount = 1;
 *   sys/bus/cam/cam_sim.c:cam_sim_free()       -> cam_sim_release(sim,..):
 *                                                  if (sim->refcount == 1) {
 *                                                      cam_simq_release(sim->devq);
 *                                                      kfree(sim, M_CAMSIM);
 *                                                  }
 *
 *   sys/dev/raid/mrsas/mrsas_cam.c:150-156  (xpt_bus_register(sim_0) fails)
 *       cam_simq_release(devq);
 *       cam_sim_free(sc->sim_0);          <-- frees sim_0, sc->sim_0 DANGLING
 *       return (ENXIO);
 *
 *   sys/dev/raid/mrsas/mrsas.c:805,838-839 (mrsas_attach: goto attach_fail_cam)
 *       mrsas_cam_detach(sc);
 *
 *   sys/dev/raid/mrsas/mrsas_cam.c:216-218 (mrsas_cam_detach)
 *       if (sc->sim_0) {
 *           xpt_bus_deregister(cam_sim_path(sc->sim_0));  <-- UAF read
 *           cam_sim_free(sc->sim_0);                      <-- DOUBLE FREE
 *       }
 *
 * Mode "buggy" reproduces the exact current call sequence and detects the
 * use-after-free (cam_sim_path on freed sim) and the double free
 * (cam_sim_release on freed sim).  Mode "fixed" applies the proposed
 * sc->sim_0 = NULL cleanup and shows both stay clean.
 *
 * Build: cc -O2 -Wall -o repro_logic repro_logic.c
 * Run:   ./repro_logic buggy ; ./repro_logic fixed
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int failures;

struct cam_devq { int refcount; int freed; };
struct cam_sim  { int refcount; int freed; struct cam_devq *devq; int path_id; };

/* ---- sys/bus/cam/cam_sim.c ---- */
static struct cam_devq *cam_simq_alloc(void)
{
	struct cam_devq *q = calloc(1, sizeof(*q));
	q->refcount = 1;
	return q;
}
static void cam_devq_reference(struct cam_devq *q) { if (q->freed) { printf("  !! devq UAF: reference on freed devq\n"); failures++; } q->refcount++; }
static void cam_simq_release(struct cam_devq *q)
{
	if (q->freed) { printf("  !! devq DOUBLE RELEASE (refcount underflow)\n"); failures++; return; }
	if (--q->refcount == 0) q->freed = 1;	/* kfree(devq) */
}
static struct cam_sim *cam_sim_alloc(struct cam_devq *queue)
{
	struct cam_sim *sim = calloc(1, sizeof(*sim));
	cam_devq_reference(queue);	/* cam_sim.c:182 */
	sim->refcount = 1;
	sim->devq = queue;
	return sim;
}
/* cam_sim_path(): plain deref of the sim pointer, as in the kernel */
static int cam_sim_path(struct cam_sim *sim)
{
	if (sim->freed) { printf("  !! sim UAF: cam_sim_path() on freed cam_sim (kernel: read of kfree'd M_CAMSIM)\n"); failures++; }
	return sim->path_id;
}
static void xpt_bus_deregister(int path_id)
{
	(void)path_id;	/* in-kernel this unlinks the registered bus; here a no-op */
}
static void cam_sim_free(struct cam_sim *sim)
{
	/* cam_sim_release(sim, CAM_SIM_SOFTC) */
	if (sim->freed) { printf("  !! sim DOUBLE FREE: cam_sim_release()/kfree() on already-freed cam_sim\n"); failures++; return; }
	if (sim->refcount == 1) {
		if (sim->devq) { cam_simq_release(sim->devq); sim->devq = NULL; }
		sim->freed = 1;		/* kfree(sim, M_CAMSIM) */
	} else {
		sim->refcount--;
	}
}

/* ---- mrsas_softc (only the fields that matter) ---- */
struct mrsas_softc { struct cam_sim *sim_0; struct cam_sim *sim_1; };

/*
 * mrsas_cam_attach(), reduced to the failing path of interest:
 * xpt_bus_register(sc->sim_0, 0) fails (mrsas_cam.c:150-156).
 * Returns ENXIO like the kernel code.
 */
static int mrsas_cam_attach_sim0_register_fails(struct mrsas_softc *sc,
						struct cam_devq *devq,
						int fixed)
{
	sc->sim_0 = cam_sim_alloc(devq);			/* mrsas_cam.c:132 */
	/* xpt_bus_register(sc->sim_0, 0) != CAM_SUCCESS */
	cam_simq_release(devq);					/* mrsas_cam.c:152 */
	cam_sim_free(sc->sim_0);				/* mrsas_cam.c:153 */
	if (fixed)
		sc->sim_0 = NULL;				/* THE FIX */
	return 1;						/* ENXIO */
}

/* mrsas_cam_detach(), mrsas_cam.c:216-218 (sim_0 part) */
static void mrsas_cam_detach(struct mrsas_softc *sc)
{
	if (sc->sim_0) {
		xpt_bus_deregister(cam_sim_path(sc->sim_0));	/* mrsas_cam.c:217 */
		cam_sim_free(sc->sim_0);			/* mrsas_cam.c:218 */
	}
}

int main(int argc, char **argv)
{
	int fixed = (argc == 2 && strcmp(argv[1], "fixed") == 0);

	printf("== DF-4438 mirror: mode=%s ==\n", fixed ? "fixed" : "buggy");
	struct mrsas_softc *sc = calloc(1, sizeof(*sc));
	struct cam_devq *devq = cam_simq_alloc();		/* mrsas_cam.c:123 */

	int rc = mrsas_cam_attach_sim0_register_fails(sc, devq, fixed);
	printf("mrsas_cam_attach returned %d -> mrsas_attach: goto attach_fail_cam -> mrsas_cam_detach()\n", rc);
	mrsas_cam_detach(sc);					/* mrsas.c:839 */

	if (failures == 0)
		printf("RESULT: clean (no UAF / double free detected)\n");
	else
		printf("RESULT: %d kernel-memory-safety violation(s) detected"
		       " (UAF + double free of struct cam_sim / M_CAMSIM)\n",
		       failures);
	return failures ? 2 : 0;
}
