kthread_park() provides no quiescence: returns without waiting for the target to park while kthread_parkme() never deschedules - the park/unpark contract drm_sched_entity_fini() and amdgpu GPU-reset rely on is void, giving a deterministic use-after-free on entity teardown
Summary
kthread_park returns immediately (no wait-for-park handshake) and kthread_parkme's deschedule is a NO-OP on a running thread - every consumer races the worker it believes stopped: drm_sched_entity_fini parks-then-frees while the worker is mid-select/pop on that entity (unpriv process-exit path on GPU systems; also every amdgpu GPU reset). Guest-verified 5/5: worker executed ~25 entity touches after park returned; entity_fini-shaped park;free;kzalloc produced a silent write-UAF into the same-address victim (10 writes/round, 82 UAF reads) + controlled panic proof. Park-handshake fix validated on rebuilt drm.ko (0/5 violations).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5200 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df5200.c | β | 10.3 KB | view raw | |
| Makefile | β | 328 B | β download | |
| build.sh | β | 94 B | view raw | |
| run.sh | β | 186 B | view raw | |
| build.log | β | 1.3 KB | view raw | |
| run.log | β | 1.5 KB | view raw | |
| run.2.log | β | 1.3 KB | view raw | |
| panic.txt | β | 5.6 KB | view raw | |
| run_fixed.log | β | 2.0 KB | view raw | |
| fix.diff | β | 2.2 KB | view raw | |
| env.txt | β | 431 B | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 4.6 KB | view raw |
Fix verification
fixedfix.diff applied to guest /usr/src, drm.ko rebuilt from patched linux_kthread.c, module re-run: baseline 5/5 RAN WHILE PARKED + 5/5 UAF corruptions + 82 UAF reads; patched 0/5 violations (visits frozen 13->13 all rounds), 0 corruptions, 0 UAF reads, guest stable.
run_fixed.log (t1: '13 -> 13 (frozen (correct))' x5, '0/5 rounds violated'; t2: '0/5 rounds corrupted the reallocated victim ... 0 UAF reads of freed memory (badmagic 0 -> 0)')
Confirmed kernel references
- sys/dev/drm/linux_kthread.c:103
- sys/dev/drm/linux_kthread.c:110
- sys/dev/drm/linux_kthread.c:126
- sys/dev/drm/linux_kthread.c:132
- sys/dev/drm/scheduler/sched_entity.c:277
- sys/dev/drm/scheduler/sched_main.c:514
- sys/dev/drm/amd/amdgpu/amdgpu_device.c:3412
- sys/dev/drm/amd/amdgpu/amdgpu_debugfs.c:868
- sys/kern/lwkt_thread.c:153
Detail
Exploit chain
unprivileged GPU client races process-exit/entity-fini (park; kfree(entity/jobs); unpark) against the ring scheduler worker that the park was supposed to quiesce -> worker UAF-writes freed drm_sched_entity/job objects (victim contents attacker-influenced via GPU submissions). Demonstrated on guest to the silent write-UAF-into-reallocated-object stage + panic proof; full uid=0 chain not constructed (no GPU on audit guest: no drm victim grooming available).
Evidence (decisive lines)
run.log (t1: 5/5 RAN WHILE PARKED), run.2.log (t2: 5/5 'UAF WRITE PROVEN ... overlap=yes', visits=10, payload[30]=0x4141414141414141, 82 badmagic reads), panic.txt (panic: DF5200 ... trace df5200_test2 <- sysctl), run_fixed.log (patched: 0/5, frozen 13->13, 0 corruptions, 0 UAF reads)
PoC changes
no seed PoC existed; authored fresh. Fixes during bring-up: sysctl node is hw.df5200.test (not hw.df5200_test); ASCII-only comments (kernel -Werror);
Verified recommended fix
Implement the park handshake: kthread_parkme() publishes KTHREAD_IS_PARKED and sleeps on its task_struct until unparked; kthread_park() waits (bounded) for that bit; kthread_unpark() drops the raw lwkt_schedule() and just clears the flag + wake_up_process().
Verdict
REPRODUCED on the QEMU guest via a KLD driving the pristine in-tree shim functions (kthread_run/kthread_should_park/kthread_parkme/kthread_park/kthread_unpark from drm.ko) with a drm_sched_main()-shaped worker. kthread_park() returns immediately and kthread_parkme()'s lwkt_deschedule_self() is a no-op on a running thread (TDF_RUNQ clear), so the worker keeps running: 5/5 rounds showed ~25 entity touches during a 500ms window that begins when kthread_park() returned (Linux contract: zero), and the drm_sched_entity_fini()-shaped sequence park(); kfree(entity); kzalloc(victim) yielded a deterministic silent write-UAF into the reallocated victim at the SAME address in 5/5 rounds (worker incremented victim->visits 10x/round, wrote 0x4141414141414141 payload slots, 82 UAF reads of freed memory), plus a controlled panic proof. Root cause: sys/dev/drm/linux_kthread.c:103-110 (no wait-for-park) + :126-133 (parkme no-op); consumers relying on the contract: sched_entity.c:273-279 (entity teardown), amdgpu_device.c:3412/3441 (GPU reset ring quiesce), amdgpu_debugfs.c:868/884.
No comments yet.