kern_dup() dup2/dup3 onto an in-flight reserved descriptor: unbounded uninterruptible retry loop + unrate-limited kprintf console/msgbuf flood
Summary
dup2 onto a descriptor a sibling thread holds reserved (pipe's fd1 window, kqueue, SCM_RIGHTS) parks 1s in a NON-PCATCH tsleep with an unrate-limited kprintf per iteration: signal-immune for the full tick per iteration, unkillable for as long as a blocked reservation holder waits (M_WAITOK under pressure), and K threads -> K lines/sec console flood (observed overwriting a panic message on serial). Unpriv two-thread rig: 10 warnings + 10 full-1s parks, SIGKILL death ~1s; krate+PCATCH fix validated (<=1 line/sec system-wide, EINTR on signal, immediate death).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5793 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| dup_directed.c | β | 1.7 KB | view raw | |
| dup_reserved.c | β | 2.7 KB | view raw | |
| build.sh | β | 102 B | view raw | |
| run.sh | β | 157 B | view raw | |
| build.log | β | 331 B | view raw | |
| run.log | β | 807 B | view raw | |
| run.2.log | β | 336 B | view raw | |
| run.3.log | β | 636 B | view raw | |
| fixrun.log | β | 808 B | view raw | |
| env.txt | β | 413 B | view raw | |
| VERDICT.md | β | 4.0 KB | β raw | |
| fix.diff | β | 1.1 KB | view raw | |
| manifest.json | β | 1.0 KB | view raw | |
| verdict.json | β | 3.3 KB | view raw |
DF-5793 VERDICT β reproduced (dos: unkillable spin + console/msgbuf flood); fix validated
Bottom line
REPRODUCED. On the stock DragonFly 6.5-DEVELOPMENT guest, an
unprivileged two-thread process triggered the kern_dup() reserved-target
retry path at ~8.5% of dup2() attempts; every hit (a) emitted an
unrate-limited Warning: dup(): target descriptor %d is reserved line to
the console/dmesg and (b) parked the dup2 thread for a full 1-second
uninterruptible tsleep, with the retry loop unbounded while the peer
thread keeps re-reserving descriptors. kill -9 delivered mid-park did
not take effect for ~1s (measured). The kernel-message flood was even
observed overwriting a kernel panic message on the serial console during
DF-5792 verification (serial_panic_run1.txt). Severity: Low (local DoS /
log flood; the unkillable-window becomes permanent if the reservation
holder blocks indefinitely, e.g. M_WAITOK kmalloc under memory pressure).
Root cause (path:line)
sys/kern/kern_descrip.c:1143-1151β plainkprintfper retry iteration +tsleep(fdp, 0, "fdres", hz)(flags=0 β not PCATCH) +goto retrywith no attempt bound.- Reservations spanning blocking work:
sys/kern/sys_pipe.c:290-338(fd1 reserved across the second falloc and both fsetfds β the code comment even acknowledges racing dup()s),sys/kern/kern_event.c:730,sys/kern/uipc_usrreq.c:1593-1620(per-fd reservation loop),sys/kern/kern_checkpoint.c:664. - Any thread of the process can observe a sibling's in-flight reservation by targeting the fd the sibling just allocated (lowest-free is predictable).
Evidence (stock kernel #0, unprivileged user maxx uid 1001)
run.log (directed, 10s):
spammer: attempts=117 slow=10 err=0 errno_last=9(Bad file descriptor) directed: pipes=5129795 dmesg reserved-warnings: 10 <- exactly one per 1s park Warning: dup(): target descriptor 3 is reserved, waiting for it to be resolved (x10)
run2.log (sweep variant, 12s):
DF-5793: pipes=4525216 dup2 attempts=16184 slow(>=250ms dup2)=11 err=0 total reserved-warnings now: 22 <- 12 new
kill.log (SIGKILL latency, stock):
pid=1315 SIGKILL-to-death sec: 1 <- uninterruptible park survives SIGKILL ~1s warnings: 32
Note: only 117 dup2 attempts completed in 10s β each of the 10 hits consumed a full second parked; steady state is one warning line + one parked second per hit, per thread, forever.
Fix validation (kernel #2 Mon Oct 12 19:39:15 UTC 2026)
fix.diff: krateprintf(&krate_dupres /* .freq = 1 */, ...) and
tsleep(fdp, PCATCH, "fdres", hz) β return (EINTR) on signal.
fixrun.log:
- patched directed run:
errno_last=4 (Interrupted system call)β the once-uninterruptible wait now returns EINTR; - 4 concurrent rigs Γ 10s β 10 warning lines total (krate cap; the unpatched per-thread rate of 10 lines/10s would give ~40);
- SIGKILL mid-park β death in 0s (vs 1s stock).
fix_status: fixed (flood bounded to β€1 line/sec system-wide; interruptible; no behavior change for the non-racing dup2 path β the DF-5792 storms ran 20.8M opens/closes plus dup traffic on the same patched kernel with zero anomalies).
Rig notes (what had to be fixed vs a naive PoC)
- dup2() returns the new fd on success (not 0) β a
== 0success check silently miscounts; - the dup2 source fd must live outside the contested range (e.g. dup2'd to fd 250) or the churner's close-storm closes it and every dup2 returns EBADF;
- ssh sessions may run with fd 0 closed β never use fd 0 as the source.
Environment
Same guest as DF-5792 (see ../DF-5792/env.txt): DragonFly 6.5-DEVELOPMENT
0 baseline; patched #2 for fix validation. Guest reset afterwards.
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
fixedBaseline: unbounded 1 line/sec/thread + 1s uninterruptible parks (SIGKILL deferred). Patched: <=1 line/sec system-wide (4 rigs -> 10 lines/10s vs ~40 extrapolated), dup2 returns EINTR when signalled, SIGKILL death 0s.
fixrun.log sections a/b/c; dmesg counts in run.log/run.2.log/fixrun.log.
Confirmed kernel references
Detail
Exploit chain
thread A: pipe() churn keeps the predictable lowest-free fd (3) transiently reserved (~8.5% of wall time measured); thread B: dup2(srcfd=250, 3) -> kern_dup reserved branch -> kprintf + tsleep(fdp,0,'fdres',hz) + goto retry, no bound, no PCATCH. K threads -> K msgbuf/console lines per second and K unkillable-parked threads.
Evidence (decisive lines)
run.log (attempts=117 slow=10, 10 dmesg warnings), run.2.log (sweep: 16184 attempts, 11 slow, +12 lines), run.3.log (SIGKILL-to-death 1s), fixrun.log (patched: EINTR observed, 4 rigs/10s -> 10 lines total vs ~40 expected, SIGKILL-to-death 0s).
PoC changes
Rigs written fresh. Bring-up fixes: dup2() success check must be >=0 (returns new fd, not 0); the dup2 source fd must be moved out of the contested lowest-free range (fd 250) or the pipe churner closes it (every dup2 EBADF); never use fd 0 as source over ssh (stdin closed).
Verified recommended fix
krateprintf the warning and make the tsleep PCATCH returning EINTR (see fix.diff).
Verdict
Unprivileged two-thread process hits kern_dup()'s reserved-target path (kern_descrip.c:1143-1151): pipe() holds the read-side fd reserved across its second falloc/fsetfd (sys_pipe.c:290-338), and a sibling thread's dup2() onto that fd takes an unbounded retry loop of 1-second NON-PCATCH tsleeps, each printing an unrate-limited kernel warning. Measured on stock guest: 10/117 dup2 attempts parked the full 1s and printed 10 dmesg lines in 10s (1 line/sec/thread sustained, scales per thread); SIGKILL delivered mid-park took ~1s to kill the process (uninterruptible); the same flood was observed overwriting a kernel panic message on the serial console during DF-5792 verification. With a persistent reservation (memory-pressure-blocked kmalloc, SCM_RIGHTS receive loop) the dup2 thread is unkillable indefinitely.
No comments yet.