DF-4724 / premtest.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | /*- * 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); |