/*-
 * DF-4580 mechanism demo: DragonFly GFP_KERNEL allocation-failure semantics.
 *
 * The radeon CS ioctl (sys/dev/drm/radeon/radeon_cs.c:287,298,346) sizes
 * wired kernel allocations from the fully attacker-controlled u32
 * cs->num_chunks / chunk length_dw using GFP_KERNEL.
 *
 * On DragonFly GFP_KERNEL == M_WAITOK|M_CACHEALIGN  WITHOUT  M_NULLOK
 * (sys/dev/drm/include/linux/gfp.h:40).  With M_WAITOK and no M_NULLOK,
 * _kmalloc() can NEVER return NULL:
 *   - type limit exceeded  -> panic("%s: malloc limit exceeded")
 *                             (sys/kern/kern_slaballoc.c:877)
 *   - kernel_map full      -> panic("kmem_slab_alloc(): kernel_map ran out
 *                             of space!") (sys/kern/kern_slaballoc.c:1724)
 *   - pages unavailable    -> vm_wait() retry loop, forever
 *                             (sys/kern/kern_slaballoc.c:1780-1806)
 *
 * This module reproduces the *sink*: it performs the exact same kind of
 * allocation (M_WAITOK|M_ZERO, no M_NULLOK, own malloc type whose
 * ks_limit = physmem/10 exactly like every malloc type, incl. M_DRM,
 * sys/kern/kern_slaballoc.c:372) until the type limit is tripped.
 * Expected result: kernel panic "df4580: malloc limit exceeded".
 *
 * Not a radeon exploit: the radeon ioctl path itself needs AMD GPU
 * hardware (absent on the audit guest); this proves the sink behaviour the
 * ioctl would hit.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#include <vm/vm_page.h>

MALLOC_DEFINE(M_DF4580, "df4580", "DF-4580 GFP_KERNEL limit-panic mechanism demo");

static void *slots[64];
static int nslots;

static int
df4580_modevent(module_t mod, int type, void *data)
{
	size_t lim, sz;
	int i;

	switch (type) {
	case MOD_LOAD:
		/* Mirrors malloc_init(): every malloc type gets
		 * ks_limit = min(physmem, KvaSize)/10. */
		lim = (size_t)vmstats.v_page_count * PAGE_SIZE;
		if (lim > KvaSize)
			lim = KvaSize;
		lim /= 10;
		sz = 32 * 1024 * 1024;

		kprintf("df4580: malloc-type limit ~%zu MB; looping "
		    "kmalloc(%zu MB, M_WAITOK|M_ZERO) [no M_NULLOK], "
		    "GFP_KERNEL-equivalent\n", lim >> 20, sz >> 20);
		for (i = 0; i < 64; i++) {
			kprintf("df4580: alloc #%d (type usage ~%d MB)\n",
			    i, i * (int)(sz >> 20));
			slots[i] = kmalloc(sz, M_DF4580, M_WAITOK | M_ZERO);
			if (slots[i] == NULL) {
				kprintf("df4580: alloc #%d returned NULL "
				    "(NOT EXPECTED on DF)\n", i);
				nslots = i;
				return (0);
			}
			nslots = i + 1;
		}
		kprintf("df4580: loop finished without panic (unexpected)\n");
		return (0);
	case MOD_UNLOAD:
		for (i = 0; i < nslots; i++)
			kfree(slots[i], M_DF4580);
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t df4580_mod = { "df4580", df4580_modevent, NULL };
DECLARE_MODULE(df4580, df4580_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
