DF-2849 / gtq_null.c
/* * DF-2849 PoC variant A — mode 1: enqueue racing taskqgroup_detach() in the * window where gtask->gt_taskqueue is already NULL * (subr_gtaskqueue.c:746) but the task object is still shared. * * grouptaskqueue_enqueue(queue==NULL) hits the INVARIANTS * panic("queue == NULL") (subr_gtaskqueue.c:203-207); without INVARIANTS * it is TQ_LOCK(NULL) -> lockmgr on a NULL-derived address -> kernel trap. * * The plain GROUPTASK_ENQUEUE macro is exactly what any in-tree-style * interrupt handler would do. */ #include <sys/param.h> #include <sys/conf.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/lock.h> #include <sys/gtaskqueue.h> #include <sys/thread.h> #include <sys/thread2.h> static struct taskqgroup *rg; static struct grouptask gt; static volatile int stop; static struct thread *racer_td, *det_td; static void gt_fn(void *ctx) { } static void racer(void *arg) { while (!stop) { GROUPTASK_ENQUEUE(>); lwkt_yield(); /* avoid starving the CPU under test */ } lwkt_exit(); } static void detacher(void *arg) { int i; for (i = 0; i < 2000000 && !stop; i++) { taskqgroup_attach(rg, >, NULL, NULL, NULL, "gt"); taskqgroup_detach(rg, >); } stop = 1; lwkt_exit(); } static int gtq_null_ev(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: rg = taskqgroup_create("gtq_null", 2, 1); if (rg == NULL) return (ENOMEM); GROUPTASK_INIT(>, 0, gt_fn, NULL); gt.gt_taskqueue = NULL; taskqgroup_attach(rg, >, NULL, NULL, NULL, "gt"); lwkt_create(racer, NULL, &racer_td, NULL, TDF_NOSTART, 1, "gtnrace"); lwkt_setpri_initial(racer_td, TDPRI_KERN_DAEMON); lwkt_schedule(racer_td); lwkt_create(detacher, NULL, &det_td, NULL, TDF_NOSTART, 2, "gtndet"); lwkt_setpri_initial(det_td, TDPRI_KERN_DAEMON); lwkt_schedule(det_td); kprintf("gtq_null: racer/detacher running, expect panic\n"); return (0); default: break; } return (EOPNOTSUPP); } DEV_MODULE(gtq_null, gtq_null_ev, NULL); |