linux_task_alloc() leaks the task_struct of every pure kernel thread that evaluates `current` - no drop path exists on the kthread-exit (lwkt_exit) route
Summary
The only drop hook is on lwp_exit; pure kthreads exit via lwkt_exit which has none - every kernel thread touching drm code (irq/fence/syncer paths) permanently leaks a task_struct + spinlock, and the parked td keeps a dangling back-pointer (recycled-td aliasing killed: lwkt_init_thread bzeroes on reuse). Guest: 8/8 leaked baseline vs 8/8 clean with the lwkt_exit hook rebuilt into the kernel. Distinct from DF-2196 (different site/trigger).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5201 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df5201.c | β | 7.6 KB | view raw | |
| Makefile | β | 326 B | β download | |
| build.sh | β | 91 B | view raw | |
| run.sh | β | 136 B | view raw | |
| build.log | β | 1.4 KB | view raw | |
| run.log | β | 1.6 KB | view raw | |
| run_fixed.log | β | 1.1 KB | view raw | |
| fix.diff | β | 757 B | view raw | |
| env.txt | β | 431 B | view raw | |
| manifest.json | β | 1.0 KB | view raw | |
| verdict.json | β | 3.7 KB | view raw |
Fix verification
fixedfix.diff adds the lwkt_exit() drop hook; X86_64_GENERIC kernel rebuilt with it and booted: baseline (original kernel) 8/8 exited kthreads left leaked task_structs on their parked tds; patched 8/8 clean (task dropped at lwkt_exit), leak gone.
run.log (baseline 8/8 'task LEAKED') vs run_fixed.log (8/8 'task dropped at lwkt_exit'); objdump of lwkt_exit in the rebuilt kernel shows the td_linux_task check + linux_task_drop_callback indirect call
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (baseline, original kernel: 8/8 'PARKED: exited td=... still carries td_linux_task=... -> task LEAKED', plus W1/W2 td-recycling rounds entering clean); run_fixed.log (patched kernel: 8/8 'exited td=... is clean (task dropped at lwkt_exit)')
PoC changes
no seed PoC; authored fresh. Fixes during bring-up: sysctl node hw.df5201.test; sys/kthread.h include for kthread_create; ASCII-only comments; added deterministic parked-td metric (read W1's td_linux_task after exit) after the address-recycle indicator proved cpu-migration sensitive (0/8 on both kernels).
Verified recommended fix
Hook lwkt_exit() to call linux_task_drop_callback when td_linux_task is set (mirrors the lwp_exit hook), in the may-block cleanup region after dsched_exit_thread().
Verdict
REPRODUCED on the QEMU guest via a KLD driving the real linux_task_alloc (drm.ko) from pure kthreads created with kthread_create(). Every kernel thread that evaluates the current macro gets a task_struct installed at linux_sched.c:88, but the only drop path (linux_task_drop_callback) fires from lwp_exit (kern_exit.c:708-709), which pure kthreads never execute (they exit via kthread_exit -> lwkt_exit, lwkt_thread.c:1684-1744). Baseline: 8/8 exited kthreads left their parked td still carrying the leaked, never-dropped task_struct pointer (8 distinct leaked allocations, addresses never recycled). Suspected escalation to cross-thread current aliasing via recycled tds was investigated and KILLED: lwkt_init_thread (lwkt_thread.c:440) bzeroes the whole struct thread on reuse, so a recycled td always starts clean (observed: rounds where W2 received exactly W1's td entered clean) - the surviving defect is the unbounded permanent leak, one task_struct+spinlock per kernel thread that ever executes drm code (dma_fence_wait/prepare_to_wait/set_current_state/... on irq, callout, syncer, bufdaemon threads).
No comments yet.