/*-
 * DF-4724 premise verification KLD.
 *
 * Simulates, with zero risk, the exact newbus pattern of
 * sys/dev/sound/pci/aureal.c au_pci_attach():
 *
 *   mixer_init(dev, ...)  ->  make_dev(&mixer_ops, ..., 0666, "mixer%d")
 *   ... later step fails  ->  goto bad; free(au); return ENXIO
 *
 * i.e. a driver whose attach() creates a world-writable devfs node and
 * then FAILS. The claim to verify on the live DragonFly kernel:
 *
 *   (1) newbus does NOT call detach when attach returns an error, so
 *       nothing ever destroys the node;
 *   (2) the /dev node persists with 0666 perms after the failure;
 *   (3) an UNPRIVILEGED user can open() it and reach module code that,
 *       in the real driver, would dereference the freed `au` softc.
 *
 * The probe only ever matches PCI device 0x7113:8086 (the unbound PIIX4
 * PM/ACPI function reported as none0 in this QEMU guest), so no real
 * device can be hijacked.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/bus.h>
#include <sys/proc.h>
#include <bus/pci/pcivar.h>
#include <bus/pci/pcireg.h>

static struct cdev *prem_cdev = NULL;

static int
prem_open(struct dev_open_args *ap)
{
	kprintf("PREMTEST: open() reached module code "
	    "(node alive after failed attach)\n");
	return (0);
}

static int
prem_ioctl(struct dev_ioctl_args *ap)
{
	kprintf("PREMTEST: ioctl %lx reached module code\n",
	    (unsigned long)ap->a_cmd);
	return (0);
}

static struct dev_ops prem_ops = {
	{ "premtest", 0, D_TRACKCLOSE },
	.d_open = prem_open,
	.d_ioctl = prem_ioctl,
};

static int
prem_probe(device_t dev)
{
	if (pci_get_devid(dev) == 0x71138086)	/* unbound PIIX4 PM fn */
		return (BUS_PROBE_HOOVER);
	return (ENXIO);
}

static int
prem_attach(device_t dev)
{
	kprintf("PREMTEST: attach() on %s%d: make_dev(0666) then FAIL ENXIO "
	    "(simulates aureal mixer_init-ok + later goto bad)\n",
	    device_get_name(dev), device_get_unit(dev));
	/*
	 * Same shape as mixer_init(): world-writable node created during
	 * attach, pointer to it never recorded anywhere that would clean
	 * it up on attach failure.
	 */
	prem_cdev = make_dev(&prem_ops, device_get_unit(dev),
	    UID_ROOT, GID_WHEEL, 0666, "premtest%d", device_get_unit(dev));

	return (ENXIO);		/* <- the later-step failure (goto bad) */
}

static device_method_t prem_methods[] = {
	DEVMETHOD(device_probe,		prem_probe),
	DEVMETHOD(device_attach,	prem_attach),
	{ 0, 0 }
};

static driver_t prem_driver = {
	"premtest",
	prem_methods,
	0
};

static devclass_t prem_devclass;

static int
prem_modevent(module_t mod, int type, void *arg)
{
	switch (type) {
	case MOD_LOAD:
		return (0);
	case MOD_UNLOAD:
		/*
		 * Guest hygiene only: the real aureal driver has no such
		 * cleanup (that is the bug); we destroy the node on unload
		 * so the test guest is left clean.
		 */
		if (prem_cdev != NULL) {
			destroy_dev(prem_cdev);
			prem_cdev = NULL;
		}
		return (0);
	}
	return (0);
}

DRIVER_MODULE(premtest, pci, prem_driver, prem_devclass, prem_modevent, NULL);
MODULE_VERSION(premtest, 1);
