DF-4580 / df4580_limitpanic.c
/*- * 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); |