DF-5200 / df5200.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 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 | /* * df5200.c - DF-5200: DragonFly's LinuxKPI kthread shim provides NO park * quiescence: kthread_park() returns immediately without waiting for the * target to park, and kthread_parkme() never actually deschedules the * thread (lwkt_deschedule_self() on a running curthread is a no-op - * TDF_RUNQ is already clear, sys/kern/lwkt_thread.c:153-163). * * sys/dev/drm/linux_kthread.c:103-118,126-133: * * int kthread_park(struct task_struct *ts) * { * set_bit(KTHREAD_SHOULD_PARK, &ts->kt_flags); * wake_up_process(ts); * return ts->kt_exitvalue; <- returns IMMEDIATELY: * } no wait-for-park at all * * void kthread_parkme(void) * { * if (test_bit(KTHREAD_SHOULD_PARK, ¤t->kt_flags) == 0) * return; * lwkt_deschedule_self(curthread); <- NO-OP for a running thread; * } no lwkt_switch(), bit never * cleared here either * * Linux semantics (kernel/kthread.c): kthread_park() blocks on * wait_for_completion(&self->parked) until the target has actually parked; * the whole park/unpark API exists to QUIESCE a worker. In-tree callers * depend on that contract: * * drm_sched_entity_fini() sys/dev/drm/scheduler/sched_entity.c:273-279 * [comment in-tree: "Park the kernel for a moment to make sure it * isn't processing our enity."] * kthread_park(sched->thread); * kthread_unpark(sched->thread); * ... drm_sched_entity_kill_jobs(entity) + kfree(entity->rq_list) * amdgpu ring stop/start across GPU reset * sys/dev/drm/amd/amdgpu/amdgpu_device.c:3412,3441 * amdgpu debugfs ring reset sys/dev/drm/amd/amdgpu/amdgpu_debugfs.c:868,884 * * Because the shim's park returns while the worker is still anywhere in * its loop (including mid drm_sched_select_entity()/entity-pop on the * entity being freed), the fini-side frees race the worker's use of the * same structures -> use-after-free. * * This module drives the REAL in-tree shim functions (kthread_run / * kthread_should_park / kthread_parkme / kthread_park / kthread_unpark * resolved from drm.ko) with a drm_sched_main()-shaped worker loop. * * test=1: quiescence measurement - after kthread_park() returns, the * worker MUST be frozen (Linux contract). Count how many times * the "parked" worker kept running during a 500 ms window. * test=2: the drm_sched_entity_fini() shape - park(); kfree(entity); * kzalloc(victim) [memory reuse]; [worker must be unable to run * here]; check victim integrity after the window. * test=3: one round of test=2, then a deliberate panic() when the victim * corruption is observed (proof-of-primitive crash signature, * same style as DF-4842's KKASSERT panic). * * Root-only module test on the audit guest (no GPU required): * kldload amdgpu # pulls in drm.ko; provides the shim * kldload /tmp/df5200/df5200.ko * sysctl hw.df5200_test=1 # quiescence violation counts (no crash) * sysctl hw.df5200_test=2 # UAF read/write evidence (no crash) * sysctl hw.df5200_test=3 # same, then panic on proven corruption * * NOTE: teardown NEVER calls kthread_stop() - that is the (known, * separately-filed) DF-4842 bug; the worker exits via its own loop flag * and the DragonFly kthread machinery reclaims its td. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/sysctl.h> #include <sys/types.h> #include <sys/errno.h> #include <sys/module.h> #include <linux/slab.h> #include <linux/bitops.h> #include <linux/kthread.h> #include <linux/sched.h> #define DF5200_MAGIC 0x00c0ffee00c0ffeeUL #define DF5200_VICTIM 0xdeadbeefbaadf00dUL #define DF5200_ROUNDS 5 #define DF5200_SHOULD_PARK_BIT 2 /* KTHREAD_SHOULD_PARK, linux_kthread.c:80 */ /* * A drm-sched-entity-shaped object (512-byte class). The worker reads * magic (entity state) and writes payload slots + visits (job processing) * every loop iteration, exactly like drm_sched_main() touches its entity * after select_entity() hands it out. */ struct df5200_obj { uint64_t magic; uint64_t canary; uint64_t visits; uint64_t payload[61]; } __packed; static struct task_struct *g_task; static struct df5200_obj *g_ent; /* the live "entity" */ static volatile int g_stop; static volatile int g_exited; static volatile int g_writes; /* entity touches */ static volatile int g_badmagic; /* reads of stale/freed state */ /* * drm_sched_main()-shaped worker (sched_main.c:541-560): * while (!kthread_should_stop()) { * wait_event_interruptible(wq, (!drm_sched_blocked(sched) && * (entity = drm_sched_select_entity(sched))) ...) * ... process entity ... * } * drm_sched_blocked() -> kthread_should_park()/kthread_parkme() is our * park check; per-iteration entity capture is g_ent. */ static int df5200_worker(void *arg __unused) { struct df5200_obj *ent; int slot = 0; while (!g_stop) { if (kthread_should_park()) kthread_parkme(); ent = g_ent; if (ent != NULL) { if (ent->magic != DF5200_MAGIC) atomic_add_int(&g_badmagic, 1); /* UAF read */ ent->payload[slot] = 0x4141414141414141UL; ent->visits++; atomic_add_int(&g_writes, 1); /* UAF write (post-free) */ slot = (slot + 1) % 61; } tsleep(df5200_worker, 0, "df5200w", 1); } g_exited = 1; return (0); } static void df5200_init_worker(void) { g_ent = kzalloc(sizeof(*g_ent), GFP_KERNEL); g_ent->magic = DF5200_MAGIC; g_ent->canary = DF5200_MAGIC; g_stop = 0; g_exited = 0; g_writes = 0; g_badmagic = 0; g_task = kthread_run(df5200_worker, NULL, "df5200wkr"); /* let the worker start touching the entity */ tsleep(df5200_init_worker, 0, "df5200i", hz / 4); } static void df5200_fini_worker(void) { int i; g_stop = 1; for (i = 0; i < 40 && !g_exited; i++) /* up to 4 s */ tsleep(df5200_fini_worker, 0, "df5200f", hz / 10); /* kthread_stop() deliberately NOT used: that is DF-4842's bug. * The DFly kthread machinery reclaims the td itself; the * task_struct leak is the (known) DF-2196 issue. */ g_task = NULL; kfree(g_ent); g_ent = NULL; } static int df5200_test1(void) { uint64_t v0, v1; int r, viol = 0, total_d = 0; df5200_init_worker(); for (r = 1; r <= DF5200_ROUNDS; r++) { kthread_park(g_task); /* Linux: must not return unparked */ v0 = g_ent->visits; tsleep(df5200_test1, 0, "df5200q", hz / 2); v1 = g_ent->visits; kthread_unpark(g_task); kprintf("[DF5200] t1 r%d/%d: park() returned; worker visits " "during 500ms 'parked' window: %ju -> %ju (%s) " "SHOULD_PARK bit now=%d\n", r, DF5200_ROUNDS, (uintmax_t)v0, (uintmax_t)v1, (v1 > v0) ? "RAN WHILE PARKED" : "frozen (correct)", test_bit(DF5200_SHOULD_PARK_BIT, &g_task->kt_flags) ? 1 : 0); if (v1 > v0) { viol++; total_d += (int)(v1 - v0); } tsleep(df5200_test1, 0, "df5200q2", hz / 10); } kprintf("[DF5200] t1 RESULT: %d/%d rounds violated the park " "quiescence contract; %d post-park visits total\n", viol, DF5200_ROUNDS, total_d); df5200_fini_worker(); return (0); } static int df5200_test2(int dopanic) { struct df5200_obj *old, *victim; int r, corruptions = 0, overlaps = 0, bad0; df5200_init_worker(); bad0 = g_badmagic; for (r = 1; r <= DF5200_ROUNDS; r++) { kthread_park(g_task); /* returns immediately (bug) */ old = g_ent; kfree(old); /* drm_sched_entity_fini() frees * the entity right here */ victim = kzalloc(sizeof(*victim), GFP_KERNEL); /* reuse */ victim->magic = DF5200_VICTIM; victim->canary = DF5200_VICTIM; victim->visits = 0; if ((void *)victim == (void *)old) overlaps++; /* * The racing worker still has `old` captured for this loop * iteration (g_ent is still `old` - select_entity() had * already handed it out when fini struck). In Linux the * worker CANNOT run here: it is parked. In this shim it * keeps looping: reads old->magic (freed memory / UAF read) * and writes old->payload[*] + old->visits - hitting the * reallocated victim when the addresses overlap. */ tsleep(df5200_test2, 0, "df5200u", hz / 5); if (victim->visits != 0 || victim->payload[0] != 0 || victim->payload[30] != 0) { corruptions++; kprintf("[DF5200] t2 r%d/%d: UAF WRITE PROVEN: live " "victim %p (freed entity %p, overlap=%s) " "corrupted by the 'parked' worker: " "visits=%ju payload[0]=%#jx payload[30]=%#jx\n", r, DF5200_ROUNDS, victim, old, ((void *)victim == (void *)old) ? "yes" : "no", (uintmax_t)victim->visits, (uintmax_t)victim->payload[0], (uintmax_t)victim->payload[30]); if (dopanic) { panic("DF5200: kthread_park() quiescence " "broken - 'parked' worker UAF-wrote " "freed-then-reallocated memory " "(victim->visits=%ju payload[0]=%#jx)", (uintmax_t)victim->visits, (uintmax_t)victim->payload[0]); } } /* * Re-arm a fresh entity BEFORE un-parking the worker: on a * fixed kernel the worker is truly parked and never touches * the freed memory at all; on the buggy kernel it already * did during the window above. */ kfree(victim); g_ent = kzalloc(sizeof(*g_ent), GFP_KERNEL); g_ent->magic = DF5200_MAGIC; g_ent->canary = DF5200_MAGIC; kthread_unpark(g_task); tsleep(df5200_test2, 0, "df5200u2", hz / 10); } kprintf("[DF5200] t2 RESULT: %d/%d rounds corrupted the reallocated " "victim (%d same-address overlaps); %d UAF reads of freed " "memory (badmagic %d -> %d); total worker writes=%d\n", corruptions, DF5200_ROUNDS, overlaps, g_badmagic - bad0, bad0, g_badmagic, g_writes); df5200_fini_worker(); return (0); } static int sysctl_df5200_test(SYSCTL_HANDLER_ARGS) { int test, error; error = sysctl_handle_int(oidp, &test, 0, req); if (error != 0 || req->newptr == NULL) return (error); switch (test) { case 1: return (df5200_test1()); case 2: return (df5200_test2(0)); case 3: return (df5200_test2(1)); } return (EINVAL); } SYSCTL_NODE(_hw, OID_AUTO, df5200, CTLFLAG_RW, 0, "DF-5200 test"); SYSCTL_PROC(_hw_df5200, OID_AUTO, test, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_df5200_test, "I", "DF-5200 park-quiescence test selector"); static int df5200_modev(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("[DF5200] loaded (kthread shim from drm.ko)\n"); return (0); case MOD_UNLOAD: /* each test tears down its own worker before returning */ return (0); } return (0); } static moduledata_t df5200_mod = { "df5200", df5200_modev, 0 }; DECLARE_MODULE(df5200, df5200_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |