/*
 * DF-5220 mechanism demonstration (no GPU required).
 *
 * Reproduces the corruption primitive behind the i915 engine-timeline
 * use-after-free WITHOUT any Intel graphics hardware:
 *
 *   1. objects A and C are linked into a private intrusive list
 *        (mirrors list_add(&engine->timeline.link, &gt.timelines)
 *         at sys/dev/drm/i915/i915_timeline.c:28, with the
 *         kernel-context ring timeline node as the second entry)
 *   2. A is kfree()d while still linked
 *        (mirrors kfree(engine) at sys/dev/drm/i915/intel_engine_cs.c:417
 *         executed while engine->timeline.link is live on gt.timelines,
 *         because the execlists init error path never calls
 *         intel_engine_cleanup_common / i915_timeline_fini)
 *   3. the freed chunk is reclaimed by an unrelated allocation D (slab LIFO)
 *   4. unlinking the NEIGHBOR C writes a pointer into D
 *        (mirrors i915_timeline_fini()'s list_del at
 *         sys/dev/drm/i915/i915_timeline.c:75, executed via
 *         i915_gem_contexts_fini() on the kernel-context ring timeline
 *         during the i915_gem_init error unwind)
 *
 * Success criterion: D (a zeroed, reallocated chunk) contains a non-NULL
 * pointer at the offset of A's le_prev slot after step 4 -> proof that
 * unlinking a neighbor performs a write into reallocated freed memory.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/queue.h>

MALLOC_DEFINE(M_DF5220, "df5220", "DF-5220 list-neighbor write demo");

struct obj {
	char             pad[64];
	LIST_ENTRY(obj)  link;   /* le_next at +64, le_prev at +72 */
	char             tail[64];
};

#define ATTEMPTS 4

static LIST_HEAD(listhead, obj) head;

static int
df5220_modevent(module_t mod, int type, void *arg)
{
	int i;
	int proven = 0;

	switch (type) {
	case MOD_LOAD:
		for (i = 0; i < ATTEMPTS && !proven; i++) {
			struct obj *a, *c, *d;
			void **slot;

			a = kmalloc(sizeof(*a), M_DF5220, M_WAITOK | M_ZERO);
			c = kmalloc(sizeof(*c), M_DF5220, M_WAITOK | M_ZERO);

			/* link A, then C at head:  head -> C -> A */
			LIST_INSERT_HEAD(&head, a, link);
			LIST_INSERT_HEAD(&head, c, link);

			kprintf("df5220[%d]: A=%p C=%p A.le_prev=%p "
			    "C.le_next=%p\n", i, a, c, a->link.le_prev,
			    c->link.le_next);

			/* BUG MIRROR: kfree(A) while A is still linked */
			kfree(a, M_DF5220);

			/* unrelated allocation reclaims the freed chunk */
			d = kmalloc(sizeof(*d), M_DF5220, M_WAITOK | M_ZERO);
			kprintf("df5220[%d]: D=%p (reclaim of A: %s)\n",
			    i, d, (d == a) ? "HIT" : "MISS");

			/*
			 * NEIGHBOR UNLINK.  LIST_REMOVE(C) executes
			 *   C->link.le_next->le_prev = C->link.le_prev;
			 * C.le_next == &A.link (freed) -> pointer write into
			 * the reclaimed chunk at offset 72.
			 */
			LIST_REMOVE(c, link);

			slot = (void **)((char *)d +
			    offsetof(struct obj, link));
			kprintf("df5220[%d]: after LIST_REMOVE(C): "
			    "D.le_next=%p D.le_prev=%p\n",
			    i, slot[0], slot[1]);

			if (d == a && slot[1] != NULL) {
				kprintf("df5220[%d]: VULN-WRITE PROVEN: "
				    "pointer %p written into reallocated "
				    "chunk D at offset %d after kfree(A)\n",
				    i, slot[1],
				    (int)(offsetof(struct obj, link) +
				      sizeof(void *)));
				proven = 1;
			}

			kfree(c, M_DF5220);
			kfree(d, M_DF5220);
		}
		if (!proven)
			kprintf("df5220: INCONCLUSIVE (reclaim miss x%d)\n",
			    ATTEMPTS);
		else
			kprintf("df5220: mechanism demonstrated: neighbor "
			    "unlink writes into freed-and-reallocated "
			    "memory\n");
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t df5220_mod = { "df5220", df5220_modevent, 0 };
DECLARE_MODULE(df5220, df5220_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
