DF-5201 / verdict.json
{ "finding_id": "DF-5201", "status": "reproduced", "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 W1s 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).", "reproduced": true, "impact": "leak", "confidence": "certain", "exploit_chain": "", "evidence": "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))", "kernel_refs": [ "sys/dev/drm/linux_sched.c:58", "sys/dev/drm/linux_sched.c:88", "sys/dev/drm/include/asm/current.h:44", "sys/kern/kern_exit.c:708", "sys/kern/lwkt_thread.c:1684", "sys/kern/lwkt_thread.c:1740", "sys/kern/lwkt_thread.c:440" ], "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 W1s td_linux_task after exit) after the address-recycle indicator proved cpu-migration sensitive (0/8 on both kernels).", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 10, "guest_dirty": 1, "build_cmd": "kldload drm; cd /tmp/DF-5201 && make", "run_cmd": "kldload /tmp/DF-5201/df5201.ko; sysctl hw.df5201.test=1", "code_hash": "3c041c0e2e9f83842a2b43f560f0b967b61fd9f26de71e99a5206a8d3f5784f6", "notes": "Distinct from DF-2196 (kthread_runs task_struct): different alloc site (linux_task_alloc via the current macro), different trigger (any non-drm kernel thread touching drm code, no kthread_run involved). The same bzero that kills the aliasing also bounds DF-2196s dangling-pointer consequence - useful pass-2 intel. mm refcount protocol audited and found balanced for user threads (task ref + proc ref, ordered drops at kern_exit.c:708-711).", "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().", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.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.", "fix_evidence": "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" } |