/*
 * DF-2679 / DF-2680 PoC (kernel side churn): adds N children to root_bus
 * driven by a real driver so they attach (devadded -> devctl events ->
 * ksignal(async_proc)) and detach (devremoved) like ordinary devices.
 * On MOD_UNLOAD the driver is deleted and the children are deleted
 * (device_delete_child -> kobj_delete -> kfree).
 *
 * DF-2679: the deletes race unprivileged sysctl_devices() walkers.
 * DF-2680: each attach/detach fires devctl_queue_data() -> ksignal().
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/bus_private.h>
#include <sys/malloc.h>

/* root_bus is declared in <sys/bus.h>; root_devclass is a global kernel
 * symbol without a public prototype */
extern devclass_t root_devclass;

#define NDEV 512

MALLOC_DEFINE(M_DFRACE, "dfrace", "DF-2679 poison chunks");

static device_t dfrace_devs[NDEV];
static void *dfrace_junk[NDEV];

static int	dfrace_probe(device_t dev);
static int	dfrace_attach(device_t dev);
static int	dfrace_detach(device_t dev);

static device_method_t dfrace_methods[] = {
	DEVMETHOD(device_probe,		dfrace_probe),
	DEVMETHOD(device_attach,	dfrace_attach),
	DEVMETHOD(device_detach,	dfrace_detach),
	DEVMETHOD_END
};

static driver_t dfrace_driver = {
	"dfrace",			/* must equal the child devclass name */
	dfrace_methods,
	1,			/* no softc */
};

static int
dfrace_probe(device_t dev)
{
	return (0);
}

static int
dfrace_attach(device_t dev)
{
	return (0);
}

static int
dfrace_detach(device_t dev)
{
	return (0);
}

static int
dfrace_modev(module_t mod, int what, void *arg)
{
	int i, n;

	switch (what) {
	case MOD_LOAD:
		for (i = 0; i < NDEV; i++) {
			if (dfrace_junk[i]) {
				kfree(dfrace_junk[i], M_DFRACE);
				dfrace_junk[i] = NULL;
			}
		}
		n = 0;
		for (i = 0; i < NDEV; i++) {
			dfrace_devs[i] = device_add_child(root_bus, "dfrace", i);
			if (dfrace_devs[i])
				n++;
		}
		/* register driver on the root bus devclass -> probe+attach */
		devclass_add_driver(root_devclass, &dfrace_driver);
		kprintf("dfrace: added+attached %d children to root_bus\n", n);
		return (0);
	case MOD_UNLOAD:
		devclass_delete_driver(root_devclass, &dfrace_driver);
		n = 0;
		for (i = 0; i < NDEV; i++) {
			if (dfrace_devs[i]) {
				if (device_delete_child(root_bus,
							dfrace_devs[i]) == 0) {
					dfrace_devs[i] = NULL;
					n++;
				}
			}
			/*
			 * Back-fill the just-freed chunk with a poisoned
			 * same-bucket allocation (models any racing kernel
			 * allocation; makes the UAF fault loudly).
			 */
			dfrace_junk[i] = kmalloc(sizeof(struct bsd_device),
						 M_DFRACE, M_WAITOK | M_ZERO);
			if (dfrace_junk[i])
				memset(dfrace_junk[i], 0xAA,
				       sizeof(struct bsd_device));
		}
		kprintf("dfrace: detached+deleted %d children\n", n);
		for (i = 0; i < NDEV; i++) {
			if (dfrace_junk[i]) {
				kfree(dfrace_junk[i], M_DFRACE);
				dfrace_junk[i] = NULL;
			}
		}
		return (0);
	default:
		return (0);
	}
}

static moduledata_t dfrace_mod = {
	"dfrace",
	dfrace_modev,
	NULL
};
DECLARE_MODULE(dfrace, dfrace_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(dfrace, 1);
