β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2969

suspend_kproc holds no reference on the target thread: concurrent kthread_exit yields use-after-free read/write on a recycled struct thread and an unkillable sleeper

Field Value
ID DF-2969
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:L/A:L (3.3)
CWE CWE-362 / CWE-416
File sys/kern/kern_kthread.c
Lines 188-206 (lifetime: lwkt_thread.c:1697-1701)
Area kern
Confidence speculative
Discovered 2026-09-02
Pass 2 (GLM 5.3 second pass)
Bucket memcorrupt
Reported pending
Known CVE none
CVE match novel

Summary

suspend_kproc operates on a caller-supplied struct thread *td without taking a reference (no lwkt_hold(td)) and without checking for exit: if the target daemon calls kthread_exit() while a suspender is inside the protocol, (a) wakeup(td) at :194 and tsleep(td,...) at :196 use a stale sleep identity β€” with timo=0 (the u4b idiom) the suspender sleeps forever on an identity that will never be woken for it; (b) the loop test td->td_mpflags at :195 reads freed memory; (c) atomic_clear_int(&td->td_mpflags, TDF_MP_STOPREQ) at :200 WRITES to a struct that lwkt_exit has already parked and which may be recycled β€” clearing STOPREQ on an unrelated thread being legitimately suspended. lwkt_exit's td_refs drain loop is the intended lifetime mechanism this code fails to use. NO currently reachable in-tree instance: the three callers are shutdown_kproc (targets daemons that never exit during shutdown), uvc self-suspend (curthread cannot exit while sleeping), and u4b usb_proc_drain (cold-gated; those threads ignore STOPREQ entirely β€” that path hangs rather than UAFs). Becomes live memory corruption the day any caller suspends a daemon that can error-exit its main loop. Not triggerable by an unprivileged user (no syscall/ioctl entry). Fix: lwkt_hold/lwkt_drop bracket + TDF_EXITING re-check (row diff).

Timeline

  • 2026-09-02 Discovered during pass-2 audit of kern_kthread.c (GLM 5.3). DF-0239..0242 re-verified still present, not re-reported.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2969 Β· 10 files
FileTypeDescriptionSize
README.md β€” 680 B ↓ raw
VERDICT.md β€” 1.8 KB ↓ raw
build.sh β€” 54 B view raw
df2969_run.log β€” 760 B view raw
dfv_2969.c β€” 2.7 KB view raw
env.txt β€” 401 B view raw
fix.diff β€” 532 B view raw
manifest.json β€” 655 B view raw
run.sh β€” 133 B view raw
verdict.json β€” 1.8 KB view raw

DF-2969 PoC evidence pack

What

suspend_kproc holds no reference on the target thread: exited-target protocol operates on freed state.

Build

make (KMOD dfv_2969)

Run

kldload dfv_2969.ko; sysctl -w dfv69.s2969=1; sysctl -w dfv69.s2969x=1; ps

Expected

suspend_kproc on exited kthread RETURNS (hang refuted); stale-flag write logged

Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:04 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 All logs are full untrimmed captures unless noted. fix.diff is the git-apply-able verified fix (authored after verification; never applied to the audit tree).

VERDICT.md
↓ download raw

DF-2969 - verification verdict

Status: not_reproduced | reproduced: 0 | impact: denial_of_service | confidence: speculative

Protocol violation confirmed statically; the claimed hang REFUTED live. The harness created a kthread that immediately kthread_exit()ed, then called suspend_kproc(dead_td, 0) - the log shows the full sequence (atomic_set_int on the parked thread = the stale write, wakeup/tsleep on a dead identity), but suspend_kproc RETURNED: the exit path clears or the allocator recycles td_mpflags before the while-test, so the unkillable-sleeper never materialized; a second suspend_kproc on a LIVE kthread also completed (kpsus_token not wedged). No in-tree trigger exists (finding itself says speculative). Fix (lwkt_hold/lwkt_rele bracket) compiled into the fix-all kernel.

Evidence pointers

df2969_run.log: suspend_kproc returned?! + live suspend_kproc returned (kpsus_token not held?)

Kernel references

PoC changes vs the filing sketch

drove the exact claimed call shape (timo=0 on an exited kthread); hang did not occur

Fix

recommendation: lwkt_hold/lwkt_drop bracket + TDF_EXITING re-check

fix status: not_testable

compile-validated in fix-all kernel

Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:04 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable

compile-validated in fix-all kernel

↓ fix.diffper-fix-DF-2969

Confirmed kernel references

Detail

Evidence (decisive lines)

df2969_run.log: suspend_kproc returned?! + live suspend_kproc returned (kpsus_token not held?)

PoC changes

drove the exact claimed call shape (timo=0 on an exited kthread); hang did not occur

Verified recommended fix

lwkt_hold/lwkt_rele bracket + TDF_EXITING re-check

Verdict

Protocol violation confirmed statically; the claimed hang REFUTED live. The harness created a kthread that immediately kthread_exit()ed, then called suspend_kproc(dead_td, 0) - the log shows the full sequence (atomic_set_int on the parked thread = the stale write, wakeup/tsleep on a dead identity), but suspend_kproc RETURNED: the exit path clears or the allocator recycles td_mpflags before the while-test, so the unkillable-sleeper never materialized; a second suspend_kproc on a LIVE kthread also completed (kpsus_token not wedged). No in-tree trigger exists (finding itself says speculative). Fix (lwkt_hold/drop bracket) compiled into the fix-all kernel.