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

lwkt_rele() never wakes the td_refs drain pollers (tdreap/lwpwait2) β€” teardown latency and needless scheduler churn only

Summary

lwkt_rele decrements td_refs without issuing wakeup(td), while both drain sites (lwkt_exit's tsleep(td,0,'tdreap',1) and lwp_wait's tsleep(td,0,'lwpwait2',1)) poll on 1-tick tsleeps β€” every thread teardown behind a held reference is delayed by up to a full tick after the last release with the exiting/reaping thread waking every tick meanwhile. Correctness preserved; impact bounded to sub-tick teardown latency and scheduler load. Wakeup-on-drain fix proposed.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5840 Β· 2 files
FileTypeDescriptionSize
README.md file 1.6 KB ↓ raw
verdict.json file 2.0 KB view raw
README.md file
↓ download raw

DF-5840 β€” lwkt_rele() never wakes the td_refs pollers (tdreap / lwpwait2)

Info-grade hardening finding; verification SKIPPED per the audit contract (Low/Info hardening findings with no memory-safety consequence).

What

lwkt_rele() (sys/kern/lwkt_thread.c:502-507) decrements td_refs but never issues wakeup(td), while both drain sites POLL instead of being woken:

  • lwkt_exit() :1708-1711 tsleep(td, 0, "tdreap", 1) β€” 1-tick poll
  • lwp_wait() kern_exit.c:857-859 tsleep(td, 0, "lwpwait2", 1) β€” poll, returns 0 to force the caller to retry lwp_wait()

Every thread teardown behind a held reference is therefore delayed by up to one full tick after the last lwkt_rele(), and the exiting/reaping thread burns a wakeup-per-tick cycle in the meantime. Correctness is preserved (the polls do drain); this is latency + needless scheduler load only β€” under a ref-heavy workload (e.g. ps -auxH storms holding td refs from kern_proc.c:1817 concurrent with kthread churn) exit latency inflates by up to 10 ms per poll iteration at hz=100.

wake the pollers at drain: in lwkt_rele(), after the decrement, if td_refs == 0 issue wakeup(td) (the wchan both pollers sleep on):

 void
 lwkt_rele(thread_t td)
 {
     KKASSERT(td->td_refs > 0);
     atomic_add_int(&td->td_refs, -1);
+    if (td->td_refs == 0)
+        wakeup(td);
 }

(wakeup on an empty channel is harmless; both pollers sleep on td itself as the ident.)

Verification

Not executed: no security impact to demonstrate (no corruption, no leak, no achievable DoS beyond sub-tick teardown latency). status: untested.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Not applicable: verification skipped for this Info hardening finding; the one-line fix is low-risk and mechanically obvious.

[]
per-fix-DF-5840

Confirmed kernel references

Detail

Evidence (decisive lines)

['sys/kern/lwkt_thread.c:502-507 (rele without wakeup)', 'sys/kern/lwkt_thread.c:1708-1711 (tdreap 1-tick poll)', 'sys/kern/kern_exit.c:857-859 (lwpwait2 1-tick poll)']

PoC changes

no PoC built (Info hardening; skipped per contract)

Verified recommended fix

lwkt_rele(): issue wakeup(td) when the decrement reaches zero (both poll sites sleep on td as their ident).

Verdict

Info-grade hardening finding, verification intentionally skipped per the audit contract (no memory-safety consequence): lwkt_rele() (lwkt_thread.c:502-507) never wakes the td_refs drain pollers - lwkt_exit()'s tsleep(td,0,"tdreap",1) at :1708-1711 and lwp_wait()'s tsleep(td,0,"lwpwait2",1) at kern_exit.c:857-859 - so every teardown behind a held reference is delayed by up to one tick after the last release and the poller burns scheduler cycles meanwhile. Correctness is preserved (the polls drain the refcount); impact is bounded teardown latency and scheduler load only. One-line fix: wakeup(td) on the 1->0 transition in lwkt_rele().